From aab7156d2764d8ce3ceec887c6f4369106f1807c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Saulius=20Menkevi=C4=8Dius?= Date: Fri, 15 Nov 2024 12:28:18 +0200 Subject: [PATCH] Add tests for textDocument/findReferences, implement support for Context.IncludeDeclaration (#199) * Add tests for "textDocument/references" handler * Respect Context.IncludeDeclaration in handler for "textDocument/findReferences" * Update CHANGELOG.md * Fix an issue in Windows tests .. by reverting https://github.com/razzmatazz/csharp-language-server/pull/189 (will fix later) * another attempt at it --- CHANGELOG.md | 2 + src/CSharpLanguageServer/Conversions.fs | 2 +- src/CSharpLanguageServer/Handlers/CodeLens.fs | 9 +- .../Handlers/References.fs | 8 +- .../State/ServerRequestContext.fs | 16 +++- .../CSharpLanguageServer.Tests.fsproj | 1 + .../ReferenceTests.fs | 85 +++++++++++++++++++ .../testReferenceWorks/Project/Class.cs | 11 +++ .../testReferenceWorks/Project/Project.csproj | 6 ++ tests/CSharpLanguageServer.Tests/Tooling.fs | 5 +- 10 files changed, 131 insertions(+), 14 deletions(-) create mode 100644 tests/CSharpLanguageServer.Tests/ReferenceTests.fs create mode 100644 tests/CSharpLanguageServer.Tests/TestData/testReferenceWorks/Project/Class.cs create mode 100644 tests/CSharpLanguageServer.Tests/TestData/testReferenceWorks/Project/Project.csproj diff --git a/CHANGELOG.md b/CHANGELOG.md index ca2966f2..6948ac7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). * Upgrade Roslyn to 4.11.0 * Fix an issue where server breaks when inspecting class/properties involved in source-generated code: - By @granitrocky in https://github.com/razzmatazz/csharp-language-server/pull/189 +* Make sure textDocument/findReferences respects Context.IncludeDeclaration + - https://github.com/razzmatazz/csharp-language-server/pull/199 ## [0.15.0] - 2024-08-15 / Šventoji * Upgrade Roslyn to 4.10.0: diff --git a/src/CSharpLanguageServer/Conversions.fs b/src/CSharpLanguageServer/Conversions.fs index 4ad46651..f821c54d 100644 --- a/src/CSharpLanguageServer/Conversions.fs +++ b/src/CSharpLanguageServer/Conversions.fs @@ -23,7 +23,7 @@ module Uri = if path.StartsWith(metadataPrefix) then "csharp:/metadata/" + path.Substring(metadataPrefix.Length) else - Uri(path, UriKind.RelativeOrAbsolute).ToString() + Uri(path).ToString() let toWorkspaceFolder(uri: string): WorkspaceFolder = { Uri = uri diff --git a/src/CSharpLanguageServer/Handlers/CodeLens.fs b/src/CSharpLanguageServer/Handlers/CodeLens.fs index 10bf462b..05a542af 100644 --- a/src/CSharpLanguageServer/Handlers/CodeLens.fs +++ b/src/CSharpLanguageServer/Handlers/CodeLens.fs @@ -162,13 +162,12 @@ module CodeLens = match! context.FindSymbol lensData.DocumentUri lensData.Position with | None -> return p |> success | Some symbol -> - let! refs = context.FindReferences symbol + let! locations = context.FindReferences symbol false // FIXME: refNum is wrong. There are lots of false positive even if we distinct locations by - // (l.Location.SourceTree.FilePath, l.Location.SourceSpan) + // (l.SourceTree.FilePath, l.SourceSpan) let refNum = - refs - |> Seq.collect (fun r -> r.Locations) - |> Seq.distinctBy (fun l -> (l.Location.SourceTree.FilePath, l.Location.SourceSpan)) + locations + |> Seq.distinctBy (fun l -> (l.SourceTree.FilePath, l.SourceSpan)) |> Seq.length let title = sprintf "%d Reference(s)" refNum diff --git a/src/CSharpLanguageServer/Handlers/References.fs b/src/CSharpLanguageServer/Handlers/References.fs index f1602d8f..81f39ac6 100644 --- a/src/CSharpLanguageServer/Handlers/References.fs +++ b/src/CSharpLanguageServer/Handlers/References.fs @@ -42,11 +42,11 @@ module References = match! context.FindSymbol p.TextDocument.Uri p.Position with | None -> return None |> success | Some symbol -> - let! refs = context.FindReferences symbol + let! locations = context.FindReferences symbol p.Context.IncludeDeclaration + return - refs - |> Seq.collect (fun r -> r.Locations) - |> Seq.map (fun rl -> Location.fromRoslynLocation rl.Location) + locations + |> Seq.map Location.fromRoslynLocation |> Seq.distinct |> Seq.toArray |> Some diff --git a/src/CSharpLanguageServer/State/ServerRequestContext.fs b/src/CSharpLanguageServer/State/ServerRequestContext.fs index 23bf796d..133395c5 100644 --- a/src/CSharpLanguageServer/State/ServerRequestContext.fs +++ b/src/CSharpLanguageServer/State/ServerRequestContext.fs @@ -193,7 +193,7 @@ type ServerRequestContext (requestId: int, state: ServerState, emitServerEvent) return aggregatedLspLocations } - member this.FindSymbols (pattern: string option): Async = async { + member this.FindSymbols (pattern: string option): Async = async { let findTask ct = match pattern with | Some pat -> @@ -209,14 +209,24 @@ type ServerRequestContext (requestId: int, state: ServerState, emitServerEvent) return! findTask ct solution |> Async.AwaitTask } - member this.FindReferences (symbol: ISymbol): Async = async { + member this.FindReferences (symbol: ISymbol) (withDefinition: bool): Async = async { match this.State.Solution with | None -> return [] | Some solution -> let! ct = Async.CancellationToken - return! + + let locationsFromReferencedSym (r: ReferencedSymbol) = + let locations = r.Locations |> Seq.map (fun rl -> rl.Location) + + match withDefinition with + | true -> locations |> Seq.append r.Definition.Locations + | false -> locations + + let! refs = SymbolFinder.FindReferencesAsync(symbol, solution, cancellationToken=ct) |> Async.AwaitTask + + return refs |> Seq.collect locationsFromReferencedSym } member this.GetDocumentVersion (uri: DocumentUri): int option = diff --git a/tests/CSharpLanguageServer.Tests/CSharpLanguageServer.Tests.fsproj b/tests/CSharpLanguageServer.Tests/CSharpLanguageServer.Tests.fsproj index aadf245d..a7f4bac1 100644 --- a/tests/CSharpLanguageServer.Tests/CSharpLanguageServer.Tests.fsproj +++ b/tests/CSharpLanguageServer.Tests/CSharpLanguageServer.Tests.fsproj @@ -13,6 +13,7 @@ + diff --git a/tests/CSharpLanguageServer.Tests/ReferenceTests.fs b/tests/CSharpLanguageServer.Tests/ReferenceTests.fs new file mode 100644 index 00000000..c60d4265 --- /dev/null +++ b/tests/CSharpLanguageServer.Tests/ReferenceTests.fs @@ -0,0 +1,85 @@ +module CSharpLanguageServer.Tests.ReferenceTests + +open NUnit.Framework +open Ionide.LanguageServerProtocol.Types + +open CSharpLanguageServer.Tests.Tooling + +[] +let testReferenceWorks() = + use client = setupServerClient defaultClientProfile "TestData/testReferenceWorks" + client.StartAndWaitForSolutionLoad() + + use classFile = client.Open("Project/Class.cs") + + // + // try references request at empty line line 1 -- should return 0 results + // + let referenceParams0: ReferenceParams = + { TextDocument = { Uri = classFile.Uri } + Position = { Line = 0u; Character = 0u } + WorkDoneToken = None + PartialResultToken = None + Context = { IncludeDeclaration = false } + } + + let locations0: Location[] option = classFile.Request("textDocument/references", referenceParams0) + Assert.IsTrue(locations0.IsNone) + + // + // try references request at MethodA declaration on line 2 + // + let referenceParams1: ReferenceParams = + { TextDocument = { Uri = classFile.Uri } + Position = { Line = 2u; Character = 16u } + WorkDoneToken = None + PartialResultToken = None + Context = { IncludeDeclaration = false } + } + + let locations1: Location[] option = classFile.Request("textDocument/references", referenceParams1) + + let expectedLocations1: Location array = + [| + { Uri = classFile.Uri + Range = { + Start = { Line = 8u; Character = 8u } + End = { Line = 8u; Character = 15u } + } + } + |] + + Assert.AreEqual(expectedLocations1, locations1.Value) + + // + // try references request at MethodA declaration on line 2 + // (with IncludeDeclaration=true) + // + let referenceParams2: ReferenceParams = + { TextDocument = { Uri = classFile.Uri } + Position = { Line = 2u; Character = 16u } + WorkDoneToken = None + PartialResultToken = None + Context = { IncludeDeclaration = true } + } + + let locations2: Location[] option = classFile.Request("textDocument/references", referenceParams2) + + let expectedLocations2: Location array = + [| + { Uri = classFile.Uri + Range = { + Start = { Line = 2u; Character = 16u } + End = { Line = 2u; Character = 23u } + } + } + + { Uri = classFile.Uri + Range = { + Start = { Line = 8u; Character = 8u } + End = { Line = 8u; Character = 15u } + } + } + |] + + Assert.AreEqual(expectedLocations2, locations2.Value) diff --git a/tests/CSharpLanguageServer.Tests/TestData/testReferenceWorks/Project/Class.cs b/tests/CSharpLanguageServer.Tests/TestData/testReferenceWorks/Project/Class.cs new file mode 100644 index 00000000..644b743a --- /dev/null +++ b/tests/CSharpLanguageServer.Tests/TestData/testReferenceWorks/Project/Class.cs @@ -0,0 +1,11 @@ +class Class +{ + public void MethodA(string arg) + { + } + + public void MethodB(string arg) + { + MethodA(arg); + } +} diff --git a/tests/CSharpLanguageServer.Tests/TestData/testReferenceWorks/Project/Project.csproj b/tests/CSharpLanguageServer.Tests/TestData/testReferenceWorks/Project/Project.csproj new file mode 100644 index 00000000..dcb96604 --- /dev/null +++ b/tests/CSharpLanguageServer.Tests/TestData/testReferenceWorks/Project/Project.csproj @@ -0,0 +1,6 @@ + + + Exe + net8.0 + + diff --git a/tests/CSharpLanguageServer.Tests/Tooling.fs b/tests/CSharpLanguageServer.Tests/Tooling.fs index f946dd25..508eb8c1 100644 --- a/tests/CSharpLanguageServer.Tests/Tooling.fs +++ b/tests/CSharpLanguageServer.Tests/Tooling.fs @@ -613,7 +613,10 @@ type ClientController (client: MailboxProcessor, testDataDir: Direc && (m.Message.["method"] |> string) = rpcMethod) member __.Open(filename: string): FileController = - let uri = "file://" + projectDir.Value + "/" + filename + let uri = + match System.Environment.OSVersion.Platform with + | PlatformID.Win32NT -> ("file:///" + projectDir.Value + "/" + filename).Replace("\\", "/") + | _ -> "file://" + projectDir.Value + "/" + filename let fileText = Path.Combine(projectDir.Value, filename)