From 023145d072f93bc898b1779bb14c542e6051a061 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 26 Feb 2025 14:15:50 +1100 Subject: [PATCH 1/8] Unskip tests --- .../Cohost/CodeActions/ExtractToCodeBehindTests.cs | 3 +-- .../Cohost/CohostFindAllReferencesEndpointTest.cs | 6 +++--- .../Cohost/CohostGoToDefinitionEndpointTest.cs | 10 +++++----- .../Cohost/CohostGoToImplementationEndpointTest.cs | 6 +++--- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CodeActions/ExtractToCodeBehindTests.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CodeActions/ExtractToCodeBehindTests.cs index c6e3b6323a0..b5a479aff17 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CodeActions/ExtractToCodeBehindTests.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CodeActions/ExtractToCodeBehindTests.cs @@ -1,7 +1,6 @@ // 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.Threading.Tasks; using Microsoft.AspNetCore.Razor.Test.Common; using Xunit.Abstractions; @@ -11,7 +10,7 @@ namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost.CodeActions; public class ExtractToCodeBehindTests(FuseTestContext context, ITestOutputHelper testOutputHelper) : CohostCodeActionsEndpointTestBase(context, testOutputHelper) { - [FuseFact(Skip = "Need to map uri back to source generated document")] + [FuseFact] public async Task ExtractToCodeBehind() { await VerifyCodeActionAsync( diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostFindAllReferencesEndpointTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostFindAllReferencesEndpointTest.cs index 80011167cd7..e08fccbd385 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostFindAllReferencesEndpointTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostFindAllReferencesEndpointTest.cs @@ -17,7 +17,7 @@ namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost; public class CohostFindAllReferencesEndpointTest(FuseTestContext context, ITestOutputHelper testOutputHelper) : CohostEndpointTestBase(testOutputHelper), IClassFixture { - [FuseTheory(Skip = "IFilePathService does not yet map generated documents")] + [FuseTheory] [CombinatorialData] public Task FindCSharpMember(bool supportsVSExtensions) => VerifyFindAllReferencesAsync(""" @@ -36,7 +36,7 @@ string M() """, supportsVSExtensions); - [FuseTheory(Skip = "IFilePathService does not yet map generated documents")] + [FuseTheory] [CombinatorialData] public async Task ComponentAttribute(bool supportsVSExtensions) { @@ -60,7 +60,7 @@ await VerifyFindAllReferencesAsync(input, supportsVSExtensions, (FilePath("SurveyPrompt.razor"), surveyPrompt)); } - [FuseTheory(Skip = "IFilePathService does not yet map generated documents")] + [FuseTheory] [CombinatorialData] public async Task OtherCSharpFile(bool supportsVSExtensions) { 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 1bfd5c1165d..9ab960df2b0 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 @@ -21,7 +21,7 @@ namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost; public class CohostGoToDefinitionEndpointTest(FuseTestContext context, ITestOutputHelper testOutputHelper) : CohostEndpointTestBase(testOutputHelper), IClassFixture { - [FuseFact(Skip = "IFilePathService does not yet map generated documents")] + [FuseFact] public async Task CSharp_Method() { var input = """ @@ -40,7 +40,7 @@ public async Task CSharp_Method() await VerifyGoToDefinitionAsync(input); } - [FuseFact(Skip = "IFilePathService does not yet map generated documents")] + [FuseFact] public async Task CSharp_Local() { var input = """ @@ -87,7 +87,7 @@ public async Task CSharp_MetadataReference() Assert.Contains("public sealed class String", line); } - [FuseTheory(Skip = "IFilePathService does not yet map generated documents")] + [FuseTheory] [InlineData("$$IncrementCount")] [InlineData("In$$crementCount")] [InlineData("IncrementCount$$")] @@ -107,7 +107,7 @@ public async Task Attribute_SameFile(string method) await VerifyGoToDefinitionAsync(input, FileKinds.Component); } - [FuseFact(Skip = "IFilePathService does not yet map generated documents")] + [FuseFact] public async Task AttributeValue_BindAfter() { var input = """ @@ -157,7 +157,7 @@ public async Task Component() Assert.Equal(range, location.Range); } - [FuseTheory(Skip = "IFilePathService does not yet map generated documents")] + [FuseTheory] [InlineData("Ti$$tle")] [InlineData("$$@bind-Title")] [InlineData("@$$bind-Title")] diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostGoToImplementationEndpointTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostGoToImplementationEndpointTest.cs index 6db852ad742..7bc1983423e 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostGoToImplementationEndpointTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostGoToImplementationEndpointTest.cs @@ -19,7 +19,7 @@ namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost; public class CohostGoToImplementationEndpointTest(FuseTestContext context, ITestOutputHelper testOutputHelper) : CohostEndpointTestBase(testOutputHelper), IClassFixture { - [FuseFact(Skip = "IFilePathService does not yet map generated documents")] + [FuseFact] public async Task CSharp_Method() { var input = """ @@ -40,7 +40,7 @@ public async Task CSharp_Method() await VerifyCSharpGoToImplementationAsync(input); } - [FuseFact(Skip = "IFilePathService does not yet map generated documents")] + [FuseFact] public async Task CSharp_Field() { var input = """ @@ -64,7 +64,7 @@ string GetX() await VerifyCSharpGoToImplementationAsync(input); } - [FuseFact(Skip = "IFilePathService does not yet map generated documents")] + [FuseFact] public async Task CSharp_Multiple() { var input = """ From e0211f12b3fef1627c81c36cd5e9368b3a616b7b Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 26 Feb 2025 14:16:21 +1100 Subject: [PATCH 2/8] Strongly type generated documents --- .../Extensions/ProjectExtensions.cs | 2 +- .../Completion/RemoteCompletionService.cs | 2 +- .../ProjectSystem/RemoteDocumentSnapshot.cs | 4 ++-- .../ProjectSystem/RemoteProjectSnapshot.cs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/ProjectExtensions.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/ProjectExtensions.cs index 879059f3d9b..3ca6762a5c1 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/ProjectExtensions.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/ProjectExtensions.cs @@ -40,7 +40,7 @@ public static bool TryGetCSharpDocument(this Project project, Uri csharpDocument /// /// Finds source generated documents by iterating through all of them. In OOP there are better options! /// - public static async Task TryGetSourceGeneratedDocumentFromHintNameAsync(this Project project, string? hintName, CancellationToken cancellationToken) + public static async Task TryGetSourceGeneratedDocumentFromHintNameAsync(this Project project, string? hintName, CancellationToken cancellationToken) { // TODO: use this when the location is case-insensitive on windows (https://github.com/dotnet/roslyn/issues/76869) //var generator = typeof(RazorSourceGenerator); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Completion/RemoteCompletionService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Completion/RemoteCompletionService.cs index b5878950d4b..ef6a031d8eb 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Completion/RemoteCompletionService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Completion/RemoteCompletionService.cs @@ -171,7 +171,7 @@ private async ValueTask GetCompletionAsync( var generatedText = await generatedDocument.GetTextAsync(cancellationToken).ConfigureAwait(false); var change = generatedText.GetTextChange(provisionalTextEdit); generatedText = generatedText.WithChanges([change]); - generatedDocument = generatedDocument.WithText(generatedText); + generatedDocument = (SourceGeneratedDocument)generatedDocument.WithText(generatedText); } // This is, to say the least, not ideal. In future we're going to normalize on to Roslyn LSP types, and this can go. 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 7a8ea98f4e7..84009a57db4 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs @@ -22,7 +22,7 @@ internal sealed class RemoteDocumentSnapshot : IDocumentSnapshot public RemoteProjectSnapshot ProjectSnapshot { get; } private RazorCodeDocument? _codeDocument; - private Document? _generatedDocument; + private SourceGeneratedDocument? _generatedDocument; public RemoteDocumentSnapshot(TextDocument textDocument, RemoteProjectSnapshot projectSnapshot) { @@ -103,7 +103,7 @@ public IDocumentSnapshot WithText(SourceText text) return snapshotManager.GetSnapshot(newDocument); } - public async ValueTask GetGeneratedDocumentAsync(CancellationToken cancellationToken) + public async ValueTask GetGeneratedDocumentAsync(CancellationToken cancellationToken) { if (_generatedDocument is not null) { diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteProjectSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteProjectSnapshot.cs index 3bfb4d3eb25..68359ec1f51 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteProjectSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteProjectSnapshot.cs @@ -169,7 +169,7 @@ public bool TryGetDocument(string filePath, [NotNullWhen(true)] out IDocumentSna return generatorResult.GetCodeDocument(documentSnapshot.FilePath); } - internal async Task GetGeneratedDocumentAsync(IDocumentSnapshot documentSnapshot, CancellationToken cancellationToken) + internal async Task GetGeneratedDocumentAsync(IDocumentSnapshot documentSnapshot, CancellationToken cancellationToken) { var generatorResult = await GetRazorGeneratorResultAsync(cancellationToken).ConfigureAwait(false); if (generatorResult is null) From d283f3ee6aeda1998b29066ffe7136f8a108b779 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 26 Feb 2025 14:16:36 +1100 Subject: [PATCH 3/8] Create reverse map in source generator outputs --- .../src/SourceGenerators/RazorGeneratorResult.cs | 8 +++++--- .../src/SourceGenerators/RazorSourceGenerator.cs | 15 ++++++++++++--- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorGeneratorResult.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorGeneratorResult.cs index e44cfa7684d..943913aa799 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorGeneratorResult.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorGeneratorResult.cs @@ -7,12 +7,14 @@ namespace Microsoft.NET.Sdk.Razor.SourceGenerators { - internal sealed class RazorGeneratorResult(IReadOnlyList tagHelpers, ImmutableDictionary documents) + internal sealed class RazorGeneratorResult(IReadOnlyList tagHelpers, ImmutableDictionary filePathToDocument, ImmutableDictionary hintNameToFilePath) { public IReadOnlyList TagHelpers => tagHelpers; - public RazorCodeDocument? GetCodeDocument(string physicalPath) => documents.TryGetValue(physicalPath, out var pair) ? pair.document : null; + public RazorCodeDocument? GetCodeDocument(string physicalPath) => filePathToDocument.TryGetValue(physicalPath, out var pair) ? pair.document : null; - public string? GetHintName(string physicalPath) => documents.TryGetValue(physicalPath, out var pair) ? pair.hintName : null; + public string? GetHintName(string physicalPath) => filePathToDocument.TryGetValue(physicalPath, out var pair) ? pair.hintName : null; + + public string? GetFilePath(string hintName) => hintNameToFilePath.TryGetValue(hintName, out var filePath) ? filePath : null; } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorSourceGenerator.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorSourceGenerator.cs index 5c1d6482e54..2fdb76a1686 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorSourceGenerator.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorSourceGenerator.cs @@ -3,10 +3,11 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.IO; using System.Linq; +using Microsoft.AspNetCore.Razor; using Microsoft.AspNetCore.Razor.Language; +using Microsoft.AspNetCore.Razor.PooledObjects; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -348,8 +349,16 @@ public void Initialize(IncrementalGeneratorInitializationContext context) if (!isGeneratorSuppressed) { - var documentDictionary = documents.Select(p => KeyValuePair.Create(p.codeDocument.Source.FilePath!, (p.hintName, p.codeDocument))).ToImmutableDictionary(); - context.AddOutput(nameof(RazorGeneratorResult), new RazorGeneratorResult(tagHelpers, documentDictionary)); + using var filePathToDocument = new PooledDictionaryBuilder(); + using var hintNameToFilePath = new PooledDictionaryBuilder(); + + foreach (var (hintName, codeDocument, _) in documents) + { + filePathToDocument.Add(codeDocument.Source.FilePath!, (hintName, codeDocument)); + hintNameToFilePath.Add(hintName, codeDocument.Source.FilePath!); + } + + context.AddOutput(nameof(RazorGeneratorResult), new RazorGeneratorResult(tagHelpers, filePathToDocument.ToImmutable(), hintNameToFilePath.ToImmutable())); } }); } From 723d98f36f42a2148169be2f64ce1128ba312bd1 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 26 Feb 2025 14:17:16 +1100 Subject: [PATCH 4/8] Add some helpers for dealing with source generator documents --- .../Extensions/ProjectExtensions.cs | 13 +++++++++++++ .../ProjectSystem/RemoteProjectSnapshot.cs | 17 +++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/ProjectExtensions.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/ProjectExtensions.cs index 3ca6762a5c1..e1664aad2c7 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/ProjectExtensions.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/ProjectExtensions.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor; +using Microsoft.CodeAnalysis.ExternalAccess.Razor; namespace Microsoft.CodeAnalysis; @@ -51,4 +52,16 @@ public static bool TryGetCSharpDocument(this Project project, Uri csharpDocument var generatedDocuments = await project.GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false); return generatedDocuments.SingleOrDefault(d => d.HintName == hintName); } + + /// + /// Finds source generated documents by iterating through all of them. In OOP there are better options! + /// + public static async Task TryGetHintNameFromGeneratedDocumentUriAsync(this Project project, Uri generatedDocumentUri, CancellationToken cancellationToken) + { + // TODO: Call SourceGeneratedDocumentUri.DeserializeIdentity: https://github.com/dotnet/razor/issues/11557 + + var generatedDocuments = await project.GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false); + var document = generatedDocuments.SingleOrDefault(d => generatedDocumentUri.Equals(d.CreateUri())); + return document?.HintName; + } } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteProjectSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteProjectSnapshot.cs index 68359ec1f51..38fa5179f45 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteProjectSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteProjectSnapshot.cs @@ -184,6 +184,23 @@ public bool TryGetDocument(string filePath, [NotNullWhen(true)] out IDocumentSna return generatedDocument ?? throw new InvalidOperationException("Couldn't get the source generated document for a hint name that we got from the generator?"); } + public async Task TryGetCodeDocumentFromGeneratedHintNameAsync(string generatedDocumentHintName, CancellationToken cancellationToken) + { + var runResult = await GetRazorGeneratorResultAsync(cancellationToken).ConfigureAwait(false); + if (runResult is null) + { + return null; + } + + if (runResult.GetFilePath(generatedDocumentHintName) is { } razorFilePath && + runResult.GetCodeDocument(razorFilePath) is { } codeDocument) + { + return codeDocument; + } + + return null; + } + private async Task GetRazorGeneratorResultAsync(CancellationToken cancellationToken) { var result = await _project.GetSourceGeneratorRunResultAsync(cancellationToken).ConfigureAwait(false); From 612327139cee3256212493bcb432be8c28a4465f Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 26 Feb 2025 14:17:50 +1100 Subject: [PATCH 5/8] Update how the span mapper deals with generated document Uris --- .../RemoteDocumentMappingService.cs | 40 +++++++------------ 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentMapping/RemoteDocumentMappingService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentMapping/RemoteDocumentMappingService.cs index e4f6acf3615..efd0d887ab4 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentMapping/RemoteDocumentMappingService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentMapping/RemoteDocumentMappingService.cs @@ -2,11 +2,13 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Composition; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor; using Microsoft.AspNetCore.Razor.Language; +using Microsoft.CodeAnalysis.ExternalAccess.Razor; using Microsoft.CodeAnalysis.Razor.DocumentMapping; using Microsoft.CodeAnalysis.Razor.Logging; using Microsoft.CodeAnalysis.Razor.Workspaces; @@ -32,11 +34,10 @@ internal sealed class RemoteDocumentMappingService( LinePositionSpan generatedDocumentRange, CancellationToken cancellationToken) { - var razorDocumentUri = _filePathService.GetRazorDocumentUri(generatedDocumentUri); - // For Html we just map the Uri, the range will be the same if (_filePathService.IsVirtualHtmlFile(generatedDocumentUri)) { + var razorDocumentUri = _filePathService.GetRazorDocumentUri(generatedDocumentUri); return (razorDocumentUri, generatedDocumentRange); } @@ -46,31 +47,18 @@ internal sealed class RemoteDocumentMappingService( return (generatedDocumentUri, generatedDocumentRange); } - var solution = originSnapshot.TextDocument.Project.Solution; - if (!solution.TryGetRazorDocument(razorDocumentUri, out var razorDocument)) - { - return (generatedDocumentUri, generatedDocumentRange); - } - - var razorDocumentSnapshot = _snapshotManager.GetSnapshot(razorDocument); - - var razorCodeDocument = await razorDocumentSnapshot - .GetGeneratedOutputAsync(cancellationToken) - .ConfigureAwait(false); - - if (razorCodeDocument is null) - { - return (generatedDocumentUri, generatedDocumentRange); - } - - if (!razorCodeDocument.TryGetGeneratedDocument(generatedDocumentUri, _filePathService, out var generatedDocument)) - { - return Assumed.Unreachable<(Uri, LinePositionSpan)>(); - } - - if (TryMapToHostDocumentRange(generatedDocument, generatedDocumentRange, MappingBehavior.Strict, out var mappedRange)) + var project = originSnapshot.TextDocument.Project; + var solution = project.Solution; + if (await project.TryGetHintNameFromGeneratedDocumentUriAsync(generatedDocumentUri, cancellationToken).ConfigureAwait(false) is { } hintName && + await project.TryGetSourceGeneratedDocumentFromHintNameAsync(hintName, cancellationToken).ConfigureAwait(false) is { } generatedDocument && + await _snapshotManager.GetSnapshot(generatedDocument.Project).TryGetCodeDocumentFromGeneratedHintNameAsync(hintName, cancellationToken).ConfigureAwait(false) is { } razorCodeDocument && + TryMapToHostDocumentRange(razorCodeDocument.GetCSharpDocument(), generatedDocumentRange, MappingBehavior.Strict, out var mappedRange)) { - return (razorDocumentUri, mappedRange); + // TODO: Should we have a better way to do this? Store Uri in RazorCodeDocument? + var filePath = razorCodeDocument.Source.FilePath; + var documentId = solution.GetDocumentIdsWithFilePath(filePath).First(); + var document = solution.GetAdditionalDocument(documentId).AssumeNotNull(); + return (document.CreateUri(), mappedRange); } return (generatedDocumentUri, generatedDocumentRange); From 02de28db69eba3445fb285a7f51b7ef049f77c21 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 26 Feb 2025 14:17:59 +1100 Subject: [PATCH 6/8] Roslyn changed their Uri scheme --- .../RemoteFilePathService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RemoteFilePathService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RemoteFilePathService.cs index d73148a321d..cf92013ab83 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RemoteFilePathService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RemoteFilePathService.cs @@ -23,6 +23,6 @@ public override Uri GetRazorDocumentUri(Uri virtualDocumentUri) public override bool IsVirtualCSharpFile(Uri uri) { - return uri.Scheme == "source-generated"; + return uri.Scheme == "roslyn-source-generated"; } } From 6a2936fd28371f1c9c59bf8e03fb90160c7a4fbd Mon Sep 17 00:00:00 2001 From: David Wengier Date: Mon, 3 Mar 2025 09:00:03 +1100 Subject: [PATCH 7/8] PR Feedback --- .../RemoteDocumentMappingService.cs | 14 +++++++------ .../ProjectSystem/RemoteProjectSnapshot.cs | 21 ++++++++++++------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentMapping/RemoteDocumentMappingService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentMapping/RemoteDocumentMappingService.cs index efd0d887ab4..be70330e9fc 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentMapping/RemoteDocumentMappingService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentMapping/RemoteDocumentMappingService.cs @@ -48,13 +48,15 @@ internal sealed class RemoteDocumentMappingService( } var project = originSnapshot.TextDocument.Project; - var solution = project.Solution; - if (await project.TryGetHintNameFromGeneratedDocumentUriAsync(generatedDocumentUri, cancellationToken).ConfigureAwait(false) is { } hintName && - await project.TryGetSourceGeneratedDocumentFromHintNameAsync(hintName, cancellationToken).ConfigureAwait(false) is { } generatedDocument && - await _snapshotManager.GetSnapshot(generatedDocument.Project).TryGetCodeDocumentFromGeneratedHintNameAsync(hintName, cancellationToken).ConfigureAwait(false) is { } razorCodeDocument && - TryMapToHostDocumentRange(razorCodeDocument.GetCSharpDocument(), generatedDocumentRange, MappingBehavior.Strict, out var mappedRange)) + var razorCodeDocument = await _snapshotManager.GetSnapshot(project).TryGetCodeDocumentFromGeneratedDocumentUriAsync(generatedDocumentUri, cancellationToken).ConfigureAwait(false); + if (razorCodeDocument is null) { - // TODO: Should we have a better way to do this? Store Uri in RazorCodeDocument? + return (generatedDocumentUri, generatedDocumentRange); + } + + if (TryMapToHostDocumentRange(razorCodeDocument.GetCSharpDocument(), generatedDocumentRange, MappingBehavior.Strict, out var mappedRange)) + { + var solution = project.Solution; var filePath = razorCodeDocument.Source.FilePath; var documentId = solution.GetDocumentIdsWithFilePath(filePath).First(); var document = solution.GetAdditionalDocument(documentId).AssumeNotNull(); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteProjectSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteProjectSnapshot.cs index 38fa5179f45..8b6dd166c2b 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteProjectSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteProjectSnapshot.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using EnvDTE; using Microsoft.AspNetCore.Razor; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.ProjectEngineHost; @@ -184,21 +185,27 @@ public bool TryGetDocument(string filePath, [NotNullWhen(true)] out IDocumentSna return generatedDocument ?? throw new InvalidOperationException("Couldn't get the source generated document for a hint name that we got from the generator?"); } - public async Task TryGetCodeDocumentFromGeneratedHintNameAsync(string generatedDocumentHintName, CancellationToken cancellationToken) + public async Task TryGetCodeDocumentFromGeneratedDocumentUriAsync(Uri generatedDocumentUri, CancellationToken cancellationToken) { - var runResult = await GetRazorGeneratorResultAsync(cancellationToken).ConfigureAwait(false); - if (runResult is null) + if (await _project.TryGetHintNameFromGeneratedDocumentUriAsync(generatedDocumentUri, cancellationToken).ConfigureAwait(false) is not { } hintName) { return null; } - if (runResult.GetFilePath(generatedDocumentHintName) is { } razorFilePath && - runResult.GetCodeDocument(razorFilePath) is { } codeDocument) + return await TryGetCodeDocumentFromGeneratedHintNameAsync(hintName, cancellationToken).ConfigureAwait(false); + } + + public async Task TryGetCodeDocumentFromGeneratedHintNameAsync(string generatedDocumentHintName, CancellationToken cancellationToken) + { + var runResult = await GetRazorGeneratorResultAsync(cancellationToken).ConfigureAwait(false); + if (runResult is null) { - return codeDocument; + return null; } - return null; + return runResult.GetFilePath(generatedDocumentHintName) is { } razorFilePath + ? runResult.GetCodeDocument(razorFilePath) + : null; } private async Task GetRazorGeneratorResultAsync(CancellationToken cancellationToken) From 55c5ff59c80bcfa7d2e0c8ab80823e73830e1dbb Mon Sep 17 00:00:00 2001 From: David Wengier Date: Mon, 3 Mar 2025 13:26:41 +1100 Subject: [PATCH 8/8] Remote unused using --- .../ProjectSystem/RemoteProjectSnapshot.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteProjectSnapshot.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteProjectSnapshot.cs index 8b6dd166c2b..c7f9d255ebd 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteProjectSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteProjectSnapshot.cs @@ -9,7 +9,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using EnvDTE; using Microsoft.AspNetCore.Razor; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.ProjectEngineHost;