Skip to content

Commit d84fe4c

Browse files
authored
Update document mapping to work with the source generator (#11558)
Fixes a bunch of cohost functionality post source generator hookup Part of #10693
2 parents 950b842 + 55c5ff5 commit d84fe4c

File tree

12 files changed

+82
-46
lines changed

12 files changed

+82
-46
lines changed

src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorGeneratorResult.cs

+5-3
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@
77

88
namespace Microsoft.NET.Sdk.Razor.SourceGenerators
99
{
10-
internal sealed class RazorGeneratorResult(IReadOnlyList<TagHelperDescriptor> tagHelpers, ImmutableDictionary<string, (string hintName, RazorCodeDocument document)> documents)
10+
internal sealed class RazorGeneratorResult(IReadOnlyList<TagHelperDescriptor> tagHelpers, ImmutableDictionary<string, (string hintName, RazorCodeDocument document)> filePathToDocument, ImmutableDictionary<string, string> hintNameToFilePath)
1111
{
1212
public IReadOnlyList<TagHelperDescriptor> TagHelpers => tagHelpers;
1313

14-
public RazorCodeDocument? GetCodeDocument(string physicalPath) => documents.TryGetValue(physicalPath, out var pair) ? pair.document : null;
14+
public RazorCodeDocument? GetCodeDocument(string physicalPath) => filePathToDocument.TryGetValue(physicalPath, out var pair) ? pair.document : null;
1515

16-
public string? GetHintName(string physicalPath) => documents.TryGetValue(physicalPath, out var pair) ? pair.hintName : null;
16+
public string? GetHintName(string physicalPath) => filePathToDocument.TryGetValue(physicalPath, out var pair) ? pair.hintName : null;
17+
18+
public string? GetFilePath(string hintName) => hintNameToFilePath.TryGetValue(hintName, out var filePath) ? filePath : null;
1719
}
1820
}

src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorSourceGenerator.cs

+12-3
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33

44
using System;
55
using System.Collections.Generic;
6-
using System.Collections.Immutable;
76
using System.IO;
87
using System.Linq;
8+
using Microsoft.AspNetCore.Razor;
99
using Microsoft.AspNetCore.Razor.Language;
10+
using Microsoft.AspNetCore.Razor.PooledObjects;
1011
using Microsoft.CodeAnalysis;
1112
using Microsoft.CodeAnalysis.CSharp;
1213

@@ -346,8 +347,16 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
346347

347348
if (!isGeneratorSuppressed)
348349
{
349-
var documentDictionary = documents.Select(p => KeyValuePair.Create(p.codeDocument.Source.FilePath!, (p.hintName, p.codeDocument))).ToImmutableDictionary();
350-
context.AddOutput(nameof(RazorGeneratorResult), new RazorGeneratorResult(tagHelpers, documentDictionary));
350+
using var filePathToDocument = new PooledDictionaryBuilder<string, (string, RazorCodeDocument)>();
351+
using var hintNameToFilePath = new PooledDictionaryBuilder<string, string>();
352+
353+
foreach (var (hintName, codeDocument, _) in documents)
354+
{
355+
filePathToDocument.Add(codeDocument.Source.FilePath!, (hintName, codeDocument));
356+
hintNameToFilePath.Add(hintName, codeDocument.Source.FilePath!);
357+
}
358+
359+
context.AddOutput(nameof(RazorGeneratorResult), new RazorGeneratorResult(tagHelpers, filePathToDocument.ToImmutable(), hintNameToFilePath.ToImmutable()));
351360
}
352361
});
353362
}

src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/ProjectExtensions.cs

+14-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Threading;
88
using System.Threading.Tasks;
99
using Microsoft.AspNetCore.Razor;
10+
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
1011

1112
namespace Microsoft.CodeAnalysis;
1213

@@ -40,7 +41,7 @@ public static bool TryGetCSharpDocument(this Project project, Uri csharpDocument
4041
/// <summary>
4142
/// Finds source generated documents by iterating through all of them. In OOP there are better options!
4243
/// </summary>
43-
public static async Task<Document?> TryGetSourceGeneratedDocumentFromHintNameAsync(this Project project, string? hintName, CancellationToken cancellationToken)
44+
public static async Task<SourceGeneratedDocument?> TryGetSourceGeneratedDocumentFromHintNameAsync(this Project project, string? hintName, CancellationToken cancellationToken)
4445
{
4546
// TODO: use this when the location is case-insensitive on windows (https://github.com/dotnet/roslyn/issues/76869)
4647
//var generator = typeof(RazorSourceGenerator);
@@ -51,4 +52,16 @@ public static bool TryGetCSharpDocument(this Project project, Uri csharpDocument
5152
var generatedDocuments = await project.GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false);
5253
return generatedDocuments.SingleOrDefault(d => d.HintName == hintName);
5354
}
55+
56+
/// <summary>
57+
/// Finds source generated documents by iterating through all of them. In OOP there are better options!
58+
/// </summary>
59+
public static async Task<string?> TryGetHintNameFromGeneratedDocumentUriAsync(this Project project, Uri generatedDocumentUri, CancellationToken cancellationToken)
60+
{
61+
// TODO: Call SourceGeneratedDocumentUri.DeserializeIdentity: https://github.com/dotnet/razor/issues/11557
62+
63+
var generatedDocuments = await project.GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false);
64+
var document = generatedDocuments.SingleOrDefault(d => generatedDocumentUri.Equals(d.CreateUri()));
65+
return document?.HintName;
66+
}
5467
}

