From efceb90f2d3bf946f6cdc1ab8d4ac35a10dd1c4a Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 20 Aug 2024 17:06:26 +1000 Subject: [PATCH 1/8] Allow LSP and cohosting to provide specialized methods to get a syntax tree for a C# document --- .../RazorComponentDefinitionService.cs | 10 ++++++++ ...AbstractRazorComponentDefinitionService.cs | 5 +++- .../RazorComponentDefinitionHelpers.cs | 10 ++++---- .../RazorComponentDefinitionService.cs | 25 +++++++++++++++++++ .../RazorComponentDefinitionHelpersTest.cs | 7 +++++- 5 files changed, 50 insertions(+), 7 deletions(-) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Definition/RazorComponentDefinitionService.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Definition/RazorComponentDefinitionService.cs index 9a330263563..658c30fc53d 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Definition/RazorComponentDefinitionService.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Definition/RazorComponentDefinitionService.cs @@ -5,10 +5,15 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Razor.DocumentMapping; using Microsoft.CodeAnalysis.Razor.GoToDefinition; using Microsoft.CodeAnalysis.Razor.Logging; +using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Razor.Workspaces; namespace Microsoft.AspNetCore.Razor.LanguageServer.Definition; @@ -19,4 +24,9 @@ internal sealed class RazorComponentDefinitionService( ILoggerFactory loggerFactory) : AbstractRazorComponentDefinitionService(componentSearchEngine, documentMappingService, loggerFactory.GetOrCreateLogger()) { + protected override ValueTask GetCSharpSyntaxTreeAsync(IDocumentSnapshot documentSnapshot, RazorCodeDocument codeDocument, CancellationToken cancellationToken) + { + var csharpText = codeDocument.GetCSharpSourceText(); + return new(CSharpSyntaxTree.ParseText(csharpText, cancellationToken: cancellationToken)); + } } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/GoToDefinition/AbstractRazorComponentDefinitionService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/GoToDefinition/AbstractRazorComponentDefinitionService.cs index 737614147f9..c28f78efb4e 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/GoToDefinition/AbstractRazorComponentDefinitionService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/GoToDefinition/AbstractRazorComponentDefinitionService.cs @@ -70,9 +70,10 @@ private async Task GetNavigateRangeAsync(IDocumentSnapshot documentSna _logger.LogInformation($"Attempting to get definition from an attribute directly."); var originCodeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); + var syntaxTree = await GetCSharpSyntaxTreeAsync(documentSnapshot, originCodeDocument, cancellationToken).ConfigureAwait(false); var range = await RazorComponentDefinitionHelpers - .TryGetPropertyRangeAsync(originCodeDocument, attributeDescriptor.GetPropertyName(), _documentMappingService, _logger, cancellationToken) + .TryGetPropertyRangeAsync(originCodeDocument, syntaxTree, attributeDescriptor.GetPropertyName(), _documentMappingService, _logger, cancellationToken) .ConfigureAwait(false); if (range is not null) @@ -87,4 +88,6 @@ private async Task GetNavigateRangeAsync(IDocumentSnapshot documentSna // at least then press F7 to go there. return VsLspFactory.DefaultRange; } + + protected abstract ValueTask GetCSharpSyntaxTreeAsync(IDocumentSnapshot documentSnapshot, RazorCodeDocument codeDocument, CancellationToken cancellationToken); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/GoToDefinition/RazorComponentDefinitionHelpers.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/GoToDefinition/RazorComponentDefinitionHelpers.cs index 66177fb6be2..f01d60d4ac8 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/GoToDefinition/RazorComponentDefinitionHelpers.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/GoToDefinition/RazorComponentDefinitionHelpers.cs @@ -8,7 +8,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.Syntax; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Razor.DocumentMapping; using Microsoft.CodeAnalysis.Razor.Logging; @@ -131,12 +130,13 @@ static bool TryGetTagName(RazorSyntaxNode node, [NotNullWhen(true)] out RazorSyn public static async Task TryGetPropertyRangeAsync( RazorCodeDocument codeDocument, + SyntaxTree csharpSyntaxTree, string propertyName, IDocumentMappingService documentMappingService, ILogger logger, CancellationToken cancellationToken) { - // Parse the C# file and find the property that matches the name. + // Process the C# tree and find the property that matches the name. // We don't worry about parameter attributes here for two main reasons: // 1. We don't have symbolic information, so the best we could do would be checking for any // attribute named Parameter, regardless of which namespace. It also means we would have @@ -147,9 +147,8 @@ static bool TryGetTagName(RazorSyntaxNode node, [NotNullWhen(true)] out RazorSyn // tag helper attribute. If they don't have the [Parameter] attribute then the Razor compiler // will error, but allowing them to Go To Def on that property regardless, actually helps // them fix the error. - var csharpText = codeDocument.GetCSharpSourceText(); - var syntaxTree = CSharpSyntaxTree.ParseText(csharpText, cancellationToken: cancellationToken); - var root = await syntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); + + var root = await csharpSyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); // Since we know how the compiler generates the C# source we can be a little specific here, and avoid // long tree walks. If the compiler ever changes how they generate their code, the tests for this will break @@ -169,6 +168,7 @@ static bool TryGetTagName(RazorSyntaxNode node, [NotNullWhen(true)] out RazorSyn return null; } + var csharpText = codeDocument.GetCSharpSourceText(); var range = csharpText.GetRange(property.Identifier.Span); if (documentMappingService.TryMapToHostDocumentRange(codeDocument.GetCSharpDocument(), range, out var originalRange)) { diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToDefinition/RazorComponentDefinitionService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToDefinition/RazorComponentDefinitionService.cs index 790ad2c5f2f..50cc8679519 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToDefinition/RazorComponentDefinitionService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToDefinition/RazorComponentDefinitionService.cs @@ -2,10 +2,17 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System.Composition; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor; +using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis.Razor.DocumentMapping; using Microsoft.CodeAnalysis.Razor.GoToDefinition; using Microsoft.CodeAnalysis.Razor.Logging; +using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Razor.Workspaces; +using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; namespace Microsoft.CodeAnalysis.Remote.Razor.GoToDefinition; @@ -14,7 +21,25 @@ namespace Microsoft.CodeAnalysis.Remote.Razor.GoToDefinition; internal sealed class RazorComponentDefinitionService( IRazorComponentSearchEngine componentSearchEngine, IDocumentMappingService documentMappingService, + IFilePathService filePathService, ILoggerFactory loggerFactory) : AbstractRazorComponentDefinitionService(componentSearchEngine, documentMappingService, loggerFactory.GetOrCreateLogger()) { + private readonly IFilePathService _filePathService = filePathService; + + protected override async ValueTask GetCSharpSyntaxTreeAsync(IDocumentSnapshot documentSnapshot, RazorCodeDocument codeDocument, CancellationToken cancellationToken) + { + Debug.Assert(documentSnapshot is RemoteDocumentSnapshot, "This method only works on document snapshots created in the OOP process"); + + var remoteSnapshot = (RemoteDocumentSnapshot)documentSnapshot; + var document = await remoteSnapshot.GetGeneratedDocumentAsync(_filePathService).ConfigureAwait(false); + + if (document.TryGetSyntaxTree(out var syntaxTree)) + { + return syntaxTree; + } + + var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + return tree.AssumeNotNull(); + } } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Definition/RazorComponentDefinitionHelpersTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Definition/RazorComponentDefinitionHelpersTest.cs index 90e37dbeefa..470f4fb33d3 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Definition/RazorComponentDefinitionHelpersTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Definition/RazorComponentDefinitionHelpersTest.cs @@ -1,11 +1,13 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.LanguageServer.Completion; using Microsoft.AspNetCore.Razor.Test.Common; using Microsoft.AspNetCore.Razor.Test.Common.LanguageServer; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Razor.GoToDefinition; using Microsoft.CodeAnalysis.Testing; using Microsoft.VisualStudio.LanguageServer.Protocol; @@ -392,7 +394,10 @@ private async Task VerifyTryGetPropertyRangeAsync(string content, string propert var documentMappingService = new LspDocumentMappingService(FilePathService, new TestDocumentContextFactory(), LoggerFactory); - var range = await RazorComponentDefinitionHelpers.TryGetPropertyRangeAsync(codeDocument, propertyName, documentMappingService, Logger, DisposalToken); + var csharpText = codeDocument.GetCSharpSourceText(); + var syntaxTree = CSharpSyntaxTree.ParseText(csharpText); + + var range = await RazorComponentDefinitionHelpers.TryGetPropertyRangeAsync(codeDocument, syntaxTree, propertyName, documentMappingService, Logger, DisposalToken); Assert.NotNull(range); Assert.Equal(expectedRange, range); } From 5ef490fbf3a859db7a01a94a46cd4fd454276bfa Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 20 Aug 2024 17:09:47 +1000 Subject: [PATCH 2/8] Defer to C# for component attribute GTD in cohosting This was a subtle future bug, that we would have hit when we removed the C# syntax tree bits in future. By checking for Razor GTD first we weren't deferring to Roslyn for plain attributes (ie, the case without `@bind-` in the tests) which means at best we do some extra unnecessary work, and at worst we could lose features for things Roslyn supports but we don't. I did not intend to find this bug when I started. Sorry for missing it in the PR Dustin. The key is that `ignoreAttributes` should have been `true` when porting over the code, and changing that would have made the test fail in your branch. --- .../RemoteGoToDefinitionService.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToDefinition/RemoteGoToDefinitionService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToDefinition/RemoteGoToDefinitionService.cs index 02a7aba2217..851d83ec1b9 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToDefinition/RemoteGoToDefinitionService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToDefinition/RemoteGoToDefinitionService.cs @@ -62,14 +62,6 @@ protected override IRemoteGoToDefinitionService CreateService(in ServiceArgs arg var positionInfo = GetPositionInfo(codeDocument, hostDocumentIndex); - // First, see if this is a Razor component. - var componentLocation = await _componentDefinitionService.GetDefinitionAsync(context.Snapshot, positionInfo, ignoreAttributes: false, cancellationToken).ConfigureAwait(false); - if (componentLocation is not null) - { - // Convert from VS LSP Location to Roslyn. This can be removed when Razor moves fully onto Roslyn's LSP types. - return Results([RoslynLspFactory.CreateLocation(componentLocation.Uri, componentLocation.Range.ToLinePositionSpan())]); - } - if (positionInfo.LanguageKind == RazorLanguageKind.Html) { // Sometimes Html can actually be mapped to C#, like for example component attributes, which map to @@ -83,9 +75,17 @@ protected override IRemoteGoToDefinitionService CreateService(in ServiceArgs arg } } - // If it isn't a Razor component, and it isn't C#, let the server know to delegate to HTML. - if (positionInfo.LanguageKind != RazorLanguageKind.CSharp) + if (positionInfo.LanguageKind is RazorLanguageKind.Html or RazorLanguageKind.Razor) { + // First, see if this is a Razor component. We ignore attributes here, because they're better served by the C# handler. + var componentLocation = await _componentDefinitionService.GetDefinitionAsync(context.Snapshot, positionInfo, ignoreAttributes: true, cancellationToken).ConfigureAwait(false); + if (componentLocation is not null) + { + // Convert from VS LSP Location to Roslyn. This can be removed when Razor moves fully onto Roslyn's LSP types. + return Results([RoslynLspFactory.CreateLocation(componentLocation.Uri, componentLocation.Range.ToLinePositionSpan())]); + } + + // If it isn't a Razor component, and it isn't C#, let the server know to delegate to HTML. return CallHtml; } From ad16b168df6987e21a34ba0c04525fb770b1c05c Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 20 Aug 2024 17:09:56 +1000 Subject: [PATCH 3/8] Add missing test case --- .../CohostGoToDefinitionEndpointTest.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostGoToDefinitionEndpointTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostGoToDefinitionEndpointTest.cs index 911176ecc5d..1c61c7d714f 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostGoToDefinitionEndpointTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostGoToDefinitionEndpointTest.cs @@ -124,6 +124,51 @@ public async Task AttributeValue_BindAfter() await VerifyGoToDefinitionAsync(input, FileKinds.Component); } + [Fact] + public async Task Component() + { + TestCode input = """ + + """; + + TestCode surveyPrompt = """ + [||]@namespace SomeProject + +
+ + @code + { + [Parameter] + public string Title { get; set; } + } + """; + + TestCode surveyPromptGeneratedCode = """ + using Microsoft.AspNetCore.Components; + + namespace SomeProject + { + public partial class SurveyPrompt : ComponentBase + { + [Parameter] + public string Title { get; set; } + } + } + """; + + var result = await GetGoToDefinitionResultAsync(input, FileKinds.Component, + (FileName("SurveyPrompt.razor"), surveyPrompt.Text), + (FileName("SurveyPrompt.razor.g.cs"), surveyPromptGeneratedCode.Text)); + + Assert.NotNull(result.Value.Second); + var locations = result.Value.Second; + var location = Assert.Single(locations); + + var text = SourceText.From(surveyPrompt.Text); + var range = RoslynLspExtensions.GetRange(text, surveyPrompt.Span); + Assert.Equal(range, location.Range); + } + [Theory] [InlineData("Ti$$tle")] [InlineData("$$@bind-Title")] From a8b425907fb0ab5ad8d0f9435c281e3b752a6c55 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 20 Aug 2024 17:10:49 +1000 Subject: [PATCH 4/8] Directly test the component definition service in cohosting See comment in the file, but essentially this is the only way for the code that returns the generated documents syntax tree to be hit in cohosting. --- .../RazorComponentDefinitionServiceTest.cs | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/RazorComponentDefinitionServiceTest.cs diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/RazorComponentDefinitionServiceTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/RazorComponentDefinitionServiceTest.cs new file mode 100644 index 00000000000..22c43a1a755 --- /dev/null +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/RazorComponentDefinitionServiceTest.cs @@ -0,0 +1,99 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.AspNetCore.Razor.Test.Common; +using Microsoft.CodeAnalysis.Razor.DocumentMapping; +using Microsoft.CodeAnalysis.Razor.GoToDefinition; +using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; +using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.LanguageServer.Protocol; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost; + +public class RazorComponentDefinitionServiceTest(ITestOutputHelper testOutputHelper) : CohostEndpointTestBase(testOutputHelper) +{ + // PREAMBLE: Right now these tests are about ensuring we don't accidentally introduce a future bug + // in the generated document handling code in the RazorComponentDefinitionService in OOP. + // Right now in cohosting none of the code under test is actually used. This is because + // the logic for manually looking up properties from attributes is only necessary when + // "Single Server Mode" is off, which is currently only VS Code. When cohosting comes to + // VS Code, that will no longer be true, and VS Code will use the same code paths as VS, + // even then these tests will be exercising uncalled code. + // The tests, and the "ignoreAttributes" parameter in the call to GetDefinitionAsync, should + // be deleted entirely at that point. "ignoreAttributes" will essentially always be true, + // as directly calling Roslyn provides better results. + + [Fact] + public async Task Do() + { + TestCode input = """ + + + @code + { + private string? InputValue { get; set; } + + private void BindAfter() + { + } + } + """; + + TestCode surveyPrompt = """ + @namespace SomeProject + +
+ + @code + { + [Parameter] + public string [|Title|] { get; set; } + } + """; + + TestCode surveyPromptGeneratedCode = """ + using Microsoft.AspNetCore.Components; + + namespace SomeProject + { + public partial class SurveyPrompt : ComponentBase + { + [Parameter] + public string Title { get; set; } + } + } + """; + + await VerifyDefinitionAsync(input, surveyPrompt, (FileName("SurveyPrompt.razor"), surveyPrompt.Text), + (FileName("SurveyPrompt.razor.g.cs"), surveyPromptGeneratedCode.Text)); + } + + private async Task VerifyDefinitionAsync(TestCode input, TestCode expectedDocument, params (string fileName, string contents)[]? additionalFiles) + { + var document = CreateProjectAndRazorDocument(input.Text, FileKinds.Component, additionalFiles); + + var service = OOPExportProvider.GetExportedValue(); + var documentSnapshotFactory = OOPExportProvider.GetExportedValue(); + var documentMappingService = OOPExportProvider.GetExportedValue(); + + var documentSnapshot = documentSnapshotFactory.GetOrCreate(document); + var codeDocument = await documentSnapshot.GetGeneratedOutputAsync(); + var positionInfo = documentMappingService.GetPositionInfo(codeDocument, input.Position); + + var location = await service.GetDefinitionAsync(documentSnapshot, positionInfo, ignoreAttributes: false, DisposalToken); + + Assert.NotNull(location); + + var text = SourceText.From(expectedDocument.Text); + var range = text.GetRange(expectedDocument.Span); + Assert.Equal(range, location.Range); + } + + private static string FileName(string projectRelativeFileName) + => Path.Combine(TestProjectData.SomeProjectPath, projectRelativeFileName); +} From 7470352912cd7bb3524ec8523ae4192e044f7d5a Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 20 Aug 2024 17:11:29 +1000 Subject: [PATCH 5/8] Rework how we get generated documents In light of "should we get rid of document context" convo this morning, figured this made sense to do (and I needed _something_ on IDocumentSnapshot in order to actually make this whole idea work) --- .../RemoteDocumentHighlightService.cs | 2 +- .../RemoteFoldingRangeService.cs | 2 +- .../RemoteGoToDefinitionService.cs | 2 +- .../InlayHints/RemoteInlayHintService.cs | 4 +- .../DocumentContextExtensions.cs | 48 ------------------- .../ProjectSystem/RemoteDocumentContext.cs | 4 +- .../ProjectSystem/RemoteDocumentSnapshot.cs | 24 ++++++++-- .../Rename/RemoteRenameService.cs | 2 +- .../RemoteCSharpSemanticTokensProvider.cs | 6 ++- .../RemoteSignatureHelpService.cs | 9 ++-- 10 files changed, 39 insertions(+), 64 deletions(-) delete mode 100644 src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/DocumentContextExtensions.cs diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentHighlight/RemoteDocumentHighlightService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentHighlight/RemoteDocumentHighlightService.cs index ec782be065c..a043b2b8e85 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentHighlight/RemoteDocumentHighlightService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentHighlight/RemoteDocumentHighlightService.cs @@ -68,7 +68,7 @@ private async ValueTask GetHighlightsAsync( var csharpDocument = codeDocument.GetCSharpDocument(); if (_documentMappingService.TryMapToGeneratedDocumentPosition(csharpDocument, index, out var mappedPosition, out _)) { - var generatedDocument = await context.GetGeneratedDocumentAsync(_filePathService, cancellationToken).ConfigureAwait(false); + var generatedDocument = await context.Snapshot.GetGeneratedDocumentAsync(_filePathService).ConfigureAwait(false); var highlights = await DocumentHighlights.GetHighlightsAsync(generatedDocument, mappedPosition, cancellationToken).ConfigureAwait(false); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/FoldingRanges/RemoteFoldingRangeService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/FoldingRanges/RemoteFoldingRangeService.cs index 5fed4a0a437..6974e6d886b 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/FoldingRanges/RemoteFoldingRangeService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/FoldingRanges/RemoteFoldingRangeService.cs @@ -43,7 +43,7 @@ private async ValueTask> GetFoldingRangesAsyn ImmutableArray htmlRanges, CancellationToken cancellationToken) { - var generatedDocument = await context.GetGeneratedDocumentAsync(_filePathService, cancellationToken).ConfigureAwait(false); + var generatedDocument = await context.Snapshot.GetGeneratedDocumentAsync(_filePathService).ConfigureAwait(false); var csharpRanges = await ExternalHandlers.FoldingRanges.GetFoldingRangesAsync(generatedDocument, cancellationToken).ConfigureAwait(false); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToDefinition/RemoteGoToDefinitionService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToDefinition/RemoteGoToDefinitionService.cs index 851d83ec1b9..5814bfc4993 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToDefinition/RemoteGoToDefinitionService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToDefinition/RemoteGoToDefinitionService.cs @@ -96,7 +96,7 @@ protected override IRemoteGoToDefinitionService CreateService(in ServiceArgs arg } // Finally, call into C#. - var generatedDocument = await context.GetGeneratedDocumentAsync(_filePathService, cancellationToken).ConfigureAwait(false); + var generatedDocument = await context.Snapshot.GetGeneratedDocumentAsync(_filePathService).ConfigureAwait(false); var locations = await ExternalHandlers.GoToDefinition .GetDefinitionsAsync( diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/InlayHints/RemoteInlayHintService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/InlayHints/RemoteInlayHintService.cs index 2f80dd60b34..8913c752950 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/InlayHints/RemoteInlayHintService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/InlayHints/RemoteInlayHintService.cs @@ -52,7 +52,7 @@ protected override IRemoteInlayHintService CreateService(in ServiceArgs args) return null; } - var generatedDocument = await context.GetGeneratedDocumentAsync(_filePathService, cancellationToken).ConfigureAwait(false); + var generatedDocument = await context.Snapshot.GetGeneratedDocumentAsync(_filePathService).ConfigureAwait(false); var textDocument = inlayHintParams.TextDocument.WithUri(generatedDocument.CreateUri()); var range = projectedLinePositionSpan.ToRange(); @@ -106,7 +106,7 @@ public ValueTask ResolveHintAsync(JsonSerializableRazorPinnedSolution private async ValueTask ResolveInlayHintAsync(RemoteDocumentContext context, InlayHint inlayHint, CancellationToken cancellationToken) { - var generatedDocument = await context.GetGeneratedDocumentAsync(_filePathService, cancellationToken).ConfigureAwait(false); + var generatedDocument = await context.Snapshot.GetGeneratedDocumentAsync(_filePathService).ConfigureAwait(false); return await InlayHints.ResolveInlayHintAsync(generatedDocument, inlayHint, cancellationToken).ConfigureAwait(false); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/DocumentContextExtensions.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/DocumentContextExtensions.cs deleted file mode 100644 index bd29cf52ab3..00000000000 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/DocumentContextExtensions.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root for license information. - -using System.Diagnostics; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Razor; -using Microsoft.CodeAnalysis.Razor.ProjectSystem; -using Microsoft.CodeAnalysis.Razor.Workspaces; -using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; - -namespace Microsoft.CodeAnalysis.Remote.Razor; - -internal static class DocumentContextExtensions -{ - public static async Task GetGeneratedDocumentAsync( - this VersionedDocumentContext documentContext, - IFilePathService filePathService, - CancellationToken cancellationToken) - { - Debug.Assert(documentContext.Snapshot is RemoteDocumentSnapshot, "This method only works on document contexts created in the OOP process"); - - var snapshot = (RemoteDocumentSnapshot)documentContext.Snapshot; - - return await snapshot.GetOrAddGeneratedDocumentAsync( - (snapshot, documentContext, filePathService, cancellationToken), - static async arg => - { - var (snapshot, documentContext, filePathService, cancellationToken) = arg; - - var razorDocument = snapshot.TextDocument; - var projectKey = snapshot.Project.Key; - var solution = razorDocument.Project.Solution; - - // TODO: A real implementation needs to get the SourceGeneratedDocument from the solution - - var generatedFilePath = filePathService.GetRazorCSharpFilePath(projectKey, razorDocument.FilePath.AssumeNotNull()); - var generatedDocumentId = solution.GetDocumentIdsWithFilePath(generatedFilePath).First(d => d.ProjectId == razorDocument.Project.Id); - var generatedDocument = solution.GetRequiredDocument(generatedDocumentId); - - var csharpSourceText = await documentContext.GetCSharpSourceTextAsync(cancellationToken).ConfigureAwait(false); - - // HACK: We're not in the same solution fork as the LSP server that provides content for this document - return generatedDocument.WithText(csharpSourceText); - }); - } -} diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentContext.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentContext.cs index bfd3a34bdff..b536f85c418 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentContext.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentContext.cs @@ -8,7 +8,9 @@ namespace Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; internal class RemoteDocumentContext : VersionedDocumentContext { - public TextDocument TextDocument => ((RemoteDocumentSnapshot)Snapshot).TextDocument; + public TextDocument TextDocument => Snapshot.TextDocument; + + public new RemoteDocumentSnapshot Snapshot => (RemoteDocumentSnapshot)base.Snapshot; public RemoteDocumentContext(Uri uri, RemoteDocumentSnapshot snapshot) // HACK: Need to revisit version and projectContext here I guess diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs index e7a2232c0a6..1ea25038cc2 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs @@ -1,13 +1,14 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. -using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor; using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis.Razor.ProjectSystem; +using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; @@ -88,14 +89,31 @@ public bool TryGetGeneratedOutput([NotNullWhen(true)] out RazorCodeDocument? res return result is not null; } - public async Task GetOrAddGeneratedDocumentAsync(TArg arg, Func> createGeneratedDocument) + public async Task GetGeneratedDocumentAsync(IFilePathService filePathService) { if (_generatedDocument is Document generatedDocument) { return generatedDocument; } - generatedDocument = await createGeneratedDocument(arg); + generatedDocument = await HACK_GenerateDocumentAsync(filePathService).ConfigureAwait(false); return InterlockedOperations.Initialize(ref _generatedDocument, generatedDocument); } + + private async Task HACK_GenerateDocumentAsync(IFilePathService filePathService) + { + // TODO: A real implementation needs to get the SourceGeneratedDocument from the solution + + var solution = TextDocument.Project.Solution; + var generatedFilePath = filePathService.GetRazorCSharpFilePath(Project.Key, FilePath.AssumeNotNull()); + var projectId = TextDocument.Project.Id; + var generatedDocumentId = solution.GetDocumentIdsWithFilePath(generatedFilePath).First(d => d.ProjectId == projectId); + var generatedDocument = solution.GetRequiredDocument(generatedDocumentId); + + var codeDocument = await this.GetGeneratedOutputAsync().ConfigureAwait(false); + var csharpSourceText = codeDocument.GetCSharpSourceText(); + + // HACK: We're not in the same solution fork as the LSP server that provides content for this document + return generatedDocument.WithText(csharpSourceText); + } } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Rename/RemoteRenameService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Rename/RemoteRenameService.cs index be31a947509..6f9f6294ef8 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Rename/RemoteRenameService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Rename/RemoteRenameService.cs @@ -53,7 +53,7 @@ protected override IRemoteRenameService CreateService(in ServiceArgs args) return NoFurtherHandling; } - var generatedDocument = await context.GetGeneratedDocumentAsync(_filePathService, cancellationToken).ConfigureAwait(false); + var generatedDocument = await context.Snapshot.GetGeneratedDocumentAsync(_filePathService).ConfigureAwait(false); var razorEdit = await _renameService.TryGetRazorRenameEditsAsync(context, positionInfo, newName, cancellationToken).ConfigureAwait(false); if (razorEdit is not null) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SemanticTokens/RemoteCSharpSemanticTokensProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SemanticTokens/RemoteCSharpSemanticTokensProvider.cs index afd2f6463ab..bb67d2967e0 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SemanticTokens/RemoteCSharpSemanticTokensProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SemanticTokens/RemoteCSharpSemanticTokensProvider.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Immutable; using System.Composition; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Telemetry; @@ -11,6 +12,7 @@ using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Razor.SemanticTokens; using Microsoft.CodeAnalysis.Razor.Workspaces; +using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Remote.Razor.SemanticTokens; @@ -27,7 +29,9 @@ internal class RemoteCSharpSemanticTokensProvider(IFilePathService filePathServi using var _ = _telemetryReporter.TrackLspRequest(nameof(SemanticTokensRange.GetSemanticTokensAsync), Constants.ExternalAccessServerName, correlationId); // We have a razor document, lets find the generated C# document - var generatedDocument = await documentContext.GetGeneratedDocumentAsync(_filePathService, cancellationToken).ConfigureAwait(false); + Debug.Assert(documentContext is RemoteDocumentContext, "This method only works on document snapshots created in the OOP process"); + var snapshot = (RemoteDocumentSnapshot)documentContext.Snapshot; + var generatedDocument = await snapshot.GetGeneratedDocumentAsync(_filePathService).ConfigureAwait(false); var data = await SemanticTokensRange.GetSemanticTokensAsync( generatedDocument, diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SignatureHelp/RemoteSignatureHelpService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SignatureHelp/RemoteSignatureHelpService.cs index 0aa63984efc..23fbec462fa 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SignatureHelp/RemoteSignatureHelpService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SignatureHelp/RemoteSignatureHelpService.cs @@ -11,11 +11,10 @@ using Microsoft.CodeAnalysis.Text; using Roslyn.LanguageServer.Protocol; using ExternalHandlers = Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers; +using LspSignatureHelp = Roslyn.LanguageServer.Protocol.SignatureHelp; namespace Microsoft.CodeAnalysis.Remote.Razor; -using SignatureHelp = Roslyn.LanguageServer.Protocol.SignatureHelp; - internal sealed class RemoteSignatureHelpService(in ServiceArgs args) : RazorDocumentServiceBase(in args), IRemoteSignatureHelpService { internal sealed class Factory : FactoryBase @@ -26,20 +25,20 @@ protected override IRemoteSignatureHelpService CreateService(in ServiceArgs args private readonly IFilePathService _filePathService = args.ExportProvider.GetExportedValue(); - public ValueTask GetSignatureHelpAsync(JsonSerializableRazorPinnedSolutionInfoWrapper solutionInfo, JsonSerializableDocumentId documentId, Position position, CancellationToken cancellationToken) + public ValueTask GetSignatureHelpAsync(JsonSerializableRazorPinnedSolutionInfoWrapper solutionInfo, JsonSerializableDocumentId documentId, Position position, CancellationToken cancellationToken) => RunServiceAsync( solutionInfo, documentId, context => GetSignatureHelpsAsync(context, position, cancellationToken), cancellationToken); - private async ValueTask GetSignatureHelpsAsync(RemoteDocumentContext context, Position position, CancellationToken cancellationToken) + private async ValueTask GetSignatureHelpsAsync(RemoteDocumentContext context, Position position, CancellationToken cancellationToken) { var codeDocument = await context.GetCodeDocumentAsync(cancellationToken).ConfigureAwait(false); var linePosition = new LinePosition(position.Line, position.Character); var absoluteIndex = codeDocument.Source.Text.GetRequiredAbsoluteIndex(linePosition); - var generatedDocument = await context.GetGeneratedDocumentAsync(_filePathService, cancellationToken).ConfigureAwait(false); + var generatedDocument = await context.Snapshot.GetGeneratedDocumentAsync(_filePathService).ConfigureAwait(false); if (DocumentMappingService.TryMapToGeneratedDocumentPosition(codeDocument.GetCSharpDocument(), absoluteIndex, out var mappedPosition, out _)) { From 7d5b2060c6bc201fc4e1e3fce9f950bfa62b9a89 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 21 Aug 2024 09:06:50 +1000 Subject: [PATCH 6/8] Remove code document parameter and just use document snapshot --- .../Definition/RazorComponentDefinitionService.cs | 5 +++-- .../AbstractRazorComponentDefinitionService.cs | 4 ++-- .../GoToDefinition/RazorComponentDefinitionService.cs | 3 +-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Definition/RazorComponentDefinitionService.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Definition/RazorComponentDefinitionService.cs index 658c30fc53d..fdd1c6cc6e9 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Definition/RazorComponentDefinitionService.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Definition/RazorComponentDefinitionService.cs @@ -24,9 +24,10 @@ internal sealed class RazorComponentDefinitionService( ILoggerFactory loggerFactory) : AbstractRazorComponentDefinitionService(componentSearchEngine, documentMappingService, loggerFactory.GetOrCreateLogger()) { - protected override ValueTask GetCSharpSyntaxTreeAsync(IDocumentSnapshot documentSnapshot, RazorCodeDocument codeDocument, CancellationToken cancellationToken) + protected override async ValueTask GetCSharpSyntaxTreeAsync(IDocumentSnapshot documentSnapshot, CancellationToken cancellationToken) { + var codeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); var csharpText = codeDocument.GetCSharpSourceText(); - return new(CSharpSyntaxTree.ParseText(csharpText, cancellationToken: cancellationToken)); + return CSharpSyntaxTree.ParseText(csharpText, cancellationToken: cancellationToken); } } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/GoToDefinition/AbstractRazorComponentDefinitionService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/GoToDefinition/AbstractRazorComponentDefinitionService.cs index c28f78efb4e..fea7e1c7137 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/GoToDefinition/AbstractRazorComponentDefinitionService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/GoToDefinition/AbstractRazorComponentDefinitionService.cs @@ -70,7 +70,7 @@ private async Task GetNavigateRangeAsync(IDocumentSnapshot documentSna _logger.LogInformation($"Attempting to get definition from an attribute directly."); var originCodeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); - var syntaxTree = await GetCSharpSyntaxTreeAsync(documentSnapshot, originCodeDocument, cancellationToken).ConfigureAwait(false); + var syntaxTree = await GetCSharpSyntaxTreeAsync(documentSnapshot, cancellationToken).ConfigureAwait(false); var range = await RazorComponentDefinitionHelpers .TryGetPropertyRangeAsync(originCodeDocument, syntaxTree, attributeDescriptor.GetPropertyName(), _documentMappingService, _logger, cancellationToken) @@ -89,5 +89,5 @@ private async Task GetNavigateRangeAsync(IDocumentSnapshot documentSna return VsLspFactory.DefaultRange; } - protected abstract ValueTask GetCSharpSyntaxTreeAsync(IDocumentSnapshot documentSnapshot, RazorCodeDocument codeDocument, CancellationToken cancellationToken); + protected abstract ValueTask GetCSharpSyntaxTreeAsync(IDocumentSnapshot documentSnapshot, CancellationToken cancellationToken); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToDefinition/RazorComponentDefinitionService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToDefinition/RazorComponentDefinitionService.cs index 50cc8679519..156a8f19345 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToDefinition/RazorComponentDefinitionService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToDefinition/RazorComponentDefinitionService.cs @@ -6,7 +6,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor; -using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis.Razor.DocumentMapping; using Microsoft.CodeAnalysis.Razor.GoToDefinition; using Microsoft.CodeAnalysis.Razor.Logging; @@ -27,7 +26,7 @@ internal sealed class RazorComponentDefinitionService( { private readonly IFilePathService _filePathService = filePathService; - protected override async ValueTask GetCSharpSyntaxTreeAsync(IDocumentSnapshot documentSnapshot, RazorCodeDocument codeDocument, CancellationToken cancellationToken) + protected override async ValueTask GetCSharpSyntaxTreeAsync(IDocumentSnapshot documentSnapshot, CancellationToken cancellationToken) { Debug.Assert(documentSnapshot is RemoteDocumentSnapshot, "This method only works on document snapshots created in the OOP process"); From 53fec86c28830aa213bd0db3a8b121e6702bd98e Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 21 Aug 2024 09:49:42 +1000 Subject: [PATCH 7/8] Inject file path service into the document snapshot --- .../RemoteDocumentHighlightService.cs | 4 +--- .../FoldingRanges/RemoteFoldingRangeService.cs | 4 +--- .../RazorComponentDefinitionService.cs | 5 +---- .../GoToDefinition/RemoteGoToDefinitionService.cs | 6 ++---- .../InlayHints/RemoteInlayHintService.cs | 6 ++---- .../ProjectSystem/DocumentSnapshotFactory.cs | 14 ++++++++------ .../ProjectSystem/ProjectSnapshotFactory.cs | 8 ++++---- .../ProjectSystem/RemoteDocumentSnapshot.cs | 13 +++++++------ .../Rename/RemoteRenameService.cs | 3 +-- .../RemoteCSharpSemanticTokensProvider.cs | 2 +- .../SignatureHelp/RemoteSignatureHelpService.cs | 5 +---- 11 files changed, 29 insertions(+), 41 deletions(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentHighlight/RemoteDocumentHighlightService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentHighlight/RemoteDocumentHighlightService.cs index a043b2b8e85..6303415b308 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentHighlight/RemoteDocumentHighlightService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentHighlight/RemoteDocumentHighlightService.cs @@ -11,7 +11,6 @@ using Microsoft.CodeAnalysis.Razor.Protocol; using Microsoft.CodeAnalysis.Razor.Protocol.DocumentHighlight; using Microsoft.CodeAnalysis.Razor.Remote; -using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Text; using static Microsoft.VisualStudio.LanguageServer.Protocol.VsLspExtensions; @@ -29,7 +28,6 @@ protected override IRemoteDocumentHighlightService CreateService(in ServiceArgs } private readonly IDocumentMappingService _documentMappingService = args.ExportProvider.GetExportedValue(); - private readonly IFilePathService _filePathService = args.ExportProvider.GetExportedValue(); public ValueTask GetHighlightsAsync( RazorPinnedSolutionInfoWrapper solutionInfo, @@ -68,7 +66,7 @@ private async ValueTask GetHighlightsAsync( var csharpDocument = codeDocument.GetCSharpDocument(); if (_documentMappingService.TryMapToGeneratedDocumentPosition(csharpDocument, index, out var mappedPosition, out _)) { - var generatedDocument = await context.Snapshot.GetGeneratedDocumentAsync(_filePathService).ConfigureAwait(false); + var generatedDocument = await context.Snapshot.GetGeneratedDocumentAsync().ConfigureAwait(false); var highlights = await DocumentHighlights.GetHighlightsAsync(generatedDocument, mappedPosition, cancellationToken).ConfigureAwait(false); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/FoldingRanges/RemoteFoldingRangeService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/FoldingRanges/RemoteFoldingRangeService.cs index 6974e6d886b..87e4673b004 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/FoldingRanges/RemoteFoldingRangeService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/FoldingRanges/RemoteFoldingRangeService.cs @@ -9,7 +9,6 @@ using Microsoft.CodeAnalysis.Razor.FoldingRanges; using Microsoft.CodeAnalysis.Razor.Protocol.Folding; using Microsoft.CodeAnalysis.Razor.Remote; -using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; using Microsoft.VisualStudio.LanguageServer.Protocol; using ExternalHandlers = Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers; @@ -25,7 +24,6 @@ protected override IRemoteFoldingRangeService CreateService(in ServiceArgs args) } private readonly IFoldingRangeService _foldingRangeService = args.ExportProvider.GetExportedValue(); - private readonly IFilePathService _filePathService = args.ExportProvider.GetExportedValue(); public ValueTask> GetFoldingRangesAsync( RazorPinnedSolutionInfoWrapper solutionInfo, @@ -43,7 +41,7 @@ private async ValueTask> GetFoldingRangesAsyn ImmutableArray htmlRanges, CancellationToken cancellationToken) { - var generatedDocument = await context.Snapshot.GetGeneratedDocumentAsync(_filePathService).ConfigureAwait(false); + var generatedDocument = await context.Snapshot.GetGeneratedDocumentAsync().ConfigureAwait(false); var csharpRanges = await ExternalHandlers.FoldingRanges.GetFoldingRangesAsync(generatedDocument, cancellationToken).ConfigureAwait(false); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToDefinition/RazorComponentDefinitionService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToDefinition/RazorComponentDefinitionService.cs index 156a8f19345..4c0abf315f5 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToDefinition/RazorComponentDefinitionService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToDefinition/RazorComponentDefinitionService.cs @@ -20,18 +20,15 @@ namespace Microsoft.CodeAnalysis.Remote.Razor.GoToDefinition; internal sealed class RazorComponentDefinitionService( IRazorComponentSearchEngine componentSearchEngine, IDocumentMappingService documentMappingService, - IFilePathService filePathService, ILoggerFactory loggerFactory) : AbstractRazorComponentDefinitionService(componentSearchEngine, documentMappingService, loggerFactory.GetOrCreateLogger()) { - private readonly IFilePathService _filePathService = filePathService; - protected override async ValueTask GetCSharpSyntaxTreeAsync(IDocumentSnapshot documentSnapshot, CancellationToken cancellationToken) { Debug.Assert(documentSnapshot is RemoteDocumentSnapshot, "This method only works on document snapshots created in the OOP process"); var remoteSnapshot = (RemoteDocumentSnapshot)documentSnapshot; - var document = await remoteSnapshot.GetGeneratedDocumentAsync(_filePathService).ConfigureAwait(false); + var document = await remoteSnapshot.GetGeneratedDocumentAsync().ConfigureAwait(false); if (document.TryGetSyntaxTree(out var syntaxTree)) { diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToDefinition/RemoteGoToDefinitionService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToDefinition/RemoteGoToDefinitionService.cs index 5814bfc4993..58d6abd3e0a 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToDefinition/RemoteGoToDefinitionService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToDefinition/RemoteGoToDefinitionService.cs @@ -10,7 +10,6 @@ using Microsoft.CodeAnalysis.Razor.GoToDefinition; using Microsoft.CodeAnalysis.Razor.Protocol; using Microsoft.CodeAnalysis.Razor.Remote; -using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.CodeAnalysis.Remote.Razor.DocumentMapping; using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Text; @@ -33,7 +32,6 @@ protected override IRemoteGoToDefinitionService CreateService(in ServiceArgs arg } private readonly IRazorComponentDefinitionService _componentDefinitionService = args.ExportProvider.GetExportedValue(); - private readonly IFilePathService _filePathService = args.ExportProvider.GetExportedValue(); protected override IDocumentPositionInfoStrategy DocumentPositionInfoStrategy => PreferAttributeNameDocumentPositionInfoStrategy.Instance; @@ -96,7 +94,7 @@ protected override IRemoteGoToDefinitionService CreateService(in ServiceArgs arg } // Finally, call into C#. - var generatedDocument = await context.Snapshot.GetGeneratedDocumentAsync(_filePathService).ConfigureAwait(false); + var generatedDocument = await context.Snapshot.GetGeneratedDocumentAsync().ConfigureAwait(false); var locations = await ExternalHandlers.GoToDefinition .GetDefinitionsAsync( @@ -121,7 +119,7 @@ protected override IRemoteGoToDefinitionService CreateService(in ServiceArgs arg var (uri, range) = location; var (mappedDocumentUri, mappedRange) = await DocumentMappingService - .MapToHostDocumentUriAndRangeAsync((RemoteDocumentSnapshot)context.Snapshot, uri, range.ToLinePositionSpan(), cancellationToken) + .MapToHostDocumentUriAndRangeAsync(context.Snapshot, uri, range.ToLinePositionSpan(), cancellationToken) .ConfigureAwait(false); var mappedLocation = RoslynLspFactory.CreateLocation(mappedDocumentUri, mappedRange); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/InlayHints/RemoteInlayHintService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/InlayHints/RemoteInlayHintService.cs index 8913c752950..67244b3edcd 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/InlayHints/RemoteInlayHintService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/InlayHints/RemoteInlayHintService.cs @@ -26,8 +26,6 @@ protected override IRemoteInlayHintService CreateService(in ServiceArgs args) => new RemoteInlayHintService(in args); } - private readonly IFilePathService _filePathService = args.ExportProvider.GetExportedValue(); - public ValueTask GetInlayHintsAsync(JsonSerializableRazorPinnedSolutionInfoWrapper solutionInfo, JsonSerializableDocumentId razorDocumentId, InlayHintParams inlayHintParams, bool displayAllOverride, CancellationToken cancellationToken) => RunServiceAsync( solutionInfo, @@ -52,7 +50,7 @@ protected override IRemoteInlayHintService CreateService(in ServiceArgs args) return null; } - var generatedDocument = await context.Snapshot.GetGeneratedDocumentAsync(_filePathService).ConfigureAwait(false); + var generatedDocument = await context.Snapshot.GetGeneratedDocumentAsync().ConfigureAwait(false); var textDocument = inlayHintParams.TextDocument.WithUri(generatedDocument.CreateUri()); var range = projectedLinePositionSpan.ToRange(); @@ -106,7 +104,7 @@ public ValueTask ResolveHintAsync(JsonSerializableRazorPinnedSolution private async ValueTask ResolveInlayHintAsync(RemoteDocumentContext context, InlayHint inlayHint, CancellationToken cancellationToken) { - var generatedDocument = await context.Snapshot.GetGeneratedDocumentAsync(_filePathService).ConfigureAwait(false); + var generatedDocument = await context.Snapshot.GetGeneratedDocumentAsync().ConfigureAwait(false); return await InlayHints.ResolveInlayHintAsync(generatedDocument, inlayHint, cancellationToken).ConfigureAwait(false); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/DocumentSnapshotFactory.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/DocumentSnapshotFactory.cs index 241458f1efc..853fe37386e 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/DocumentSnapshotFactory.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/DocumentSnapshotFactory.cs @@ -4,26 +4,28 @@ using System; using System.Composition; using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis.Razor.Workspaces; namespace Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; [Export(typeof(DocumentSnapshotFactory)), Shared] [method: ImportingConstructor] -internal class DocumentSnapshotFactory(Lazy projectSnapshotFactory) +internal class DocumentSnapshotFactory(Lazy projectSnapshotFactory, IFilePathService filePathService) { - private static readonly ConditionalWeakTable _documentSnapshots = new(); + private static readonly ConditionalWeakTable s_documentSnapshots = new(); private readonly Lazy _projectSnapshotFactory = projectSnapshotFactory; + private readonly IFilePathService _filePathService = filePathService; public RemoteDocumentSnapshot GetOrCreate(TextDocument textDocument) { - lock (_documentSnapshots) + lock (s_documentSnapshots) { - if (!_documentSnapshots.TryGetValue(textDocument, out var documentSnapshot)) + if (!s_documentSnapshots.TryGetValue(textDocument, out var documentSnapshot)) { var projectSnapshot = _projectSnapshotFactory.Value.GetOrCreate(textDocument.Project); - documentSnapshot = new RemoteDocumentSnapshot(textDocument, projectSnapshot); - _documentSnapshots.Add(textDocument, documentSnapshot); + documentSnapshot = new RemoteDocumentSnapshot(textDocument, projectSnapshot, _filePathService); + s_documentSnapshots.Add(textDocument, documentSnapshot); } return documentSnapshot; diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/ProjectSnapshotFactory.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/ProjectSnapshotFactory.cs index f5b16a49e51..b397be7b03a 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/ProjectSnapshotFactory.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/ProjectSnapshotFactory.cs @@ -11,19 +11,19 @@ namespace Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; [method: ImportingConstructor] internal class ProjectSnapshotFactory(DocumentSnapshotFactory documentSnapshotFactory, ITelemetryReporter telemetryReporter) { - private static readonly ConditionalWeakTable _projectSnapshots = new(); + private static readonly ConditionalWeakTable s_projectSnapshots = new(); private readonly DocumentSnapshotFactory _documentSnapshotFactory = documentSnapshotFactory; private readonly ITelemetryReporter _telemetryReporter = telemetryReporter; public RemoteProjectSnapshot GetOrCreate(Project project) { - lock (_projectSnapshots) + lock (s_projectSnapshots) { - if (!_projectSnapshots.TryGetValue(project, out var projectSnapshot)) + if (!s_projectSnapshots.TryGetValue(project, out var projectSnapshot)) { projectSnapshot = new RemoteProjectSnapshot(project, _documentSnapshotFactory, _telemetryReporter); - _projectSnapshots.Add(project, projectSnapshot); + s_projectSnapshots.Add(project, projectSnapshot); } return projectSnapshot; diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs index 1ea25038cc2..2421936ea91 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs @@ -13,10 +13,11 @@ namespace Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; -internal class RemoteDocumentSnapshot(TextDocument textDocument, RemoteProjectSnapshot projectSnapshot) : IDocumentSnapshot +internal class RemoteDocumentSnapshot(TextDocument textDocument, RemoteProjectSnapshot projectSnapshot, IFilePathService filePathService) : IDocumentSnapshot { private readonly TextDocument _textDocument = textDocument; private readonly RemoteProjectSnapshot _projectSnapshot = projectSnapshot; + private readonly IFilePathService _filePathService = filePathService; // TODO: Delete this field when the source generator is hooked up private Document? _generatedDocument; @@ -80,7 +81,7 @@ public IDocumentSnapshot WithText(SourceText text) var id = _textDocument.Id; var newDocument = _textDocument.Project.Solution.WithAdditionalDocumentText(id, text).GetAdditionalDocument(id).AssumeNotNull(); - return new RemoteDocumentSnapshot(newDocument, _projectSnapshot); + return new RemoteDocumentSnapshot(newDocument, _projectSnapshot, _filePathService); } public bool TryGetGeneratedOutput([NotNullWhen(true)] out RazorCodeDocument? result) @@ -89,23 +90,23 @@ public bool TryGetGeneratedOutput([NotNullWhen(true)] out RazorCodeDocument? res return result is not null; } - public async Task GetGeneratedDocumentAsync(IFilePathService filePathService) + public async Task GetGeneratedDocumentAsync() { if (_generatedDocument is Document generatedDocument) { return generatedDocument; } - generatedDocument = await HACK_GenerateDocumentAsync(filePathService).ConfigureAwait(false); + generatedDocument = await HACK_GenerateDocumentAsync().ConfigureAwait(false); return InterlockedOperations.Initialize(ref _generatedDocument, generatedDocument); } - private async Task HACK_GenerateDocumentAsync(IFilePathService filePathService) + private async Task HACK_GenerateDocumentAsync() { // TODO: A real implementation needs to get the SourceGeneratedDocument from the solution var solution = TextDocument.Project.Solution; - var generatedFilePath = filePathService.GetRazorCSharpFilePath(Project.Key, FilePath.AssumeNotNull()); + var generatedFilePath = _filePathService.GetRazorCSharpFilePath(Project.Key, FilePath.AssumeNotNull()); var projectId = TextDocument.Project.Id; var generatedDocumentId = solution.GetDocumentIdsWithFilePath(generatedFilePath).First(d => d.ProjectId == projectId); var generatedDocument = solution.GetRequiredDocument(generatedDocumentId); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Rename/RemoteRenameService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Rename/RemoteRenameService.cs index 6f9f6294ef8..c29af5af998 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Rename/RemoteRenameService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Rename/RemoteRenameService.cs @@ -25,7 +25,6 @@ protected override IRemoteRenameService CreateService(in ServiceArgs args) } private readonly IRenameService _renameService = args.ExportProvider.GetExportedValue(); - private readonly IFilePathService _filePathService = args.ExportProvider.GetExportedValue(); private readonly IEditMappingService _editMappingService = args.ExportProvider.GetExportedValue(); public ValueTask> GetRenameEditAsync( @@ -53,7 +52,7 @@ protected override IRemoteRenameService CreateService(in ServiceArgs args) return NoFurtherHandling; } - var generatedDocument = await context.Snapshot.GetGeneratedDocumentAsync(_filePathService).ConfigureAwait(false); + var generatedDocument = await context.Snapshot.GetGeneratedDocumentAsync().ConfigureAwait(false); var razorEdit = await _renameService.TryGetRazorRenameEditsAsync(context, positionInfo, newName, cancellationToken).ConfigureAwait(false); if (razorEdit is not null) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SemanticTokens/RemoteCSharpSemanticTokensProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SemanticTokens/RemoteCSharpSemanticTokensProvider.cs index bb67d2967e0..442b31bec7a 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SemanticTokens/RemoteCSharpSemanticTokensProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SemanticTokens/RemoteCSharpSemanticTokensProvider.cs @@ -31,7 +31,7 @@ internal class RemoteCSharpSemanticTokensProvider(IFilePathService filePathServi // We have a razor document, lets find the generated C# document Debug.Assert(documentContext is RemoteDocumentContext, "This method only works on document snapshots created in the OOP process"); var snapshot = (RemoteDocumentSnapshot)documentContext.Snapshot; - var generatedDocument = await snapshot.GetGeneratedDocumentAsync(_filePathService).ConfigureAwait(false); + var generatedDocument = await snapshot.GetGeneratedDocumentAsync().ConfigureAwait(false); var data = await SemanticTokensRange.GetSemanticTokensAsync( generatedDocument, diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SignatureHelp/RemoteSignatureHelpService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SignatureHelp/RemoteSignatureHelpService.cs index 23fbec462fa..e32b005716e 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SignatureHelp/RemoteSignatureHelpService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SignatureHelp/RemoteSignatureHelpService.cs @@ -6,7 +6,6 @@ using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis.ExternalAccess.Razor; using Microsoft.CodeAnalysis.Razor.Remote; -using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Text; using Roslyn.LanguageServer.Protocol; @@ -23,8 +22,6 @@ protected override IRemoteSignatureHelpService CreateService(in ServiceArgs args => new RemoteSignatureHelpService(in args); } - private readonly IFilePathService _filePathService = args.ExportProvider.GetExportedValue(); - public ValueTask GetSignatureHelpAsync(JsonSerializableRazorPinnedSolutionInfoWrapper solutionInfo, JsonSerializableDocumentId documentId, Position position, CancellationToken cancellationToken) => RunServiceAsync( solutionInfo, @@ -38,7 +35,7 @@ protected override IRemoteSignatureHelpService CreateService(in ServiceArgs args var linePosition = new LinePosition(position.Line, position.Character); var absoluteIndex = codeDocument.Source.Text.GetRequiredAbsoluteIndex(linePosition); - var generatedDocument = await context.Snapshot.GetGeneratedDocumentAsync(_filePathService).ConfigureAwait(false); + var generatedDocument = await context.Snapshot.GetGeneratedDocumentAsync().ConfigureAwait(false); if (DocumentMappingService.TryMapToGeneratedDocumentPosition(codeDocument.GetCSharpDocument(), absoluteIndex, out var mappedPosition, out _)) { From a3edec0c8a8ab702bcfb43780361081533f612ff Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 21 Aug 2024 10:17:06 +1000 Subject: [PATCH 8/8] Move GetSyntaxTree to document snapshot --- .../RazorComponentDefinitionService.cs | 16 -------------- ...AbstractRazorComponentDefinitionService.cs | 7 +------ .../RazorComponentDefinitionHelpers.cs | 6 ++++-- .../ProjectSystem/DocumentSnapshot.cs | 9 ++++++++ .../ProjectSystem/IDocumentSnapshot.cs | 7 +++++++ .../ProjectSystem/ImportDocumentSnapshot.cs | 4 ++++ .../RazorComponentDefinitionService.cs | 21 ------------------- .../ProjectSystem/RemoteDocumentSnapshot.cs | 7 +++++++ .../RazorComponentDefinitionHelpersTest.cs | 9 +++----- 9 files changed, 35 insertions(+), 51 deletions(-) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Definition/RazorComponentDefinitionService.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Definition/RazorComponentDefinitionService.cs index fdd1c6cc6e9..a95d0f3fdcb 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Definition/RazorComponentDefinitionService.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Definition/RazorComponentDefinitionService.cs @@ -1,19 +1,9 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Razor.Language; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Razor.DocumentMapping; using Microsoft.CodeAnalysis.Razor.GoToDefinition; using Microsoft.CodeAnalysis.Razor.Logging; -using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Razor.Workspaces; namespace Microsoft.AspNetCore.Razor.LanguageServer.Definition; @@ -24,10 +14,4 @@ internal sealed class RazorComponentDefinitionService( ILoggerFactory loggerFactory) : AbstractRazorComponentDefinitionService(componentSearchEngine, documentMappingService, loggerFactory.GetOrCreateLogger()) { - protected override async ValueTask GetCSharpSyntaxTreeAsync(IDocumentSnapshot documentSnapshot, CancellationToken cancellationToken) - { - var codeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); - var csharpText = codeDocument.GetCSharpSourceText(); - return CSharpSyntaxTree.ParseText(csharpText, cancellationToken: cancellationToken); - } } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/GoToDefinition/AbstractRazorComponentDefinitionService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/GoToDefinition/AbstractRazorComponentDefinitionService.cs index fea7e1c7137..789d1fbdc05 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/GoToDefinition/AbstractRazorComponentDefinitionService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/GoToDefinition/AbstractRazorComponentDefinitionService.cs @@ -69,11 +69,8 @@ private async Task GetNavigateRangeAsync(IDocumentSnapshot documentSna { _logger.LogInformation($"Attempting to get definition from an attribute directly."); - var originCodeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); - var syntaxTree = await GetCSharpSyntaxTreeAsync(documentSnapshot, cancellationToken).ConfigureAwait(false); - var range = await RazorComponentDefinitionHelpers - .TryGetPropertyRangeAsync(originCodeDocument, syntaxTree, attributeDescriptor.GetPropertyName(), _documentMappingService, _logger, cancellationToken) + .TryGetPropertyRangeAsync(documentSnapshot, attributeDescriptor.GetPropertyName(), _documentMappingService, _logger, cancellationToken) .ConfigureAwait(false); if (range is not null) @@ -88,6 +85,4 @@ private async Task GetNavigateRangeAsync(IDocumentSnapshot documentSna // at least then press F7 to go there. return VsLspFactory.DefaultRange; } - - protected abstract ValueTask GetCSharpSyntaxTreeAsync(IDocumentSnapshot documentSnapshot, CancellationToken cancellationToken); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/GoToDefinition/RazorComponentDefinitionHelpers.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/GoToDefinition/RazorComponentDefinitionHelpers.cs index f01d60d4ac8..8d5352dc20c 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/GoToDefinition/RazorComponentDefinitionHelpers.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/GoToDefinition/RazorComponentDefinitionHelpers.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Razor.DocumentMapping; using Microsoft.CodeAnalysis.Razor.Logging; +using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.VisualStudio.LanguageServer.Protocol; using LspRange = Microsoft.VisualStudio.LanguageServer.Protocol.Range; @@ -129,8 +130,7 @@ static bool TryGetTagName(RazorSyntaxNode node, [NotNullWhen(true)] out RazorSyn } public static async Task TryGetPropertyRangeAsync( - RazorCodeDocument codeDocument, - SyntaxTree csharpSyntaxTree, + IDocumentSnapshot documentSnapshot, string propertyName, IDocumentMappingService documentMappingService, ILogger logger, @@ -148,7 +148,9 @@ static bool TryGetTagName(RazorSyntaxNode node, [NotNullWhen(true)] out RazorSyn // will error, but allowing them to Go To Def on that property regardless, actually helps // them fix the error. + var csharpSyntaxTree = await documentSnapshot.GetCSharpSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); var root = await csharpSyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); + var codeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); // Since we know how the compiler generates the C# source we can be a little specific here, and avoid // long tree walks. If the compiler ever changes how they generate their code, the tests for this will break diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs index e43ca72a06c..a15d6fafa85 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs @@ -3,8 +3,10 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Razor.ProjectSystem; @@ -62,4 +64,11 @@ public IDocumentSnapshot WithText(SourceText text) { return new DocumentSnapshot(ProjectInternal, State.WithText(text, VersionStamp.Create())); } + + public async Task GetCSharpSyntaxTreeAsync(CancellationToken cancellationToken) + { + var codeDocument = await GetGeneratedOutputAsync().ConfigureAwait(false); + var csharpText = codeDocument.GetCSharpSourceText(); + return CSharpSyntaxTree.ParseText(csharpText, cancellationToken: cancellationToken); + } } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshot.cs index c1306beee09..c833a671a02 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshot.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System.Diagnostics.CodeAnalysis; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis.Text; @@ -20,6 +21,12 @@ internal interface IDocumentSnapshot Task GetTextVersionAsync(); Task GetGeneratedOutputAsync(); + /// + /// Gets the Roslyn syntax tree for the generated C# for this Razor document + /// + /// Using this from the LSP server side of things is not ideal. Use sparingly :) + Task GetCSharpSyntaxTreeAsync(CancellationToken cancellationToken); + bool TryGetText([NotNullWhen(true)] out SourceText? result); bool TryGetTextVersion(out VersionStamp result); bool TryGetGeneratedOutput([NotNullWhen(true)] out RazorCodeDocument? result); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ImportDocumentSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ImportDocumentSnapshot.cs index 68f7b345d16..c6bee4903da 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ImportDocumentSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ImportDocumentSnapshot.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis.Text; @@ -75,4 +76,7 @@ public bool TryGetGeneratedOutput([NotNullWhen(true)] out RazorCodeDocument? res public IDocumentSnapshot WithText(SourceText text) => throw new NotSupportedException(); + + public Task GetCSharpSyntaxTreeAsync(CancellationToken cancellationToken) + => throw new NotSupportedException(); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToDefinition/RazorComponentDefinitionService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToDefinition/RazorComponentDefinitionService.cs index 4c0abf315f5..790ad2c5f2f 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToDefinition/RazorComponentDefinitionService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToDefinition/RazorComponentDefinitionService.cs @@ -2,16 +2,10 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System.Composition; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Razor; using Microsoft.CodeAnalysis.Razor.DocumentMapping; using Microsoft.CodeAnalysis.Razor.GoToDefinition; using Microsoft.CodeAnalysis.Razor.Logging; -using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Razor.Workspaces; -using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; namespace Microsoft.CodeAnalysis.Remote.Razor.GoToDefinition; @@ -23,19 +17,4 @@ internal sealed class RazorComponentDefinitionService( ILoggerFactory loggerFactory) : AbstractRazorComponentDefinitionService(componentSearchEngine, documentMappingService, loggerFactory.GetOrCreateLogger()) { - protected override async ValueTask GetCSharpSyntaxTreeAsync(IDocumentSnapshot documentSnapshot, CancellationToken cancellationToken) - { - Debug.Assert(documentSnapshot is RemoteDocumentSnapshot, "This method only works on document snapshots created in the OOP process"); - - var remoteSnapshot = (RemoteDocumentSnapshot)documentSnapshot; - var document = await remoteSnapshot.GetGeneratedDocumentAsync().ConfigureAwait(false); - - if (document.TryGetSyntaxTree(out var syntaxTree)) - { - return syntaxTree; - } - - var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - return tree.AssumeNotNull(); - } } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs index 2421936ea91..e31bc695da3 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs @@ -117,4 +117,11 @@ private async Task HACK_GenerateDocumentAsync() // HACK: We're not in the same solution fork as the LSP server that provides content for this document return generatedDocument.WithText(csharpSourceText); } + + public async Task GetCSharpSyntaxTreeAsync(CancellationToken cancellationToken) + { + var document = await GetGeneratedDocumentAsync().ConfigureAwait(false); + var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + return tree.AssumeNotNull(); + } } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Definition/RazorComponentDefinitionHelpersTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Definition/RazorComponentDefinitionHelpersTest.cs index 470f4fb33d3..8f18f575019 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Definition/RazorComponentDefinitionHelpersTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Definition/RazorComponentDefinitionHelpersTest.cs @@ -1,13 +1,12 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. -using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.LanguageServer.Completion; using Microsoft.AspNetCore.Razor.Test.Common; using Microsoft.AspNetCore.Razor.Test.Common.LanguageServer; -using Microsoft.CodeAnalysis.CSharp; +using Microsoft.AspNetCore.Razor.Test.Common.ProjectSystem; using Microsoft.CodeAnalysis.Razor.GoToDefinition; using Microsoft.CodeAnalysis.Testing; using Microsoft.VisualStudio.LanguageServer.Protocol; @@ -391,13 +390,11 @@ private async Task VerifyTryGetPropertyRangeAsync(string content, string propert var codeDocument = CreateCodeDocument(content); var expectedRange = codeDocument.Source.Text.GetRange(selection); + var snapshot = TestDocumentSnapshot.Create("test.razor", content).With(codeDocument); var documentMappingService = new LspDocumentMappingService(FilePathService, new TestDocumentContextFactory(), LoggerFactory); - var csharpText = codeDocument.GetCSharpSourceText(); - var syntaxTree = CSharpSyntaxTree.ParseText(csharpText); - - var range = await RazorComponentDefinitionHelpers.TryGetPropertyRangeAsync(codeDocument, syntaxTree, propertyName, documentMappingService, Logger, DisposalToken); + var range = await RazorComponentDefinitionHelpers.TryGetPropertyRangeAsync(snapshot, propertyName, documentMappingService, Logger, DisposalToken); Assert.NotNull(range); Assert.Equal(expectedRange, range); }