File: CallHierarchy\CallHierarchyTests.cs
Web Access
Project: src\src\LanguageServer\ProtocolUnitTests\Microsoft.CodeAnalysis.LanguageServer.Protocol.UnitTests.csproj (Microsoft.CodeAnalysis.LanguageServer.Protocol.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Roslyn.Test.Utilities;
using Xunit;
using Xunit.Abstractions;
using LSP = Roslyn.LanguageServer.Protocol;
 
namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.CallHierarchy;
 
public sealed class CallHierarchyTests(ITestOutputHelper testOutputHelper) : AbstractLanguageServerProtocolTests(testOutputHelper)
{
    [Theory, CombinatorialData]
    public async Task TestPrepareCallHierarchyIncludesContainingTypeInName(bool mutatingLspWorkspace)
    {
        var markup = """
            class C
            {
                void {|definition:M|}()
                {
                }
 
                void N()
                {
                    {|caret:|}M();
                }
            }
            """;
 
        await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace);
 
        var preparedItems = await RunPrepareCallHierarchyAsync(testLspServer, testLspServer.GetLocations("caret").Single());
 
        var preparedItem = Assert.Single(preparedItems);
        var definition = testLspServer.GetLocations("definition").Single();
 
        Assert.Equal("C.M()", preparedItem.Name);
        Assert.Equal(definition.DocumentUri, preparedItem.Uri);
        Assert.Equal(0, CompareRange(definition.Range, preparedItem.SelectionRange));
        Assert.NotNull(preparedItem.Data);
    }
 
    [Theory, CombinatorialData]
    public async Task TestOutgoingCallsIncludeImplicitConstructors(bool mutatingLspWorkspace)
    {
        var markup = """
            class {|type:C|}
            {
                public void {|targetMethod:M|}()
                {
                }
            }
 
            class Caller
            {
                void {|caret:N|}()
                {
                    var c = new C();
                    c.M();
                }
            }
            """;
 
        await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace);
 
        var preparedItem = Assert.Single(await RunPrepareCallHierarchyAsync(testLspServer, testLspServer.GetLocations("caret").Single()));
        var outgoingCalls = await RunOutgoingCallsAsync(testLspServer, preparedItem);
 
        Assert.Equal(2, outgoingCalls.Length);
 
        var constructorCall = Assert.Single(outgoingCalls, static call => call.To.Name == "C.C()");
        var methodCall = Assert.Single(outgoingCalls, static call => call.To.Name == "C.M()");
 
        var typeLocation = testLspServer.GetLocations("type").Single();
        var methodLocation = testLspServer.GetLocations("targetMethod").Single();
 
        Assert.Equal(typeLocation.DocumentUri, constructorCall.To.Uri);
        Assert.Equal(0, CompareRange(typeLocation.Range, constructorCall.To.SelectionRange));
        Assert.Single(constructorCall.FromRanges);
 
        Assert.Equal(methodLocation.DocumentUri, methodCall.To.Uri);
        Assert.Equal(0, CompareRange(methodLocation.Range, methodCall.To.SelectionRange));
        Assert.Single(methodCall.FromRanges);
    }
 
    [Theory, CombinatorialData]
    public async Task TestIncomingCallsIncludesManyCallers(bool mutatingLspWorkspace)
    {
        var markup = """
            class Target
            {
                void {|definition:M|}()
                {
                }
            }
 
            class Caller
            {
                void {|caller1:Caller1|}()
                {
                    var target = new Target();
                    target.{|caret:|}M();
                }
 
                void {|caller2:Caller2|}()
                {
                    var target = new Target();
                    target.M();
                }
            }
            """;
 
        await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace);
 
        var preparedItem = Assert.Single(await RunPrepareCallHierarchyAsync(testLspServer, testLspServer.GetLocations("caret").Single()));
        var incomingCalls = await RunIncomingCallsAsync(testLspServer, preparedItem);
 
        Assert.Equal(2, incomingCalls.Length);
 
        var firstCaller = Assert.Single(incomingCalls, static call => call.From.Name == "Caller.Caller1()");
        var secondCaller = Assert.Single(incomingCalls, static call => call.From.Name == "Caller.Caller2()");
 
        var firstCallerLocation = testLspServer.GetLocations("caller1").Single();
        var secondCallerLocation = testLspServer.GetLocations("caller2").Single();
 
        Assert.Equal(0, CompareRange(firstCallerLocation.Range, firstCaller.From.SelectionRange));
        Assert.Equal(0, CompareRange(secondCallerLocation.Range, secondCaller.From.SelectionRange));
        Assert.Single(firstCaller.FromRanges);
        Assert.Single(secondCaller.FromRanges);
    }
 
    [Theory, CombinatorialData]
    public async Task TestOutgoingCallsIncludesMethodPropertyAndField(bool mutatingLspWorkspace)
    {
        var markup = """
            class C
            {
                int {|field:F|};
 
                int {|property:P|} => F;
 
                void {|method:M|}()
                {
                }
 
                void {|caret:N|}()
                {
                    M();
                    var p = P;
                    var f = F;
                }
            }
            """;
 
        await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace);
 
        var preparedItem = Assert.Single(await RunPrepareCallHierarchyAsync(testLspServer, testLspServer.GetLocations("caret").Single()));
        var outgoingCalls = await RunOutgoingCallsAsync(testLspServer, preparedItem);
 
        Assert.Equal(3, outgoingCalls.Length);
 
        var methodCall = Assert.Single(outgoingCalls, static call => call.To.Name == "C.M()");
        var propertyCall = Assert.Single(outgoingCalls, static call => call.To.Name == "C.P");
        var fieldCall = Assert.Single(outgoingCalls, static call => call.To.Name == "C.F");
 
        Assert.Equal(0, CompareRange(testLspServer.GetLocations("method").Single().Range, methodCall.To.SelectionRange));
        Assert.Equal(0, CompareRange(testLspServer.GetLocations("property").Single().Range, propertyCall.To.SelectionRange));
        Assert.Equal(0, CompareRange(testLspServer.GetLocations("field").Single().Range, fieldCall.To.SelectionRange));
 
        Assert.Single(methodCall.FromRanges);
        Assert.Single(propertyCall.FromRanges);
        Assert.Single(fieldCall.FromRanges);
    }
 
    private static async Task<LSP.CallHierarchyItem[]> RunPrepareCallHierarchyAsync(TestLspServer testLspServer, LSP.Location caret)
        => await testLspServer.ExecuteRequestAsync<LSP.CallHierarchyPrepareParams, LSP.CallHierarchyItem[]?>(
            LSP.Methods.PrepareCallHierarchyName,
            new LSP.CallHierarchyPrepareParams
            {
                TextDocument = CreateTextDocumentIdentifier(caret.DocumentUri),
                Position = caret.Range.Start,
            },
            CancellationToken.None) ?? [];
 
    private static async Task<LSP.CallHierarchyOutgoingCall[]> RunOutgoingCallsAsync(TestLspServer testLspServer, LSP.CallHierarchyItem item)
        => await testLspServer.ExecuteRequestAsync<LSP.CallHierarchyOutgoingCallsParams, LSP.CallHierarchyOutgoingCall[]?>(
            LSP.Methods.CallHierarchyOutgoingCallsName,
            new LSP.CallHierarchyOutgoingCallsParams
            {
                Item = item,
            },
            CancellationToken.None) ?? [];
 
    private static async Task<LSP.CallHierarchyIncomingCall[]> RunIncomingCallsAsync(TestLspServer testLspServer, LSP.CallHierarchyItem item)
        => await testLspServer.ExecuteRequestAsync<LSP.CallHierarchyIncomingCallsParams, LSP.CallHierarchyIncomingCall[]?>(
            LSP.Methods.CallHierarchyIncomingCallsName,
            new LSP.CallHierarchyIncomingCallsParams
            {
                Item = item,
            },
            CancellationToken.None) ?? [];
}