src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Completion/RemoteCompletionService.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ private async ValueTask<Response> GetCompletionAsync(
171171
var generatedText = await generatedDocument.GetTextAsync(cancellationToken).ConfigureAwait(false);
172172
var change = generatedText.GetTextChange(provisionalTextEdit);
173173
generatedText = generatedText.WithChanges([change]);
174-
generatedDocument = generatedDocument.WithText(generatedText);
174+
generatedDocument = (SourceGeneratedDocument)generatedDocument.WithText(generatedText);
175175
}
176176

177177
// This is, to say the least, not ideal. In future we're going to normalize on to Roslyn LSP types, and this can go.

src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentMapping/RemoteDocumentMappingService.cs

+11-21
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
// Licensed under the MIT license. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Collections.Generic;
56
using System.Composition;
67
using System.Threading;
78
using System.Threading.Tasks;
89
using Microsoft.AspNetCore.Razor;
910
using Microsoft.AspNetCore.Razor.Language;
11+
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
1012
using Microsoft.CodeAnalysis.Razor.DocumentMapping;
1113
using Microsoft.CodeAnalysis.Razor.Logging;
1214
using Microsoft.CodeAnalysis.Razor.Workspaces;
@@ -32,11 +34,10 @@ internal sealed class RemoteDocumentMappingService(
3234
LinePositionSpan generatedDocumentRange,
3335
CancellationToken cancellationToken)
3436
{
35-
var razorDocumentUri = _filePathService.GetRazorDocumentUri(generatedDocumentUri);
36-
3737
// For Html we just map the Uri, the range will be the same
3838
if (_filePathService.IsVirtualHtmlFile(generatedDocumentUri))
3939
{
40+
var razorDocumentUri = _filePathService.GetRazorDocumentUri(generatedDocumentUri);
4041
return (razorDocumentUri, generatedDocumentRange);
4142
}
4243

@@ -46,31 +47,20 @@ internal sealed class RemoteDocumentMappingService(
4647
return (generatedDocumentUri, generatedDocumentRange);
4748
}
4849

49-
var solution = originSnapshot.TextDocument.Project.Solution;
50-
if (!solution.TryGetRazorDocument(razorDocumentUri, out var razorDocument))
51-
{
52-
return (generatedDocumentUri, generatedDocumentRange);
53-
}
54-
55-
var razorDocumentSnapshot = _snapshotManager.GetSnapshot(razorDocument);
56-
57-
var razorCodeDocument = await razorDocumentSnapshot
58-
.GetGeneratedOutputAsync(cancellationToken)
59-
.ConfigureAwait(false);
60-
50+
var project = originSnapshot.TextDocument.Project;
51+
var razorCodeDocument = await _snapshotManager.GetSnapshot(project).TryGetCodeDocumentFromGeneratedDocumentUriAsync(generatedDocumentUri, cancellationToken).ConfigureAwait(false);
6152
if (razorCodeDocument is null)
6253
{
6354
return (generatedDocumentUri, generatedDocumentRange);
6455
}
6556

66-
if (!razorCodeDocument.TryGetGeneratedDocument(generatedDocumentUri, _filePathService, out var generatedDocument))
67-
{
68-
return Assumed.Unreachable<(Uri, LinePositionSpan)>();
69-
}
70-
71-
if (TryMapToHostDocumentRange(generatedDocument, generatedDocumentRange, MappingBehavior.Strict, out var mappedRange))
57+
if (TryMapToHostDocumentRange(razorCodeDocument.GetCSharpDocument(), generatedDocumentRange, MappingBehavior.Strict, out var mappedRange))
7258
{
73-
return (razorDocumentUri, mappedRange);
59+
var solution = project.Solution;
60+
var filePath = razorCodeDocument.Source.FilePath;
61+
var documentId = solution.GetDocumentIdsWithFilePath(filePath).First();
62+
var document = solution.GetAdditionalDocument(documentId).AssumeNotNull();
63+
return (document.CreateUri(), mappedRange);
7464
}
7565

7666
return (generatedDocumentUri, generatedDocumentRange);

src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ internal sealed class RemoteDocumentSnapshot : IDocumentSnapshot
2222
public RemoteProjectSnapshot ProjectSnapshot { get; }
2323

2424
private RazorCodeDocument? _codeDocument;
25-
private Document? _generatedDocument;
25+
private SourceGeneratedDocument? _generatedDocument;
2626

2727
public RemoteDocumentSnapshot(TextDocument textDocument, RemoteProjectSnapshot projectSnapshot)
2828
{
@@ -103,7 +103,7 @@ public IDocumentSnapshot WithText(SourceText text)
103103
return snapshotManager.GetSnapshot(newDocument);
104104
}
105105

106-
public async ValueTask<Document> GetGeneratedDocumentAsync(CancellationToken cancellationToken)
106+
public async ValueTask<SourceGeneratedDocument> GetGeneratedDocumentAsync(CancellationToken cancellationToken)
107107
{
108108
if (_generatedDocument is not null)
109109
{

src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteProjectSnapshot.cs

+24-1
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ public bool TryGetDocument(string filePath, [NotNullWhen(true)] out IDocumentSna
169169
return generatorResult.GetCodeDocument(documentSnapshot.FilePath);
170170
}
171171

172-
internal async Task<Document?> GetGeneratedDocumentAsync(IDocumentSnapshot documentSnapshot, CancellationToken cancellationToken)
172+
internal async Task<SourceGeneratedDocument?> GetGeneratedDocumentAsync(IDocumentSnapshot documentSnapshot, CancellationToken cancellationToken)
173173
{
174174
var generatorResult = await GetRazorGeneratorResultAsync(cancellationToken).ConfigureAwait(false);
175175
if (generatorResult is null)
@@ -184,6 +184,29 @@ public bool TryGetDocument(string filePath, [NotNullWhen(true)] out IDocumentSna
184184
return generatedDocument ?? throw new InvalidOperationException("Couldn't get the source generated document for a hint name that we got from the generator?");
185185
}
186186

187+
public async Task<RazorCodeDocument?> TryGetCodeDocumentFromGeneratedDocumentUriAsync(Uri generatedDocumentUri, CancellationToken cancellationToken)
188+
{
189+
if (await _project.TryGetHintNameFromGeneratedDocumentUriAsync(generatedDocumentUri, cancellationToken).ConfigureAwait(false) is not { } hintName)
190+
{
191+
return null;
192+
}
193+
194+
return await TryGetCodeDocumentFromGeneratedHintNameAsync(hintName, cancellationToken).ConfigureAwait(false);
195+
}
196+
197+
public async Task<RazorCodeDocument?> TryGetCodeDocumentFromGeneratedHintNameAsync(string generatedDocumentHintName, CancellationToken cancellationToken)
198+
{
199+
var runResult = await GetRazorGeneratorResultAsync(cancellationToken).ConfigureAwait(false);
200+
if (runResult is null)
201+
{
202+
return null;
203+
}
204+
205+
return runResult.GetFilePath(generatedDocumentHintName) is { } razorFilePath
206+
? runResult.GetCodeDocument(razorFilePath)
207+
: null;
208+
}
209+
187210
private async Task<RazorGeneratorResult?> GetRazorGeneratorResultAsync(CancellationToken cancellationToken)
188211
{
189212
var result = await _project.GetSourceGeneratorRunResultAsync(cancellationToken).ConfigureAwait(false);

src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RemoteFilePathService.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,6 @@ public override Uri GetRazorDocumentUri(Uri virtualDocumentUri)
2323

2424
public override bool IsVirtualCSharpFile(Uri uri)
2525
{
26-
return uri.Scheme == "source-generated";
26+
return uri.Scheme == "roslyn-source-generated";
2727
}
2828
}

src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CodeActions/ExtractToCodeBehindTests.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the MIT license. See License.txt in the project root for license information.
33

4-
using System;
54
using System.Threading.Tasks;
65
using Microsoft.AspNetCore.Razor.Test.Common;
76
using Xunit.Abstractions;
@@ -11,7 +10,7 @@ namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost.CodeActions;
1110

1211
public class ExtractToCodeBehindTests(FuseTestContext context, ITestOutputHelper testOutputHelper) : CohostCodeActionsEndpointTestBase(context, testOutputHelper)
1312
{
14-
[FuseFact(Skip = "Need to map uri back to source generated document")]
13+
[FuseFact]
1514
public async Task ExtractToCodeBehind()
1615
{
1716
await VerifyCodeActionAsync(

src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostFindAllReferencesEndpointTest.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
1717

1818
public class CohostFindAllReferencesEndpointTest(FuseTestContext context, ITestOutputHelper testOutputHelper) : CohostEndpointTestBase(testOutputHelper), IClassFixture<FuseTestContext>
1919
{
20-
[FuseTheory(Skip = "IFilePathService does not yet map generated documents")]
20+
[FuseTheory]
2121
[CombinatorialData]
2222
public Task FindCSharpMember(bool supportsVSExtensions)
2323
=> VerifyFindAllReferencesAsync("""
@@ -36,7 +36,7 @@ string M()
3636
""",
3737
supportsVSExtensions);
3838

39-
[FuseTheory(Skip = "IFilePathService does not yet map generated documents")]
39+
[FuseTheory]
4040
[CombinatorialData]
4141
public async Task ComponentAttribute(bool supportsVSExtensions)
4242
{
@@ -60,7 +60,7 @@ await VerifyFindAllReferencesAsync(input, supportsVSExtensions,
6060
(FilePath("SurveyPrompt.razor"), surveyPrompt));
6161
}
6262

63-
[FuseTheory(Skip = "IFilePathService does not yet map generated documents")]
63+
[FuseTheory]
6464
[CombinatorialData]
6565
public async Task OtherCSharpFile(bool supportsVSExtensions)
6666
{

src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostGoToDefinitionEndpointTest.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
2121

2222
public class CohostGoToDefinitionEndpointTest(FuseTestContext context, ITestOutputHelper testOutputHelper) : CohostEndpointTestBase(testOutputHelper), IClassFixture<FuseTestContext>
2323
{
24-
[FuseFact(Skip = "IFilePathService does not yet map generated documents")]
24+
[FuseFact]
2525
public async Task CSharp_Method()
2626
{
2727
var input = """
@@ -40,7 +40,7 @@ public async Task CSharp_Method()
4040
await VerifyGoToDefinitionAsync(input);
4141
}
4242

43-
[FuseFact(Skip = "IFilePathService does not yet map generated documents")]
43+
[FuseFact]
4444
public async Task CSharp_Local()
4545
{
4646
var input = """
@@ -87,7 +87,7 @@ public async Task CSharp_MetadataReference()
8787
Assert.Contains("public sealed class String", line);
8888
}
8989

90-
[FuseTheory(Skip = "IFilePathService does not yet map generated documents")]
90+
[FuseTheory]
9191
[InlineData("$$IncrementCount")]
9292
[InlineData("In$$crementCount")]
9393
[InlineData("IncrementCount$$")]
@@ -107,7 +107,7 @@ public async Task Attribute_SameFile(string method)
107107
await VerifyGoToDefinitionAsync(input, FileKinds.Component);
108108
}
109109

110-
[FuseFact(Skip = "IFilePathService does not yet map generated documents")]
110+
[FuseFact]
111111
public async Task AttributeValue_BindAfter()
112112
{
113113
var input = """
@@ -157,7 +157,7 @@ public async Task Component()
157157
Assert.Equal(range, location.Range);
158158
}
159159

160-
[FuseTheory(Skip = "IFilePathService does not yet map generated documents")]
160+
[FuseTheory]
161161
[InlineData("Ti$$tle")]
162162
[InlineData("$$@bind-Title")]
163163
[InlineData("@$$bind-Title")]

src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostGoToImplementationEndpointTest.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
1919

2020
public class CohostGoToImplementationEndpointTest(FuseTestContext context, ITestOutputHelper testOutputHelper) : CohostEndpointTestBase(testOutputHelper), IClassFixture<FuseTestContext>
2121
{
22-
[FuseFact(Skip = "IFilePathService does not yet map generated documents")]
22+
[FuseFact]
2323
public async Task CSharp_Method()
2424
{
2525
var input = """
@@ -40,7 +40,7 @@ public async Task CSharp_Method()
4040
await VerifyCSharpGoToImplementationAsync(input);
4141
}
4242

43-
[FuseFact(Skip = "IFilePathService does not yet map generated documents")]
43+
[FuseFact]
4444
public async Task CSharp_Field()
4545
{
4646
var input = """
@@ -64,7 +64,7 @@ string GetX()
6464
await VerifyCSharpGoToImplementationAsync(input);
6565
}
6666

67-
[FuseFact(Skip = "IFilePathService does not yet map generated documents")]
67+
[FuseFact]
6868
public async Task CSharp_Multiple()
6969
{
7070
var input = """

0 commit comments

Comments
 (0)