Skip to content

Commit 45103c5

Browse files
authored
Remove most direct calls to GetOrParseCSharpSyntaxTree (#12407)
Found while doing #12396, and indeed the first commit is straight from there, that a few features were calling `GetOrParseCSharpSyntaxTree`, which always parses the C# document. Instead, if we call `GetCSharpSyntaxTreeAsync` on the document snapshot, then in non-cohosting that will still call `GetOrParseCSharpSyntaxTree`, with the benefit of caching, and in cohosting it will get the tree from Roslyn, which in almost all cases I'm sure will have already been realised and cached by Roslyn, and more importantly will have that cached value shared with way more features across Roslyn and Razor.
2 parents c166218 + 6d631a2 commit 45103c5

File tree

8 files changed

+25
-11
lines changed

8 files changed

+25
-11
lines changed

src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Debugging/RazorBreakpointSpanEndpoint.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public Uri GetTextDocumentIdentifier(RazorBreakpointSpanParams request)
6767
}
6868

6969
// Now ask Roslyn to adjust the breakpoint to a valid location in the code
70-
var syntaxTree = codeDocument.GetOrParseCSharpSyntaxTree(cancellationToken);
70+
var syntaxTree = await documentContext.Snapshot.GetCSharpSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
7171
if (!RazorBreakpointSpans.TryGetBreakpointSpan(syntaxTree, projectedIndex, cancellationToken, out var csharpBreakpointSpan))
7272
{
7373
return null;

src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Debugging/RazorProximityExpressionsEndpoint.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public Uri GetTextDocumentIdentifier(RazorProximityExpressionsParams request)
6868
}
6969

