@@ -17,28 +17,39 @@ open Microsoft.FSharp.Compiler.Range
1717open Microsoft.FSharp .Compiler .SourceCodeServices
1818open Symbols
1919
20-
2120module private UnusedOpens =
22-
23-
24- let rec visitSynModuleOrNamespaceDecls ( parent : Ast.LongIdent ) decls : ( Set < string > * range )list =
21+ /// Represents single open statement.
22+ type OpenStatement =
23+ { /// Open namespace or module effective names.
24+ Names: Set < string >
25+ /// Range of open statement itself.
26+ Range: range
27+ /// Enclosing module or namespace range (that is, the scope on in which this open statement is visible).
28+ ModuleRange: range }
29+
30+ let rec visitSynModuleOrNamespaceDecls ( parent : Ast.LongIdent ) ( decls : SynModuleDecls ) ( moduleRange : range ) : OpenStatement list =
2531[ for declin declsdo
2632match declwith
2733| SynModuleDecl.Open( LongIdentWithDots.LongIdentWithDots( id= longId), range) ->
2834yield
29- set[ yield ( longId|> List.map( fun l -> l.idText) |> String.concat" ." )
30- // `open N.M` can open N.M module from parent module as well, if it's non empty
31- if not ( List.isEmpty parent) then
32- yield ( parent@ longId|> List.map( fun l -> l.idText) |> String.concat" ." ) ], range
33- | SynModuleDecl.NestedModule( SynComponentInfo.ComponentInfo( longId= longId),_, decls,_,_) ->
34- yield ! visitSynModuleOrNamespaceDecls longId decls
35+ { Names=
36+ set[ yield ( longId|> List.map( fun l -> l.idText) |> String.concat" ." )
37+ // `open N.M` can open N.M module from parent module as well, if it's non empty
38+ if not ( List.isEmpty parent) then
39+ yield ( parent@ longId|> List.map( fun l -> l.idText) |> String.concat" ." ) ]
40+ Range= range
41+ ModuleRange= moduleRange}
42+
43+ | SynModuleDecl.NestedModule( SynComponentInfo.ComponentInfo( longId= longId),_, decls,_, moduleRange) ->
44+ yield ! visitSynModuleOrNamespaceDecls longId decls moduleRange
3545| _ -> () ]
3646
37- let getOpenStatements = function
47+ let getOpenStatements ( parsedInput : ParsedInput ) : OpenStatement list =
48+ match parsedInputwith
3849| ParsedInput.ImplFile( ParsedImplFileInput( modules= modules)) ->
3950[ for mdin modulesdo
40- let SynModuleOrNamespace ( longId = longId; decls= decls) = md
41- yield ! visitSynModuleOrNamespaceDecls longId decls]
51+ let SynModuleOrNamespace ( longId = longId; decls= decls; range = moduleRange ) = md
52+ yield ! visitSynModuleOrNamespaceDecls longId declsmoduleRange ]
4253| _ -> []
4354
4455let getAutoOpenAccessPath ( ent : FSharpEntity ) =
@@ -79,7 +90,11 @@ module private UnusedOpens =
7990| Some( island, _, _) -> island= fullName
8091| None-> false
8192
82- let getUnusedOpens ( sourceText : SourceText ) ( parsedInput : ParsedInput ) ( symbolUses : FSharpSymbolUse []) =
93+ type NamespaceUse =
94+ { Ident: string
95+ Location: range }
96+
97+ let getUnusedOpens ( sourceText : SourceText ) ( parsedInput : ParsedInput ) ( symbolUses : FSharpSymbolUse []) : range list =
8398
8499let getPartNamespace ( symbolUse : FSharpSymbolUse ) ( fullName : string ) =
85100// given a symbol range such as `Text.ISegment` and a full name of `MonoDevelop.Core.Text.ISegment`, return `MonoDevelop.Core`
@@ -117,25 +132,32 @@ module private UnusedOpens =
117132yield ! entityNamespace declaringEntity]
118133} |> Option.toList|> List.concat|> List.choose id
119134
120- let namespacesInUse =
135+ let namespacesInUse : NamespaceUse list =
121136 symbolUses
122- |> Seq.filter( fun ( s : FSharpSymbolUse ) -> not s.IsFromDefinition)
123- |> Seq.collect getPossibleNamespaces
124- |> Set.ofSeq
125-
126- let filter list : ( Set < string > * range )list =
127- let rec filterInner acc list ( seenNamespaces : Set < string >) =
128- let notUsed ns = not ( namespacesInUse.Contains ns) || seenNamespaces.Contains ns
137+ |> Array.filter( fun ( s : FSharpSymbolUse ) -> not s.IsFromDefinition)
138+ |> Array.toList
139+ |> List.collect( fun x ->
140+ getPossibleNamespaces x
141+ |> List.distinct
142+ |> List.map( fun ns -> { Ident= ns; Location= x.RangeAlternate}))
143+
144+ let filter list : OpenStatement list =
145+ let rec filterInner acc ( list : OpenStatement list ) ( seenOpenStatements : OpenStatement list ) =
146+
147+ let notUsed ( os : OpenStatement ) =
148+ not ( namespacesInUse|> List.exists( fun nsu -> rangeContainsRange os.ModuleRange nsu.Location&& os.Names|> Set.contains nsu.Ident))
149+ || seenOpenStatements|> List.contains os
150+
129151match listwith
130- | ( ns , range ) :: xswhen ns |> Set.forall notUsed->
131- filterInner(( ns , range ) :: acc) xs( seenNamespaces |> Set.union ns )
132- | ( ns , _) :: xs->
133- filterInner acc xs( seenNamespaces |> Set.union ns )
152+ | os :: xswhen notUsed os ->
153+ filterInner( os :: acc) xs( os :: seenOpenStatements )
154+ | os :: xs->
155+ filterInner acc xs( os :: seenOpenStatements )
134156| [] -> List.rev acc
135- filterInner[] list Set.empty
157+
158+ filterInner[] list[]
136159
137- let openStatements = getOpenStatements parsedInput
138- openStatements|> filter|> List.map snd
160+ parsedInput|> getOpenStatements|> filter|> List.map( fun os -> os.Range)
139161
140162[<DiagnosticAnalyzer( FSharpConstants.FSharpLanguageName) >]
141163type internal UnusedOpensDiagnosticAnalyzer () =
@@ -158,7 +180,7 @@ type internal UnusedOpensDiagnosticAnalyzer() =
158180override __.SupportedDiagnostics = ImmutableArray.Create Descriptor
159181override this.AnalyzeSyntaxAsync ( _ , _ ) = Task.FromResult ImmutableArray< Diagnostic>. Empty
160182
161- static member GetUnusedOpenRanges ( document : Document , options , checker : FSharpChecker ) =
183+ static member GetUnusedOpenRanges ( document : Document , options , checker : FSharpChecker ) : Async < Option < range list >> =
162184asyncMaybe {
163185do ! Option.guard Settings.CodeFixes.UnusedOpens
164186let! sourceText = document.GetTextAsync()
@@ -178,10 +200,10 @@ type internal UnusedOpensDiagnosticAnalyzer() =
178200
179201return
180202 unusedOpens
181- |> List.map( fun m ->
203+ |> List.map( fun range ->
182204 Diagnostic.Create(
183205 Descriptor,
184- RoslynHelpers.RangeToLocation( m , sourceText, document.FilePath)))
206+ RoslynHelpers.RangeToLocation( range , sourceText, document.FilePath)))
185207|> Seq.toImmutableArray
186208}
187209|> Async.map( Option.defaultValue ImmutableArray.Empty)