1
+ // Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
2
+
3
+ namespace rec Microsoft.VisualStudio.FSharp.Editor
4
+
5
+ open System
6
+ open System.Collections .Immutable
7
+ open System.Threading .Tasks
8
+ open System.Collections .Generic
9
+
10
+ open Microsoft.CodeAnalysis
11
+ open Microsoft.CodeAnalysis .Diagnostics
12
+ open Microsoft.FSharp .Compiler .SourceCodeServices
13
+
14
+ [<DiagnosticAnalyzer( FSharpConstants.FSharpLanguageName) >]
15
+ type internal UnusedDeclarationsAnalyzer () =
16
+ inherit DocumentDiagnosticAnalyzer()
17
+
18
+ let getProjectInfoManager ( document : Document ) = document.Project.Solution.Workspace.Services.GetService< FSharpCheckerWorkspaceService>() .ProjectInfoManager
19
+ let getChecker ( document : Document ) = document.Project.Solution.Workspace.Services.GetService< FSharpCheckerWorkspaceService>() .Checker
20
+ let [<Literal>] DescriptorId = " FS1182"
21
+
22
+ let Descriptor =
23
+ DiagnosticDescriptor(
24
+ id = DescriptorId,
25
+ title = SR.TheValueIsUnused.Value,
26
+ messageFormat = SR.TheValueIsUnused.Value,
27
+ category = DiagnosticCategory.Style,
28
+ defaultSeverity = DiagnosticSeverity.Hidden,
29
+ isEnabledByDefault = true ,
30
+ customTags = DiagnosticCustomTags.Unnecessary)
31
+
32
+ let symbolUseComparer =
33
+ { new IEqualityComparer< FSharpSymbolUse> with
34
+ member __.Equals ( x , y ) = x.Symbol.IsEffectivelySameAs y.Symbol
35
+ member __.GetHashCode x = x.Symbol.GetHashCode() }
36
+
37
+ let countSymbolsUses ( symbolsUses : FSharpSymbolUse []) =
38
+ let result = Dictionary< FSharpSymbolUse, int>( symbolUseComparer)
39
+
40
+ for symbolUse in symbolsUses do
41
+ match result.TryGetValue symbolUse with
42
+ | true , count -> result.[ symbolUse] <- count + 1
43
+ | _ -> result.[ symbolUse] <- 1
44
+ result
45
+
46
+ let getSingleDeclarations ( symbolsUses : FSharpSymbolUse []) ( isScript : bool ) =
47
+ let declarations =
48
+ countSymbolsUses symbolsUses
49
+ |> Seq.choose ( fun ( KeyValue ( symbolUse , count )) ->
50
+ match symbolUse.Symbol with
51
+ // Determining that a record, DU or module is used anywhere requires inspecting all their enclosed entities (fields, cases and func / vals)
52
+ // for usages, which is too expensive to do. Hence we never gray them out.
53
+ | :? FSharpEntity as e when e.IsFSharpRecord || e.IsFSharpUnion || e.IsInterface || e.IsFSharpModule || e.IsClass -> None
54
+ // FCS returns inconsistent results for override members; we're skipping these symbols.
55
+ | :? FSharpMemberOrFunctionOrValue as f when
56
+ f.IsOverrideOrExplicitInterfaceImplementation ||
57
+ f.IsConstructorThisValue ||
58
+ f.IsBaseValue ||
59
+ f.IsConstructor -> None
60
+ // Usage of DU case parameters does not give any meaningful feedback; we never gray them out.
61
+ | :? FSharpParameter when symbolUse.IsFromDefinition -> None
62
+ | _ when count = 1 && symbolUse.IsFromDefinition && ( isScript || symbolUse.IsPrivateToFile) -> Some symbolUse
63
+ | _ -> None)
64
+ HashSet( declarations, symbolUseComparer)
65
+
66
+ override __.SupportedDiagnostics = ImmutableArray.Create Descriptor
67
+
68
+ override this.AnalyzeSyntaxAsync ( _ , _ ) = Task.FromResult ImmutableArray< Diagnostic>. Empty
69
+
70
+ override this.AnalyzeSemanticsAsync ( document , cancellationToken ) =
71
+ asyncMaybe {
72
+ match getProjectInfoManager( document). TryGetOptionsForEditingDocumentOrProject( document) with
73
+ | Some options ->
74
+ let! sourceText = document.GetTextAsync()
75
+ let checker = getChecker document
76
+ let! _ , _ , checkResults = checker.ParseAndCheckDocument( document, options, sourceText = sourceText, allowStaleResults = true )
77
+ let! allSymbolUsesInFile = checkResults.GetAllUsesOfAllSymbolsInFile() |> liftAsync
78
+ let unusedDeclarations = getSingleDeclarations allSymbolUsesInFile ( isScriptFile document.FilePath)
79
+ return
80
+ unusedDeclarations
81
+ |> Seq.filter ( fun symbolUse -> not ( symbolUse.Symbol.DisplayName.StartsWith " _" ))
82
+ |> Seq.map ( fun symbolUse -> Diagnostic.Create( Descriptor, RoslynHelpers.RangeToLocation( symbolUse.RangeAlternate, sourceText, document.FilePath)))
83
+ |> Seq.toImmutableArray
84
+ | None -> return ImmutableArray.Empty
85
+ }
86
+ |> Async.map ( Option.defaultValue ImmutableArray.Empty)
87
+ |> RoslynHelpers.StartAsyncAsTask cancellationToken
88
+
89
+ interface IBuiltInAnalyzer with
90
+ member __.OpenFileOnly _ = true
91
+ member __.GetAnalyzerCategory () = DiagnosticAnalyzerCategory.SemanticDocumentAnalysis
0 commit comments