7070
// Now ask Roslyn to adjust the breakpoint to a valid location in the code
71-
var syntaxTree = codeDocument.GetOrParseCSharpSyntaxTree(cancellationToken);
71+
var syntaxTree = await documentContext.Snapshot.GetCSharpSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
7272
var expressions = RazorCSharpProximityExpressionResolverService.GetProximityExpressions(syntaxTree, projectedIndex, cancellationToken)?.ToList();
7373
if (expressions == null)
7474
{

src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/CodeActions/Razor/GenerateMethodCodeActionResolver.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,8 @@ private async Task<WorkspaceEdit> GenerateMethodInCodeBlockAsync(
131131
if (edits.Length == 3
132132
&& razorClassName is not null
133133
&& (razorNamespace is not null || code.TryGetNamespace(fallbackToRootNamespace: true, out razorNamespace))
134-
&& GetCSharpClassDeclarationSyntax(code.GetOrParseCSharpSyntaxTree(cancellationToken), razorNamespace, razorClassName) is { } @class)
134+
&& await documentContext.Snapshot.GetCSharpSyntaxTreeAsync(cancellationToken).ConfigureAwait(false) is { } csharpSyntaxTree
135+
&& GetCSharpClassDeclarationSyntax(csharpSyntaxTree, razorNamespace, razorClassName) is { } @class)
135136
{
136137
// There is no existing @code block. This means that there is no code block source mapping in the generated C# document
137138
// to place the code, so we cannot utilize the document mapping service and the formatting service.

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using Microsoft.AspNetCore.Razor.Language.Syntax;
1111
using Microsoft.CodeAnalysis;
1212
using Microsoft.CodeAnalysis.Razor;
13+
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
1314
using Microsoft.CodeAnalysis.Razor.Protocol;
1415
using Microsoft.CodeAnalysis.Text;
1516

@@ -42,6 +43,11 @@ public static SourceText GetHtmlSourceText(this RazorCodeDocument document)
4243
/// Retrieves a cached Roslyn <see cref="SyntaxTree"/> from the generated C# document.
4344
/// If a tree has not yet been cached, a new one will be parsed and added to the cache.
4445
/// </summary>
46+
/// <remarks>
47+
/// If possible, prefer calling <see cref="IDocumentSnapshot.GetCSharpSyntaxTreeAsync(CancellationToken)" />
48+
/// because it will either call this method, or in cohosting get the syntax tree from Roslyn, where the cached
49+
/// tree can be shared with many more features.
50+
/// </remarks>
4551
public static SyntaxTree GetOrParseCSharpSyntaxTree(this RazorCodeDocument document, CancellationToken cancellationToken)
4652
=> GetCachedData(document).GetOrParseCSharpSyntaxTree(cancellationToken);
4753

src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/AddUsingsHelper.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
using Microsoft.AspNetCore.Razor.Language.Syntax;
1919
using Microsoft.AspNetCore.Razor.PooledObjects;
2020
using Microsoft.CodeAnalysis.CSharp.Syntax;
21+
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
2122
using Microsoft.CodeAnalysis.Text;
2223
using RazorSyntaxNode = Microsoft.AspNetCore.Razor.Language.Syntax.SyntaxNode;
2324

@@ -29,7 +30,7 @@ internal static class AddUsingsHelper
2930

3031
private readonly record struct RazorUsingDirective(RazorDirectiveSyntax Node, AddImportChunkGenerator Statement);
3132

32-
public static async Task<TextEdit[]> GetUsingStatementEditsAsync(RazorCodeDocument codeDocument, SourceText changedCSharpText, CancellationToken cancellationToken)
33+
public static async Task<TextEdit[]> GetUsingStatementEditsAsync(IDocumentSnapshot documentSnapshot, SourceText changedCSharpText, CancellationToken cancellationToken)
3334
{
3435
// Now that we're done with everything, lets see if there are any using statements to fix up
3536
// We do this by comparing the original generated C# code, and the changed C# code, and look for a difference
@@ -44,7 +45,8 @@ public static async Task<TextEdit[]> GetUsingStatementEditsAsync(RazorCodeDocume
4445
// So because of the above, we look for a difference in C# using directive nodes directly from the C# syntax tree, and apply them manually
4546
// to the Razor document.
4647

47-
var originalCSharpSyntaxTree = codeDocument.GetOrParseCSharpSyntaxTree(cancellationToken);
48+
var codeDocument = await documentSnapshot.GetGeneratedOutputAsync(cancellationToken).ConfigureAwait(false);
49+
var originalCSharpSyntaxTree = await documentSnapshot.GetCSharpSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
4850
var changedCSharpSyntaxTree = originalCSharpSyntaxTree.WithChangedText(changedCSharpText);
4951
var oldUsings = await FindUsingDirectiveStringsAsync(originalCSharpSyntaxTree, cancellationToken).ConfigureAwait(false);
5052
var newUsings = await FindUsingDirectiveStringsAsync(changedCSharpSyntaxTree, cancellationToken).ConfigureAwait(false);

src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/CSharpFormatter.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
using System.Linq;
77
using System.Threading;
88
using System.Threading.Tasks;
9-
using Microsoft.AspNetCore.Razor.Language;
109
using Microsoft.AspNetCore.Razor.PooledObjects;
1110
using Microsoft.CodeAnalysis;
1211
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -46,7 +45,7 @@ private static async Task<Dictionary<int, int>> GetCSharpIndentationCoreAsync(
4645
return [];
4746
}
4847

49-
var (indentationMap, syntaxTree) = InitializeIndentationData(context, projectedDocumentLocations, cancellationToken);
48+
var (indentationMap, syntaxTree) = await InitializeIndentationDataAsync(context, projectedDocumentLocations, cancellationToken).ConfigureAwait(false);
5049

5150
var root = await syntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
5251

@@ -244,7 +243,7 @@ static bool IgnoreInitializerExpression(InitializerExpressionSyntax initializer,
244243
}
245244
}
246245

247-
private static (Dictionary<int, IndentationMapData>, SyntaxTree) InitializeIndentationData(
246+
private static async Task<(Dictionary<int, IndentationMapData>, SyntaxTree)> InitializeIndentationDataAsync(
248247
FormattingContext context,
249248
IEnumerable<int> projectedDocumentLocations,
250249
CancellationToken cancellationToken)
@@ -258,8 +257,8 @@ private static (Dictionary<int, IndentationMapData>, SyntaxTree) InitializeInden
258257

259258
using var changes = new PooledArrayBuilder<TextChange>();
260259

261-
var syntaxTree = context.CodeDocument.GetOrParseCSharpSyntaxTree(cancellationToken);
262-
var root = syntaxTree.GetRoot(cancellationToken);
260+
var syntaxTree = await context.CurrentSnapshot.GetCSharpSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
261+
var root = await syntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
263262

264263
var previousMarkerOffset = 0;
265264
foreach (var projectedDocumentIndex in projectedDocumentLocations)

src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/FormattingContext.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ internal sealed class FormattingContext
2525
private FormattingContext(
2626
IDocumentSnapshot originalSnapshot,
2727
RazorCodeDocument codeDocument,
28+
IDocumentSnapshot currentSnapshot,
2829
RazorFormattingOptions options,
2930
IFormattingLogger? logger,
3031
bool automaticallyAddUsings,
@@ -33,6 +34,7 @@ private FormattingContext(
3334
{
3435
OriginalSnapshot = originalSnapshot;
3536
CodeDocument = codeDocument;
37+
CurrentSnapshot = currentSnapshot;
3638
Options = options;
3739
Logger = logger;
3840
AutomaticallyAddUsings = automaticallyAddUsings;
@@ -44,6 +46,7 @@ private FormattingContext(
4446

4547
public IDocumentSnapshot OriginalSnapshot { get; }
4648
public RazorCodeDocument CodeDocument { get; }
49+
public IDocumentSnapshot CurrentSnapshot { get; }
4750
public RazorFormattingOptions Options { get; }
4851
public IFormattingLogger? Logger { get; }
4952
public bool AutomaticallyAddUsings { get; }
@@ -232,6 +235,7 @@ public async Task<FormattingContext> WithTextAsync(SourceText changedText, Cance
232235
var newContext = new FormattingContext(
233236
OriginalSnapshot,
234237
codeDocument,
238+
currentSnapshot: changedSnapshot,
235239
Options,
236240
Logger,
237241
AutomaticallyAddUsings,
@@ -271,6 +275,7 @@ public static FormattingContext CreateForOnTypeFormatting(
271275
return new FormattingContext(
272276
originalSnapshot,
273277
codeDocument,
278+
currentSnapshot: originalSnapshot,
274279
options,
275280
logger,
276281
automaticallyAddUsings,
@@ -287,6 +292,7 @@ public static FormattingContext Create(
287292
return new FormattingContext(
288293
originalSnapshot,
289294
codeDocument,
295+
currentSnapshot: originalSnapshot,
290296
options,
291297
logger,
292298
automaticallyAddUsings: false,

src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/CSharpOnTypeFormattingPass.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ private static async Task<ImmutableArray<TextChange>> AddUsingStatementEditsIfNe
212212
// Because we need to parse the C# code twice for this operation, lets do a quick check to see if its even necessary
213213
if (changes.Any(static e => e.NewText is not null && e.NewText.IndexOf("using") != -1))
214214
{
215-
var usingStatementEdits = await AddUsingsHelper.GetUsingStatementEditsAsync(context.CodeDocument, originalTextWithChanges, cancellationToken).ConfigureAwait(false);
215+
var usingStatementEdits = await AddUsingsHelper.GetUsingStatementEditsAsync(context.CurrentSnapshot, originalTextWithChanges, cancellationToken).ConfigureAwait(false);
216216
var usingStatementChanges = usingStatementEdits.Select(context.CodeDocument.Source.Text.GetTextChange);
217217
finalChanges = [.. usingStatementChanges, .. finalChanges];
218218
}

0 commit comments

Comments
 (0)