diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test.Common/CSharpTestLspServerHelpers.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test.Common/CSharpTestLspServerHelpers.cs index 12a85ea9d04..26890783244 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test.Common/CSharpTestLspServerHelpers.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test.Common/CSharpTestLspServerHelpers.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.LanguageServer.Test.Common.Extensions; @@ -23,6 +24,9 @@ internal class CSharpTestLspServerHelpers { private const string EditRangeSetting = "editRange"; + public static Task CreateCSharpLspServerAsync(SourceText csharpSourceText, Uri csharpDocumentUri, ServerCapabilities serverCapabilities) => + CreateCSharpLspServerAsync(csharpSourceText, csharpDocumentUri, serverCapabilities, new EmptyMappingService()); + public static async Task CreateCSharpLspServerAsync( SourceText csharpSourceText, Uri csharpDocumentUri, @@ -111,5 +115,14 @@ private static AdhocWorkspace CreateCSharpTestWorkspace( } private record CSharpFile(Uri DocumentUri, SourceText CSharpSourceText); + + private class EmptyMappingService : IRazorSpanMappingService + { + public Task> MapSpansAsync(Document document, IEnumerable spans, CancellationToken cancellationToken) + { + var result = Enumerable.Empty().ToImmutableArray(); + return Task.FromResult(result); + } + } } } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Completion/Delegation/DelegatedCompletionItemResolverTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Completion/Delegation/DelegatedCompletionItemResolverTest.cs index 47b5381c574..73e9a1107f8 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Completion/Delegation/DelegatedCompletionItemResolverTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Completion/Delegation/DelegatedCompletionItemResolverTest.cs @@ -14,14 +14,33 @@ using Microsoft.AspNetCore.Razor.Test.Common; using Microsoft.VisualStudio.LanguageServer.Protocol; using Xunit; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Razor.Workspaces.Extensions; +using System.Linq; +using Microsoft.VisualStudio.TestPlatform.Utilities; +using Microsoft.AspNetCore.Razor.Language; +using Xunit.Sdk; namespace Microsoft.AspNetCore.Razor.LanguageServer.Completion.Delegation { + [UseExportProvider] public class DelegatedCompletionItemResolverTest : LanguageServerTestBase { public DelegatedCompletionItemResolverTest() { - ClientCapabilities = new VSInternalClientCapabilities(); + ClientCapabilities = new VSInternalClientCapabilities() + { + TextDocument = new() + { + Completion = new VSInternalCompletionSetting() + { + CompletionList = new() + { + Data = true, + } + } + } + }; var documentContext = TestDocumentContext.From("C:/path/to/file.cshtml"); CSharpCompletionParams = new DelegatedCompletionParams(documentContext.Identifier, new Position(10, 6), RazorLanguageKind.CSharp, new VSInternalCompletionContext(), ProvisionalTextEdit: null); HtmlCompletionParams = new DelegatedCompletionParams(documentContext.Identifier, new Position(0, 0), RazorLanguageKind.Html, new VSInternalCompletionContext(), ProvisionalTextEdit: null); @@ -37,7 +56,7 @@ public DelegatedCompletionItemResolverTest() public async Task ResolveAsync_CanNotFindCompletionItem_Noops() { // Arrange - var server = TestItemResolverServer.Create(); + var server = TestDelegatedCompletionItemResolverServer.Create(); var resolver = new DelegatedCompletionItemResolver(server); var item = new VSInternalCompletionItem(); var notContainingCompletionList = new VSInternalCompletionList(); @@ -54,7 +73,7 @@ public async Task ResolveAsync_CanNotFindCompletionItem_Noops() public async Task ResolveAsync_UnknownRequestContext_Noops() { // Arrange - var server = TestItemResolverServer.Create(); + var server = TestDelegatedCompletionItemResolverServer.Create(); var resolver = new DelegatedCompletionItemResolver(server); var item = new VSInternalCompletionItem(); var containingCompletionList = new VSInternalCompletionList() { Items = new[] { item, } }; @@ -71,7 +90,7 @@ public async Task ResolveAsync_UnknownRequestContext_Noops() public async Task ResolveAsync_UsesItemsData() { // Arrange - var server = TestItemResolverServer.Create(); + var server = TestDelegatedCompletionItemResolverServer.Create(); var resolver = new DelegatedCompletionItemResolver(server); var expectedData = new object(); var item = new VSInternalCompletionItem() @@ -92,7 +111,7 @@ public async Task ResolveAsync_UsesItemsData() public async Task ResolveAsync_InheritsOriginalCompletionListData() { // Arrange - var server = TestItemResolverServer.Create(); + var server = TestDelegatedCompletionItemResolverServer.Create(); var resolver = new DelegatedCompletionItemResolver(server); var item = new VSInternalCompletionItem(); var containingCompletionList = new VSInternalCompletionList() { Items = new[] { item, }, Data = new object() }; @@ -109,21 +128,11 @@ public async Task ResolveAsync_InheritsOriginalCompletionListData() [Fact] public async Task ResolveAsync_CSharp_Resolves() { - // Arrange - var expectedResolvedItem = new VSInternalCompletionItem(); - var server = TestItemResolverServer.Create(expectedResolvedItem); - var resolver = new DelegatedCompletionItemResolver(server); - var item = new VSInternalCompletionItem(); - var containingCompletionList = new VSInternalCompletionList() { Items = new[] { item, } }; - var originalRequestContext = new DelegatedCompletionResolutionContext(CSharpCompletionParams, new object()); - - // Act - var resolvedItem = await resolver.ResolveAsync(item, containingCompletionList, originalRequestContext, ClientCapabilities, CancellationToken.None); + // Arrange & Act + var resolvedItem = await ResolveCompletionItemAsync("@$$", itemToResolve: "typeof", CancellationToken.None).ConfigureAwait(false); // Assert - Assert.Same(CSharpCompletionParams.HostDocument, server.DelegatedParams.HostDocument); - Assert.Equal(RazorLanguageKind.CSharp, server.DelegatedParams.OriginatingKind); - Assert.Same(expectedResolvedItem, resolvedItem); + Assert.NotNull(resolvedItem.Description); } [Fact] @@ -131,7 +140,7 @@ public async Task ResolveAsync_Html_Resolves() { // Arrange var expectedResolvedItem = new VSInternalCompletionItem(); - var server = TestItemResolverServer.Create(expectedResolvedItem); + var server = TestDelegatedCompletionItemResolverServer.Create(expectedResolvedItem); var resolver = new DelegatedCompletionItemResolver(server); var item = new VSInternalCompletionItem(); var containingCompletionList = new VSInternalCompletionList() { Items = new[] { item, } }; @@ -146,11 +155,66 @@ public async Task ResolveAsync_Html_Resolves() Assert.Same(expectedResolvedItem, resolvedItem); } - internal class TestItemResolverServer : TestOmnisharpLanguageServer + private async Task ResolveCompletionItemAsync(string content, string itemToResolve, CancellationToken none) + { + TestFileMarkupParser.GetPosition(content, out var documentContent, out var cursorPosition); + var codeDocument = CreateCodeDocument(documentContent); + await using var csharpServer = await CreateCSharpServerAsync(codeDocument).ConfigureAwait(false); + + var server = TestDelegatedCompletionItemResolverServer.Create(csharpServer); + var resolver = new DelegatedCompletionItemResolver(server); + var (containingCompletionList, csharpCompletionParams) = await GetCompletionListAndOriginalParamsAsync(cursorPosition, codeDocument, csharpServer).ConfigureAwait(false); + + var originalRequestContext = new DelegatedCompletionResolutionContext(csharpCompletionParams, containingCompletionList.Data); + var item = (VSInternalCompletionItem)containingCompletionList.Items.FirstOrDefault(item => item.Label == itemToResolve); + + if (item is null) + { + throw new XunitException($"Could not locate completion item '{item.Label}' for completion resolve test"); + } + + var resolvedItem = await resolver.ResolveAsync(item, containingCompletionList, originalRequestContext, ClientCapabilities, CancellationToken.None).ConfigureAwait(false); + return resolvedItem; + } + + private async Task CreateCSharpServerAsync(RazorCodeDocument codeDocument) + { + var csharpSourceText = codeDocument.GetCSharpSourceText(); + var csharpDocumentUri = new Uri("C:/path/to/file.razor__virtual.g.cs"); + var serverCapabilities = new ServerCapabilities() + { + CompletionProvider = new CompletionOptions + { + ResolveProvider = true, + TriggerCharacters = new[] { " ", "(", "=", "#", ".", "<", "[", "{", "\"", "/", ":", "~" } + } + }; + var csharpServer = await CSharpTestLspServerHelpers.CreateCSharpLspServerAsync(csharpSourceText, csharpDocumentUri, serverCapabilities).ConfigureAwait(false); + + await csharpServer.OpenDocumentAsync(csharpDocumentUri, csharpSourceText.ToString()).ConfigureAwait(false); + + return csharpServer; + } + + private async Task<(VSInternalCompletionList, DelegatedCompletionParams)> GetCompletionListAndOriginalParamsAsync( + int cursorPosition, + RazorCodeDocument codeDocument, + CSharpTestLspServer csharpServer) + { + var completionContext = new VSInternalCompletionContext() { TriggerKind = CompletionTriggerKind.Invoked }; + var documentContext = TestDocumentContext.From("C:/path/to/file.razor", codeDocument, hostDocumentVersion: 1337); + var provider = TestDelegatedCompletionListProvider.Create(csharpServer); + + var completionList = await provider.GetCompletionListAsync(cursorPosition, completionContext, documentContext, ClientCapabilities, CancellationToken.None).ConfigureAwait(false); + + return (completionList, provider.DelegatedParams); + } + + internal class TestDelegatedCompletionItemResolverServer : TestOmnisharpLanguageServer { - private readonly DelegatedCompletionResolveRequestHandler _requestHandler; + private readonly CompletionResolveRequestResponseFactory _requestHandler; - private TestItemResolverServer(DelegatedCompletionResolveRequestHandler requestHandler) : base(new Dictionary>>() + private TestDelegatedCompletionItemResolverServer(CompletionResolveRequestResponseFactory requestHandler) : base(new Dictionary>>() { [LanguageServerConstants.RazorCompletionResolveEndpointName] = requestHandler.OnDelegationAsync, }) @@ -160,32 +224,74 @@ private TestItemResolverServer(DelegatedCompletionResolveRequestHandler requestH public DelegatedCompletionItemResolveParams DelegatedParams => _requestHandler.DelegatedParams; - public static TestItemResolverServer Create(VSInternalCompletionItem resolveResponse = null) + public static TestDelegatedCompletionItemResolverServer Create(CSharpTestLspServer csharpServer) + { + var requestHandler = new DelegatedCSharpCompletionRequestHandler(csharpServer); + var provider = new TestDelegatedCompletionItemResolverServer(requestHandler); + return provider; + } + + public static TestDelegatedCompletionItemResolverServer Create(VSInternalCompletionItem resolveResponse = null) { resolveResponse ??= new VSInternalCompletionItem(); - var requestResponseFactory = new DelegatedCompletionResolveRequestHandler(resolveResponse); - var provider = new TestItemResolverServer(requestResponseFactory); + var requestResponseFactory = new StaticCompletionResolveRequestHandler(resolveResponse); + var provider = new TestDelegatedCompletionItemResolverServer(requestResponseFactory); return provider; } - private class DelegatedCompletionResolveRequestHandler + private class StaticCompletionResolveRequestHandler : CompletionResolveRequestResponseFactory { private readonly VSInternalCompletionItem _resolveResponse; + private DelegatedCompletionItemResolveParams _delegatedParams; - public DelegatedCompletionResolveRequestHandler(VSInternalCompletionItem resolveResponse) + public StaticCompletionResolveRequestHandler(VSInternalCompletionItem resolveResponse) { _resolveResponse = resolveResponse; } - public DelegatedCompletionItemResolveParams DelegatedParams { get; private set; } + public override DelegatedCompletionItemResolveParams DelegatedParams => _delegatedParams; - public Task OnDelegationAsync(object parameters) + public override Task OnDelegationAsync(object parameters) { - DelegatedParams = (DelegatedCompletionItemResolveParams)parameters; + var resolveParams = (DelegatedCompletionItemResolveParams)parameters; + _delegatedParams = resolveParams; return Task.FromResult(_resolveResponse); } } + + private class DelegatedCSharpCompletionRequestHandler : CompletionResolveRequestResponseFactory + { + private readonly CSharpTestLspServer _csharpServer; + private DelegatedCompletionItemResolveParams _delegatedParams; + + public DelegatedCSharpCompletionRequestHandler(CSharpTestLspServer csharpServer) + { + _csharpServer = csharpServer; + } + + public override DelegatedCompletionItemResolveParams DelegatedParams => _delegatedParams; + + public override async Task OnDelegationAsync(object parameters) + { + var resolveParams = (DelegatedCompletionItemResolveParams)parameters; + _delegatedParams = resolveParams; + + var resolvedCompletionItem = await _csharpServer.ExecuteRequestAsync( + Methods.TextDocumentCompletionResolveName, + _delegatedParams.CompletionItem, + CancellationToken.None).ConfigureAwait(false); + + return resolvedCompletionItem; + } + } + + private abstract class CompletionResolveRequestResponseFactory + { + public abstract DelegatedCompletionItemResolveParams DelegatedParams { get; } + + public abstract Task OnDelegationAsync(object parameters); + } } } } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Completion/Delegation/DelegatedCompletionListProviderTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Completion/Delegation/DelegatedCompletionListProviderTest.cs index f92b1d94c78..4d8e5b4ea20 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Completion/Delegation/DelegatedCompletionListProviderTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Completion/Delegation/DelegatedCompletionListProviderTest.cs @@ -273,7 +273,7 @@ private async Task GetCompletionListAsync(string conte } }; await using var csharpServer = await CSharpTestLspServerHelpers.CreateCSharpLspServerAsync( - csharpSourceText, csharpDocumentUri, serverCapabilities, new EmptyMappingService()).ConfigureAwait(false); + csharpSourceText, csharpDocumentUri, serverCapabilities).ConfigureAwait(false); await csharpServer.OpenDocumentAsync(csharpDocumentUri, csharpSourceText.ToString()).ConfigureAwait(false); @@ -292,14 +292,5 @@ private async Task GetCompletionListAsync(string conte var completionList = await provider.GetCompletionListAsync(absoluteIndex: cursorPosition, completionContext, documentContext, ClientCapabilities, CancellationToken.None); return completionList; } - - private class EmptyMappingService : IRazorSpanMappingService - { - public Task> MapSpansAsync(Document document, IEnumerable spans, CancellationToken cancellationToken) - { - var result = Enumerable.Empty().ToImmutableArray(); - return Task.FromResult(result); - } - } } }