@@ -226,4 +226,115 @@ module UnusedOpens =
226226 let symbolUses = splitSymbolUses symbolUses
227227 let openStatements = getOpenStatements checkFileResults.OpenDeclarations
228228 return ! filterOpenStatements symbolUses openStatements
229+ }
230+
231+ module SimplifyNames =
232+ type SimplifiableRange = {
233+ Range: range
234+ RelativeName: string
235+ }
236+
237+ let getPlidLength ( plid : string list ) = ( plid |> List.sumBy String.length) + plid.Length
238+
239+ let getSimplifiableNames ( checkFileResults : FSharpCheckFileResults , getSourceLineStr : int -> string ) : Async < SimplifiableRange list > =
240+ async {
241+ let result = ResizeArray()
242+ let! symbolUses = checkFileResults.GetAllUsesOfAllSymbolsInFile()
243+ let symbolUses =
244+ symbolUses
245+ |> Array.filter ( fun symbolUse -> not symbolUse.IsFromOpenStatement)
246+ |> Array.Parallel.map ( fun symbolUse ->
247+ let lineStr = getSourceLineStr symbolUse.RangeAlternate.StartLine
248+ // for `System.DateTime.Now` it returns ([|"System"; "DateTime"|], "Now")
249+ let partialName = QuickParse.GetPartialLongNameEx( lineStr, symbolUse.RangeAlternate.EndColumn - 1 )
250+ // `symbolUse.RangeAlternate.Start` does not point to the start of plid, it points to start of `name`,
251+ // so we have to calculate plid's start ourselves.
252+ let plidStartCol = symbolUse.RangeAlternate.EndColumn - partialName.PartialIdent.Length - ( getPlidLength partialName.QualifyingIdents)
253+ symbolUse, partialName.QualifyingIdents, plidStartCol, partialName.PartialIdent)
254+ |> Array.filter ( fun ( _ , plid , _ , name ) -> name <> " " && not ( List.isEmpty plid))
255+ |> Array.groupBy ( fun ( symbolUse , _ , plidStartCol , _ ) -> symbolUse.RangeAlternate.StartLine, plidStartCol)
256+ |> Array.map ( fun ( _ , xs ) -> xs |> Array.maxBy ( fun ( symbolUse , _ , _ , _ ) -> symbolUse.RangeAlternate.EndColumn))
257+
258+ for symbolUse, plid, plidStartCol, name in symbolUses do
259+ if not symbolUse.IsFromDefinition then
260+ let posAtStartOfName =
261+ let r = symbolUse.RangeAlternate
262+ if r.StartLine = r.EndLine then Range.mkPos r.StartLine ( r.EndColumn - name.Length)
263+ else r.Start
264+
265+ let getNecessaryPlid ( plid : string list ) : Async < string list > =
266+ let rec loop ( rest : string list ) ( current : string list ) =
267+ async {
268+ match rest with
269+ | [] -> return current
270+ | headIdent :: restPlid ->
271+ let! res = checkFileResults.IsRelativeNameResolvableFromSymbol( posAtStartOfName, current, symbolUse.Symbol)
272+ if res then return current
273+ else return ! loop restPlid ( headIdent :: current)
274+ }
275+ loop ( List.rev plid) []
276+
277+ let! necessaryPlid = getNecessaryPlid plid
278+
279+ match necessaryPlid with
280+ | necessaryPlid when necessaryPlid = plid -> ()
281+ | necessaryPlid ->
282+ let r = symbolUse.RangeAlternate
283+ let necessaryPlidStartCol = r.EndColumn - name.Length - ( getPlidLength necessaryPlid)
284+
285+ let unnecessaryRange =
286+ Range.mkRange r.FileName ( Range.mkPos r.StartLine plidStartCol) ( Range.mkPos r.EndLine necessaryPlidStartCol)
287+
288+ let relativeName = ( String.concat " ." plid) + " ." + name
289+ result.Add({ Range = unnecessaryRange; RelativeName = relativeName})
290+
291+ return List.ofSeq result
292+ }
293+
294+ module UnusedDeclarations =
295+ let isPotentiallyUnusedDeclaration ( symbol : FSharpSymbol ) : bool =
296+ match symbol with
297+ // Determining that a record, DU or module is used anywhere requires inspecting all their enclosed entities (fields, cases and func / vals)
298+ // for usages, which is too expensive to do. Hence we never gray them out.
299+ | :? FSharpEntity as e when e.IsFSharpRecord || e.IsFSharpUnion || e.IsInterface || e.IsFSharpModule || e.IsClass || e.IsNamespace -> false
300+ // FCS returns inconsistent results for override members; we're skipping these symbols.
301+ | :? FSharpMemberOrFunctionOrValue as f when
302+ f.IsOverrideOrExplicitInterfaceImplementation ||
303+ f.IsBaseValue ||
304+ f.IsConstructor -> false
305+ // Usage of DU case parameters does not give any meaningful feedback; we never gray them out.
306+ | :? FSharpParameter -> false
307+ | _ -> true
308+
309+ let getUnusedDeclarationRanges ( symbolsUses : FSharpSymbolUse []) ( isScript : bool ) =
310+ let definitions =
311+ symbolsUses
312+ |> Array.filter ( fun su ->
313+ su.IsFromDefinition &&
314+ su.Symbol.DeclarationLocation.IsSome &&
315+ ( isScript || su.IsPrivateToFile) &&
316+ not ( su.Symbol.DisplayName.StartsWith " _" ) &&
317+ isPotentiallyUnusedDeclaration su.Symbol)
318+
319+ let usages =
320+ let usages =
321+ symbolsUses
322+ |> Array.filter ( fun su -> not su.IsFromDefinition)
323+ |> Array.choose ( fun su -> su.Symbol.DeclarationLocation)
324+ HashSet( usages)
325+
326+ let unusedRanges =
327+ definitions
328+ |> Array.map ( fun defSu -> defSu, usages.Contains defSu.Symbol.DeclarationLocation.Value)
329+ |> Array.groupBy ( fun ( defSu , _ ) -> defSu.RangeAlternate)
330+ |> Array.filter ( fun ( _ , defSus ) -> defSus |> Array.forall ( fun ( _ , isUsed ) -> not isUsed))
331+ |> Array.map ( fun ( m , _ ) -> m)
332+
333+ Array.toList unusedRanges
334+
335+ let getUnusedDeclarations ( checkFileResults : FSharpCheckFileResults , isScriptFile : bool ) : Async < range list > =
336+ async {
337+ let! allSymbolUsesInFile = checkFileResults.GetAllUsesOfAllSymbolsInFile()
338+ let unusedRanges = getUnusedDeclarationRanges allSymbolUsesInFile isScriptFile
339+ return unusedRanges
229340 }
0 commit comments