Skip to content

Commit

Permalink
get reference counts working by not including declaration locations i…
Browse files Browse the repository at this point in the history
…n counts
  • Loading branch information
baronfel committed May 14, 2022
1 parent f610bfb commit 2814e4d
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 49 deletions.
23 changes: 7 additions & 16 deletions src/FsAutoComplete.Core/AbstractClassStubGenerator.fs
Original file line number Diff line number Diff line change
Expand Up @@ -38,22 +38,13 @@ let private walkTypeDefn (SynTypeDefn (info, repr, members, implicitCtor, range,

let otherMembers =
allMembers
|> List.filter







(
// filter out implicit/explicit constructors and inherit statements, as all members _must_ come after these
function
| SynMemberDefn.ImplicitCtor _
| SynMemberDefn.ImplicitInherit _ -> false
| SynMemberDefn.Member (SynBinding(valData = SynValData (Some ({ MemberKind = SynMemberKind.Constructor }), _, _)),
_) -> false
| _ -> true)
// filter out implicit/explicit constructors and inherit statements, as all members _must_ come after these
|> List.filter (function
| SynMemberDefn.ImplicitCtor _
| SynMemberDefn.ImplicitInherit _ -> false
| SynMemberDefn.Member (SynBinding(valData = SynValData (Some ({ MemberKind = SynMemberKind.Constructor }), _, _)),
_) -> false
| _ -> true)

match inheritMember with
| Some inheritMember ->
Expand Down
62 changes: 41 additions & 21 deletions src/FsAutoComplete.Core/Commands.fs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ open FSharp.Compiler.Tokenization
open SymbolLocation
open FSharp.Compiler.Symbols
open System.Collections.Immutable
open System.Collections.Generic

[<RequireQualifiedAccess>]
type LocationResponse<'a> = Use of 'a
Expand Down Expand Up @@ -1058,19 +1059,25 @@ type Commands(checker: FSharpCompilerServiceChecker, state: State, hasAnalyzers:
do! findReferencesInFile (file, symbol, p, onFound)
})

let toDict (symbolUseRanges: range seq) =
let dict = new System.Collections.Generic.Dictionary<string, range seq>()
let ranges (uses: FSharpSymbolUse[]) = uses |> Array.map (fun u -> u.Range)

let splitByDeclaration (uses: FSharpSymbolUse[]) =
uses
|> Array.partition (fun u -> u.IsFromDefinition)

let toDict (symbolUseRanges: range[]) =
let dict = new System.Collections.Generic.Dictionary<string, range[]>()

symbolUseRanges
|> Seq.collect (fun symbolUse ->
|> Array.collect (fun symbolUse ->
let file = symbolUse.FileName
// if we had a more complex project system (one that understood that the same file could be in multiple projects distinctly)
// then we'd need to map the files to some kind of document identfier and dedupe by that
// before issueing the renames. We don't, so this becomes very simple
[ file, symbolUse ])
|> Seq.groupBy fst
|> Seq.iter (fun (key, items) ->
let itemsSeq = items |> Seq.map snd
[| file, symbolUse |])
|> Array.groupBy fst
|> Array.iter (fun (key, items) ->
let itemsSeq = items |> Array.map snd
dict[key] <- itemsSeq
())

Expand All @@ -1090,12 +1097,13 @@ type Commands(checker: FSharpCompilerServiceChecker, state: State, hasAnalyzers:
| SymbolDeclarationLocation.CurrentDocument ->
let! ct = Async.CancellationToken
let symbolUses = tyRes.GetCheckResults.GetUsesOfSymbolInFile(symbol, ct)
let declarations, usages = splitByDeclaration symbolUses

let declarationRanges, usageRanges =
toDict (ranges declarations), toDict (ranges usages)

return declarationRanges, usageRanges

return
toDict (
symbolUses
|> Seq.map (fun symbolUse -> symbolUse.Range)
)
| SymbolDeclarationLocation.Projects (projects, isInternalToProject) ->
let symbolUseRanges = ImmutableArray.CreateBuilder()

Expand All @@ -1120,21 +1128,33 @@ type Commands(checker: FSharpCompilerServiceChecker, state: State, hasAnalyzers:
// Distinct these down because each TFM will produce a new 'project'.
// Unless guarded by a #if define, symbols with the same range will be added N times
let symbolUseRanges = symbolUseRanges.ToArray() |> Array.distinct
return toDict symbolUseRanges

return Dictionary<_, _> [], toDict symbolUseRanges
}

member x.RenameSymbol(pos: Position, tyRes: ParseAndCheckResults, lineStr: LineStr, text: NamedText) =
asyncResult {
let! symbolUsesByDocument = x.SymbolUseWorkspace(pos, lineStr, text, tyRes)
let! (declarationsByDocument, symbolUsesByDocument) = x.SymbolUseWorkspace(pos, lineStr, text, tyRes)
let totalSetOfRanges = Dictionary<NamedText, _>()

for (KeyValue (filePath, declUsages)) in declarationsByDocument do
let! text = state.TryGetFileSource(UMX.tag filePath)

match totalSetOfRanges.TryGetValue(text) with
| true, ranges -> totalSetOfRanges[text] <- Array.append ranges declUsages
| false, _ -> totalSetOfRanges[text] <- declUsages

let locations =
symbolUsesByDocument
|> Seq.choose (fun (KeyValue (filePath, symbolUses)) ->
match state.TryGetFileSource(UMX.tag filePath) with
| Error _ -> None
| Ok text -> Some(text, symbolUses))
for (KeyValue (filePath, symbolUses)) in symbolUsesByDocument do
let! text = state.TryGetFileSource(UMX.tag filePath)

return locations
match totalSetOfRanges.TryGetValue(text) with
| true, ranges -> totalSetOfRanges[text] <- Array.append ranges symbolUses
| false, _ -> totalSetOfRanges[text] <- symbolUses

return
totalSetOfRanges
|> Seq.map (fun (KeyValue (k, v)) -> k, v)
|> Array.ofSeq
}

member x.SymbolImplementationProject (tyRes: ParseAndCheckResults) (pos: Position) lineStr =
Expand Down
10 changes: 6 additions & 4 deletions src/FsAutoComplete/FsAutoComplete.Lsp.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1383,11 +1383,12 @@ type FSharpLspServer(state: State, lspClient: FSharpLspClient) =
p
|> x.positionHandler (fun p pos tyRes lineStr lines ->
asyncResult {
let! res =
let! declarations, useages =
commands.SymbolUseWorkspace(pos, lineStr, lines, tyRes)
|> AsyncResult.mapError (JsonRpc.Error.InternalErrorMessage)

let ranges: FSharp.Compiler.Text.Range[] = res.Values |> Seq.concat |> Seq.toArray
let ranges: FSharp.Compiler.Text.Range[] =
useages.Values |> Seq.concat |> Seq.toArray

return ranges |> Array.map fcsRangeToLspLocation |> Some
})
Expand Down Expand Up @@ -1854,11 +1855,12 @@ type FSharpLspServer(state: State, lspClient: FSharpLspClient) =
Command = ""
Arguments = None } }
)
| Ok uses ->
| Ok (declarations, uses) ->
let allUses = uses.Values |> Seq.concat |> Array.ofSeq

