Skip to content
This repository was archived by the owner on Dec 23, 2024. It is now read-only.

Commit 08e5c00

Browse files
Krzysztof-Cieslakcartermp
authored andcommitted
Expose more services (dotnet#8291)
* Expose simplify name analyzer * Move IsPrivateToFile to the FSharpSymbolUse from extensions * Expose Unused Declarations analyzer * Update src/fsharp/symbols/Symbols.fsi Co-Authored-By: Phillip Carter <[email protected]> * Apply feedback Co-authored-by: Phillip Carter <[email protected]>
1 parent baa380b commit 08e5c00

File tree

3 files changed

+13
-148
lines changed

3 files changed

+13
-148
lines changed

Diagnostics/SimplifyNameDiagnosticAnalyzer.fs

Lines changed: 11 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ open FSharp.Compiler.Range
1616
open System.Runtime.Caching
1717
open Microsoft.CodeAnalysis.Host.Mef
1818
open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Diagnostics
19+
open FSharp.Compiler.SourceCodeServices
1920

2021
type private TextVersionHash = int
2122
type private PerDocumentSavedData = { Hash: int; Diagnostics: ImmutableArray<Diagnostic> }
@@ -26,7 +27,7 @@ type internal SimplifyNameDiagnosticAnalyzer [<ImportingConstructor>] () =
2627
static let userOpName = "SimplifyNameDiagnosticAnalyzer"
2728
let getProjectInfoManager (document: Document) = document.Project.Solution.Workspace.Services.GetService<FSharpCheckerWorkspaceService>().FSharpProjectOptionsManager
2829
let getChecker (document: Document) = document.Project.Solution.Workspace.Services.GetService<FSharpCheckerWorkspaceService>().Checker
29-
let getPlidLength (plid: string list) = (plid |> List.sumBy String.length) + plid.Length
30+
3031
static let cache = new MemoryCache("FSharp.Editor." + userOpName)
3132
// Make sure only one document is being analyzed at a time, to be nice
3233
static let guard = new SemaphoreSlim(1)
@@ -52,62 +53,15 @@ type internal SimplifyNameDiagnosticAnalyzer [<ImportingConstructor>] () =
5253
let! sourceText = document.GetTextAsync()
5354
let checker = getChecker document
5455
let! _, _, checkResults = checker.ParseAndCheckDocument(document, projectOptions, sourceText = sourceText, userOpName=userOpName)
55-
let! symbolUses = checkResults.GetAllUsesOfAllSymbolsInFile() |> liftAsync
56-
let mutable result = ResizeArray()
57-
let symbolUses =
58-
symbolUses
59-
|> Array.filter (fun symbolUse -> not symbolUse.IsFromOpenStatement)
60-
|> Array.Parallel.map (fun symbolUse ->
61-
let lineStr = sourceText.Lines.[Line.toZ symbolUse.RangeAlternate.StartLine].ToString()
62-
// for `System.DateTime.Now` it returns ([|"System"; "DateTime"|], "Now")
63-
let partialName = QuickParse.GetPartialLongNameEx(lineStr, symbolUse.RangeAlternate.EndColumn - 1)
64-
// `symbolUse.RangeAlternate.Start` does not point to the start of plid, it points to start of `name`,
65-
// so we have to calculate plid's start ourselves.
66-
let plidStartCol = symbolUse.RangeAlternate.EndColumn - partialName.PartialIdent.Length - (getPlidLength partialName.QualifyingIdents)
67-
symbolUse, partialName.QualifyingIdents, plidStartCol, partialName.PartialIdent)
68-
|> Array.filter (fun (_, plid, _, name) -> name <> "" && not (List.isEmpty plid))
69-
|> Array.groupBy (fun (symbolUse, _, plidStartCol, _) -> symbolUse.RangeAlternate.StartLine, plidStartCol)
70-
|> Array.map (fun (_, xs) -> xs |> Array.maxBy (fun (symbolUse, _, _, _) -> symbolUse.RangeAlternate.EndColumn))
71-
72-
for symbolUse, plid, plidStartCol, name in symbolUses do
73-
if not symbolUse.IsFromDefinition then
74-
let posAtStartOfName =
75-
let r = symbolUse.RangeAlternate
76-
if r.StartLine = r.EndLine then Range.mkPos r.StartLine (r.EndColumn - name.Length)
77-
else r.Start
78-
79-
let getNecessaryPlid (plid: string list) : Async<string list> =
80-
let rec loop (rest: string list) (current: string list) =
81-
async {
82-
match rest with
83-
| [] -> return current
84-
| headIdent :: restPlid ->
85-
let! res = checkResults.IsRelativeNameResolvableFromSymbol(posAtStartOfName, current, symbolUse.Symbol, userOpName=userOpName)
86-
if res then return current
87-
else return! loop restPlid (headIdent :: current)
88-
}
89-
loop (List.rev plid) []
90-
91-
do! Async.Sleep DefaultTuning.SimplifyNameEachItemDelay |> liftAsync // be less intrusive, give other work priority most of the time
92-
let! necessaryPlid = getNecessaryPlid plid |> liftAsync
93-
94-
match necessaryPlid with
95-
| necessaryPlid when necessaryPlid = plid -> ()
96-
| necessaryPlid ->
97-
let r = symbolUse.RangeAlternate
98-
let necessaryPlidStartCol = r.EndColumn - name.Length - (getPlidLength necessaryPlid)
99-
100-
let unnecessaryRange =
101-
Range.mkRange r.FileName (Range.mkPos r.StartLine plidStartCol) (Range.mkPos r.EndLine necessaryPlidStartCol)
102-
103-
let relativeName = (String.concat "." plid) + "." + name
104-
result.Add(
105-
Diagnostic.Create(
106-
descriptor,
107-
RoslynHelpers.RangeToLocation(unnecessaryRange, sourceText, document.FilePath),
108-
properties = (dict [SimplifyNameDiagnosticAnalyzer.LongIdentPropertyKey, relativeName]).ToImmutableDictionary()))
109-
110-
let diagnostics = result.ToImmutableArray()
56+
let! result = SimplifyNames.getSimplifiableNames(checkResults, fun lineNumber -> sourceText.Lines.[Line.toZ lineNumber].ToString()) |> liftAsync
57+
let mutable diag = ResizeArray()
58+
for r in result do
59+
diag.Add(
60+
Diagnostic.Create(
61+
descriptor,
62+
RoslynHelpers.RangeToLocation(r.Range, sourceText, document.FilePath),
63+
properties = (dict [SimplifyNameDiagnosticAnalyzer.LongIdentPropertyKey, r.RelativeName]).ToImmutableDictionary()))
64+
let diagnostics = diag.ToImmutableArray()
11165
cache.Remove(key) |> ignore
11266
let data = { Hash = textVersionHash; Diagnostics=diagnostics }
11367
let cacheItem = CacheItem(key, data)

Diagnostics/UnusedDeclarationsAnalyzer.fs

Lines changed: 2 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -21,69 +21,7 @@ type internal UnusedDeclarationsAnalyzer [<ImportingConstructor>] () =
2121
static let userOpName = "UnusedDeclarationsAnalyzer"
2222
let getProjectInfoManager (document: Document) = document.Project.Solution.Workspace.Services.GetService<FSharpCheckerWorkspaceService>().FSharpProjectOptionsManager
2323
let getChecker (document: Document) = document.Project.Solution.Workspace.Services.GetService<FSharpCheckerWorkspaceService>().Checker
24-
25-
let isPotentiallyUnusedDeclaration (symbol: FSharpSymbol) : bool =
26-
match symbol with
27-
// Determining that a record, DU or module is used anywhere requires inspecting all their enclosed entities (fields, cases and func / vals)
28-
// for usages, which is too expensive to do. Hence we never gray them out.
29-
| :? FSharpEntity as e when e.IsFSharpRecord || e.IsFSharpUnion || e.IsInterface || e.IsFSharpModule || e.IsClass || e.IsNamespace -> false
30-
// FCS returns inconsistent results for override members; we're skipping these symbols.
31-
| :? FSharpMemberOrFunctionOrValue as f when
32-
f.IsOverrideOrExplicitInterfaceImplementation ||
33-
f.IsBaseValue ||
34-
f.IsConstructor -> false
35-
// Usage of DU case parameters does not give any meaningful feedback; we never gray them out.
36-
| :? FSharpParameter -> false
37-
| _ -> true
38-
39-
let getUnusedDeclarationRanges (symbolsUses: FSharpSymbolUse[]) (isScript: bool) =
40-
let definitions =
41-
symbolsUses
42-
|> Array.filter (fun su ->
43-
su.IsFromDefinition &&
44-
su.Symbol.DeclarationLocation.IsSome &&
45-
(isScript || su.IsPrivateToFile) &&
46-
not (su.Symbol.DisplayName.StartsWith "_") &&
47-
isPotentiallyUnusedDeclaration su.Symbol)
48-
49-
let usages =
50-
let usages =
51-
symbolsUses
52-
|> Array.filter (fun su -> not su.IsFromDefinition)
53-
|> Array.choose (fun su -> su.Symbol.DeclarationLocation)
54-
HashSet(usages)
55-
56-
let unusedRanges =
57-
definitions
58-
|> Array.map (fun defSu -> defSu, usages.Contains defSu.Symbol.DeclarationLocation.Value)
59-
|> Array.groupBy (fun (defSu, _) -> defSu.RangeAlternate)
60-
|> Array.filter (fun (_, defSus) -> defSus |> Array.forall (fun (_, isUsed) -> not isUsed))
61-
|> Array.map (fun (m, _) -> m)
62-
63-
//#if DEBUG
64-
//let formatRange (x: FSharp.Compiler.Range.range) = sprintf "(%d, %d) - (%d, %d)" x.StartLine x.StartColumn x.EndLine x.EndColumn
65-
66-
//symbolsUses
67-
//|> Array.map (fun su -> sprintf "%s, %s, is definition = %b, Symbol (def range = %A)"
68-
// (formatRange su.RangeAlternate) su.Symbol.DisplayName su.IsFromDefinition
69-
// (su.Symbol.DeclarationLocation |> Option.map formatRange))
70-
//|> Logging.Logging.logInfof "SymbolUses:\n%+A"
71-
//
72-
//definitions
73-
//|> Seq.map (fun su -> sprintf "su range = %s, symbol range = %A, symbol name = %s"
74-
// (formatRange su.RangeAlternate) (su.Symbol.DeclarationLocation |> Option.map formatRange) su.Symbol.DisplayName)
75-
//|> Logging.Logging.logInfof "Definitions:\n%A"
76-
//
77-
//usages
78-
//|> Seq.map formatRange
79-
//|> Seq.toArray
80-
//|> Logging.Logging.logInfof "Used ranges:\n%A"
81-
//
82-
//unusedRanges
83-
//|> Array.map formatRange
84-
//|> Logging.Logging.logInfof "Unused ranges: %A"
85-
//#endif
86-
unusedRanges
24+
8725

8826
interface IFSharpUnusedDeclarationsDiagnosticAnalyzer with
8927

@@ -98,8 +36,7 @@ type internal UnusedDeclarationsAnalyzer [<ImportingConstructor>] () =
9836
let! sourceText = document.GetTextAsync()
9937
let checker = getChecker document
10038
let! _, _, checkResults = checker.ParseAndCheckDocument(document, projectOptions, sourceText = sourceText, userOpName = userOpName)
101-
let! allSymbolUsesInFile = checkResults.GetAllUsesOfAllSymbolsInFile() |> liftAsync
102-
let unusedRanges = getUnusedDeclarationRanges allSymbolUsesInFile (isScriptFile document.FilePath)
39+
let! unusedRanges = UnusedDeclarations.getUnusedDeclarations( checkResults, (isScriptFile document.FilePath)) |> liftAsync
10340
return
10441
unusedRanges
10542
|> Seq.map (fun m -> Diagnostic.Create(descriptor, RoslynHelpers.RangeToLocation(m, sourceText, document.FilePath)))

LanguageService/Symbols.fs

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -76,32 +76,6 @@ type FSharpSymbolUse with
7676
| projects -> Some (SymbolDeclarationLocation.Projects (projects, isSymbolLocalForProject))
7777
| None -> None
7878

79-
member this.IsPrivateToFile =
80-
let isPrivate =
81-
match this.Symbol with
82-
| :? FSharpMemberOrFunctionOrValue as m -> not m.IsModuleValueOrMember || m.Accessibility.IsPrivate
83-
| :? FSharpEntity as m -> m.Accessibility.IsPrivate
84-
| :? FSharpGenericParameter -> true
85-
| :? FSharpUnionCase as m -> m.Accessibility.IsPrivate
86-
| :? FSharpField as m -> m.Accessibility.IsPrivate
87-
| _ -> false
88-
89-
let declarationLocation =
90-
match this.Symbol.SignatureLocation with
91-
| Some x -> Some x
92-
| _ ->
93-
match this.Symbol.DeclarationLocation with
94-
| Some x -> Some x
95-
| _ -> this.Symbol.ImplementationLocation
96-
97-
let declaredInTheFile =
98-
match declarationLocation with
99-
| Some declRange -> declRange.FileName = this.RangeAlternate.FileName
100-
| _ -> false
101-
102-
isPrivate && declaredInTheFile
103-
104-
10579
type FSharpMemberOrFunctionOrValue with
10680

10781
member x.IsConstructor = x.CompiledName = ".ctor"

0 commit comments

Comments
 (0)