Skip to content

Commit

Permalink
Add tests for textDocument/findReferences, implement support for Cont…
Browse files Browse the repository at this point in the history
…ext.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 #189 (will fix later)

* another attempt at it
  • Loading branch information
razzmatazz authored Nov 15, 2024
1 parent 437146e commit aab7156
Show file tree
Hide file tree
Showing 10 changed files with 131 additions and 14 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion src/CSharpLanguageServer/Conversions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 4 additions & 5 deletions src/CSharpLanguageServer/Handlers/CodeLens.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions src/CSharpLanguageServer/Handlers/References.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 13 additions & 3 deletions src/CSharpLanguageServer/State/ServerRequestContext.fs
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ type ServerRequestContext (requestId: int, state: ServerState, emitServerEvent)
return aggregatedLspLocations
}

member this.FindSymbols (pattern: string option): Async<ISymbol seq> = async {
member this.FindSymbols (pattern: string option): Async<Microsoft.CodeAnalysis.ISymbol seq> = async {
let findTask ct =
match pattern with
| Some pat ->
Expand All @@ -209,14 +209,24 @@ type ServerRequestContext (requestId: int, state: ServerState, emitServerEvent)
return! findTask ct solution |> Async.AwaitTask
}

member this.FindReferences (symbol: ISymbol): Async<ReferencedSymbol seq> = async {
member this.FindReferences (symbol: ISymbol) (withDefinition: bool): Async<Microsoft.CodeAnalysis.Location seq> = 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 =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<Compile Include="DocumentationTests.fs" />
<Compile Include="HoverTests.fs" />
<Compile Include="InitializationTests.fs" />
<Compile Include="ReferenceTests.fs" />
</ItemGroup>

<ItemGroup>
Expand Down
85 changes: 85 additions & 0 deletions tests/CSharpLanguageServer.Tests/ReferenceTests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
module CSharpLanguageServer.Tests.ReferenceTests

open NUnit.Framework
open Ionide.LanguageServerProtocol.Types

open CSharpLanguageServer.Tests.Tooling

[<TestCase>]
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)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class Class
{
public void MethodA(string arg)
{
}

public void MethodB(string arg)
{
MethodA(arg);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
</Project>
5 changes: 4 additions & 1 deletion tests/CSharpLanguageServer.Tests/Tooling.fs
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,10 @@ type ClientController (client: MailboxProcessor<ClientEvent>, 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)
Expand Down

0 comments on commit aab7156

Please sign in to comment.