// allUses includes the declaration, so we need to reduce it by one to get the number of 'external' references
let cmd =
if allUses.Length = 1 then
if allUses.Length = 0 then
{ Title = "0 References"
Command = ""
Arguments = None }
Expand Down
41 changes: 33 additions & 8 deletions test/FsAutoComplete.Tests.Lsp/CodeLensTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ open Helpers
open Ionide.LanguageServerProtocol.Types
open Utils.Server
open Utils.ServerTests
open Utils.ServerTests
open Utils.TextEdit
open Utils.Utils
open Utils.CursorbasedTests
open Utils.Tests.TextEdit
open Newtonsoft.Json.Linq

module private CodeLens =
let assertNoDiagnostics (ds: Diagnostic []) =
Expand Down Expand Up @@ -45,7 +45,7 @@ module private CodeLens =
resolved
|> List.filter (fun lens -> Range.overlapsStrictly textRange lens.Range)

checkLenses lensesForRange
checkLenses (doc, lensesForRange)
}
|> AsyncResult.foldResult id (fun e -> failtest $"{e}")

Expand All @@ -58,7 +58,7 @@ let tests state =
"""
module X =
$0let func x = x + 1$0
""" (fun lenses ->
""" (fun (doc, lenses) ->
Expect.hasLength lenses 2 "should have a type lens and a reference lens"
let typeLens = lenses[0]
Expect.equal typeLens.Command.Value.Title "int -> int" "first lens should be a type hint of int to int"
Expand All @@ -67,18 +67,43 @@ let tests state =
)

testCaseAsync
"can show codelens for reference count" <|
"can show codelens for 0 reference count" <|
CodeLens.check server
"""
module X =
$0let func x = x + 1$0
""" (fun lenses ->
""" (fun (doc, lenses) ->
Expect.hasLength lenses 2 "should have a type lens and a reference lens"
let referenceLens = lenses[1]
Expect.equal referenceLens.Command.Value.Title "0 References" "second lens should show the references"
Expect.isSome referenceLens.Command.Value.Arguments "Reference lenses should carry data"
Expect.equal referenceLens.Command.Value.Command "fsharp.showReferences" "Reference lens should call command"
let emptyCommand = Some { Title = "0 References"; Arguments = None; Command = "" }
Expect.equal referenceLens.Command emptyCommand "There should be no command or args for zero references"
)
testCaseAsync
"can show codelens for multi reference count" <|
CodeLens.check server
"""
module X =
$0let func x = x + 1$0
let doThing () = func 1
""" (fun (doc, lenses) ->
Expect.hasLength lenses 2 "should have a type lens and a reference lens"
let referenceLens = lenses[1]
Expect.isSome referenceLens.Command "There should be a command for multiple references"
let referenceCommand = referenceLens.Command.Value
Expect.equal referenceCommand.Title "1 References" "There should be a title for multiple references"
Expect.equal referenceCommand.Command "fsharp.showReferences" "There should be a command for multiple references"
Expect.isSome referenceCommand.Arguments "There should be arguments for multiple references"
let args = referenceCommand.Arguments.Value
Expect.equal args.Length 3 "There should be 2 args"
let filePath, triggerPos, referenceRanges =
args[0].Value<string>(),
(args[1] :?> JObject).ToObject<Ionide.LanguageServerProtocol.Types.Position>(),
(args[2] :?> JArray) |> Seq.map (fun t -> (t:?>JObject).ToObject<Ionide.LanguageServerProtocol.Types.Location>()) |> Array.ofSeq
Expect.equal filePath doc.Uri "File path should be the doc we're checking"
Expect.equal triggerPos { Line = 1; Character = 8 } "Position should be 0:0"
Expect.hasLength referenceRanges 1 "There should be 1 reference range for the `func` function"
Expect.equal referenceRanges[0] { Uri = doc.Uri; Range = { Start = { Line = 3; Character = 19 }; End = { Line = 3; Character = 23 } } } "Reference range should be 0:0"
)
]
)
Expand Down

0 comments on commit 2814e4d

Please sign in to comment.