@@ -16,23 +16,20 @@ module UnusedOpens =
1616 Idents: Set < string >
1717/// Range of open statement itself.
1818 Range: range
19- ///Enclosing module or namespace range (that is, the scope onin which this openstatement isvisible) .
20- ModuleRange : range }
19+ ///Scope on which this opendeclaration isapplied .
20+ AppliedScope : range }
2121
22- let getOpenStatements ( openDeclarations : OpenDeclaration list) : OpenStatement list =
22+ let getOpenStatements ( openDeclarations : FSharpOpenDeclaration list) : OpenStatement list =
2323 openDeclarations
2424|> List.choose( fun openDeclaration ->
2525match openDeclarationwith
26- | OpenDeclaration.Open( longId, moduleRefs, scopem) when not ( List.isEmpty longId) ->
27- Some{ Idents=
28- moduleRefs
29- |> List.choose( fun x -> x.PublicPath|> Option.map( fun ( Tast.PublicPath.PubPath path ) -> path|> String.concat" ." ))
30- |> Set.ofList
26+ | FSharpOpenDeclaration.Open( longId, modules, appliedScope) when not ( List.isEmpty longId) ->
27+ Some{ Idents= modules|> List.choose( fun x -> x.TryFullName) |> Set.ofList
3128 Range=
3229let first = List.head longId
3330let last = List.last longId
34- mkRangescopem .FileName first.idRange.Start last.idRange.End
35- ModuleRange = scopem }
31+ mkRangeappliedScope .FileName first.idRange.Start last.idRange.End
32+ AppliedScope = appliedScope }
3633| _ -> None// for now
3734)
3835
@@ -77,99 +74,105 @@ module UnusedOpens =
7774type NamespaceUse =
7875{ Ident: string
7976 Location: range }
80-
81- let getUnusedOpens ( symbolUses : FSharpSymbolUse [], openDeclarations : OpenDeclaration list , getSourceLineStr : int -> string ) : range list =
82- let getPartNamespace ( symbolUse : FSharpSymbolUse ) ( fullName : string ) =
83- // given a symbol range such as `Text.ISegment` and a full name of `MonoDevelop.Core.Text.ISegment`, return `MonoDevelop.Core`
84- let length = symbolUse.RangeAlternate.EndColumn- symbolUse.RangeAlternate.StartColumn
85- let lengthDiff = fullName.Length- length- 2
86- if lengthDiff<= 0 || lengthDiff> fullName.Length- 1 then None
87- else Some fullName.[ 0 .. lengthDiff]
88-
89- let getPossibleNamespaces ( symbolUse : FSharpSymbolUse ) : string list =
90- let isQualified = symbolIsFullyQualified getSourceLineStr symbolUse
91-
92- ( match symbolUsewith
93- | SymbolUse.Entity( ent, cleanFullNames) when not ( cleanFullNames|> List.exists isQualified) ->
94- Some( cleanFullNames, Some ent)
95- | SymbolUse.Field fwhen not ( isQualified f.FullName) ->
96- Some([ f.FullName], Some f.DeclaringEntity)
97- | SymbolUse.MemberFunctionOrValue mfvwhen not ( isQualified mfv.FullName) ->
98- Some([ mfv.FullName], mfv.EnclosingEntity)
99- | SymbolUse.Operator opwhen not ( isQualified op.FullName) ->
100- Some([ op.FullName], op.EnclosingEntity)
101- | SymbolUse.ActivePattern apwhen not ( isQualified ap.FullName) ->
102- Some([ ap.FullName], ap.EnclosingEntity)
103- | SymbolUse.ActivePatternCase apcwhen not ( isQualified apc.FullName) ->
104- Some([ apc.FullName], apc.Group.EnclosingEntity)
105- | SymbolUse.UnionCase ucwhen not ( isQualified uc.FullName) ->
106- Some([ uc.FullName], Some uc.ReturnType.TypeDefinition)
107- | SymbolUse.Parameter pwhen not ( isQualified p.FullName) && p.Type.HasTypeDefinition->
108- Some([ p.FullName], Some p.Type.TypeDefinition)
109- | _ -> None)
110- |> Option.map( fun ( fullNames , declaringEntity ) ->
111- [ for namein fullNamesdo
112- let partNamespace = getPartNamespace symbolUse name
113- yield partNamespace
114- yield ! entityNamespace declaringEntity])
115- |> Option.toList
116- |> List.concat
117- |> List.choose id
118-
119- let namespacesInUse : NamespaceUse list =
120- let importantSymbolUses =
121- symbolUses
122- |> Array.filter( fun ( symbolUse : FSharpSymbolUse ) ->
123- not symbolUse.IsFromDefinition&&
124- match symbolUse.Symbolwith
125- | :? FSharpEntityas e-> not e.IsNamespace
126- | _ -> true
127- )
128-
129- importantSymbolUses
130- |> Array.toList
131- |> List.collect( fun su ->
132- let lineStr = getSourceLineStr su.RangeAlternate.StartLine
133- let partialName = QuickParse.GetPartialLongNameEx( lineStr, su.RangeAlternate.EndColumn- 1 )
134- let qualifier = partialName.QualifyingIdents|> String.concat" ."
135- getPossibleNamespaces su
136- |> List.distinct
137- |> List.choose( fun ns ->
138- if qualifier= " " then Some ns
139- elif ns= qualifierthen None
140- elif ns.EndsWith qualifierthen Some ns.[..( ns.Length- qualifier.Length) - 2 ]
141- else None)
142- |> List.map( fun ns ->
143- { Ident= ns
144- Location= su.RangeAlternate}))
145-
146- let filter list : OpenStatement list =
147- let rec filterInner acc ( list : OpenStatement list ) ( seenOpenStatements : OpenStatement list ) =
77+
78+ let getPartNamespace ( symbolUse : FSharpSymbolUse ) ( fullName : string ) =
79+ // given a symbol range such as `Text.ISegment` and a full name of `MonoDevelop.Core.Text.ISegment`, return `MonoDevelop.Core`
80+ let length = symbolUse.RangeAlternate.EndColumn- symbolUse.RangeAlternate.StartColumn
81+ let lengthDiff = fullName.Length- length- 2
82+ if lengthDiff<= 0 || lengthDiff> fullName.Length- 1 then None
83+ else Some fullName.[ 0 .. lengthDiff]
84+
85+ let getPossibleNamespaces ( getSourceLineStr : int -> string ) ( symbolUse : FSharpSymbolUse ) : string list =
86+ let isQualified = symbolIsFullyQualified getSourceLineStr symbolUse
87+
88+ ( match symbolUsewith
89+ | SymbolUse.Entity( ent, cleanFullNames) when not ( cleanFullNames|> List.exists isQualified) ->
90+ Some( cleanFullNames, Some ent)
91+ | SymbolUse.Field fwhen not ( isQualified f.FullName) ->
92+ Some([ f.FullName], Some f.DeclaringEntity)
93+ | SymbolUse.MemberFunctionOrValue mfvwhen not ( isQualified mfv.FullName) ->
94+ Some([ mfv.FullName], mfv.EnclosingEntity)
95+ | SymbolUse.Operator opwhen not ( isQualified op.FullName) ->
96+ Some([ op.FullName], op.EnclosingEntity)
97+ | SymbolUse.ActivePattern apwhen not ( isQualified ap.FullName) ->
98+ Some([ ap.FullName], ap.EnclosingEntity)
99+ | SymbolUse.ActivePatternCase apcwhen not ( isQualified apc.FullName) ->
100+ Some([ apc.FullName], apc.Group.EnclosingEntity)
101+ | SymbolUse.UnionCase ucwhen not ( isQualified uc.FullName) ->
102+ Some([ uc.FullName], Some uc.ReturnType.TypeDefinition)
103+ | SymbolUse.Parameter pwhen not ( isQualified p.FullName) && p.Type.HasTypeDefinition->
104+ Some([ p.FullName], Some p.Type.TypeDefinition)
105+ | _ -> None)
106+ |> Option.map( fun ( fullNames , declaringEntity ) ->
107+ [ for namein fullNamesdo
108+ let partNamespace = getPartNamespace symbolUse name
109+ yield partNamespace
110+ yield ! entityNamespace declaringEntity])
111+ |> Option.toList
112+ |> List.concat
113+ |> List.choose id
114+
115+ let getNamespacesInUse ( getSourceLineStr : int -> string ) ( symbolUses : FSharpSymbolUse []) : NamespaceUse list =
116+ let importantSymbolUses =
117+ symbolUses
118+ |> Array.filter( fun ( symbolUse : FSharpSymbolUse ) ->
119+ not symbolUse.IsFromDefinition&&
120+ match symbolUse.Symbolwith
121+ | :? FSharpEntityas e-> not e.IsNamespace
122+ | _ -> true
123+ )
124+
125+ importantSymbolUses
126+ |> Array.toList
127+ |> List.collect( fun su ->
128+ let lineStr = getSourceLineStr su.RangeAlternate.StartLine
129+ let partialName = QuickParse.GetPartialLongNameEx( lineStr, su.RangeAlternate.EndColumn- 1 )
130+ let qualifier = partialName.QualifyingIdents|> String.concat" ."
131+ getPossibleNamespaces getSourceLineStr su
132+ |> List.distinct
133+ |> List.choose( fun ns ->
134+ if qualifier= " " then Some ns
135+ elif ns= qualifierthen None
136+ elif ns.EndsWith qualifierthen Some ns.[..( ns.Length- qualifier.Length) - 2 ]
137+ else None)
138+ |> List.map( fun ns ->
139+ { Ident= ns
140+ Location= su.RangeAlternate}))
141+
142+ let getUnusedOpens ( checkFileResults : FSharpCheckFileResults , getSourceLineStr : int -> string ) : Async < range list > =
143+
144+ let filter ( openStatements : OpenStatement list ) ( namespacesInUse : NamespaceUse list ) : OpenStatement list =
145+ let rec filterInner acc ( openStatements : OpenStatement list ) ( seenOpenStatements : OpenStatement list ) =
148146
149147let notUsed ( os : OpenStatement ) =
150148if os.Idents|> Set.exists( fun x -> x.StartsWith MangledGlobalName) then false
151149else
152150let notUsedAnywhere =
153151not ( namespacesInUse|> List.exists( fun nsu ->
154- rangeContainsRange os.ModuleRange nsu.Location&& os.Idents|> Set.contains nsu.Ident))
152+ rangeContainsRange os.AppliedScope nsu.Location&& os.Idents|> Set.contains nsu.Ident))
155153if notUsedAnywherethen true
156154else
157155let alreadySeen =
158156 seenOpenStatements
159157|> List.exists( fun seenNs ->
160158// if such open statement has already been marked as used in this or outer module, we skip it
161159// (that is, do not mark as used so far)
162- rangeContainsRange seenNs.ModuleRange os.ModuleRange &&
160+ rangeContainsRange seenNs.AppliedScope os.AppliedScope &&
163161not ( os.Idents|> Set.intersect seenNs.Idents|> Set.isEmpty))
164162 alreadySeen
165163
166- match list with
164+ match openStatements with
167165| os:: xswhen notUsed os->
168166 filterInner( os:: acc) xs( os:: seenOpenStatements)
169167| os:: xs->
170168 filterInner acc xs( os:: seenOpenStatements)
171169| [] -> List.rev acc
172170
173- filterInner[] list[]
174-
175- openDeclarations|> getOpenStatements|> filter|> List.map( fun os -> os.Range)
171+ filterInner[] openStatements[]
172+
173+ async {
174+ let! symbolUses = checkFileResults.GetAllUsesOfAllSymbolsInFile()
175+ let namespacesInUse = getNamespacesInUse getSourceLineStr symbolUses
176+ let openStatements = getOpenStatements checkFileResults.OpenDeclarations
177+ return filter openStatements namespacesInUse|> List.map( fun os -> os.Range)
178+ }