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.Composition
7+ open System.Collections .Immutable
8+ open System.Threading
9+ open System.Threading .Tasks
10+ open System.Runtime .CompilerServices
11+
12+ open Microsoft.CodeAnalysis
13+ open Microsoft.CodeAnalysis .Diagnostics
14+ open Microsoft.FSharp .Compiler
15+ open Microsoft.FSharp .Compiler .Range
16+ open Microsoft.FSharp .Compiler .SourceCodeServices
17+
18+ open Microsoft.VisualStudio .FSharp .LanguageService
19+
20+ type private LineHash = int
21+
22+ [<DiagnosticAnalyzer( FSharpCommonConstants.FSharpLanguageName) >]
23+ type internal SimplifyNameDiagnosticAnalyzer () =
24+ inherit DocumentDiagnosticAnalyzer()
25+
26+ let getProjectInfoManager ( document : Document ) = document.Project.Solution.Workspace.Services.GetService< FSharpCheckerWorkspaceService>() .ProjectInfoManager
27+ let getChecker ( document : Document ) = document.Project.Solution.Workspace.Services.GetService< FSharpCheckerWorkspaceService>() .Checker
28+ let getPlidLength ( plid : string list ) = ( plid|> List.sumBy String.length) + plid.Length
29+
30+ static let Descriptor =
31+ DiagnosticDescriptor(
32+ IDEDiagnosticIds.SimplifyNamesDiagnosticId,
33+ SR.SimplifyName.Value,
34+ SR.NameCanBeSimplified.Value,
35+ SR.StyleCategory.Value,
36+ DiagnosticSeverity.Hidden,
37+ true ,
38+ " " ,
39+ " " ,
40+ DiagnosticCustomTags.Unnecessary)
41+
42+ static member LongIdentPropertyKey = " FullName"
43+
44+ override __.SupportedDiagnostics = ImmutableArray.Create Descriptor
45+
46+ override this.AnalyzeSyntaxAsync ( document : Document , cancellationToken : CancellationToken ) =
47+ asyncMaybe {
48+ match getProjectInfoManager( document). TryGetOptionsForEditingDocumentOrProject( document) with
49+ | Some options->
50+ let! sourceText = document.GetTextAsync()
51+ let checker = getChecker document
52+ let! _ , checkResults = checker.ParseAndCheckDocument( document, options, sourceText)
53+ let! symbolUses = checkResults.GetAllUsesOfAllSymbolsInFile() |> liftAsync
54+ let mutable result = ResizeArray()
55+ let symbolUses =
56+ symbolUses
57+ |> Array.Parallel.map( fun symbolUse ->
58+ let lineStr = sourceText.Lines.[ Line.toZ symbolUse.RangeAlternate.StartLine]. ToString()
59+ // for `System.DateTime.Now` it returns ([|"System"; "DateTime"|], "Now")
60+ let plid , name = QuickParse.GetPartialLongNameEx( lineStr, symbolUse.RangeAlternate.EndColumn- 1 )
61+ // `symbolUse.RangeAlternate.Start` does not point to the start of plid, it points to start of `name`,
62+ // so we have to calculate plid's start ourselves.
63+ let plidStartCol = symbolUse.RangeAlternate.EndColumn- name.Length- ( getPlidLength plid)
64+ symbolUse, plid, plidStartCol, name)
65+ |> Array.filter( fun ( _ , plid , _ , _ ) -> not ( List.isEmpty plid))
66+ |> Array.groupBy( fun ( symbolUse , _ , plidStartCol , _ ) -> symbolUse.RangeAlternate.StartLine, plidStartCol)
67+ |> Array.map( fun ( _ , xs ) -> xs|> Array.maxBy( fun ( symbolUse , _ , _ , _ ) -> symbolUse.RangeAlternate.EndColumn))
68+
69+ for symbolUse, plid, plidStartCol, namein symbolUsesdo
70+ if not symbolUse.IsFromDefinitionthen
71+ let posAtStartOfName =
72+ let r = symbolUse.RangeAlternate
73+ if r.StartLine= r.EndLinethen Range.mkPos r.StartLine( r.EndColumn- name.Length)
74+ else r.Start
75+
76+ let getNecessaryPlid ( plid : string list ) : Async < string list > =
77+ let rec loop ( rest : string list ) ( current : string list ) =
78+ async {
79+ match restwith
80+ | [] -> return current
81+ | headIdent:: restPlid->
82+ let! res = checkResults.IsRelativeNameResolvable( posAtStartOfName, current, symbolUse.Symbol.Item)
83+ if resthen return current
84+ else return ! loop restPlid( headIdent:: current)
85+ }
86+ loop( List.rev plid) []
87+
88+ let! necessaryPlid = getNecessaryPlid plid|> liftAsync
89+
90+ match necessaryPlidwith
91+ | necessaryPlidwhen necessaryPlid= plid-> ()
92+ | necessaryPlid->
93+ let r = symbolUse.RangeAlternate
94+ let necessaryPlidStartCol = r.EndColumn- name.Length- ( getPlidLength necessaryPlid)
95+
96+ let unnecessaryRange =
97+ Range.mkRange r.FileName( Range.mkPos r.StartLine plidStartCol) ( Range.mkPos r.EndLine necessaryPlidStartCol)
98+
99+ let relativeName = ( String.concat" ." plid) + " ." + name
100+ result.Add(
101+ Diagnostic.Create(
102+ Descriptor,
103+ CommonRoslynHelpers.RangeToLocation( unnecessaryRange, sourceText, document.FilePath),
104+ properties= ( dict[ SimplifyNameDiagnosticAnalyzer.LongIdentPropertyKey, relativeName]) .ToImmutableDictionary()))
105+
106+ return result.ToImmutableArray()
107+ | None-> return ImmutableArray.Empty
108+ }
109+ |> Async.map( Option.defaultValue ImmutableArray.Empty)
110+ |> CommonRoslynHelpers.StartAsyncAsTask cancellationToken
111+
112+ override this.AnalyzeSemanticsAsync ( _ , _ ) = Task.FromResult ImmutableArray< Diagnostic>. Empty
113+
114+ interface IBuiltInAnalyzerwith
115+ member __.OpenFileOnly _ = true
116+ member __.GetAnalyzerCategory () = DiagnosticAnalyzerCategory.SemanticDocumentAnalysis