@@ -27,6 +27,7 @@ type internal SimplifyNameDiagnosticAnalyzer() =
2727let getChecker ( document : Document ) = document.Project.Solution.Workspace.Services.GetService< FSharpCheckerWorkspaceService>() .Checker
2828let getPlidLength ( plid : string list ) = ( plid|> List.sumBy String.length) + plid.Length
2929static let cache = ConditionalWeakTable< DocumentId, TextVersionHash* ImmutableArray< Diagnostic>>()
30+ static let guard = new SemaphoreSlim( 1 )
3031
3132static let Descriptor =
3233 DiagnosticDescriptor(
@@ -50,73 +51,72 @@ type internal SimplifyNameDiagnosticAnalyzer() =
5051| Some options->
5152let! textVersion = document.GetTextVersionAsync( cancellationToken)
5253let textVersionHash = textVersion.GetHashCode()
53-
54- return ! lock cache( fun _ ->
55- asyncMaybe {
56- match cache.TryGetValue document.Idwith
57- | true , ( oldTextVersionHash, diagnostics) when oldTextVersionHash= textVersionHash-> return diagnostics
58- | _ ->
59- let! sourceText = document.GetTextAsync()
60- let checker = getChecker document
61- let! _ , checkResults = checker.ParseAndCheckDocument( document, options, sourceText)
62- let! symbolUses = checkResults.GetAllUsesOfAllSymbolsInFile() |> liftAsync
63- let mutable result = ResizeArray()
64- let symbolUses =
65- symbolUses
66- |> Array.Parallel.map( fun symbolUse ->
67- let lineStr = sourceText.Lines.[ Line.toZ symbolUse.RangeAlternate.StartLine]. ToString()
68- // for `System.DateTime.Now` it returns ([|"System"; "DateTime"|], "Now")
69- let plid , name = QuickParse.GetPartialLongNameEx( lineStr, symbolUse.RangeAlternate.EndColumn- 1 )
70- // `symbolUse.RangeAlternate.Start` does not point to the start of plid, it points to start of `name`,
71- // so we have to calculate plid's start ourselves.
72- let plidStartCol = symbolUse.RangeAlternate.EndColumn- name.Length- ( getPlidLength plid)
73- symbolUse, plid, plidStartCol, name)
74- |> Array.filter( fun ( _ , plid , _ , _ ) -> not ( List.isEmpty plid))
75- |> Array.groupBy( fun ( symbolUse , _ , plidStartCol , _ ) -> symbolUse.RangeAlternate.StartLine, plidStartCol)
76- |> Array.map( fun ( _ , xs ) -> xs|> Array.maxBy( fun ( symbolUse , _ , _ , _ ) -> symbolUse.RangeAlternate.EndColumn))
77-
78- for symbolUse, plid, plidStartCol, namein symbolUsesdo
79- if not symbolUse.IsFromDefinitionthen
80- let posAtStartOfName =
81- let r = symbolUse.RangeAlternate
82- if r.StartLine= r.EndLinethen Range.mkPos r.StartLine( r.EndColumn- name.Length)
83- else r.Start
84-
85- let getNecessaryPlid ( plid : string list ) : Async < string list > =
86- let rec loop ( rest : string list ) ( current : string list ) =
87- async {
88- match restwith
89- | [] -> return current
90- | headIdent:: restPlid->
91- let! res = checkResults.IsRelativeNameResolvable( posAtStartOfName, current, symbolUse.Symbol.Item)
92- if resthen return current
93- else return ! loop restPlid( headIdent:: current)
94- }
95- loop( List.rev plid) []
96-
97- let! necessaryPlid = getNecessaryPlid plid|> liftAsync
98-
99- match necessaryPlidwith
100- | necessaryPlidwhen necessaryPlid= plid-> ()
101- | necessaryPlid->
102- let r = symbolUse.RangeAlternate
103- let necessaryPlidStartCol = r.EndColumn- name.Length- ( getPlidLength necessaryPlid)
104-
105- let unnecessaryRange =
106- Range.mkRange r.FileName( Range.mkPos r.StartLine plidStartCol) ( Range.mkPos r.EndLine necessaryPlidStartCol)
107-
108- let relativeName = ( String.concat" ." plid) + " ." + name
109- result.Add(
110- Diagnostic.Create(
111- Descriptor,
112- CommonRoslynHelpers.RangeToLocation( unnecessaryRange, sourceText, document.FilePath),
113- properties= ( dict[ SimplifyNameDiagnosticAnalyzer.LongIdentPropertyKey, relativeName]) .ToImmutableDictionary()))
114-
115- let diagnostics = result.ToImmutableArray()
116- cache.Remove( document.Id) |> ignore
117- cache.Add( document.Id, ( textVersionHash, diagnostics))
118- return diagnostics
119- })
54+ let! _ = guard.WaitAsync( cancellationToken) |> Async.AwaitTask|> liftAsync
55+ try
56+ match cache.TryGetValue document.Idwith
57+ | true , ( oldTextVersionHash, diagnostics) when oldTextVersionHash= textVersionHash-> return diagnostics
58+ | _ ->
59+ let! sourceText = document.GetTextAsync()
60+ let checker = getChecker document
61+ let! _ , checkResults = checker.ParseAndCheckDocument( document, options, sourceText)
62+ let! symbolUses = checkResults.GetAllUsesOfAllSymbolsInFile() |> liftAsync
63+ let mutable result = ResizeArray()
64+ let symbolUses =
65+ symbolUses
66+ |> Array.Parallel.map( fun symbolUse ->
67+ let lineStr = sourceText.Lines.[ Line.toZ symbolUse.RangeAlternate.StartLine]. ToString()
68+ // for `System.DateTime.Now` it returns ([|"System"; "DateTime"|], "Now")
69+ let plid , name = QuickParse.GetPartialLongNameEx( lineStr, symbolUse.RangeAlternate.EndColumn- 1 )
70+ // `symbolUse.RangeAlternate.Start` does not point to the start of plid, it points to start of `name`,
71+ // so we have to calculate plid's start ourselves.
72+ let plidStartCol = symbolUse.RangeAlternate.EndColumn- name.Length- ( getPlidLength plid)
73+ symbolUse, plid, plidStartCol, name)
74+ |> Array.filter( fun ( _ , plid , _ , _ ) -> not ( List.isEmpty plid))
75+ |> Array.groupBy( fun ( symbolUse , _ , plidStartCol , _ ) -> symbolUse.RangeAlternate.StartLine, plidStartCol)
76+ |> Array.map( fun ( _ , xs ) -> xs|> Array.maxBy( fun ( symbolUse , _ , _ , _ ) -> symbolUse.RangeAlternate.EndColumn))
77+
78+ for symbolUse, plid, plidStartCol, namein symbolUsesdo
79+ if not symbolUse.IsFromDefinitionthen
80+ let posAtStartOfName =
81+ let r = symbolUse.RangeAlternate
82+ if r.StartLine= r.EndLinethen Range.mkPos r.StartLine( r.EndColumn- name.Length)
83+ else r.Start
84+
85+ let getNecessaryPlid ( plid : string list ) : Async < string list > =
86+ let rec loop ( rest : string list ) ( current : string list ) =
87+ async {
88+ match restwith
89+ | [] -> return current
90+ | headIdent:: restPlid->
91+ let! res = checkResults.IsRelativeNameResolvable( posAtStartOfName, current, symbolUse.Symbol.Item)
92+ if resthen return current
93+ else return ! loop restPlid( headIdent:: current)
94+ }
95+ loop( List.rev plid) []
96+
97+ let! necessaryPlid = getNecessaryPlid plid|> liftAsync
98+
99+ match necessaryPlidwith
100+ | necessaryPlidwhen necessaryPlid= plid-> ()
101+ | necessaryPlid->
102+ let r = symbolUse.RangeAlternate
103+ let necessaryPlidStartCol = r.EndColumn- name.Length- ( getPlidLength necessaryPlid)
104+
105+ let unnecessaryRange =
106+ Range.mkRange r.FileName( Range.mkPos r.StartLine plidStartCol) ( Range.mkPos r.EndLine necessaryPlidStartCol)
107+
108+ let relativeName = ( String.concat" ." plid) + " ." + name
109+ result.Add(
110+ Diagnostic.Create(
111+ Descriptor,
112+ CommonRoslynHelpers.RangeToLocation( unnecessaryRange, sourceText, document.FilePath),
113+ properties= ( dict[ SimplifyNameDiagnosticAnalyzer.LongIdentPropertyKey, relativeName]) .ToImmutableDictionary()))
114+
115+ let diagnostics = result.ToImmutableArray()
116+ cache.Remove( document.Id) |> ignore
117+ cache.Add( document.Id, ( textVersionHash, diagnostics))
118+ return diagnostics
119+ finally guard.Release() |> ignore
120120| None-> return ImmutableArray.Empty
121121}
122122|> Async.map( Option.defaultValue ImmutableArray.Empty)