diff --git a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorCSharpFormattingBenchmark.cs b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorCSharpFormattingBenchmark.cs index f79adf835b1..e713afe564d 100644 --- a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorCSharpFormattingBenchmark.cs +++ b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorCSharpFormattingBenchmark.cs @@ -58,7 +58,7 @@ public async Task InitializeRazorCSharpFormattingAsync() DocumentUri = new Uri(_filePath); DocumentSnapshot = await GetDocumentSnapshotAsync(projectFilePath, _filePath, targetPath); - DocumentText = await DocumentSnapshot.GetTextAsync(); + DocumentText = await DocumentSnapshot.GetTextAsync(CancellationToken.None); } private static void WriteSampleFormattingFile(string filePath, bool preformatted, int blocks) diff --git a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorCodeActionsBenchmark.cs b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorCodeActionsBenchmark.cs index d1d21777c6f..11fc48f289e 100644 --- a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorCodeActionsBenchmark.cs +++ b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorCodeActionsBenchmark.cs @@ -77,7 +77,7 @@ public async Task SetupAsync() DocumentUri = new Uri(_filePath); DocumentSnapshot = await GetDocumentSnapshotAsync(projectFilePath, _filePath, targetPath); - DocumentText = await DocumentSnapshot.GetTextAsync(); + DocumentText = await DocumentSnapshot.GetTextAsync(CancellationToken.None); RazorCodeActionRange = DocumentText.GetZeroWidthRange(razorCodeActionIndex); CSharpCodeActionRange = DocumentText.GetZeroWidthRange(csharpCodeActionIndex); diff --git a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorCompletionBenchmark.cs b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorCompletionBenchmark.cs index d35a168a36e..7fc0caba7bc 100644 --- a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorCompletionBenchmark.cs +++ b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorCompletionBenchmark.cs @@ -75,7 +75,7 @@ public async Task SetupAsync() DocumentUri = new Uri(_filePath); DocumentSnapshot = await GetDocumentSnapshotAsync(projectFilePath, _filePath, targetPath); - DocumentText = await DocumentSnapshot.GetTextAsync(); + DocumentText = await DocumentSnapshot.GetTextAsync(CancellationToken.None); RazorPosition = DocumentText.GetPosition(razorCodeActionIndex); diff --git a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorDocumentMappingBenchmark.cs b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorDocumentMappingBenchmark.cs index c3371b04891..d656cfc4036 100644 --- a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorDocumentMappingBenchmark.cs +++ b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorDocumentMappingBenchmark.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Threading; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using Microsoft.AspNetCore.Razor.Language; @@ -50,7 +51,7 @@ public async Task InitializeRazorCSharpFormattingAsync() DocumentSnapshot = await GetDocumentSnapshotAsync(projectFilePath, _filePath, targetPath); - var codeDocument = await DocumentSnapshot.GetGeneratedOutputAsync(); + var codeDocument = await DocumentSnapshot.GetGeneratedOutputAsync(CancellationToken.None); CSharpDocument = codeDocument.GetCSharpDocument(); } diff --git a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorSemanticTokensScrollingBenchmark.cs b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorSemanticTokensScrollingBenchmark.cs index 20bcd3f5505..216314a8876 100644 --- a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorSemanticTokensScrollingBenchmark.cs +++ b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorSemanticTokensScrollingBenchmark.cs @@ -51,7 +51,7 @@ public async Task InitializeRazorSemanticAsync() var documentSnapshot = await GetDocumentSnapshotAsync(ProjectFilePath, filePath, TargetPath); DocumentContext = new DocumentContext(documentUri, documentSnapshot, projectContext: null); - var text = await DocumentSnapshot.GetTextAsync().ConfigureAwait(false); + var text = await DocumentSnapshot.GetTextAsync(CancellationToken.None).ConfigureAwait(false); Range = VsLspFactory.CreateRange( start: (0, 0), end: (text.Lines.Count - 1, 0)); diff --git a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/ProjectSystem/BackgroundCodeGenerationBenchmark.cs b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/ProjectSystem/BackgroundCodeGenerationBenchmark.cs index 2d6dff5a590..e0d019e3bca 100644 --- a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/ProjectSystem/BackgroundCodeGenerationBenchmark.cs +++ b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/ProjectSystem/BackgroundCodeGenerationBenchmark.cs @@ -59,6 +59,6 @@ private void SnapshotManager_Changed(object sender, ProjectChangeEventArgs e) var project = ProjectManager.GetLoadedProject(e.ProjectKey); var document = project.GetDocument(e.DocumentFilePath); - Tasks.Add(document.GetGeneratedOutputAsync()); + Tasks.Add(document.GetGeneratedOutputAsync(CancellationToken.None).AsTask()); } } diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/CSharp/UnformattedRemappingCSharpCodeActionResolver.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/CSharp/UnformattedRemappingCSharpCodeActionResolver.cs index 170b4cd3417..9514184521e 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/CSharp/UnformattedRemappingCSharpCodeActionResolver.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/CSharp/UnformattedRemappingCSharpCodeActionResolver.cs @@ -70,7 +70,7 @@ public async override Task ResolveAsync( return codeAction; } - var codeDocument = await documentContext.Snapshot.GetGeneratedOutputAsync().ConfigureAwait(false); + var codeDocument = await documentContext.Snapshot.GetGeneratedOutputAsync(cancellationToken).ConfigureAwait(false); if (codeDocument.IsUnsupported()) { return codeAction; diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/CodeActionEndpoint.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/CodeActionEndpoint.cs index 1914b6f87d7..d8f4a60c2c4 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/CodeActionEndpoint.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/CodeActionEndpoint.cs @@ -65,7 +65,7 @@ internal sealed class CodeActionEndpoint( return null; } - var razorCodeActionContext = await GenerateRazorCodeActionContextAsync(request, documentContext.Snapshot).ConfigureAwait(false); + var razorCodeActionContext = await GenerateRazorCodeActionContextAsync(request, documentContext.Snapshot, cancellationToken).ConfigureAwait(false); if (razorCodeActionContext is null) { return null; @@ -134,15 +134,18 @@ public void ApplyCapabilities(VSInternalServerCapabilities serverCapabilities, V } // internal for testing - internal async Task GenerateRazorCodeActionContextAsync(VSCodeActionParams request, IDocumentSnapshot documentSnapshot) + internal async Task GenerateRazorCodeActionContextAsync( + VSCodeActionParams request, + IDocumentSnapshot documentSnapshot, + CancellationToken cancellationToken) { - var codeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); + var codeDocument = await documentSnapshot.GetGeneratedOutputAsync(cancellationToken).ConfigureAwait(false); if (codeDocument.IsUnsupported()) { return null; } - var sourceText = await documentSnapshot.GetTextAsync().ConfigureAwait(false); + var sourceText = codeDocument.Source.Text; // VS Provides `CodeActionParams.Context.SelectionRange` in addition to // `CodeActionParams.Range`. The `SelectionRange` is relative to where the diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Html/DefaultHtmlCodeActionProvider.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Html/DefaultHtmlCodeActionProvider.cs index b5d22fabb6c..16f7dd30960 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Html/DefaultHtmlCodeActionProvider.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Html/DefaultHtmlCodeActionProvider.cs @@ -49,7 +49,7 @@ public static async Task RemapAndFixHtmlCodeActionEditAsync(IEditMappingService if (codeAction.Edit.TryGetTextDocumentEdits(out var documentEdits)) { - var codeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); + var codeDocument = await documentSnapshot.GetGeneratedOutputAsync(cancellationToken).ConfigureAwait(false); var htmlSourceText = codeDocument.GetHtmlSourceText(); foreach (var edit in documentEdits) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/AddUsingsCodeActionResolver.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/AddUsingsCodeActionResolver.cs index f0516a53585..65e6a300aa0 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/AddUsingsCodeActionResolver.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/AddUsingsCodeActionResolver.cs @@ -45,7 +45,7 @@ internal sealed class AddUsingsCodeActionResolver(IDocumentContextFactory docume var documentSnapshot = documentContext.Snapshot; - var codeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); + var codeDocument = await documentSnapshot.GetGeneratedOutputAsync(cancellationToken).ConfigureAwait(false); if (codeDocument.IsUnsupported()) { return null; diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Diagnostics/DocumentPullDiagnosticsEndpoint.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Diagnostics/DocumentPullDiagnosticsEndpoint.cs index 257a785126c..ae6dbff95e5 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Diagnostics/DocumentPullDiagnosticsEndpoint.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Diagnostics/DocumentPullDiagnosticsEndpoint.cs @@ -80,7 +80,7 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(VSInternalDocumentDiagno var documentSnapshot = documentContext.Snapshot; - var razorDiagnostics = await GetRazorDiagnosticsAsync(documentSnapshot).ConfigureAwait(false); + var razorDiagnostics = await GetRazorDiagnosticsAsync(documentSnapshot, cancellationToken).ConfigureAwait(false); await ReportRZ10012TelemetryAsync(documentContext, razorDiagnostics, cancellationToken).ConfigureAwait(false); @@ -106,7 +106,9 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(VSInternalDocumentDiagno { if (report.Diagnostics is not null) { - var mappedDiagnostics = await _translateDiagnosticsService.TranslateAsync(RazorLanguageKind.CSharp, report.Diagnostics, documentSnapshot).ConfigureAwait(false); + var mappedDiagnostics = await _translateDiagnosticsService + .TranslateAsync(RazorLanguageKind.CSharp, report.Diagnostics, documentSnapshot, cancellationToken) + .ConfigureAwait(false); report.Diagnostics = mappedDiagnostics; } @@ -120,7 +122,9 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(VSInternalDocumentDiagno { if (report.Diagnostics is not null) { - var mappedDiagnostics = await _translateDiagnosticsService.TranslateAsync(RazorLanguageKind.Html, report.Diagnostics, documentSnapshot).ConfigureAwait(false); + var mappedDiagnostics = await _translateDiagnosticsService + .TranslateAsync(RazorLanguageKind.Html, report.Diagnostics, documentSnapshot, cancellationToken) + .ConfigureAwait(false); report.Diagnostics = mappedDiagnostics; } @@ -131,9 +135,9 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(VSInternalDocumentDiagno return allDiagnostics.ToArray(); } - private static async Task GetRazorDiagnosticsAsync(IDocumentSnapshot documentSnapshot) + private static async Task GetRazorDiagnosticsAsync(IDocumentSnapshot documentSnapshot, CancellationToken cancellationToken) { - var codeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); + var codeDocument = await documentSnapshot.GetGeneratedOutputAsync(cancellationToken).ConfigureAwait(false); var sourceText = codeDocument.Source.Text; var csharpDocument = codeDocument.GetCSharpDocument(); var diagnostics = csharpDocument.Diagnostics; diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Diagnostics/RazorDiagnosticsPublisher.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Diagnostics/RazorDiagnosticsPublisher.cs index 204b03658af..61925ca772e 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Diagnostics/RazorDiagnosticsPublisher.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Diagnostics/RazorDiagnosticsPublisher.cs @@ -113,10 +113,10 @@ private async ValueTask ProcessBatchAsync(ImmutableArray item } } - private async Task PublishDiagnosticsAsync(IDocumentSnapshot document, CancellationToken token) + private async Task PublishDiagnosticsAsync(IDocumentSnapshot document, CancellationToken cancellationToken) { - var result = await document.GetGeneratedOutputAsync().ConfigureAwait(false); - var csharpDiagnostics = await GetCSharpDiagnosticsAsync(document, token).ConfigureAwait(false); + var result = await document.GetGeneratedOutputAsync(cancellationToken).ConfigureAwait(false); + var csharpDiagnostics = await GetCSharpDiagnosticsAsync(document, cancellationToken).ConfigureAwait(false); var razorDiagnostics = result.GetCSharpDocument().Diagnostics; lock (_publishedDiagnostics) @@ -188,7 +188,7 @@ .. csharpDiagnostics ?? [] if (_documentContextFactory.Value.TryCreate(delegatedParams.TextDocument.Uri, projectContext: null, out var documentContext)) { return await _translateDiagnosticsService.Value - .TranslateAsync(RazorLanguageKind.CSharp, fullDiagnostics.Items, documentContext.Snapshot) + .TranslateAsync(RazorLanguageKind.CSharp, fullDiagnostics.Items, documentContext.Snapshot, cancellationToken) .ConfigureAwait(false); } } diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DocumentSnapshotTextLoader.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DocumentSnapshotTextLoader.cs index fe7330a70f8..a9963165966 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DocumentSnapshotTextLoader.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DocumentSnapshotTextLoader.cs @@ -9,23 +9,13 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer; -internal class DocumentSnapshotTextLoader : TextLoader +internal class DocumentSnapshotTextLoader(IDocumentSnapshot documentSnapshot) : TextLoader { - private readonly IDocumentSnapshot _documentSnapshot; - - public DocumentSnapshotTextLoader(IDocumentSnapshot documentSnapshot) - { - if (documentSnapshot is null) - { - throw new ArgumentNullException(nameof(documentSnapshot)); - } - - _documentSnapshot = documentSnapshot; - } + private readonly IDocumentSnapshot _documentSnapshot = documentSnapshot; public override async Task LoadTextAndVersionAsync(LoadTextOptions options, CancellationToken cancellationToken) { - var sourceText = await _documentSnapshot.GetTextAsync().ConfigureAwait(false); + var sourceText = await _documentSnapshot.GetTextAsync(cancellationToken).ConfigureAwait(false); var textAndVersion = TextAndVersion.Create(sourceText, VersionStamp.Default); return textAndVersion; diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/HtmlFormatter.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/HtmlFormatter.cs index c20911085c1..d2504d8e1ef 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/HtmlFormatter.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/HtmlFormatter.cs @@ -47,7 +47,7 @@ public async Task> GetDocumentFormattingEditsAsync( return []; } - var sourceText = await documentSnapshot.GetTextAsync().ConfigureAwait(false); + var sourceText = await documentSnapshot.GetTextAsync(cancellationToken).ConfigureAwait(false); return result.Edits.SelectAsArray(sourceText.GetTextChange); } @@ -78,7 +78,7 @@ public async Task> GetOnTypeFormattingEditsAsync( return []; } - var sourceText = await documentSnapshot.GetTextAsync().ConfigureAwait(false); + var sourceText = await documentSnapshot.GetTextAsync(cancellationToken).ConfigureAwait(false); return result.Edits.SelectAsArray(sourceText.GetTextChange); } diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/LspFormattingCodeDocumentProvider.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/LspFormattingCodeDocumentProvider.cs index e47da7f437d..e04d70fff32 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/LspFormattingCodeDocumentProvider.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/LspFormattingCodeDocumentProvider.cs @@ -1,6 +1,7 @@ // 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.CodeAnalysis.Razor.Formatting; @@ -10,9 +11,9 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting; internal sealed class LspFormattingCodeDocumentProvider : IFormattingCodeDocumentProvider { - public Task GetCodeDocumentAsync(IDocumentSnapshot snapshot) + public ValueTask GetCodeDocumentAsync(IDocumentSnapshot snapshot, CancellationToken cancellationToken) { // Formatting always uses design time - return snapshot.GetGeneratedOutputAsync(forceDesignTimeGeneratedOutput: true); + return snapshot.GetGeneratedOutputAsync(forceDesignTimeGeneratedOutput: true, cancellationToken); } } diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/MapCode/MapCodeEndpoint.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/MapCode/MapCodeEndpoint.cs index 4d61c202e32..029ddb93535 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/MapCode/MapCodeEndpoint.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/MapCode/MapCodeEndpoint.cs @@ -105,7 +105,7 @@ public void ApplyCapabilities(VSInternalServerCapabilities serverCapabilities, V // We create a new Razor file based on each content in each mapping order to get the syntax tree that we'll later use to map. var newSnapshot = snapshot.WithText(SourceText.From(content)); - var codeToMap = await newSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); + var codeToMap = await newSnapshot.GetGeneratedOutputAsync(cancellationToken).ConfigureAwait(false); var mappingSuccess = await TryMapCodeAsync( codeToMap, mapping.FocusLocations, changes, mapCodeCorrelationId, documentContext, cancellationToken).ConfigureAwait(false); @@ -228,7 +228,7 @@ private async Task TryMapCodeAsync( razorNodesToMap.Add(nodeToMap); } - var sourceText = await documentContext.Snapshot.GetTextAsync().ConfigureAwait(false); + var sourceText = await documentContext.Snapshot.GetTextAsync(cancellationToken).ConfigureAwait(false); foreach (var nodeToMap in razorNodesToMap) { diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Mapping/RazorLanguageQueryEndpoint.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Mapping/RazorLanguageQueryEndpoint.cs index da40358e0af..efcff70265d 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Mapping/RazorLanguageQueryEndpoint.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Mapping/RazorLanguageQueryEndpoint.cs @@ -42,8 +42,8 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(RazorLanguageQueryParams var documentSnapshot = documentContext.Snapshot; var documentVersion = documentContext.Snapshot.Version; - var codeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); - var sourceText = await documentSnapshot.GetTextAsync().ConfigureAwait(false); + var codeDocument = await documentSnapshot.GetGeneratedOutputAsync(cancellationToken).ConfigureAwait(false); + var sourceText = codeDocument.Source.Text; var hostDocumentIndex = sourceText.GetPosition(request.Position); var responsePosition = request.Position; diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/OpenDocumentGenerator.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/OpenDocumentGenerator.cs index 96bf466b63d..4923d948a15 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/OpenDocumentGenerator.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/OpenDocumentGenerator.cs @@ -75,7 +75,7 @@ private async ValueTask ProcessBatchAsync(ImmutableArray item return; } - var codeDocument = await document.GetGeneratedOutputAsync().ConfigureAwait(false); + var codeDocument = await document.GetGeneratedOutputAsync(token).ConfigureAwait(false); foreach (var listener in _listeners) { diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Diagnostics/RazorTranslateDiagnosticsService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Diagnostics/RazorTranslateDiagnosticsService.cs index b662d2ca2b3..23e1adc835a 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Diagnostics/RazorTranslateDiagnosticsService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Diagnostics/RazorTranslateDiagnosticsService.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.Syntax; @@ -45,18 +46,27 @@ internal class RazorTranslateDiagnosticsService(IDocumentMappingService document ]).ToFrozenSet(); /// - /// Translates code diagnostics from one representation into another. + /// Translates code diagnostics from one representation into another. /// - /// The `RazorLanguageKind` of the `Diagnostic` objects included in `diagnostics`. - /// An array of `Diagnostic` objects to translate. - /// The `DocumentContext` for the code document associated with the diagnostics. + /// + /// The of the objects + /// included in . + /// + /// + /// An array of objects to translate. + /// + /// + /// The for the code document associated with the diagnostics. + /// + /// A token that can be checked to cancel work. /// An array of translated diagnostics internal async Task TranslateAsync( RazorLanguageKind diagnosticKind, LspDiagnostic[] diagnostics, - IDocumentSnapshot documentSnapshot) + IDocumentSnapshot documentSnapshot, + CancellationToken cancellationToken) { - var codeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); + var codeDocument = await documentSnapshot.GetGeneratedOutputAsync(cancellationToken).ConfigureAwait(false); if (codeDocument.IsUnsupported() != false) { _logger.LogInformation($"Unsupported code document."); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/FormattingContext.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/FormattingContext.cs index bfae1b89b6a..52bb5579988 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/FormattingContext.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/FormattingContext.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.Syntax; @@ -224,11 +225,13 @@ public bool TryGetFormattingSpan(int absoluteIndex, [NotNullWhen(true)] out Form return false; } - public async Task WithTextAsync(SourceText changedText) + public async Task WithTextAsync(SourceText changedText, CancellationToken cancellationToken) { var changedSnapshot = OriginalSnapshot.WithText(changedText); - var codeDocument = await _codeDocumentProvider.GetCodeDocumentAsync(changedSnapshot).ConfigureAwait(false); + var codeDocument = await _codeDocumentProvider + .GetCodeDocumentAsync(changedSnapshot, cancellationToken) + .ConfigureAwait(false); DEBUG_ValidateComponents(CodeDocument, codeDocument); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/IFormattingCodeDocumentProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/IFormattingCodeDocumentProvider.cs index 0d1c25da983..34ceec8cb23 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/IFormattingCodeDocumentProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/IFormattingCodeDocumentProvider.cs @@ -1,6 +1,7 @@ // 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.CodeAnalysis.Razor.ProjectSystem; @@ -9,5 +10,5 @@ namespace Microsoft.CodeAnalysis.Razor.Formatting; internal interface IFormattingCodeDocumentProvider { - Task GetCodeDocumentAsync(IDocumentSnapshot snapshot); + ValueTask GetCodeDocumentAsync(IDocumentSnapshot snapshot, CancellationToken cancellationToken); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/CSharpFormattingPass.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/CSharpFormattingPass.cs index 591573959bb..ebff05ba806 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/CSharpFormattingPass.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/CSharpFormattingPass.cs @@ -37,7 +37,7 @@ protected async override Task> ExecuteCoreAsync(Forma if (changes.Length > 0) { changedText = changedText.WithChanges(changes); - changedContext = await context.WithTextAsync(changedText).ConfigureAwait(false); + changedContext = await context.WithTextAsync(changedText, cancellationToken).ConfigureAwait(false); } cancellationToken.ThrowIfCancellationRequested(); @@ -48,7 +48,7 @@ protected async override Task> ExecuteCoreAsync(Forma if (csharpChanges.Length > 0) { changedText = changedText.WithChanges(csharpChanges); - changedContext = await changedContext.WithTextAsync(changedText).ConfigureAwait(false); + changedContext = await changedContext.WithTextAsync(changedText, cancellationToken).ConfigureAwait(false); _logger.LogTestOnly($"After FormatCSharpAsync:\r\n{changedText}"); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/CSharpOnTypeFormattingPass.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/CSharpOnTypeFormattingPass.cs index df2f821b088..58ba8c123b9 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/CSharpOnTypeFormattingPass.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/CSharpOnTypeFormattingPass.cs @@ -109,7 +109,7 @@ protected async override Task> ExecuteCoreAsync(Forma var formattedText = ApplyChangesAndTrackChange(originalText, filteredChanges, out _, out var spanAfterFormatting); _logger.LogTestOnly($"After C# changes:\r\n{formattedText}"); - var changedContext = await context.WithTextAsync(formattedText).ConfigureAwait(false); + var changedContext = await context.WithTextAsync(formattedText, cancellationToken).ConfigureAwait(false); var linePositionSpanAfterFormatting = formattedText.GetLinePositionSpan(spanAfterFormatting); cancellationToken.ThrowIfCancellationRequested(); @@ -119,9 +119,7 @@ protected async override Task> ExecuteCoreAsync(Forma var cleanedText = formattedText.WithChanges(cleanupChanges); _logger.LogTestOnly($"After CleanupDocument:\r\n{cleanedText}"); - changedContext = await changedContext.WithTextAsync(cleanedText).ConfigureAwait(false); - - cancellationToken.ThrowIfCancellationRequested(); + changedContext = await changedContext.WithTextAsync(cleanedText, cancellationToken).ConfigureAwait(false); // At this point we should have applied all edits that adds/removes newlines. // Let's now ensure the indentation of each of those lines is correct. diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/FormattingDiagnosticValidationPass.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/FormattingDiagnosticValidationPass.cs index 818cf7a70d2..63344d0c1f1 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/FormattingDiagnosticValidationPass.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/FormattingDiagnosticValidationPass.cs @@ -27,7 +27,7 @@ public async Task> ExecuteAsync(FormattingContext con var text = context.SourceText; var changedText = text.WithChanges(changes); - var changedContext = await context.WithTextAsync(changedText).ConfigureAwait(false); + var changedContext = await context.WithTextAsync(changedText, cancellationToken).ConfigureAwait(false); var changedDiagnostics = changedContext.CodeDocument.GetSyntaxTree().Diagnostics; // We want to ensure diagnostics didn't change, but since we're formatting things, its expected diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/HtmlFormattingPassBase.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/HtmlFormattingPassBase.cs index 9f02ade9e7e..14220eef2e3 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/HtmlFormattingPassBase.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/HtmlFormattingPassBase.cs @@ -34,7 +34,7 @@ public virtual async Task> ExecuteAsync(FormattingCon changedText = originalText.WithChanges(filteredChanges); // Create a new formatting context for the changed razor document. - changedContext = await context.WithTextAsync(changedText).ConfigureAwait(false); + changedContext = await context.WithTextAsync(changedText, cancellationToken).ConfigureAwait(false); _logger.LogTestOnly($"After normalizedEdits:\r\n{changedText}"); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/RazorFormattingPass.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/RazorFormattingPass.cs index ae6af5f5406..ab892f07de9 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/RazorFormattingPass.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/RazorFormattingPass.cs @@ -30,7 +30,7 @@ public async Task> ExecuteAsync(FormattingContext con if (changes.Length > 0) { changedText = changedText.WithChanges(changes); - changedContext = await context.WithTextAsync(changedText).ConfigureAwait(false); + changedContext = await context.WithTextAsync(changedText, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/RazorFormattingService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/RazorFormattingService.cs index 838dd78cab3..326ef4093a8 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/RazorFormattingService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/RazorFormattingService.cs @@ -67,7 +67,9 @@ public async Task> GetDocumentFormattingChangesAsync( RazorFormattingOptions options, CancellationToken cancellationToken) { - var codeDocument = await _codeDocumentProvider.GetCodeDocumentAsync(documentContext.Snapshot).ConfigureAwait(false); + var codeDocument = await _codeDocumentProvider + .GetCodeDocumentAsync(documentContext.Snapshot, cancellationToken) + .ConfigureAwait(false); // Range formatting happens on every paste, and if there are Razor diagnostics in the file // that can make some very bad results. eg, given: @@ -223,8 +225,8 @@ private async Task> ApplyFormattedChangesAsync( // Code actions were computed on the regular document, which with FUSE could be a runtime document. We have to make // sure for code actions specifically we are formatting that same document, or TextChange spans may not line up var codeDocument = isCodeActionFormattingRequest - ? await documentSnapshot.GetGeneratedOutputAsync(forceDesignTimeGeneratedOutput: false).ConfigureAwait(false) - : await _codeDocumentProvider.GetCodeDocumentAsync(documentSnapshot).ConfigureAwait(false); + ? await documentSnapshot.GetGeneratedOutputAsync(cancellationToken).ConfigureAwait(false) + : await _codeDocumentProvider.GetCodeDocumentAsync(documentSnapshot, cancellationToken).ConfigureAwait(false); var context = FormattingContext.CreateForOnTypeFormatting( documentSnapshot, 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 1ac75188a5a..486713db242 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/GoToDefinition/AbstractRazorComponentDefinitionService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/GoToDefinition/AbstractRazorComponentDefinitionService.cs @@ -44,7 +44,7 @@ internal abstract class AbstractRazorComponentDefinitionService( return null; } - var codeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); + var codeDocument = await documentSnapshot.GetGeneratedOutputAsync(cancellationToken).ConfigureAwait(false); if (!RazorComponentDefinitionHelpers.TryGetBoundTagHelpers(codeDocument, positionInfo.HostDocumentIndex, ignoreAttributes, _logger, out var boundTagHelper, out var boundAttribute)) { @@ -52,7 +52,10 @@ internal abstract class AbstractRazorComponentDefinitionService( return null; } - var componentDocument = await _componentSearchEngine.TryLocateComponentAsync(boundTagHelper, solutionQueryOperations).ConfigureAwait(false); + var componentDocument = await _componentSearchEngine + .TryLocateComponentAsync(boundTagHelper, solutionQueryOperations, cancellationToken) + .ConfigureAwait(false); + if (componentDocument is null) { _logger.LogInformation($"Could not locate component document."); 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 8d5352dc20c..1b261699e0a 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/GoToDefinition/RazorComponentDefinitionHelpers.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/GoToDefinition/RazorComponentDefinitionHelpers.cs @@ -150,7 +150,7 @@ static bool TryGetTagName(RazorSyntaxNode node, [NotNullWhen(true)] out RazorSyn var csharpSyntaxTree = await documentSnapshot.GetCSharpSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); var root = await csharpSyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); - var codeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); + var codeDocument = await documentSnapshot.GetGeneratedOutputAsync(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 diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/IRazorComponentSearchEngine.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/IRazorComponentSearchEngine.cs index e2305e82160..45a998e88c1 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/IRazorComponentSearchEngine.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/IRazorComponentSearchEngine.cs @@ -1,6 +1,7 @@ // 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.CodeAnalysis.Razor.ProjectSystem; @@ -9,5 +10,8 @@ namespace Microsoft.CodeAnalysis.Razor.Workspaces; internal interface IRazorComponentSearchEngine { - Task TryLocateComponentAsync(TagHelperDescriptor tagHelper, ISolutionQueryOperations solutionQueryOperations); + Task TryLocateComponentAsync( + TagHelperDescriptor tagHelper, + ISolutionQueryOperations solutionQueryOperations, + CancellationToken cancellationToken); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentContext.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentContext.cs index 207357dbbcc..e9b24b02cbb 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentContext.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentContext.cs @@ -51,9 +51,9 @@ public ValueTask GetCodeDocumentAsync(CancellationToken cance async ValueTask GetCodeDocumentCoreAsync(CancellationToken cancellationToken) { - var codeDocument = await Snapshot.GetGeneratedOutputAsync().ConfigureAwait(false); - - cancellationToken.ThrowIfCancellationRequested(); + var codeDocument = await Snapshot + .GetGeneratedOutputAsync(cancellationToken) + .ConfigureAwait(false); // Interlock to ensure that we only ever return one instance of RazorCodeDocument. // In race scenarios, when more than one RazorCodeDocument is produced, we want to @@ -70,9 +70,7 @@ public ValueTask GetSourceTextAsync(CancellationToken cancellationTo async ValueTask GetSourceTextCoreAsync(CancellationToken cancellationToken) { - var sourceText = await Snapshot.GetTextAsync().ConfigureAwait(false); - - cancellationToken.ThrowIfCancellationRequested(); + var sourceText = await Snapshot.GetTextAsync(cancellationToken).ConfigureAwait(false); // Interlock to ensure that we only ever return one instance of RazorCodeDocument. // In race scenarios, when more than one RazorCodeDocument is produced, we want to 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 a1db753c6ea..a69c96dfbc0 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentSnapshot.cs @@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor; using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Text; @@ -12,6 +13,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem; internal sealed class DocumentSnapshot(ProjectSnapshot project, DocumentState state) : IDocumentSnapshot { + private static readonly object s_csharpSyntaxTreeKey = new(); + private readonly DocumentState _state = state; private readonly ProjectSnapshot _project = project; @@ -23,11 +26,11 @@ internal sealed class DocumentSnapshot(ProjectSnapshot project, DocumentState st public IProjectSnapshot Project => _project; public int Version => _state.Version; - public Task GetTextAsync() - => _state.GetTextAsync(); + public ValueTask GetTextAsync(CancellationToken cancellationToken) + => _state.GetTextAsync(cancellationToken); - public Task GetTextVersionAsync() - => _state.GetTextVersionAsync(); + public ValueTask GetTextVersionAsync(CancellationToken cancellationToken) + => _state.GetTextVersionAsync(cancellationToken); public bool TryGetText([NotNullWhen(true)] out SourceText? result) => _state.TryGetText(out result); @@ -37,11 +40,9 @@ public bool TryGetTextVersion(out VersionStamp result) public bool TryGetGeneratedOutput([NotNullWhen(true)] out RazorCodeDocument? result) { - if (_state.IsGeneratedOutputResultAvailable) + if (_state.TryGetGeneratedOutputAndVersion(out var outputAndVersion)) { -#pragma warning disable VSTHRD002 // Avoid problematic synchronous waits - result = _state.GetGeneratedOutputAndVersionAsync(_project, this).Result.output; -#pragma warning restore VSTHRD002 // Avoid problematic synchronous waits + result = outputAndVersion.output; return true; } @@ -54,29 +55,58 @@ public IDocumentSnapshot WithText(SourceText text) return new DocumentSnapshot(_project, _state.WithText(text, VersionStamp.Create())); } - public async Task GetCSharpSyntaxTreeAsync(CancellationToken cancellationToken) + public ValueTask GetCSharpSyntaxTreeAsync(CancellationToken cancellationToken) { - var codeDocument = await GetGeneratedOutputAsync(forceDesignTimeGeneratedOutput: false).ConfigureAwait(false); - var csharpText = codeDocument.GetCSharpSourceText(); - return CSharpSyntaxTree.ParseText(csharpText, cancellationToken: cancellationToken); + return TryGetGeneratedOutput(out var codeDocument) + ? new(GetOrParseCSharpSyntaxTree(codeDocument, cancellationToken)) + : new(GetCSharpSyntaxTreeCoreAsync(cancellationToken)); + + async Task GetCSharpSyntaxTreeCoreAsync(CancellationToken cancellationToken) + { + var codeDocument = await GetGeneratedOutputAsync(forceDesignTimeGeneratedOutput: false, cancellationToken).ConfigureAwait(false); + return GetOrParseCSharpSyntaxTree(codeDocument, cancellationToken); + } } - public async Task GetGeneratedOutputAsync(bool forceDesignTimeGeneratedOutput) + public async ValueTask GetGeneratedOutputAsync(bool forceDesignTimeGeneratedOutput, CancellationToken cancellationToken) { if (forceDesignTimeGeneratedOutput) { - return await GetDesignTimeGeneratedOutputAsync().ConfigureAwait(false); + return await GetDesignTimeGeneratedOutputAsync(cancellationToken).ConfigureAwait(false); } - var (output, _) = await _state.GetGeneratedOutputAndVersionAsync(_project, this).ConfigureAwait(false); + var (output, _) = await _state + .GetGeneratedOutputAndVersionAsync(_project, this, cancellationToken) + .ConfigureAwait(false); + return output; } - private async Task GetDesignTimeGeneratedOutputAsync() + private async Task GetDesignTimeGeneratedOutputAsync(CancellationToken cancellationToken) { - var tagHelpers = await Project.GetTagHelpersAsync(CancellationToken.None).ConfigureAwait(false); + var tagHelpers = await Project.GetTagHelpersAsync(cancellationToken).ConfigureAwait(false); var projectEngine = Project.GetProjectEngine(); - var imports = await DocumentState.GetImportsAsync(this, projectEngine).ConfigureAwait(false); - return await DocumentState.GenerateCodeDocumentAsync(this, projectEngine, imports, tagHelpers, forceRuntimeCodeGeneration: false).ConfigureAwait(false); + var imports = await DocumentState.GetImportsAsync(this, projectEngine, cancellationToken).ConfigureAwait(false); + return await DocumentState + .GenerateCodeDocumentAsync(this, projectEngine, imports, tagHelpers, forceRuntimeCodeGeneration: false, cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Retrieves a cached Roslyn from the generated C# document. + /// If a tree has not yet been cached, a new one will be parsed and added to the cache. + /// + public static SyntaxTree GetOrParseCSharpSyntaxTree(RazorCodeDocument document, CancellationToken cancellationToken) + { + if (!document.Items.TryGetValue(s_csharpSyntaxTreeKey, out SyntaxTree? syntaxTree)) + { + var csharpText = document.GetCSharpSourceText(); + syntaxTree = CSharpSyntaxTree.ParseText(csharpText, cancellationToken: cancellationToken); + document.Items[s_csharpSyntaxTreeKey] = syntaxTree; + + return syntaxTree; + } + + return syntaxTree.AssumeNotNull(); } } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.ComputedStateTracker.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.ComputedStateTracker.cs index 5351011462e..3c8cda5bd82 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.ComputedStateTracker.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.ComputedStateTracker.cs @@ -11,11 +11,11 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem; internal partial class DocumentState { - private class ComputedStateTracker + private class ComputedStateTracker(ComputedStateTracker? older = null) { - private readonly object _lock; + private readonly object _lock = new(); - private ComputedStateTracker? _older; + private ComputedStateTracker? _older = older; // We utilize a WeakReference here to avoid bloating committed memory. If pieces request document output inbetween GC collections // then we will provide the weak referenced task; otherwise we require any state requests to be re-computed. @@ -23,12 +23,6 @@ private class ComputedStateTracker private ComputedOutput? _computedOutput; - public ComputedStateTracker(DocumentState state, ComputedStateTracker? older = null) - { - _lock = state._lock; - _older = older; - } - public bool IsResultAvailable { get @@ -52,20 +46,39 @@ public bool IsResultAvailable } } - public async Task<(RazorCodeDocument, VersionStamp)> GetGeneratedOutputAndVersionAsync(ProjectSnapshot project, IDocumentSnapshot document) + public bool TryGetGeneratedOutputAndVersion(out (RazorCodeDocument Output, VersionStamp InputVersion) result) + { + if (_computedOutput?.TryGetCachedOutput(out var output, out var version) == true) + { + result = (output, version); + return true; + } + + result = default; + return false; + } + + public async Task<(RazorCodeDocument, VersionStamp)> GetGeneratedOutputAndVersionAsync( + ProjectSnapshot project, + IDocumentSnapshot document, + CancellationToken cancellationToken) { if (_computedOutput?.TryGetCachedOutput(out var cachedCodeDocument, out var cachedInputVersion) == true) { return (cachedCodeDocument, cachedInputVersion); } - var (codeDocument, inputVersion) = await GetMemoizedGeneratedOutputAndVersionAsync(project, document).ConfigureAwait(false); + var (codeDocument, inputVersion) = await GetMemoizedGeneratedOutputAndVersionAsync(project, document, cancellationToken) + .ConfigureAwait(false); _computedOutput = new ComputedOutput(codeDocument, inputVersion); return (codeDocument, inputVersion); } - private Task<(RazorCodeDocument, VersionStamp)> GetMemoizedGeneratedOutputAndVersionAsync(ProjectSnapshot project, IDocumentSnapshot document) + private Task<(RazorCodeDocument, VersionStamp)> GetMemoizedGeneratedOutputAndVersionAsync( + ProjectSnapshot project, + IDocumentSnapshot document, + CancellationToken cancellationToken) { if (project is null) { @@ -105,7 +118,7 @@ public bool IsResultAvailable } // Typically in VS scenarios this will run synchronously because all resources are readily available. - var outputTask = ComputeGeneratedOutputAndVersionAsync(project, document); + var outputTask = ComputeGeneratedOutputAndVersionAsync(project, document, cancellationToken); if (outputTask.IsCompleted) { // Compiling ran synchronously, lets just immediately propagate to the TCS @@ -156,7 +169,10 @@ static void PropagateToTaskCompletionSource( } } - private async Task<(RazorCodeDocument, VersionStamp)> ComputeGeneratedOutputAndVersionAsync(ProjectSnapshot project, IDocumentSnapshot document) + private async Task<(RazorCodeDocument, VersionStamp)> ComputeGeneratedOutputAndVersionAsync( + ProjectSnapshot project, + IDocumentSnapshot document, + CancellationToken cancellationToken) { // We only need to produce the generated code if any of our inputs is newer than the // previously cached output. @@ -170,8 +186,8 @@ static void PropagateToTaskCompletionSource( var configurationVersion = project.ConfigurationVersion; var projectWorkspaceStateVersion = project.ProjectWorkspaceStateVersion; var documentCollectionVersion = project.DocumentCollectionVersion; - var imports = await GetImportsAsync(document, project.GetProjectEngine()).ConfigureAwait(false); - var documentVersion = await document.GetTextVersionAsync().ConfigureAwait(false); + var imports = await GetImportsAsync(document, project.GetProjectEngine(), cancellationToken).ConfigureAwait(false); + var documentVersion = await document.GetTextVersionAsync(cancellationToken).ConfigureAwait(false); // OK now that have the previous output and all of the versions, we can see if anything // has changed that would require regenerating the code. @@ -216,9 +232,9 @@ static void PropagateToTaskCompletionSource( } } - var tagHelpers = await project.GetTagHelpersAsync(CancellationToken.None).ConfigureAwait(false); + var tagHelpers = await project.GetTagHelpersAsync(cancellationToken).ConfigureAwait(false); var forceRuntimeCodeGeneration = project.Configuration.LanguageServerFlags?.ForceRuntimeCodeGeneration ?? false; - var codeDocument = await GenerateCodeDocumentAsync(document, project.GetProjectEngine(), imports, tagHelpers, forceRuntimeCodeGeneration).ConfigureAwait(false); + var codeDocument = await GenerateCodeDocumentAsync(document, project.GetProjectEngine(), imports, tagHelpers, forceRuntimeCodeGeneration, cancellationToken).ConfigureAwait(false); return (codeDocument, inputVersion); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs index acdd0168f7a..129bc593aa4 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs @@ -1,9 +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.Immutable; using System.Diagnostics.CodeAnalysis; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor; using Microsoft.AspNetCore.Razor.Language; @@ -14,132 +14,123 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem; internal partial class DocumentState { - private static readonly TextAndVersion s_emptyText = TextAndVersion.Create( + private static readonly LoadTextOptions s_loadTextOptions = new(SourceHashAlgorithm.Sha256); + + private static readonly TextAndVersion s_emptyTextAndVersion = TextAndVersion.Create( SourceText.From(string.Empty), VersionStamp.Default); - public static readonly Func> EmptyLoader = () => Task.FromResult(s_emptyText); + public static readonly TextLoader EmptyLoader = TextLoader.From(s_emptyTextAndVersion); + + public HostDocument HostDocument { get; } + public int Version { get; } - private readonly object _lock; + private TextAndVersion? _textAndVersion; + private readonly TextLoader _textLoader; private ComputedStateTracker? _computedState; - private readonly Func> _loader; - private Task? _loaderTask; - private SourceText? _sourceText; - private VersionStamp? _textVersion; - private readonly int _version; - - public static DocumentState Create( + private DocumentState( HostDocument hostDocument, int version, - Func>? loader) + TextAndVersion? textAndVersion, + TextLoader? textLoader) { - if (hostDocument is null) - { - throw new ArgumentNullException(nameof(hostDocument)); - } - - return new DocumentState(hostDocument, null, null, version, loader); + HostDocument = hostDocument; + Version = version; + _textAndVersion = textAndVersion; + _textLoader = textLoader ?? EmptyLoader; } - public static DocumentState Create( - HostDocument hostDocument, - Func>? loader) + // Internal for testing + internal DocumentState(HostDocument hostDocument, int version, SourceText text, VersionStamp textVersion) + : this(hostDocument, version, TextAndVersion.Create(text, textVersion), textLoader: null) { - if (hostDocument is null) - { - throw new ArgumentNullException(nameof(hostDocument)); - } - - return new DocumentState(hostDocument, null, null, version: 1, loader); } // Internal for testing - internal DocumentState( - HostDocument hostDocument, - SourceText? text, - VersionStamp? textVersion, - int version, - Func>? loader) + internal DocumentState(HostDocument hostDocument, int version, TextLoader loader) + : this(hostDocument, version, textAndVersion: null, loader) { - HostDocument = hostDocument; - _sourceText = text; - _textVersion = textVersion; - _version = version; - _loader = loader ?? EmptyLoader; - _lock = new object(); } - public HostDocument HostDocument { get; } - public int Version => _version; + public static DocumentState Create(HostDocument hostDocument, int version, TextLoader loader) + { + return new DocumentState(hostDocument, version, loader); + } + + public static DocumentState Create(HostDocument hostDocument, TextLoader loader) + { + return new DocumentState(hostDocument, version: 1, loader); + } - public bool IsGeneratedOutputResultAvailable => ComputedState.IsResultAvailable == true; + public bool IsGeneratedOutputResultAvailable => ComputedState.IsResultAvailable; private ComputedStateTracker ComputedState - { - get - { - if (_computedState is null) - { - lock (_lock) - { - _computedState ??= new ComputedStateTracker(this); - } - } + => _computedState ??= InterlockedOperations.Initialize(ref _computedState, new ComputedStateTracker()); - return _computedState; - } + public bool TryGetGeneratedOutputAndVersion(out (RazorCodeDocument output, VersionStamp inputVersion) result) + { + return ComputedState.TryGetGeneratedOutputAndVersion(out result); } - public Task<(RazorCodeDocument output, VersionStamp inputVersion)> GetGeneratedOutputAndVersionAsync(ProjectSnapshot project, DocumentSnapshot document) + public Task<(RazorCodeDocument output, VersionStamp inputVersion)> GetGeneratedOutputAndVersionAsync( + ProjectSnapshot project, + DocumentSnapshot document, + CancellationToken cancellationToken) { - return ComputedState.GetGeneratedOutputAndVersionAsync(project, document); + return ComputedState.GetGeneratedOutputAndVersionAsync(project, document, cancellationToken); } - public async Task GetTextAsync() + public ValueTask GetTextAndVersionAsync(CancellationToken cancellationToken) { - if (TryGetText(out var text)) - { - return text; - } + return _textAndVersion is TextAndVersion result + ? new(result) + : LoadTextAndVersionAsync(_textLoader, cancellationToken); - lock (_lock) + async ValueTask LoadTextAndVersionAsync(TextLoader loader, CancellationToken cancellationToken) { - _loaderTask = _loader(); - } + var textAndVersion = await loader + .LoadTextAndVersionAsync(s_loadTextOptions, cancellationToken) + .ConfigureAwait(false); - return (await _loaderTask.ConfigureAwait(false)).Text; + return InterlockedOperations.Initialize(ref _textAndVersion, textAndVersion); + } } - public async Task GetTextVersionAsync() + public ValueTask GetTextAsync(CancellationToken cancellationToken) { - if (TryGetTextVersion(out var version)) - { - return version; - } + return TryGetText(out var text) + ? new(text) + : GetTextCoreAsync(cancellationToken); - lock (_lock) + async ValueTask GetTextCoreAsync(CancellationToken cancellationToken) { - _loaderTask = _loader(); - } + var textAsVersion = await GetTextAndVersionAsync(cancellationToken).ConfigureAwait(false); - return (await _loaderTask.ConfigureAwait(false)).Version; + return textAsVersion.Text; + } } - public bool TryGetText([NotNullWhen(true)] out SourceText? result) + public ValueTask GetTextVersionAsync(CancellationToken cancellationToken) { - if (_sourceText is { } sourceText) + return TryGetTextVersion(out var version) + ? new(version) + : GetTextVersionCoreAsync(cancellationToken); + + async ValueTask GetTextVersionCoreAsync(CancellationToken cancellationToken) { - result = sourceText; - return true; + var textAsVersion = await GetTextAndVersionAsync(cancellationToken).ConfigureAwait(false); + + return textAsVersion.Version; } + } - if (_loaderTask is { } loaderTask && loaderTask.IsCompleted) + public bool TryGetText([NotNullWhen(true)] out SourceText? result) + { + if (_textAndVersion is { } textAndVersion) { -#pragma warning disable VSTHRD002 // Avoid problematic synchronous waits - result = loaderTask.Result.Text; -#pragma warning restore VSTHRD002 // Avoid problematic synchronous waits + result = textAndVersion.Text; return true; } @@ -149,17 +140,9 @@ public bool TryGetText([NotNullWhen(true)] out SourceText? result) public bool TryGetTextVersion(out VersionStamp result) { - if (_textVersion is { } version) + if (_textAndVersion is { } textAndVersion) { - result = version; - return true; - } - - if (_loaderTask is { } loaderTask && loaderTask.IsCompleted) - { -#pragma warning disable VSTHRD002 // Avoid problematic synchronous waits - result = loaderTask.Result.Version; -#pragma warning restore VSTHRD002 // Avoid problematic synchronous waits + result = textAndVersion.Version; return true; } @@ -169,13 +152,7 @@ public bool TryGetTextVersion(out VersionStamp result) public virtual DocumentState WithConfigurationChange() { - var state = new DocumentState(HostDocument, _sourceText, _textVersion, _version + 1, _loader) - { - // The source could not have possibly changed. - _sourceText = _sourceText, - _textVersion = _textVersion, - _loaderTask = _loaderTask, - }; + var state = new DocumentState(HostDocument, Version + 1, _textAndVersion, _textLoader); // Do not cache computed state @@ -184,58 +161,36 @@ public virtual DocumentState WithConfigurationChange() public virtual DocumentState WithImportsChange() { - var state = new DocumentState(HostDocument, _sourceText, _textVersion, _version + 1, _loader) - { - // The source could not have possibly changed. - _sourceText = _sourceText, - _textVersion = _textVersion, - _loaderTask = _loaderTask - }; + var state = new DocumentState(HostDocument, Version + 1, _textAndVersion, _textLoader); // Optimistically cache the computed state - state._computedState = new ComputedStateTracker(state, _computedState); + state._computedState = new ComputedStateTracker(_computedState); return state; } public virtual DocumentState WithProjectWorkspaceStateChange() { - var state = new DocumentState(HostDocument, _sourceText, _textVersion, _version + 1, _loader) - { - // The source could not have possibly changed. - _sourceText = _sourceText, - _textVersion = _textVersion, - _loaderTask = _loaderTask - }; + var state = new DocumentState(HostDocument, Version + 1, _textAndVersion, _textLoader); // Optimistically cache the computed state - state._computedState = new ComputedStateTracker(state, _computedState); + state._computedState = new ComputedStateTracker(_computedState); return state; } - public virtual DocumentState WithText(SourceText sourceText, VersionStamp textVersion) + public virtual DocumentState WithText(SourceText text, VersionStamp textVersion) { - if (sourceText is null) - { - throw new ArgumentNullException(nameof(sourceText)); - } - // Do not cache the computed state - return new DocumentState(HostDocument, sourceText, textVersion, _version + 1, null); + return new DocumentState(HostDocument, Version + 1, TextAndVersion.Create(text, textVersion), textLoader: null); } - public virtual DocumentState WithTextLoader(Func> loader) + public virtual DocumentState WithTextLoader(TextLoader textLoader) { - if (loader is null) - { - throw new ArgumentNullException(nameof(loader)); - } - // Do not cache the computed state - return new DocumentState(HostDocument, null, null, _version + 1, loader); + return new DocumentState(HostDocument, Version + 1, textAndVersion: null, textLoader); } // Internal, because we are temporarily sharing code with CohostDocumentSnapshot @@ -282,19 +237,25 @@ internal static ImmutableArray GetImportsCore(IProjectSnapsho return imports.DrainToImmutable(); } - internal static async Task GenerateCodeDocumentAsync(IDocumentSnapshot document, RazorProjectEngine projectEngine, ImmutableArray imports, ImmutableArray tagHelpers, bool forceRuntimeCodeGeneration) + internal static async Task GenerateCodeDocumentAsync( + IDocumentSnapshot document, + RazorProjectEngine projectEngine, + ImmutableArray imports, + ImmutableArray tagHelpers, + bool forceRuntimeCodeGeneration, + CancellationToken cancellationToken) { // OK we have to generate the code. using var importSources = new PooledArrayBuilder(imports.Length); foreach (var item in imports) { var importProjectItem = item.FilePath is null ? null : projectEngine.FileSystem.GetItem(item.FilePath, item.FileKind); - var sourceDocument = await GetRazorSourceDocumentAsync(item.Document, importProjectItem).ConfigureAwait(false); + var sourceDocument = await GetRazorSourceDocumentAsync(item.Document, importProjectItem, cancellationToken).ConfigureAwait(false); importSources.Add(sourceDocument); } var projectItem = document.FilePath is null ? null : projectEngine.FileSystem.GetItem(document.FilePath, document.FileKind); - var documentSource = await GetRazorSourceDocumentAsync(document, projectItem).ConfigureAwait(false); + var documentSource = await GetRazorSourceDocumentAsync(document, projectItem, cancellationToken).ConfigureAwait(false); if (forceRuntimeCodeGeneration) { @@ -304,23 +265,26 @@ internal static async Task GenerateCodeDocumentAsync(IDocumen return projectEngine.ProcessDesignTime(documentSource, fileKind: document.FileKind, importSources.DrainToImmutable(), tagHelpers); } - internal static async Task> GetImportsAsync(IDocumentSnapshot document, RazorProjectEngine projectEngine) + internal static async Task> GetImportsAsync(IDocumentSnapshot document, RazorProjectEngine projectEngine, CancellationToken cancellationToken) { var imports = GetImportsCore(document.Project, projectEngine, document.FilePath.AssumeNotNull(), document.FileKind.AssumeNotNull()); using var result = new PooledArrayBuilder(imports.Length); foreach (var snapshot in imports) { - var versionStamp = await snapshot.GetTextVersionAsync().ConfigureAwait(false); + var versionStamp = await snapshot.GetTextVersionAsync(cancellationToken).ConfigureAwait(false); result.Add(new ImportItem(snapshot.FilePath, versionStamp, snapshot)); } return result.DrainToImmutable(); } - private static async Task GetRazorSourceDocumentAsync(IDocumentSnapshot document, RazorProjectItem? projectItem) + private static async Task GetRazorSourceDocumentAsync( + IDocumentSnapshot document, + RazorProjectItem? projectItem, + CancellationToken cancellationToken) { - var sourceText = await document.GetTextAsync().ConfigureAwait(false); + var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); return RazorSourceDocument.Create(sourceText, RazorSourceDocumentProperties.Create(document.FilePath, projectItem?.RelativePhysicalPath)); } } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/GeneratedDocumentTextLoader.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/GeneratedDocumentTextLoader.cs index bf2dfab0f26..f5853dfee17 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/GeneratedDocumentTextLoader.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/GeneratedDocumentTextLoader.cs @@ -1,9 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. -#nullable disable - -using System; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -12,30 +9,19 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem; -internal class GeneratedDocumentTextLoader : TextLoader +internal class GeneratedDocumentTextLoader(IDocumentSnapshot document, string filePath) : TextLoader { - private readonly IDocumentSnapshot _document; - private readonly string _filePath; - private readonly VersionStamp _version; - - public GeneratedDocumentTextLoader(IDocumentSnapshot document, string filePath) - { - if (document is null) - { - throw new ArgumentNullException(nameof(document)); - } - - _document = document; - _filePath = filePath; - _version = VersionStamp.Create(); - } + private readonly IDocumentSnapshot _document = document; + private readonly string _filePath = filePath; + private readonly VersionStamp _version = VersionStamp.Create(); public override async Task LoadTextAndVersionAsync(LoadTextOptions options, CancellationToken cancellationToken) { - var output = await _document.GetGeneratedOutputAsync().ConfigureAwait(false); + var output = await _document.GetGeneratedOutputAsync(cancellationToken).ConfigureAwait(false); - // Providing an encoding here is important for debuggability. Without this edit-and-continue - // won't work for projects with Razor files. - return TextAndVersion.Create(SourceText.From(output.GetCSharpDocument().GeneratedCode, Encoding.UTF8), _version, _filePath); + // Providing an encoding here is important for debuggability. + // Without this, edit-and-continue won't work for projects with Razor files. + var csharpSourceText = SourceText.From(output.GetCSharpDocument().GeneratedCode, Encoding.UTF8); + return TextAndVersion.Create(csharpSourceText, _version, _filePath); } } 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 5d329557b99..82840ac3bd9 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshot.cs @@ -18,15 +18,19 @@ internal interface IDocumentSnapshot int Version { get; } - Task GetTextAsync(); - Task GetTextVersionAsync(); - Task GetGeneratedOutputAsync(bool forceDesignTimeGeneratedOutput); + ValueTask GetTextAsync(CancellationToken cancellationToken); + ValueTask GetTextVersionAsync(CancellationToken cancellationToken); + ValueTask GetGeneratedOutputAsync( + bool forceDesignTimeGeneratedOutput, + CancellationToken cancellationToken); /// - /// Gets the Roslyn syntax tree for the generated C# for this Razor document + /// 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); + /// + /// ⚠️ Should be used sparingly in language server scenarios. + /// + ValueTask GetCSharpSyntaxTreeAsync(CancellationToken cancellationToken); bool TryGetText([NotNullWhen(true)] out SourceText? result); bool TryGetTextVersion(out VersionStamp result); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshotExtensions.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshotExtensions.cs index 62a90f11432..5f44d2b1d47 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshotExtensions.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshotExtensions.cs @@ -11,7 +11,9 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem; internal static class IDocumentSnapshotExtensions { - public static async Task TryGetTagHelperDescriptorAsync(this IDocumentSnapshot documentSnapshot, CancellationToken cancellationToken) + public static async Task TryGetTagHelperDescriptorAsync( + this IDocumentSnapshot documentSnapshot, + CancellationToken cancellationToken) { // No point doing anything if its not a component if (documentSnapshot.FileKind != FileKinds.Component) @@ -19,7 +21,7 @@ internal static class IDocumentSnapshotExtensions return null; } - var razorCodeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); + var razorCodeDocument = await documentSnapshot.GetGeneratedOutputAsync(cancellationToken).ConfigureAwait(false); if (razorCodeDocument is null) { return null; @@ -53,9 +55,10 @@ public static bool IsPathCandidateForComponent(this IDocumentSnapshot documentSn return fileName.AsSpan().Equals(path.Span, FilePathComparison.Instance); } - public static Task GetGeneratedOutputAsync(this IDocumentSnapshot documentSnapshot) + public static ValueTask GetGeneratedOutputAsync( + this IDocumentSnapshot documentSnapshot, + CancellationToken cancellationToken) { - return documentSnapshot.GetGeneratedOutputAsync(forceDesignTimeGeneratedOutput: false); + return documentSnapshot.GetGeneratedOutputAsync(forceDesignTimeGeneratedOutput: false, cancellationToken); } - } 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 f73e3bce29b..ae94a5e8026 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ImportDocumentSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ImportDocumentSnapshot.cs @@ -13,12 +13,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem; internal sealed class ImportDocumentSnapshot(IProjectSnapshot project, RazorProjectItem item) : IDocumentSnapshot { - private static readonly Task s_versionTask = Task.FromResult(VersionStamp.Default); - public IProjectSnapshot Project { get; } = project; private readonly RazorProjectItem _importItem = item; - private SourceText? _sourceText; + private SourceText? _text; // The default import file does not have a kind or paths. public string? FileKind => null; @@ -27,31 +25,33 @@ internal sealed class ImportDocumentSnapshot(IProjectSnapshot project, RazorProj public int Version => 1; - public Task GetTextAsync() + public ValueTask GetTextAsync(CancellationToken cancellationToken) { - return _sourceText is SourceText sourceText - ? Task.FromResult(sourceText) - : GetTextCoreAsync(); + return TryGetText(out var text) + ? new(text) + : ReadTextAsync(); - Task GetTextCoreAsync() + ValueTask ReadTextAsync() { using var stream = _importItem.Read(); var sourceText = SourceText.From(stream); - var result = _sourceText ??= InterlockedOperations.Initialize(ref _sourceText, sourceText); - return Task.FromResult(result); + var result = _text ??= InterlockedOperations.Initialize(ref _text, sourceText); + return new(result); } } - public Task GetGeneratedOutputAsync(bool forceDesignTimeGeneratedOutput) + public ValueTask GetGeneratedOutputAsync( + bool forceDesignTimeGeneratedOutput, + CancellationToken cancellationToken) => throw new NotSupportedException(); - public Task GetTextVersionAsync() - => s_versionTask; + public ValueTask GetTextVersionAsync(CancellationToken cancellationToken) + => new(VersionStamp.Default); public bool TryGetText([NotNullWhen(true)] out SourceText? result) { - result = _sourceText; + result = _text; return result is not null; } @@ -67,6 +67,6 @@ public bool TryGetGeneratedOutput([NotNullWhen(true)] out RazorCodeDocument? res public IDocumentSnapshot WithText(SourceText text) => throw new NotSupportedException(); - public Task GetCSharpSyntaxTreeAsync(CancellationToken cancellationToken) + public ValueTask GetCSharpSyntaxTreeAsync(CancellationToken cancellationToken) => throw new NotSupportedException(); } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManager.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManager.cs index 3d7b555d6c7..fc596af32dd 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManager.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManager.cs @@ -26,8 +26,6 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem; // The implementation will create a ProjectSnapshot for each HostProject. internal partial class ProjectSnapshotManager : IProjectSnapshotManager, IDisposable { - private static readonly LoadTextOptions s_loadTextOptions = new(SourceHashAlgorithm.Sha256); - private readonly IProjectEngineFactoryProvider _projectEngineFactoryProvider; private readonly Dispatcher _dispatcher; private readonly bool _initialized; @@ -511,7 +509,7 @@ private static Entry ComputeNewEntry(Entry originalEntry, IUpdateProjectAction a switch (action) { case AddDocumentAction(var newDocument, var textLoader): - return new Entry(originalEntry.State.WithAddedHostDocument(newDocument, CreateTextAndVersionFunc(textLoader))); + return new Entry(originalEntry.State.WithAddedHostDocument(newDocument, textLoader)); case RemoveDocumentAction(var originalDocument): return new Entry(originalEntry.State.WithRemovedHostDocument(originalDocument)); @@ -526,7 +524,8 @@ private static Entry ComputeNewEntry(Entry originalEntry, IUpdateProjectAction a var state = originalEntry.State.WithChangedHostDocument( documentState.HostDocument, - () => textLoader.LoadTextAndVersionAsync(s_loadTextOptions, cancellationToken: default)); + textLoader); + return new Entry(state); } @@ -543,14 +542,9 @@ private static Entry ComputeNewEntry(Entry originalEntry, IUpdateProjectAction a } else { - var newState = originalEntry.State.WithChangedHostDocument(documentState.HostDocument, async () => - { - olderText = await documentState.GetTextAsync().ConfigureAwait(false); - olderVersion = await documentState.GetTextVersionAsync().ConfigureAwait(false); - - var version = sourceText.ContentEquals(olderText) ? olderVersion : olderVersion.GetNewerVersion(); - return TextAndVersion.Create(sourceText, version, documentState.HostDocument.FilePath); - }); + var newState = originalEntry.State.WithChangedHostDocument( + documentState.HostDocument, + new UpdatedTextLoader(documentState, sourceText)); return new Entry(newState); } @@ -560,7 +554,7 @@ private static Entry ComputeNewEntry(Entry originalEntry, IUpdateProjectAction a { var newState = originalEntry.State.WithChangedHostDocument( documentState.AssumeNotNull().HostDocument, - CreateTextAndVersionFunc(textLoader)); + textLoader); return new Entry(newState); } @@ -583,14 +577,9 @@ private static Entry ComputeNewEntry(Entry originalEntry, IUpdateProjectAction a } else { - var state = originalEntry.State.WithChangedHostDocument(documentState.HostDocument, async () => - { - olderText = await documentState.GetTextAsync().ConfigureAwait(false); - olderVersion = await documentState.GetTextVersionAsync().ConfigureAwait(false); - - var version = sourceText.ContentEquals(olderText) ? olderVersion : olderVersion.GetNewerVersion(); - return TextAndVersion.Create(sourceText, version, documentState.HostDocument.FilePath); - }); + var state = originalEntry.State.WithChangedHostDocument( + documentState.HostDocument, + new UpdatedTextLoader(documentState, sourceText)); return new Entry(state); } @@ -605,12 +594,19 @@ private static Entry ComputeNewEntry(Entry originalEntry, IUpdateProjectAction a default: throw new InvalidOperationException($"Unexpected action type {action.GetType()}"); } + } - static Func> CreateTextAndVersionFunc(TextLoader textLoader) + private sealed class UpdatedTextLoader(DocumentState oldState, SourceText newSourceText) : TextLoader + { + public override async Task LoadTextAndVersionAsync(LoadTextOptions options, CancellationToken cancellationToken) { - return textLoader is null - ? DocumentState.EmptyLoader - : (() => textLoader.LoadTextAndVersionAsync(s_loadTextOptions, CancellationToken.None)); + var oldTextAndVersion = await oldState.GetTextAndVersionAsync(cancellationToken).ConfigureAwait(false); + + var version = newSourceText.ContentEquals(oldTextAndVersion.Text) + ? oldTextAndVersion.Version + : oldTextAndVersion.Version.GetNewerVersion(); + + return TextAndVersion.Create(newSourceText, version); } } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectState.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectState.cs index dde23d8e562..9e753414420 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectState.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectState.cs @@ -6,7 +6,6 @@ using System.Collections.Immutable; using System.IO; using System.Linq; -using System.Threading.Tasks; using Microsoft.AspNetCore.Razor; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.ProjectEngineHost; @@ -224,7 +223,7 @@ RazorProjectEngine CreateProjectEngine() public VersionStamp ConfigurationVersion { get; } - public ProjectState WithAddedHostDocument(HostDocument hostDocument, Func> loader) + public ProjectState WithAddedHostDocument(HostDocument hostDocument, TextLoader loader) { if (hostDocument is null) { @@ -321,7 +320,7 @@ public ProjectState WithChangedHostDocument(HostDocument hostDocument, SourceTex return state; } - public ProjectState WithChangedHostDocument(HostDocument hostDocument, Func> loader) + public ProjectState WithChangedHostDocument(HostDocument hostDocument, TextLoader loader) { if (hostDocument is null) { diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorComponentSearchEngine.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorComponentSearchEngine.cs index 80de2423de9..070970f6b65 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorComponentSearchEngine.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorComponentSearchEngine.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis.Razor.Logging; @@ -13,18 +14,35 @@ internal class RazorComponentSearchEngine(ILoggerFactory loggerFactory) : IRazor { private readonly ILogger _logger = loggerFactory.GetOrCreateLogger(); - /// Search for a component in a project based on its tag name and fully qualified name. + /// + /// Search for a component in a project based on its tag name and fully qualified name. + /// + /// + /// A to find the corresponding Razor component for. + /// + /// + /// An to enumerate project snapshots. + /// + /// + /// A token that is checked to cancel work. + /// + /// + /// The corresponding if found, otherwise. + /// /// - /// This method makes several assumptions about the nature of components. First, it assumes that a component - /// a given name "Name" will be located in a file "Name.razor". Second, it assumes that the namespace the - /// component is present in has the same name as the assembly its corresponding tag helper is loaded from. - /// Implicitly, this method inherits any assumptions made by TrySplitNamespaceAndType. + /// This method makes several assumptions about the nature of components. First, + /// it assumes that a component a given name "Name" will be located in a file + /// "Name.razor". Second, it assumes that the namespace the component is present in + /// has the same name as the assembly its corresponding tag helper is loaded from. + /// Implicitly, this method inherits any assumptions made by TrySplitNamespaceAndType. /// - /// A TagHelperDescriptor to find the corresponding Razor component for. - /// An to enumerate project snapshots. - /// The corresponding DocumentSnapshot if found, null otherwise. - /// Thrown if is null. - public async Task TryLocateComponentAsync(TagHelperDescriptor tagHelper, ISolutionQueryOperations solutionQueryOperations) + /// + /// Thrown if is . + /// + public async Task TryLocateComponentAsync( + TagHelperDescriptor tagHelper, + ISolutionQueryOperations solutionQueryOperations, + CancellationToken cancellationToken) { var typeName = tagHelper.GetTypeNameIdentifier(); var namespaceName = tagHelper.GetTypeNamespace(); @@ -52,7 +70,7 @@ internal class RazorComponentSearchEngine(ILoggerFactory loggerFactory) : IRazor continue; } - var razorCodeDocument = await document.GetGeneratedOutputAsync().ConfigureAwait(false); + var razorCodeDocument = await document.GetGeneratedOutputAsync(cancellationToken).ConfigureAwait(false); if (razorCodeDocument is null) { continue; diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Rename/RenameService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Rename/RenameService.cs index 4cb22989adf..15283267027 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Rename/RenameService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Rename/RenameService.cs @@ -58,7 +58,9 @@ internal class RenameService( return null; } - var originComponentDocumentSnapshot = await _componentSearchEngine.TryLocateComponentAsync(originTagHelpers.First(), solutionQueryOperations).ConfigureAwait(false); + var originComponentDocumentSnapshot = await _componentSearchEngine + .TryLocateComponentAsync(originTagHelpers.First(), solutionQueryOperations, cancellationToken) + .ConfigureAwait(false); if (originComponentDocumentSnapshot is null) { return null; @@ -80,7 +82,7 @@ internal class RenameService( foreach (var documentSnapshot in documentSnapshots) { - await AddEditsForCodeDocumentAsync(documentChanges, originTagHelpers, newName, documentSnapshot).ConfigureAwait(false); + await AddEditsForCodeDocumentAsync(documentChanges, originTagHelpers, newName, documentSnapshot, cancellationToken).ConfigureAwait(false); } foreach (var documentChange in documentChanges) @@ -165,14 +167,15 @@ private async Task AddEditsForCodeDocumentAsync( List> documentChanges, ImmutableArray originTagHelpers, string newName, - IDocumentSnapshot documentSnapshot) + IDocumentSnapshot documentSnapshot, + CancellationToken cancellationToken) { if (!FileKinds.IsComponent(documentSnapshot.FileKind)) { return; } - var codeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); + var codeDocument = await documentSnapshot.GetGeneratedOutputAsync(cancellationToken).ConfigureAwait(false); if (codeDocument.IsUnsupported()) { return; diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs index 119d984e730..27dcc789526 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/AutoInsert/RemoteAutoInsertService.cs @@ -137,7 +137,9 @@ private async ValueTask TryResolveInsertionInCSharpAsync( return Response.NoFurtherHandling; } - var generatedDocument = await remoteDocumentContext.Snapshot.GetGeneratedDocumentAsync().ConfigureAwait(false); + var generatedDocument = await remoteDocumentContext.Snapshot + .GetGeneratedDocumentAsync(cancellationToken) + .ConfigureAwait(false); var autoInsertResponseItem = await OnAutoInsert.GetOnAutoInsertResponseAsync( generatedDocument, diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Diagnostics/RemoteDiagnosticsService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Diagnostics/RemoteDiagnosticsService.cs index 4f110734989..26fa41db058 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Diagnostics/RemoteDiagnosticsService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Diagnostics/RemoteDiagnosticsService.cs @@ -49,8 +49,8 @@ private async ValueTask> GetDiagnosticsAsync( return [ .. RazorDiagnosticConverter.Convert(razorDiagnostics, codeDocument.Source.Text, context.Snapshot), - .. await _translateDiagnosticsService.TranslateAsync(RazorLanguageKind.CSharp, csharpDiagnostics, context.Snapshot), - .. await _translateDiagnosticsService.TranslateAsync(RazorLanguageKind.Html, htmlDiagnostics, context.Snapshot) + .. await _translateDiagnosticsService.TranslateAsync(RazorLanguageKind.CSharp, csharpDiagnostics, context.Snapshot, cancellationToken), + .. await _translateDiagnosticsService.TranslateAsync(RazorLanguageKind.Html, htmlDiagnostics, context.Snapshot, cancellationToken) ]; } } 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 82ea5371b76..0fdb8119224 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentHighlight/RemoteDocumentHighlightService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentHighlight/RemoteDocumentHighlightService.cs @@ -64,7 +64,9 @@ private async ValueTask GetHighlightsAsync( var csharpDocument = codeDocument.GetCSharpDocument(); if (DocumentMappingService.TryMapToGeneratedDocumentPosition(csharpDocument, index, out var mappedPosition, out _)) { - var generatedDocument = await context.Snapshot.GetGeneratedDocumentAsync().ConfigureAwait(false); + var generatedDocument = await context.Snapshot + .GetGeneratedDocumentAsync(cancellationToken) + .ConfigureAwait(false); var highlights = await DocumentHighlights.GetHighlightsAsync(generatedDocument, mappedPosition, cancellationToken).ConfigureAwait(false); 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 a849fc46e0d..a70d05dafb2 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentMapping/RemoteDocumentMappingService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentMapping/RemoteDocumentMappingService.cs @@ -54,8 +54,9 @@ internal sealed class RemoteDocumentMappingService( var razorDocumentSnapshot = _snapshotManager.GetSnapshot(razorDocument); - var razorCodeDocument = await razorDocumentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); + var razorCodeDocument = await razorDocumentSnapshot + .GetGeneratedOutputAsync(cancellationToken) + .ConfigureAwait(false); if (razorCodeDocument is null) { diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentSymbols/RemoteDocumentSymbolService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentSymbols/RemoteDocumentSymbolService.cs index 8cda0e8854c..d04786bfa5c 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentSymbols/RemoteDocumentSymbolService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentSymbols/RemoteDocumentSymbolService.cs @@ -33,7 +33,10 @@ protected override IRemoteDocumentSymbolService CreateService(in ServiceArgs arg private async ValueTask?> GetDocumentSymbolsAsync(RemoteDocumentContext context, bool useHierarchicalSymbols, CancellationToken cancellationToken) { - var generatedDocument = await context.Snapshot.GetGeneratedDocumentAsync().ConfigureAwait(false); + var generatedDocument = await context.Snapshot + .GetGeneratedDocumentAsync(cancellationToken) + .ConfigureAwait(false); + var csharpSymbols = await ExternalHandlers.DocumentSymbols.GetDocumentSymbolsAsync(generatedDocument, useHierarchicalSymbols, cancellationToken).ConfigureAwait(false); var codeDocument = await context.GetCodeDocumentAsync(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 87e4673b004..0e587b74773 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/FoldingRanges/RemoteFoldingRangeService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/FoldingRanges/RemoteFoldingRangeService.cs @@ -41,7 +41,9 @@ private async ValueTask> GetFoldingRangesAsyn ImmutableArray htmlRanges, CancellationToken cancellationToken) { - var generatedDocument = await context.Snapshot.GetGeneratedDocumentAsync().ConfigureAwait(false); + var generatedDocument = await context.Snapshot + .GetGeneratedDocumentAsync(cancellationToken) + .ConfigureAwait(false); var csharpRanges = await ExternalHandlers.FoldingRanges.GetFoldingRangesAsync(generatedDocument, cancellationToken).ConfigureAwait(false); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteFormattingCodeDocumentProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteFormattingCodeDocumentProvider.cs index 64c90c21cb5..914e05b6139 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteFormattingCodeDocumentProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Formatting/RemoteFormattingCodeDocumentProvider.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System.Composition; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis.Razor.Formatting; @@ -12,9 +13,9 @@ namespace Microsoft.CodeAnalysis.Remote.Razor.Formatting; [Export(typeof(IFormattingCodeDocumentProvider)), Shared] internal sealed class RemoteFormattingCodeDocumentProvider : IFormattingCodeDocumentProvider { - public Task GetCodeDocumentAsync(IDocumentSnapshot snapshot) + public ValueTask GetCodeDocumentAsync(IDocumentSnapshot snapshot, CancellationToken cancellationToken) { // Formatting always uses design time - return snapshot.GetGeneratedOutputAsync(forceDesignTimeGeneratedOutput: true); + return snapshot.GetGeneratedOutputAsync(forceDesignTimeGeneratedOutput: true, cancellationToken); } } 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 e39437c1d79..e5a496e6ac2 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToDefinition/RemoteGoToDefinitionService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToDefinition/RemoteGoToDefinitionService.cs @@ -83,7 +83,9 @@ protected override IRemoteGoToDefinitionService CreateService(in ServiceArgs arg } // Finally, call into C#. - var generatedDocument = await context.Snapshot.GetGeneratedDocumentAsync().ConfigureAwait(false); + var generatedDocument = await context.Snapshot + .GetGeneratedDocumentAsync(cancellationToken) + .ConfigureAwait(false); var locations = await ExternalHandlers.GoToDefinition .GetDefinitionsAsync( diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToImplementation/RemoteGoToImplementationService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToImplementation/RemoteGoToImplementationService.cs index 920df3589f2..aabf32dfb63 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToImplementation/RemoteGoToImplementationService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/GoToImplementation/RemoteGoToImplementationService.cs @@ -73,7 +73,9 @@ protected override IRemoteGoToImplementationService CreateService(in ServiceArgs } // Finally, call into C#. - var generatedDocument = await context.Snapshot.GetGeneratedDocumentAsync().ConfigureAwait(false); + var generatedDocument = await context.Snapshot + .GetGeneratedDocumentAsync(cancellationToken) + .ConfigureAwait(false); var locations = await ExternalHandlers.GoToImplementation .FindImplementationsAsync( 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 ff8d90753a7..b8235e8ff98 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/InlayHints/RemoteInlayHintService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/InlayHints/RemoteInlayHintService.cs @@ -61,7 +61,9 @@ protected override IRemoteInlayHintService CreateService(in ServiceArgs args) return null; } - var generatedDocument = await context.Snapshot.GetGeneratedDocumentAsync().ConfigureAwait(false); + var generatedDocument = await context.Snapshot + .GetGeneratedDocumentAsync(cancellationToken) + .ConfigureAwait(false); var textDocument = inlayHintParams.TextDocument.WithUri(generatedDocument.CreateUri()); var range = projectedLinePositionSpan.ToRange(); @@ -115,7 +117,9 @@ public ValueTask ResolveHintAsync(JsonSerializableRazorPinnedSolution private async ValueTask ResolveInlayHintAsync(RemoteDocumentContext context, InlayHint inlayHint, CancellationToken cancellationToken) { - var generatedDocument = await context.Snapshot.GetGeneratedDocumentAsync().ConfigureAwait(false); + var generatedDocument = await context.Snapshot + .GetGeneratedDocumentAsync(cancellationToken) + .ConfigureAwait(false); return await InlayHints.ResolveInlayHintAsync(generatedDocument, inlayHint, cancellationToken).ConfigureAwait(false); } 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 bb711518430..4c566a4da5c 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/ProjectSystem/RemoteDocumentSnapshot.cs @@ -42,11 +42,19 @@ public RemoteDocumentSnapshot(TextDocument textDocument, RemoteProjectSnapshot p public int Version => -999; // We don't expect to use this in cohosting, but plenty of existing code logs it's value - public Task GetTextAsync() - => TextDocument.GetTextAsync(); + public ValueTask GetTextAsync(CancellationToken cancellationToken) + { + return TryGetText(out var result) + ? new(result) + : new(TextDocument.GetTextAsync(cancellationToken)); + } - public Task GetTextVersionAsync() - => TextDocument.GetTextVersionAsync(); + public ValueTask GetTextVersionAsync(CancellationToken cancellationToken) + { + return TryGetTextVersion(out var result) + ? new(result) + : new(TextDocument.GetTextVersionAsync(cancellationToken)); + } public bool TryGetText([NotNullWhen(true)] out SourceText? result) => TextDocument.TryGetText(out result); @@ -54,36 +62,40 @@ public bool TryGetText([NotNullWhen(true)] out SourceText? result) public bool TryGetTextVersion(out VersionStamp result) => TextDocument.TryGetTextVersion(out result); - public async Task GetGeneratedOutputAsync(bool forceDesignTimeGeneratedOutput) + public ValueTask GetGeneratedOutputAsync( + bool forceDesignTimeGeneratedOutput, + CancellationToken cancellationToken) { // TODO: We don't need to worry about locking if we get called from the didOpen/didChange LSP requests, as CLaSP // takes care of that for us, and blocks requests until those are complete. If that doesn't end up happening, // then a locking mechanism here would prevent concurrent compilations. - if (TryGetGeneratedOutput(out var codeDocument)) + return TryGetGeneratedOutput(out var codeDocument) + ? new(codeDocument) + : new(GetGeneratedOutputCoreAsync(cancellationToken)); + + async Task GetGeneratedOutputCoreAsync(CancellationToken cancellationToken) { - return codeDocument; + // The non-cohosted DocumentSnapshot implementation uses DocumentState to get the generated output, and we could do that too + // but most of that code is optimized around caching pre-computed results when things change that don't affect the compilation. + // We can't do that here because we are using Roslyn's project snapshots, which don't contain the info that Razor needs. We could + // in future provide a side-car mechanism so we can cache things, but still take advantage of snapshots etc. but the working + // assumption for this code is that the source generator will be used, and it will do all of that, so this implementation is naive + // and simply compiles when asked, and if a new document snapshot comes in, we compile again. This is presumably worse for perf + // but since we don't expect users to ever use cohosting without source generators, it's fine for now. + + var projectEngine = await ProjectSnapshot.GetProjectEngine_CohostOnlyAsync(cancellationToken).ConfigureAwait(false); + var tagHelpers = await ProjectSnapshot.GetTagHelpersAsync(cancellationToken).ConfigureAwait(false); + var imports = await DocumentState.GetImportsAsync(this, projectEngine, cancellationToken).ConfigureAwait(false); + + // TODO: Get the configuration for forceRuntimeCodeGeneration + // var forceRuntimeCodeGeneration = _projectSnapshot.Configuration.LanguageServerFlags?.ForceRuntimeCodeGeneration ?? false; + + codeDocument = await DocumentState + .GenerateCodeDocumentAsync(this, projectEngine, imports, tagHelpers, forceRuntimeCodeGeneration: false, cancellationToken) + .ConfigureAwait(false); + + return _codeDocument ??= InterlockedOperations.Initialize(ref _codeDocument, codeDocument); } - - // The non-cohosted DocumentSnapshot implementation uses DocumentState to get the generated output, and we could do that too - // but most of that code is optimized around caching pre-computed results when things change that don't affect the compilation. - // We can't do that here because we are using Roslyn's project snapshots, which don't contain the info that Razor needs. We could - // in future provide a side-car mechanism so we can cache things, but still take advantage of snapshots etc. but the working - // assumption for this code is that the source generator will be used, and it will do all of that, so this implementation is naive - // and simply compiles when asked, and if a new document snapshot comes in, we compile again. This is presumably worse for perf - // but since we don't expect users to ever use cohosting without source generators, it's fine for now. - - var projectEngine = await ProjectSnapshot.GetProjectEngine_CohostOnlyAsync(CancellationToken.None).ConfigureAwait(false); - var tagHelpers = await ProjectSnapshot.GetTagHelpersAsync(CancellationToken.None).ConfigureAwait(false); - var imports = await DocumentState.GetImportsAsync(this, projectEngine).ConfigureAwait(false); - - // TODO: Get the configuration for forceRuntimeCodeGeneration - // var forceRuntimeCodeGeneration = _projectSnapshot.Configuration.LanguageServerFlags?.ForceRuntimeCodeGeneration ?? false; - - codeDocument = await DocumentState - .GenerateCodeDocumentAsync(this, projectEngine, imports, tagHelpers, forceRuntimeCodeGeneration: false) - .ConfigureAwait(false); - - return _codeDocument ??= InterlockedOperations.Initialize(ref _codeDocument, codeDocument); } public IDocumentSnapshot WithText(SourceText text) @@ -104,18 +116,26 @@ public bool TryGetGeneratedOutput([NotNullWhen(true)] out RazorCodeDocument? res return result is not null; } - public async Task GetGeneratedDocumentAsync() + public ValueTask GetGeneratedDocumentAsync(CancellationToken cancellationToken) { - if (_generatedDocument is Document generatedDocument) + return TryGetGeneratedDocument(out var generatedDocument) + ? new(generatedDocument) + : GetGeneratedDocumentCoreAsync(cancellationToken); + + async ValueTask GetGeneratedDocumentCoreAsync(CancellationToken cancellationToken) { - return generatedDocument; + var generatedDocument = await HACK_GenerateDocumentAsync(cancellationToken).ConfigureAwait(false); + return _generatedDocument ??= InterlockedOperations.Initialize(ref _generatedDocument, generatedDocument); } + } - generatedDocument = await HACK_GenerateDocumentAsync().ConfigureAwait(false); - return _generatedDocument ??= InterlockedOperations.Initialize(ref _generatedDocument, generatedDocument); + public bool TryGetGeneratedDocument([NotNullWhen(true)] out Document? generatedDocument) + { + generatedDocument = _generatedDocument; + return generatedDocument is not null; } - private async Task HACK_GenerateDocumentAsync() + private async Task HACK_GenerateDocumentAsync(CancellationToken cancellationToken) { // TODO: A real implementation needs to get the SourceGeneratedDocument from the solution @@ -126,17 +146,29 @@ private async Task HACK_GenerateDocumentAsync() var generatedDocumentId = solution.GetDocumentIdsWithFilePath(generatedFilePath).First(d => d.ProjectId == projectId); var generatedDocument = solution.GetRequiredDocument(generatedDocumentId); - var codeDocument = await this.GetGeneratedOutputAsync().ConfigureAwait(false); + var codeDocument = await this.GetGeneratedOutputAsync(cancellationToken).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); } - public async Task GetCSharpSyntaxTreeAsync(CancellationToken cancellationToken) + public ValueTask GetCSharpSyntaxTreeAsync(CancellationToken cancellationToken) { - var document = await GetGeneratedDocumentAsync().ConfigureAwait(false); - var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - return tree.AssumeNotNull(); + if (TryGetGeneratedDocument(out var document) && + document.TryGetSyntaxTree(out var tree)) + { + return new(tree.AssumeNotNull()); + } + + return GetCSharpSyntaxTreeCoreAsync(document, cancellationToken); + + async ValueTask GetCSharpSyntaxTreeCoreAsync(Document? document, CancellationToken cancellationToken) + { + document ??= await GetGeneratedDocumentAsync(cancellationToken).ConfigureAwait(false); + + var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + return tree.AssumeNotNull(); + } } } 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 3010c332547..c0eac5efae7 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Rename/RemoteRenameService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Rename/RemoteRenameService.cs @@ -52,7 +52,9 @@ protected override IRemoteRenameService CreateService(in ServiceArgs args) return NoFurtherHandling; } - var generatedDocument = await context.Snapshot.GetGeneratedDocumentAsync().ConfigureAwait(false); + var generatedDocument = await context.Snapshot + .GetGeneratedDocumentAsync(cancellationToken) + .ConfigureAwait(false); var razorEdit = await _renameService .TryGetRazorRenameEditsAsync(context, positionInfo, newName, context.GetSolutionQueryOperations(), cancellationToken) 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 45ca918831e..fd1fe4a78d2 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SemanticTokens/RemoteCSharpSemanticTokensProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SemanticTokens/RemoteCSharpSemanticTokensProvider.cs @@ -31,13 +31,17 @@ 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().ConfigureAwait(false); + var generatedDocument = await snapshot + .GetGeneratedDocumentAsync(cancellationToken) + .ConfigureAwait(false); - var data = await SemanticTokensRange.GetSemanticTokensAsync( - generatedDocument, - csharpRanges, - supportsVisualStudioExtensions: true, - cancellationToken).ConfigureAwait(false); + var data = await SemanticTokensRange + .GetSemanticTokensAsync( + generatedDocument, + csharpRanges, + supportsVisualStudioExtensions: true, + cancellationToken) + .ConfigureAwait(false); return data; } 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 e32b005716e..a81b7774d55 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SignatureHelp/RemoteSignatureHelpService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SignatureHelp/RemoteSignatureHelpService.cs @@ -35,7 +35,9 @@ 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().ConfigureAwait(false); + var generatedDocument = await context.Snapshot + .GetGeneratedDocumentAsync(cancellationToken) + .ConfigureAwait(false); if (DocumentMappingService.TryMapToGeneratedDocumentPosition(codeDocument.GetCSharpDocument(), absoluteIndex, out var mappedPosition, out _)) { diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SpellCheck/RemoteCSharpSpellCheckRangeProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SpellCheck/RemoteCSharpSpellCheckRangeProvider.cs index b965764762b..3c1960061bf 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SpellCheck/RemoteCSharpSpellCheckRangeProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/SpellCheck/RemoteCSharpSpellCheckRangeProvider.cs @@ -21,7 +21,7 @@ public async Task> GetCSharpSpellCheckRangesAsyn // 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().ConfigureAwait(false); + var generatedDocument = await snapshot.GetGeneratedDocumentAsync(cancellationToken).ConfigureAwait(false); var csharpRanges = await ExternalAccess.Razor.Cohost.Handlers.SpellCheck.GetSpellCheckSpansAsync(generatedDocument, cancellationToken).ConfigureAwait(false); diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Documents/RazorCodeDocumentProvidingSnapshotChangeTrigger.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Documents/RazorCodeDocumentProvidingSnapshotChangeTrigger.cs index ababab2a545..e1889a7495a 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Documents/RazorCodeDocumentProvidingSnapshotChangeTrigger.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Documents/RazorCodeDocumentProvidingSnapshotChangeTrigger.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.ComponentModel.Composition; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.ProjectSystem; @@ -61,7 +62,7 @@ private void ProjectManager_Changed(object sender, ProjectChangeEventArgs e) } } - public async Task GetRazorCodeDocumentAsync(string filePath) + public async Task GetRazorCodeDocumentAsync(string filePath, CancellationToken cancellationToken) { if (!_documentProjectMap.TryGetValue(filePath, out var projectKey)) { @@ -79,8 +80,6 @@ private void ProjectManager_Changed(object sender, ProjectChangeEventArgs e) return null; } - var razorDocument = await document.GetGeneratedOutputAsync().ConfigureAwait(false); - - return razorDocument; + return await document.GetGeneratedOutputAsync(cancellationToken).ConfigureAwait(false); } } diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/DynamicFiles/BackgroundDocumentGenerator.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/DynamicFiles/BackgroundDocumentGenerator.cs index f0460315f62..3cecdf51d6f 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/DynamicFiles/BackgroundDocumentGenerator.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/DynamicFiles/BackgroundDocumentGenerator.cs @@ -71,9 +71,9 @@ public void Dispose() protected Task WaitUntilCurrentBatchCompletesAsync() => _workQueue.WaitUntilCurrentBatchCompletesAsync(); - protected virtual async Task ProcessDocumentAsync(IProjectSnapshot project, IDocumentSnapshot document) + protected virtual async Task ProcessDocumentAsync(IProjectSnapshot project, IDocumentSnapshot document, CancellationToken cancellationToken) { - await document.GetGeneratedOutputAsync().ConfigureAwait(false); + await document.GetGeneratedOutputAsync(cancellationToken).ConfigureAwait(false); UpdateFileInfo(project, document); } @@ -116,7 +116,7 @@ protected virtual async ValueTask ProcessBatchAsync(ImmutableArray<(IProjectSnap try { - await ProcessDocumentAsync(project, document).ConfigureAwait(false); + await ProcessDocumentAsync(project, document, token).ConfigureAwait(false); } catch (UnauthorizedAccessException) { diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/DynamicFiles/RazorDocumentExcerptService.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/DynamicFiles/RazorDocumentExcerptService.cs index 7fb8d7fb0cf..281cdfc29e6 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/DynamicFiles/RazorDocumentExcerptService.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/DynamicFiles/RazorDocumentExcerptService.cs @@ -46,7 +46,7 @@ internal class RazorDocumentExcerptService( return null; } - var razorDocumentText = await razorDocument.GetTextAsync().ConfigureAwait(false); + var razorDocumentText = await razorDocument.GetTextAsync(cancellationToken).ConfigureAwait(false); var razorDocumentSpan = razorDocumentText.Lines.GetTextSpan(mappedSpans[0].LinePositionSpan); var generatedDocument = document; @@ -56,7 +56,7 @@ internal class RazorDocumentExcerptService( // Then we'll classify the spans based on the primary document, since that's the coordinate // space that our output mappings use. - var output = await _document.GetGeneratedOutputAsync().ConfigureAwait(false); + var output = await _document.GetGeneratedOutputAsync(cancellationToken).ConfigureAwait(false); var mappings = output.GetCSharpDocument().SourceMappings; var classifiedSpans = await ClassifyPreviewAsync( excerptSpan, diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/DynamicFiles/RazorSpanMappingService.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/DynamicFiles/RazorSpanMappingService.cs index bef6c7edd60..e8c6ccb04b3 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/DynamicFiles/RazorSpanMappingService.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/DynamicFiles/RazorSpanMappingService.cs @@ -42,8 +42,8 @@ public async Task> MapSpansAsync( return ImmutableArray.Empty; } - var source = await _document.GetTextAsync().ConfigureAwait(false); - var output = await _document.GetGeneratedOutputAsync().ConfigureAwait(false); + var output = await _document.GetGeneratedOutputAsync(cancellationToken).ConfigureAwait(false); + var source = output.Source.Text; var csharpDocument = output.GetCSharpDocument(); var filePath = output.Source.FilePath.AssumeNotNull(); diff --git a/src/Razor/src/Microsoft.VisualStudio.RazorExtension/SyntaxVisualizer/SourceMappingTagger.cs b/src/Razor/src/Microsoft.VisualStudio.RazorExtension/SyntaxVisualizer/SourceMappingTagger.cs index d2eb24543c4..3a65a49cf7d 100644 --- a/src/Razor/src/Microsoft.VisualStudio.RazorExtension/SyntaxVisualizer/SourceMappingTagger.cs +++ b/src/Razor/src/Microsoft.VisualStudio.RazorExtension/SyntaxVisualizer/SourceMappingTagger.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; -using System.Linq; +using System.Threading; using Microsoft.AspNetCore.Razor.Language; using Microsoft.VisualStudio.Razor.Documents; using Microsoft.VisualStudio.Shell; @@ -39,21 +39,22 @@ public IEnumerable> GetTags(NormalizedSnapshotSpanCol { if (!Enabled || spans.Count == 0) { - return Enumerable.Empty>(); + return []; } var snapshot = spans[0].Snapshot; if (!_textDocumentFactoryService.TryGetTextDocument(_buffer, out var textDocument)) { - return Enumerable.Empty>(); + return []; } - var codeDocument = ThreadHelper.JoinableTaskFactory.Run(() => _sourceMappingProjectChangeTrigger.Value.GetRazorCodeDocumentAsync(textDocument.FilePath)); + var codeDocument = ThreadHelper.JoinableTaskFactory.Run( + () => _sourceMappingProjectChangeTrigger.Value.GetRazorCodeDocumentAsync(textDocument.FilePath, CancellationToken.None)); if (codeDocument is null) { - return Enumerable.Empty>(); + return []; } return GetTagsWorker(codeDocument, snapshot); diff --git a/src/Razor/src/Microsoft.VisualStudio.RazorExtension/SyntaxVisualizer/SyntaxVisualizerControl.xaml.cs b/src/Razor/src/Microsoft.VisualStudio.RazorExtension/SyntaxVisualizer/SyntaxVisualizerControl.xaml.cs index 9261c3db0a2..cfe269b9fc7 100644 --- a/src/Razor/src/Microsoft.VisualStudio.RazorExtension/SyntaxVisualizer/SyntaxVisualizerControl.xaml.cs +++ b/src/Razor/src/Microsoft.VisualStudio.RazorExtension/SyntaxVisualizer/SyntaxVisualizerControl.xaml.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Threading; using System.Windows; using System.Windows.Controls; using Microsoft.AspNetCore.Razor.Language; @@ -539,7 +540,8 @@ private void treeView_KeyUp(object sender, System.Windows.Input.KeyEventArgs e) var filePath = hostDocumentUri.GetAbsoluteOrUNCPath().Replace('/', '\\'); - var codeDocument = _joinableTaskFactory.Run(() => _codeDocumentProvider.GetRazorCodeDocumentAsync(filePath)); + var codeDocument = _joinableTaskFactory.Run( + () => _codeDocumentProvider.GetRazorCodeDocumentAsync(filePath, CancellationToken.None)); if (codeDocument is null) { return null; diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CSharp/DefaultCSharpCodeActionProviderTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CSharp/DefaultCSharpCodeActionProviderTest.cs index 2f668586916..c8dca61a6fa 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CSharp/DefaultCSharpCodeActionProviderTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CSharp/DefaultCSharpCodeActionProviderTest.cs @@ -9,12 +9,12 @@ using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.LanguageServer.CodeActions.Models; using Microsoft.AspNetCore.Razor.LanguageServer.Hosting; +using Microsoft.AspNetCore.Razor.Test.Common; using Microsoft.AspNetCore.Razor.Test.Common.LanguageServer; using Microsoft.AspNetCore.Razor.Test.Common.Workspaces; using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Razor.Protocol.CodeActions; using Microsoft.CodeAnalysis.Testing; -using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; using Moq; using Xunit; @@ -332,15 +332,24 @@ private static RazorCodeActionContext CreateRazorCodeActionContext( var csharpDocumentWithDiagnostic = new RazorCSharpDocument(codeDocument, csharpDocument.GeneratedCode, csharpDocument.Options, [diagnostic]); codeDocument.SetCSharpDocument(csharpDocumentWithDiagnostic); - var documentSnapshot = Mock.Of(document => - document.GetGeneratedOutputAsync(It.IsAny()) == Task.FromResult(codeDocument) && - document.GetTextAsync() == Task.FromResult(codeDocument.Source.Text) && - document.Project.GetTagHelpersAsync(It.IsAny()) == new ValueTask>(tagHelpers), MockBehavior.Strict); - - var sourceText = SourceText.From(text); - - var context = new RazorCodeActionContext(request, documentSnapshot, codeDocument, location, sourceText, supportsFileCreation, supportsCodeActionResolve); - - return context; + var documentSnapshotMock = new StrictMock(); + documentSnapshotMock + .Setup(x => x.GetGeneratedOutputAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(codeDocument); + documentSnapshotMock + .Setup(x => x.GetTextAsync(It.IsAny())) + .ReturnsAsync(codeDocument.Source.Text); + documentSnapshotMock + .Setup(x => x.Project.GetTagHelpersAsync(It.IsAny())) + .ReturnsAsync(tagHelpers); + + return new RazorCodeActionContext( + request, + documentSnapshotMock.Object, + codeDocument, + location, + codeDocument.Source.Text, + supportsFileCreation, + supportsCodeActionResolve); } } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CSharp/TypeAccessibilityCodeActionProviderTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CSharp/TypeAccessibilityCodeActionProviderTest.cs index 86d1b8239e5..8a24b095c2b 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CSharp/TypeAccessibilityCodeActionProviderTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CSharp/TypeAccessibilityCodeActionProviderTest.cs @@ -15,7 +15,6 @@ using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Razor.Protocol; using Microsoft.CodeAnalysis.Razor.Protocol.CodeActions; -using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; using Moq; using Xunit; @@ -463,15 +462,24 @@ private static RazorCodeActionContext CreateRazorCodeActionContext( var csharpDocumentWithDiagnostic = new RazorCSharpDocument(codeDocument, csharpDocument.GeneratedCode, csharpDocument.Options, [diagnostic]); codeDocument.SetCSharpDocument(csharpDocumentWithDiagnostic); - var documentSnapshot = Mock.Of(document => - document.GetGeneratedOutputAsync(It.IsAny()) == Task.FromResult(codeDocument) && - document.GetTextAsync() == Task.FromResult(codeDocument.Source.Text) && - document.Project.GetTagHelpersAsync(It.IsAny()) == new ValueTask>(tagHelpers), MockBehavior.Strict); - - var sourceText = SourceText.From(text); - - var context = new RazorCodeActionContext(request, documentSnapshot, codeDocument, location, sourceText, supportsFileCreation, supportsCodeActionResolve); - - return context; + var documentSnapshotMock = new StrictMock(); + documentSnapshotMock + .Setup(x => x.GetGeneratedOutputAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(codeDocument); + documentSnapshotMock + .Setup(x => x.GetTextAsync(It.IsAny())) + .ReturnsAsync(codeDocument.Source.Text); + documentSnapshotMock + .Setup(x => x.Project.GetTagHelpersAsync(It.IsAny())) + .ReturnsAsync(tagHelpers); + + return new RazorCodeActionContext( + request, + documentSnapshotMock.Object, + codeDocument, + location, + codeDocument.Source.Text, + supportsFileCreation, + supportsCodeActionResolve); } } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CodeActionEndpointTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CodeActionEndpointTest.cs index 882c953ce78..1067442f7e2 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CodeActionEndpointTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CodeActionEndpointTest.cs @@ -471,7 +471,7 @@ public async Task GenerateRazorCodeActionContextAsync_WithSelectionRange() }; // Act - var razorCodeActionContext = await codeActionEndpoint.GenerateRazorCodeActionContextAsync(request, documentContext.Snapshot); + var razorCodeActionContext = await codeActionEndpoint.GenerateRazorCodeActionContextAsync(request, documentContext.Snapshot, DisposalToken); // Assert Assert.NotNull(razorCodeActionContext); @@ -499,7 +499,7 @@ public async Task GenerateRazorCodeActionContextAsync_WithoutSelectionRange() }; // Act - var razorCodeActionContext = await codeActionEndpoint.GenerateRazorCodeActionContextAsync(request, documentContext.Snapshot); + var razorCodeActionContext = await codeActionEndpoint.GenerateRazorCodeActionContextAsync(request, documentContext.Snapshot, DisposalToken); // Assert Assert.NotNull(razorCodeActionContext); @@ -524,7 +524,7 @@ public async Task GetCSharpCodeActionsFromLanguageServerAsync_InvalidRangeMappin Context = new VSInternalCodeActionContext() }; - var context = await codeActionEndpoint.GenerateRazorCodeActionContextAsync(request, documentContext.Snapshot); + var context = await codeActionEndpoint.GenerateRazorCodeActionContextAsync(request, documentContext.Snapshot, DisposalToken); Assert.NotNull(context); // Act @@ -560,7 +560,7 @@ public async Task GetCSharpCodeActionsFromLanguageServerAsync_ReturnsCodeActions } }; - var context = await codeActionEndpoint.GenerateRazorCodeActionContextAsync(request, documentContext.Snapshot); + var context = await codeActionEndpoint.GenerateRazorCodeActionContextAsync(request, documentContext.Snapshot, DisposalToken); Assert.NotNull(context); // Act diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Html/DefaultHtmlCodeActionProviderTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Html/DefaultHtmlCodeActionProviderTest.cs index 8bcc81013b1..76ae4e0e63d 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Html/DefaultHtmlCodeActionProviderTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Html/DefaultHtmlCodeActionProviderTest.cs @@ -14,7 +14,6 @@ using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Razor.Protocol.CodeActions; using Microsoft.CodeAnalysis.Testing; -using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; using Moq; using Xunit; @@ -151,15 +150,25 @@ private static RazorCodeActionContext CreateRazorCodeActionContext( var projectEngine = RazorProjectEngine.Create(builder => builder.AddTagHelpers(tagHelpers)); var codeDocument = projectEngine.ProcessDesignTime(sourceDocument, FileKinds.Component, importSources: default, tagHelpers); - var documentSnapshot = Mock.Of(document => - document.GetGeneratedOutputAsync(It.IsAny()) == Task.FromResult(codeDocument) && - document.GetTextAsync() == Task.FromResult(codeDocument.Source.Text) && - document.Project.GetTagHelpersAsync(It.IsAny()) == new ValueTask>(tagHelpers), MockBehavior.Strict); + var documentSnapshotMock = new StrictMock(); + documentSnapshotMock + .Setup(x => x.GetGeneratedOutputAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(codeDocument); + documentSnapshotMock + .Setup(x => x.GetTextAsync(It.IsAny())) + .ReturnsAsync(codeDocument.Source.Text); + documentSnapshotMock + .Setup(x => x.Project.GetTagHelpersAsync(It.IsAny())) + .ReturnsAsync(tagHelpers); + + return new RazorCodeActionContext( + request, + documentSnapshotMock.Object, + codeDocument, + location, + codeDocument.Source.Text, + supportsFileCreation, + supportsCodeActionResolve); - var sourceText = SourceText.From(text); - - var context = new RazorCodeActionContext(request, documentSnapshot, codeDocument, location, sourceText, supportsFileCreation, supportsCodeActionResolve); - - return context; } } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ComponentAccessibilityCodeActionProviderTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ComponentAccessibilityCodeActionProviderTest.cs index 2a0e033c4b5..6e7c6e1af3c 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ComponentAccessibilityCodeActionProviderTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ComponentAccessibilityCodeActionProviderTest.cs @@ -7,11 +7,11 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.Components; +using Microsoft.AspNetCore.Razor.Test.Common; using Microsoft.AspNetCore.Razor.Test.Common.LanguageServer; using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Razor.Protocol.CodeActions; using Microsoft.CodeAnalysis.Testing; -using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; using Moq; using Xunit; @@ -463,15 +463,24 @@ private static RazorCodeActionContext CreateRazorCodeActionContext(VSCodeActionP var csharpDocumentWithDiagnostic = new RazorCSharpDocument(codeDocument, csharpDocument.GeneratedCode, csharpDocument.Options, [diagnostic]); codeDocument.SetCSharpDocument(csharpDocumentWithDiagnostic); - var documentSnapshot = Mock.Of(document => - document.GetGeneratedOutputAsync(It.IsAny()) == Task.FromResult(codeDocument) && - document.GetTextAsync() == Task.FromResult(codeDocument.Source.Text) && - document.Project.GetTagHelpersAsync(It.IsAny()) == new ValueTask>(tagHelpers), MockBehavior.Strict); - - var sourceText = SourceText.From(text); - - var context = new RazorCodeActionContext(request, documentSnapshot, codeDocument, location, sourceText, supportsFileCreation, SupportsCodeActionResolve: true); - - return context; + var documentSnapshotMock = new StrictMock(); + documentSnapshotMock + .Setup(x => x.GetGeneratedOutputAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(codeDocument); + documentSnapshotMock + .Setup(x => x.GetTextAsync(It.IsAny())) + .ReturnsAsync(codeDocument.Source.Text); + documentSnapshotMock + .Setup(x => x.Project.GetTagHelpersAsync(It.IsAny())) + .ReturnsAsync(tagHelpers); + + return new RazorCodeActionContext( + request, + documentSnapshotMock.Object, + codeDocument, + location, + codeDocument.Source.Text, + supportsFileCreation, + SupportsCodeActionResolve: true); } } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToCodeBehindCodeActionProviderTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToCodeBehindCodeActionProviderTest.cs index 698d462b310..c1bfb21c0fc 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToCodeBehindCodeActionProviderTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToCodeBehindCodeActionProviderTest.cs @@ -5,11 +5,13 @@ using System.Collections.Immutable; using System.Linq; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.Components; using Microsoft.AspNetCore.Razor.Language.Extensions; using Microsoft.AspNetCore.Razor.LanguageServer.CodeActions.Models; +using Microsoft.AspNetCore.Razor.Test.Common; using Microsoft.AspNetCore.Razor.Test.Common.LanguageServer; using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Razor.Protocol.CodeActions; @@ -395,14 +397,21 @@ private static RazorCodeActionContext CreateRazorCodeActionContext(VSCodeActionP })); codeDocument.SetSyntaxTree(syntaxTree); - var documentSnapshot = Mock.Of(document => - document.GetGeneratedOutputAsync(It.IsAny()) == Task.FromResult(codeDocument) && - document.GetTextAsync() == Task.FromResult(codeDocument.Source.Text), MockBehavior.Strict); - - var sourceText = SourceText.From(text); - - var context = new RazorCodeActionContext(request, documentSnapshot, codeDocument, location, sourceText, supportsFileCreation, SupportsCodeActionResolve: true); - - return context; + var documentSnapshotMock = new StrictMock(); + documentSnapshotMock + .Setup(x => x.GetGeneratedOutputAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(codeDocument); + documentSnapshotMock + .Setup(x => x.GetTextAsync(It.IsAny())) + .ReturnsAsync(codeDocument.Source.Text); + + return new RazorCodeActionContext( + request, + documentSnapshotMock.Object, + codeDocument, + location, + codeDocument.Source.Text, + supportsFileCreation, + SupportsCodeActionResolve: true); } } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeDocumentReferenceHolderTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeDocumentReferenceHolderTest.cs index 2367e07764f..7662dfbf0f4 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeDocumentReferenceHolderTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeDocumentReferenceHolderTest.cs @@ -44,7 +44,7 @@ public async Task DocumentProcessed_ReferencesGeneratedCodeDocument() { // Arrange var documentSnapshot = await CreateDocumentSnapshotAsync(); - var codeDocumentReference = await ProcessDocumentAndRetrieveOutputAsync(documentSnapshot); + var codeDocumentReference = await ProcessDocumentAndRetrieveOutputAsync(documentSnapshot, DisposalToken); // Act PerformFullGC(); @@ -70,8 +70,8 @@ public async Task UnrelatedDocumentChanged_ReferencesGeneratedCodeDocument() Assert.NotNull(unrelatedDocumentSnapshot); - var mainCodeDocumentReference = await ProcessDocumentAndRetrieveOutputAsync(documentSnapshot); - var unrelatedCodeDocumentReference = await ProcessDocumentAndRetrieveOutputAsync(unrelatedDocumentSnapshot); + var mainCodeDocumentReference = await ProcessDocumentAndRetrieveOutputAsync(documentSnapshot, DisposalToken); + var unrelatedCodeDocumentReference = await ProcessDocumentAndRetrieveOutputAsync(unrelatedDocumentSnapshot, DisposalToken); // Act await _projectManager.UpdateAsync(updater => @@ -91,7 +91,7 @@ public async Task DocumentChanged_DereferencesGeneratedCodeDocument() { // Arrange var documentSnapshot = await CreateDocumentSnapshotAsync(); - var codeDocumentReference = await ProcessDocumentAndRetrieveOutputAsync(documentSnapshot); + var codeDocumentReference = await ProcessDocumentAndRetrieveOutputAsync(documentSnapshot, DisposalToken); // Act @@ -111,7 +111,7 @@ public async Task DocumentRemoved_DereferencesGeneratedCodeDocument() { // Arrange var documentSnapshot = await CreateDocumentSnapshotAsync(); - var codeDocumentReference = await ProcessDocumentAndRetrieveOutputAsync(documentSnapshot); + var codeDocumentReference = await ProcessDocumentAndRetrieveOutputAsync(documentSnapshot, DisposalToken); // Act await _projectManager.UpdateAsync(updater => @@ -130,7 +130,7 @@ public async Task ProjectChanged_DereferencesGeneratedCodeDocument() { // Arrange var documentSnapshot = await CreateDocumentSnapshotAsync(); - var codeDocumentReference = await ProcessDocumentAndRetrieveOutputAsync(documentSnapshot); + var codeDocumentReference = await ProcessDocumentAndRetrieveOutputAsync(documentSnapshot, DisposalToken); // Act await _projectManager.UpdateAsync(updater => @@ -149,7 +149,7 @@ public async Task ProjectRemoved_DereferencesGeneratedCodeDocument() { // Arrange var documentSnapshot = await CreateDocumentSnapshotAsync(); - var codeDocumentReference = await ProcessDocumentAndRetrieveOutputAsync(documentSnapshot); + var codeDocumentReference = await ProcessDocumentAndRetrieveOutputAsync(documentSnapshot, DisposalToken); // Act await _projectManager.UpdateAsync(updater => @@ -176,9 +176,9 @@ private Task CreateDocumentSnapshotAsync() } [MethodImpl(MethodImplOptions.NoInlining)] - private async Task> ProcessDocumentAndRetrieveOutputAsync(IDocumentSnapshot documentSnapshot) + private async Task> ProcessDocumentAndRetrieveOutputAsync(IDocumentSnapshot documentSnapshot, CancellationToken cancellationToken) { - var codeDocument = await documentSnapshot.GetGeneratedOutputAsync(); + var codeDocument = await documentSnapshot.GetGeneratedOutputAsync(cancellationToken); _referenceHolder.DocumentProcessed(codeDocument, documentSnapshot); diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/DocumentSnapshotTextLoaderTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/DocumentSnapshotTextLoaderTest.cs index 0df799b3119..a24180b07cb 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/DocumentSnapshotTextLoaderTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/DocumentSnapshotTextLoaderTest.cs @@ -1,8 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. -#nullable disable - +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Test.Common; using Microsoft.CodeAnalysis.Razor.ProjectSystem; @@ -13,24 +12,21 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer; -public class DocumentSnapshotTextLoaderTest : ToolingTestBase +public class DocumentSnapshotTextLoaderTest(ITestOutputHelper testOutput) : ToolingTestBase(testOutput) { - public DocumentSnapshotTextLoaderTest(ITestOutputHelper testOutput) - : base(testOutput) - { - } - [Fact] public async Task LoadTextAndVersionAsync_CreatesTextAndVersionFromDocumentsText() { // Arrange var expectedSourceText = SourceText.From("Hello World"); - var result = Task.FromResult(expectedSourceText); - var snapshot = Mock.Of(doc => doc.GetTextAsync() == result, MockBehavior.Strict); - var textLoader = new DocumentSnapshotTextLoader(snapshot); + var snapshotMock = new StrictMock(); + snapshotMock + .Setup(x => x.GetTextAsync(It.IsAny())) + .ReturnsAsync(expectedSourceText); + var textLoader = new DocumentSnapshotTextLoader(snapshotMock.Object); // Act - var actual = await textLoader.LoadTextAndVersionAsync(default, default); + var actual = await textLoader.LoadTextAndVersionAsync(options: default, DisposalToken); // Assert Assert.Same(expectedSourceText, actual.Text); diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingContentValidationPassTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingContentValidationPassTest.cs index 69ef7632120..50ce29c43a5 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingContentValidationPassTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingContentValidationPassTest.cs @@ -99,9 +99,9 @@ private static (RazorCodeDocument, IDocumentSnapshot) CreateCodeDocumentAndSnaps var projectEngine = RazorProjectEngine.Create(builder => builder.SetRootNamespace("Test")); var codeDocument = projectEngine.ProcessDesignTime(sourceDocument, fileKind, importSources: default, tagHelpers); - var documentSnapshot = new Mock(MockBehavior.Strict); + var documentSnapshot = new StrictMock(); documentSnapshot - .Setup(d => d.GetGeneratedOutputAsync(It.IsAny())) + .Setup(d => d.GetGeneratedOutputAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(codeDocument); documentSnapshot .Setup(d => d.TargetPath) diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingTestBase.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingTestBase.cs index 6a4ae7846fc..4dbd6f2920b 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingTestBase.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingTestBase.cs @@ -273,9 +273,9 @@ @using Microsoft.AspNetCore.Components.Web var importsPath = new Uri("file:///path/to/_Imports.razor").AbsolutePath; var importsSourceText = SourceText.From(DefaultImports); var importsDocument = RazorSourceDocument.Create(importsSourceText, RazorSourceDocumentProperties.Create(importsPath, importsPath)); - var importsSnapshot = new Mock(MockBehavior.Strict); + var importsSnapshot = new StrictMock(); importsSnapshot - .Setup(d => d.GetTextAsync()) + .Setup(d => d.GetTextAsync(It.IsAny())) .ReturnsAsync(importsSourceText); importsSnapshot .Setup(d => d.FilePath) @@ -286,8 +286,7 @@ @using Microsoft.AspNetCore.Components.Web var projectFileSystem = new TestRazorProjectFileSystem([ new TestRazorProjectItem(path, fileKind: fileKind), - new TestRazorProjectItem(importsPath, fileKind: FileKinds.ComponentImport), - ]); + new TestRazorProjectItem(importsPath, fileKind: FileKinds.ComponentImport)]); var projectEngine = RazorProjectEngine.Create( new RazorConfiguration(RazorLanguageVersion.Latest, "TestConfiguration", Extensions: [], LanguageServerFlags: new LanguageServerFlags(forceRuntimeCodeGeneration)), @@ -315,9 +314,9 @@ @using Microsoft.AspNetCore.Components.Web internal static IDocumentSnapshot CreateDocumentSnapshot(string path, ImmutableArray tagHelpers, string? fileKind, ImmutableArray importsDocuments, ImmutableArray imports, RazorProjectEngine projectEngine, RazorCodeDocument codeDocument, bool inGlobalNamespace = false) { - var documentSnapshot = new Mock(MockBehavior.Strict); + var documentSnapshot = new StrictMock(); documentSnapshot - .Setup(d => d.GetGeneratedOutputAsync(It.IsAny())) + .Setup(d => d.GetGeneratedOutputAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(codeDocument); documentSnapshot .Setup(d => d.FilePath) @@ -332,7 +331,7 @@ internal static IDocumentSnapshot CreateDocumentSnapshot(string path, ImmutableA .Setup(d => d.Project.Configuration) .Returns(projectEngine.Configuration); documentSnapshot - .Setup(d => d.GetTextAsync()) + .Setup(d => d.GetTextAsync(It.IsAny())) .ReturnsAsync(codeDocument.Source.Text); documentSnapshot .Setup(d => d.Project.GetTagHelpersAsync(It.IsAny())) diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Hover/HoverServiceTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Hover/HoverServiceTest.cs index a0456bbbe7e..6dc5f7eda36 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Hover/HoverServiceTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Hover/HoverServiceTest.cs @@ -933,10 +933,10 @@ public async Task Handle_Hover_SingleServer_AddTagHelper() var documentSnapshotMock = new StrictMock(); documentSnapshotMock - .Setup(x => x.GetGeneratedOutputAsync(It.IsAny())) + .Setup(x => x.GetGeneratedOutputAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(codeDocument); documentSnapshotMock - .Setup(x => x.GetTextAsync()) + .Setup(x => x.GetTextAsync(It.IsAny())) .ReturnsAsync(codeDocument.Source.Text); documentSnapshotMock .SetupGet(x => x.FilePath) diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/OpenDocumentGeneratorTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/OpenDocumentGeneratorTest.cs index b5041a4ee7e..7c05c89c158 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/OpenDocumentGeneratorTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/OpenDocumentGeneratorTest.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.ProjectSystem; using Microsoft.AspNetCore.Razor.Test.Common.LanguageServer; +using Microsoft.AspNetCore.Razor.Test.Common.ProjectSystem; using Microsoft.AspNetCore.Razor.Test.Common.Workspaces; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Razor.ProjectSystem; @@ -39,7 +40,7 @@ await projectManager.UpdateAsync(updater => { updater.ProjectAdded(_hostProject1); updater.ProjectAdded(_hostProject2); - updater.DocumentAdded(_hostProject1.Key, _documents[0], null!); + updater.DocumentAdded(_hostProject1.Key, _documents[0], _documents[0].CreateEmptyTextLoader()); updater.DocumentOpened(_hostProject1.Key, _documents[0].FilePath, SourceText.From(string.Empty)); }); @@ -49,7 +50,7 @@ await projectManager.UpdateAsync(updater => await projectManager.UpdateAsync(updater => { updater.DocumentRemoved(_hostProject1.Key, _documents[0]); - updater.DocumentAdded(_hostProject2.Key, _documents[0], null!); + updater.DocumentAdded(_hostProject2.Key, _documents[0], _documents[0].CreateEmptyTextLoader()); }); // Assert @@ -72,7 +73,7 @@ await projectManager.UpdateAsync(updater => updater.ProjectAdded(_hostProject2); // Act - updater.DocumentAdded(_hostProject1.Key, _documents[0], null!); + updater.DocumentAdded(_hostProject1.Key, _documents[0], _documents[0].CreateEmptyTextLoader()); }); // Assert @@ -91,7 +92,7 @@ await projectManager.UpdateAsync(updater => { updater.ProjectAdded(_hostProject1); updater.ProjectAdded(_hostProject2); - updater.DocumentAdded(_hostProject1.Key, _documents[0], null!); + updater.DocumentAdded(_hostProject1.Key, _documents[0], _documents[0].CreateEmptyTextLoader()); // Act updater.DocumentChanged(_hostProject1.Key, _documents[0].FilePath, SourceText.From("new")); @@ -113,7 +114,7 @@ await projectManager.UpdateAsync(updater => { updater.ProjectAdded(_hostProject1); updater.ProjectAdded(_hostProject2); - updater.DocumentAdded(_hostProject1.Key, _documents[0], null!); + updater.DocumentAdded(_hostProject1.Key, _documents[0], _documents[0].CreateEmptyTextLoader()); updater.DocumentOpened(_hostProject1.Key, _documents[0].FilePath, SourceText.From(string.Empty)); // Act @@ -138,7 +139,7 @@ await projectManager.UpdateAsync(updater => { updater.ProjectAdded(_hostProject1); updater.ProjectAdded(_hostProject2); - updater.DocumentAdded(_hostProject1.Key, _documents[0], null!); + updater.DocumentAdded(_hostProject1.Key, _documents[0], _documents[0].CreateEmptyTextLoader()); // Act updater.ProjectWorkspaceStateChanged(_hostProject1.Key, @@ -161,7 +162,7 @@ await projectManager.UpdateAsync(updater => { updater.ProjectAdded(_hostProject1); updater.ProjectAdded(_hostProject2); - updater.DocumentAdded(_hostProject1.Key, _documents[0], null!); + updater.DocumentAdded(_hostProject1.Key, _documents[0], _documents[0].CreateEmptyTextLoader()); updater.DocumentOpened(_hostProject1.Key, _documents[0].FilePath, SourceText.From(string.Empty)); // Act diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/RazorComponentSearchEngineTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/RazorComponentSearchEngineTest.cs index cff23e4b5b4..9dfc3afdccd 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/RazorComponentSearchEngineTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/RazorComponentSearchEngineTest.cs @@ -101,8 +101,8 @@ public async Task Handle_SearchFound_GenericComponent() var searchEngine = new RazorComponentSearchEngine(LoggerFactory); // Act - var documentSnapshot1 = await searchEngine.TryLocateComponentAsync(tagHelperDescriptor1, _projectManager.GetQueryOperations()); - var documentSnapshot2 = await searchEngine.TryLocateComponentAsync(tagHelperDescriptor2, _projectManager.GetQueryOperations()); + var documentSnapshot1 = await searchEngine.TryLocateComponentAsync(tagHelperDescriptor1, _projectManager.GetQueryOperations(), DisposalToken); + var documentSnapshot2 = await searchEngine.TryLocateComponentAsync(tagHelperDescriptor2, _projectManager.GetQueryOperations(), DisposalToken); // Assert Assert.NotNull(documentSnapshot1); @@ -120,8 +120,8 @@ public async Task Handle_SearchFound() var searchEngine = new RazorComponentSearchEngine(LoggerFactory); // Act - var documentSnapshot1 = await searchEngine.TryLocateComponentAsync(tagHelperDescriptor1, _projectManager.GetQueryOperations()); - var documentSnapshot2 = await searchEngine.TryLocateComponentAsync(tagHelperDescriptor2, _projectManager.GetQueryOperations()); + var documentSnapshot1 = await searchEngine.TryLocateComponentAsync(tagHelperDescriptor1, _projectManager.GetQueryOperations(), DisposalToken); + var documentSnapshot2 = await searchEngine.TryLocateComponentAsync(tagHelperDescriptor2, _projectManager.GetQueryOperations(), DisposalToken); // Assert Assert.NotNull(documentSnapshot1); @@ -138,7 +138,7 @@ public async Task Handle_SearchFound_SetNamespace() var searchEngine = new RazorComponentSearchEngine(LoggerFactory); // Act - var documentSnapshot = await searchEngine.TryLocateComponentAsync(tagHelperDescriptor, _projectManager.GetQueryOperations()); + var documentSnapshot = await searchEngine.TryLocateComponentAsync(tagHelperDescriptor, _projectManager.GetQueryOperations(), DisposalToken); // Assert Assert.NotNull(documentSnapshot); @@ -153,7 +153,7 @@ public async Task Handle_SearchMissing_IncorrectAssembly() var searchEngine = new RazorComponentSearchEngine(LoggerFactory); // Act - var documentSnapshot = await searchEngine.TryLocateComponentAsync(tagHelperDescriptor, _projectManager.GetQueryOperations()); + var documentSnapshot = await searchEngine.TryLocateComponentAsync(tagHelperDescriptor, _projectManager.GetQueryOperations(), DisposalToken); // Assert Assert.Null(documentSnapshot); @@ -167,7 +167,7 @@ public async Task Handle_SearchMissing_IncorrectNamespace() var searchEngine = new RazorComponentSearchEngine(LoggerFactory); // Act - var documentSnapshot = await searchEngine.TryLocateComponentAsync(tagHelperDescriptor, _projectManager.GetQueryOperations()); + var documentSnapshot = await searchEngine.TryLocateComponentAsync(tagHelperDescriptor, _projectManager.GetQueryOperations(), DisposalToken); // Assert Assert.Null(documentSnapshot); @@ -181,7 +181,7 @@ public async Task Handle_SearchMissing_IncorrectComponent() var searchEngine = new RazorComponentSearchEngine(LoggerFactory); // Act - var documentSnapshot = await searchEngine.TryLocateComponentAsync(tagHelperDescriptor, _projectManager.GetQueryOperations()); + var documentSnapshot = await searchEngine.TryLocateComponentAsync(tagHelperDescriptor, _projectManager.GetQueryOperations(), DisposalToken); // Assert Assert.Null(documentSnapshot); @@ -195,7 +195,7 @@ public async Task Handle_FilePathAndAssemblyNameDifferent() var searchEngine = new RazorComponentSearchEngine(LoggerFactory); // Act - var documentSnapshot = await searchEngine.TryLocateComponentAsync(tagHelperDescriptor, _projectManager.GetQueryOperations()); + var documentSnapshot = await searchEngine.TryLocateComponentAsync(tagHelperDescriptor, _projectManager.GetQueryOperations(), DisposalToken); // Assert Assert.NotNull(documentSnapshot); diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Semantic/SemanticTokensTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Semantic/SemanticTokensTest.cs index da02e54cda3..6ef88b7406d 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Semantic/SemanticTokensTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Semantic/SemanticTokensTest.cs @@ -955,10 +955,10 @@ private static DocumentContext CreateDocumentContext( .SetupGet(x => x.Project) .Returns(projectSnapshot.Object); documentSnapshotMock - .Setup(x => x.GetGeneratedOutputAsync(It.IsAny())) + .Setup(x => x.GetGeneratedOutputAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(document); documentSnapshotMock - .Setup(x => x.GetTextAsync()) + .Setup(x => x.GetTextAsync(It.IsAny())) .ReturnsAsync(document.Source.Text); documentSnapshotMock .SetupGet(x => x.Version) diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/ProjectSystem/TestDocumentSnapshot.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/ProjectSystem/TestDocumentSnapshot.cs index 1c7c4e75e03..07cc9ddcf81 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/ProjectSystem/TestDocumentSnapshot.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/ProjectSystem/TestDocumentSnapshot.cs @@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.ProjectSystem; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Text; @@ -39,12 +38,7 @@ public static TestDocumentSnapshot Create(string filePath, string text, ProjectW var sourceText = SourceText.From(text); var textVersion = VersionStamp.Default; - var documentState = new DocumentState( - hostDocument, - sourceText, - textVersion, - version, - () => Task.FromResult(TextAndVersion.Create(sourceText, textVersion))); + var documentState = new DocumentState(hostDocument, version, sourceText, textVersion); return new TestDocumentSnapshot(project, documentState); } @@ -60,12 +54,7 @@ public static TestDocumentSnapshot Create(string filePath, RazorCodeDocument cod var sourceText = codeDocument.Source.Text; var textVersion = VersionStamp.Default; - var documentState = new DocumentState( - hostDocument, - sourceText, - textVersion, - version, - () => Task.FromResult(TextAndVersion.Create(sourceText, textVersion))); + var documentState = new DocumentState(hostDocument, version, sourceText, textVersion); return new TestDocumentSnapshot(project, documentState, codeDocument); } @@ -78,34 +67,30 @@ public static TestDocumentSnapshot Create(string filePath, RazorCodeDocument cod public IProjectSnapshot Project => RealSnapshot.Project; public int Version => RealSnapshot.Version; - public Task GetGeneratedOutputAsync(bool forceDesignTimeGeneratedOutput) + public ValueTask GetGeneratedOutputAsync( + bool forceDesignTimeGeneratedOutput, + CancellationToken cancellationToken) { return _codeDocument is null - ? RealSnapshot.GetGeneratedOutputAsync(forceDesignTimeGeneratedOutput) - : Task.FromResult(_codeDocument); + ? RealSnapshot.GetGeneratedOutputAsync(forceDesignTimeGeneratedOutput, cancellationToken) + : new(_codeDocument); } - public Task GetTextAsync() + public ValueTask GetTextAsync(CancellationToken cancellationToken) { return _codeDocument is null - ? RealSnapshot.GetTextAsync() - : Task.FromResult(_codeDocument.Source.Text); + ? RealSnapshot.GetTextAsync(cancellationToken) + : new(_codeDocument.Source.Text); } - public Task GetTextVersionAsync() - => RealSnapshot.GetTextVersionAsync(); + public ValueTask GetTextVersionAsync(CancellationToken cancellationToken) + => RealSnapshot.GetTextVersionAsync(cancellationToken); - public Task GetCSharpSyntaxTreeAsync(CancellationToken cancellationToken) + public ValueTask GetCSharpSyntaxTreeAsync(CancellationToken cancellationToken) { - if (_codeDocument is { } codeDocument) - { - var csharpText = codeDocument.GetCSharpSourceText(); - var csharpSyntaxTree = CSharpSyntaxTree.ParseText(csharpText, cancellationToken: cancellationToken); - - return Task.FromResult(csharpSyntaxTree); - } - - return RealSnapshot.GetCSharpSyntaxTreeAsync(cancellationToken); + return _codeDocument is null + ? RealSnapshot.GetCSharpSyntaxTreeAsync(cancellationToken) + : new(DocumentSnapshot.GetOrParseCSharpSyntaxTree(_codeDocument, cancellationToken)); } public bool TryGetGeneratedOutput([NotNullWhen(true)] out RazorCodeDocument? result) diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/TestMocks.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/TestMocks.cs index 0aeb4832b6e..9abf55ceff3 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/TestMocks.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/TestMocks.cs @@ -31,6 +31,21 @@ public static TextLoader CreateTextLoader(string filePath, SourceText text) return mock.Object; } + public static TextLoader CreateTextLoader(string text, VersionStamp version) + => CreateTextLoader(SourceText.From(text), version); + + public static TextLoader CreateTextLoader(SourceText text, VersionStamp version) + { + var mock = new StrictMock(); + + var textAndVersion = TextAndVersion.Create(text, version); + + mock.Setup(x => x.LoadTextAndVersionAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(textAndVersion); + + return mock.Object; + } + public interface IClientConnectionBuilder { void SetupSendRequest(string method, TResponse response, bool verifiable = false); diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Workspaces/DocumentExcerptServiceTestBase.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Workspaces/DocumentExcerptServiceTestBase.cs index d6370f7eb2a..f013c6cab13 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Workspaces/DocumentExcerptServiceTestBase.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Workspaces/DocumentExcerptServiceTestBase.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.ProjectSystem; @@ -40,9 +41,11 @@ public static (SourceText sourceText, TextSpan span) CreateText(string text) // Adds the text to a ProjectSnapshot, generates code, and updates the workspace. private (IDocumentSnapshot primary, Document secondary) InitializeDocument(SourceText sourceText) { - var project = new ProjectSnapshot( - ProjectState.Create(ProjectEngineFactoryProvider, _hostProject, ProjectWorkspaceState.Default) - .WithAddedHostDocument(_hostDocument, () => Task.FromResult(TextAndVersion.Create(sourceText, VersionStamp.Create())))); + var state = ProjectState + .Create(ProjectEngineFactoryProvider, _hostProject, ProjectWorkspaceState.Default) + .WithAddedHostDocument(_hostDocument, TestMocks.CreateTextLoader(sourceText, VersionStamp.Create())); + + var project = new ProjectSnapshot(state); var primary = project.GetDocument(_hostDocument.FilePath).AssumeNotNull(); @@ -66,9 +69,9 @@ public static (SourceText sourceText, TextSpan span) CreateText(string text) // Maps a span in the primary buffer to the secondary buffer. This is only valid for C# code // that appears in the primary buffer. - private static async Task GetSecondarySpanAsync(IDocumentSnapshot primary, TextSpan primarySpan, Document secondary) + private static async Task GetSecondarySpanAsync(IDocumentSnapshot primary, TextSpan primarySpan, Document secondary, CancellationToken cancellationToken) { - var output = await primary.GetGeneratedOutputAsync(); + var output = await primary.GetGeneratedOutputAsync(cancellationToken); foreach (var mapping in output.GetCSharpDocument().SourceMappings) { @@ -78,8 +81,8 @@ private static async Task GetSecondarySpanAsync(IDocumentSnapshot prim var offset = mapping.GeneratedSpan.AbsoluteIndex - mapping.OriginalSpan.AbsoluteIndex; var secondarySpan = new TextSpan(primarySpan.Start + offset, primarySpan.Length); Assert.Equal( - (await primary.GetTextAsync()).GetSubText(primarySpan).ToString(), - (await secondary.GetTextAsync()).GetSubText(secondarySpan).ToString()); + (await primary.GetTextAsync(cancellationToken)).ToString(primarySpan), + (await secondary.GetTextAsync(cancellationToken)).ToString(secondarySpan)); return secondarySpan; } } @@ -87,19 +90,19 @@ private static async Task GetSecondarySpanAsync(IDocumentSnapshot prim throw new InvalidOperationException("Could not map the primary span to the generated code."); } - public async Task<(Document generatedDocument, SourceText razorSourceText, TextSpan primarySpan, TextSpan generatedSpan)> InitializeAsync(string razorSource) + public async Task<(Document generatedDocument, SourceText razorSourceText, TextSpan primarySpan, TextSpan generatedSpan)> InitializeAsync(string razorSource, CancellationToken cancellationToken) { var (razorSourceText, primarySpan) = CreateText(razorSource); var (primary, generatedDocument) = InitializeDocument(razorSourceText); - var generatedSpan = await GetSecondarySpanAsync(primary, primarySpan, generatedDocument); + var generatedSpan = await GetSecondarySpanAsync(primary, primarySpan, generatedDocument, cancellationToken); return (generatedDocument, razorSourceText, primarySpan, generatedSpan); } - internal async Task<(IDocumentSnapshot primary, Document generatedDocument, TextSpan generatedSpan)> InitializeWithSnapshotAsync(string razorSource) + internal async Task<(IDocumentSnapshot primary, Document generatedDocument, TextSpan generatedSpan)> InitializeWithSnapshotAsync(string razorSource, CancellationToken cancellationToken) { var (razorSourceText, primarySpan) = CreateText(razorSource); var (primary, generatedDocument) = InitializeDocument(razorSourceText); - var generatedSpan = await GetSecondarySpanAsync(primary, primarySpan, generatedDocument); + var generatedSpan = await GetSecondarySpanAsync(primary, primarySpan, generatedDocument, cancellationToken); return (primary, generatedDocument, generatedSpan); } } diff --git a/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultDocumentSnapshotTest.cs b/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultDocumentSnapshotTest.cs index 4f34133138c..8fb1c912ad6 100644 --- a/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultDocumentSnapshotTest.cs +++ b/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultDocumentSnapshotTest.cs @@ -36,18 +36,18 @@ public DefaultDocumentSnapshotTest(ITestOutputHelper testOutput) var projectState = ProjectState.Create(ProjectEngineFactoryProvider, TestProjectData.SomeProject, ProjectWorkspaceState.Default); var project = new ProjectSnapshot(projectState); - var textAndVersion = TextAndVersion.Create(_sourceText, _version); + var textLoader = TestMocks.CreateTextLoader(_sourceText, _version); - var documentState = DocumentState.Create(s_legacyHostDocument, () => Task.FromResult(textAndVersion)); + var documentState = DocumentState.Create(s_legacyHostDocument, textLoader); _legacyDocument = new DocumentSnapshot(project, documentState); - documentState = DocumentState.Create(s_componentHostDocument, () => Task.FromResult(textAndVersion)); + documentState = DocumentState.Create(s_componentHostDocument, textLoader); _componentDocument = new DocumentSnapshot(project, documentState); - documentState = DocumentState.Create(s_componentCshtmlHostDocument, () => Task.FromResult(textAndVersion)); + documentState = DocumentState.Create(s_componentCshtmlHostDocument, textLoader); _componentCshtmlDocument = new DocumentSnapshot(project, documentState); - documentState = DocumentState.Create(s_nestedComponentHostDocument, () => Task.FromResult(textAndVersion)); + documentState = DocumentState.Create(s_nestedComponentHostDocument, textLoader); _nestedComponentDocument = new DocumentSnapshot(project, documentState); } @@ -55,7 +55,7 @@ public DefaultDocumentSnapshotTest(ITestOutputHelper testOutput) public async Task GCCollect_OutputIsNoLongerCached() { // Arrange - await Task.Run(async () => { await _legacyDocument.GetGeneratedOutputAsync(); }); + await Task.Run(async () => { await _legacyDocument.GetGeneratedOutputAsync(DisposalToken); }); // Act @@ -70,7 +70,7 @@ public async Task GCCollect_OutputIsNoLongerCached() public async Task RegeneratingWithReference_CachesOutput() { // Arrange - var output = await _legacyDocument.GetGeneratedOutputAsync(); + var output = await _legacyDocument.GetGeneratedOutputAsync(DisposalToken); // Mostly doing this to ensure "var output" doesn't get optimized out Assert.NotNull(output); @@ -86,7 +86,7 @@ public async Task RegeneratingWithReference_CachesOutput() public async Task GetGeneratedOutputAsync_CshtmlComponent_ContainsComponentImports() { // Act - var codeDocument = await _componentCshtmlDocument.GetGeneratedOutputAsync(); + var codeDocument = await _componentCshtmlDocument.GetGeneratedOutputAsync(DisposalToken); // Assert Assert.Contains("using global::Microsoft.AspNetCore.Components", codeDocument.GetCSharpSourceText().ToString(), StringComparison.Ordinal); @@ -96,7 +96,7 @@ public async Task GetGeneratedOutputAsync_CshtmlComponent_ContainsComponentImpor public async Task GetGeneratedOutputAsync_Component() { // Act - var codeDocument = await _componentDocument.GetGeneratedOutputAsync(); + var codeDocument = await _componentDocument.GetGeneratedOutputAsync(DisposalToken); // Assert Assert.Contains("ComponentBase", codeDocument.GetCSharpSourceText().ToString(), StringComparison.Ordinal); @@ -106,7 +106,7 @@ public async Task GetGeneratedOutputAsync_Component() public async Task GetGeneratedOutputAsync_NestedComponentDocument_SetsCorrectNamespaceAndClassName() { // Act - var codeDocument = await _nestedComponentDocument.GetGeneratedOutputAsync(); + var codeDocument = await _nestedComponentDocument.GetGeneratedOutputAsync(DisposalToken); // Assert Assert.Contains("ComponentBase", codeDocument.GetCSharpSourceText().ToString(), StringComparison.Ordinal); @@ -120,7 +120,7 @@ public async Task GetGeneratedOutputAsync_NestedComponentDocument_SetsCorrectNam public async Task GetGeneratedOutputAsync_Legacy() { // Act - var codeDocument = await _legacyDocument.GetGeneratedOutputAsync(); + var codeDocument = await _legacyDocument.GetGeneratedOutputAsync(DisposalToken); // Assert Assert.Contains("Template", codeDocument.GetCSharpSourceText().ToString(), StringComparison.Ordinal); diff --git a/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DocumentStateTest.cs b/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DocumentStateTest.cs index b5296525800..2632887a2ed 100644 --- a/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DocumentStateTest.cs +++ b/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DocumentStateTest.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 Microsoft.CodeAnalysis.Text; @@ -13,7 +12,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem; public class DocumentStateTest : ToolingTestBase { private readonly HostDocument _hostDocument; - private readonly Func> _textLoader; + private readonly TextLoader _textLoader; private readonly SourceText _text; public DocumentStateTest(ITestOutputHelper testOutput) @@ -21,7 +20,7 @@ public DocumentStateTest(ITestOutputHelper testOutput) { _hostDocument = TestProjectData.SomeProjectFile1; _text = SourceText.From("Hello, world!"); - _textLoader = () => Task.FromResult(TextAndVersion.Create(_text, VersionStamp.Create())); + _textLoader = TestMocks.CreateTextLoader(_text, VersionStamp.Create()); } [Fact] @@ -31,7 +30,7 @@ public async Task DocumentState_CreatedNew_HasEmptyText() var state = DocumentState.Create(_hostDocument, DocumentState.EmptyLoader); // Assert - var text = await state.GetTextAsync(); + var text = await state.GetTextAsync(DisposalToken); Assert.Equal(0, text.Length); } @@ -45,7 +44,7 @@ public async Task DocumentState_WithText_CreatesNewState() var state = original.WithText(_text, VersionStamp.Create()); // Assert - var text = await state.GetTextAsync(); + var text = await state.GetTextAsync(DisposalToken); Assert.Same(_text, text); } @@ -59,7 +58,7 @@ public async Task DocumentState_WithTextLoader_CreatesNewState() var state = original.WithTextLoader(_textLoader); // Assert - var text = await state.GetTextAsync(); + var text = await state.GetTextAsync(DisposalToken); Assert.Same(_text, text); } @@ -85,7 +84,7 @@ public async Task DocumentState_WithConfigurationChange_CachesLoadedText() var original = DocumentState.Create(_hostDocument, DocumentState.EmptyLoader) .WithTextLoader(_textLoader); - await original.GetTextAsync(); + await original.GetTextAsync(DisposalToken); // Act var state = original.WithConfigurationChange(); @@ -117,7 +116,7 @@ public async Task DocumentState_WithImportsChange_CachesLoadedText() var original = DocumentState.Create(_hostDocument, DocumentState.EmptyLoader) .WithTextLoader(_textLoader); - await original.GetTextAsync(); + await original.GetTextAsync(DisposalToken); // Act var state = original.WithImportsChange(); @@ -149,7 +148,7 @@ public async Task DocumentState_WithProjectWorkspaceStateChange_CachesLoadedText var original = DocumentState.Create(_hostDocument, DocumentState.EmptyLoader) .WithTextLoader(_textLoader); - await original.GetTextAsync(); + await original.GetTextAsync(DisposalToken); // Act var state = original.WithProjectWorkspaceStateChange(); diff --git a/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/GeneratedDocumentTextLoaderTest.cs b/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/GeneratedDocumentTextLoaderTest.cs index ee1ca19e87f..3f608cd19f2 100644 --- a/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/GeneratedDocumentTextLoaderTest.cs +++ b/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/GeneratedDocumentTextLoaderTest.cs @@ -8,7 +8,6 @@ using Microsoft.AspNetCore.Razor.ProjectSystem; using Microsoft.AspNetCore.Razor.Test.Common; using Microsoft.AspNetCore.Razor.Test.Common.Workspaces; -using Microsoft.CodeAnalysis.Text; using Xunit; using Xunit.Abstractions; @@ -32,7 +31,7 @@ public async Task LoadAsync_SpecifiesEncoding() // Arrange var project = new ProjectSnapshot( ProjectState.Create(ProjectEngineFactoryProvider, _hostProject, ProjectWorkspaceState.Default) - .WithAddedHostDocument(_hostDocument, () => Task.FromResult(TextAndVersion.Create(SourceText.From(""), VersionStamp.Create())))); + .WithAddedHostDocument(_hostDocument, TestMocks.CreateTextLoader("", VersionStamp.Create()))); var document = project.GetDocument(_hostDocument.FilePath); diff --git a/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/ProjectStateGeneratedOutputTest.cs b/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/ProjectStateGeneratedOutputTest.cs index 75dd957f057..7e80b2537b7 100644 --- a/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/ProjectStateGeneratedOutputTest.cs +++ b/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/ProjectStateGeneratedOutputTest.cs @@ -2,13 +2,13 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System.Collections.Immutable; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.ProjectSystem; using Microsoft.AspNetCore.Razor.Test.Common; using Microsoft.AspNetCore.Razor.Test.Common.Workspaces; using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Text; using Xunit; using Xunit.Abstractions; @@ -46,13 +46,13 @@ public async Task HostDocumentAdded_CachesOutput() ProjectState.Create(ProjectEngineFactoryProvider, _hostProject, ProjectWorkspaceState.Default) .WithAddedHostDocument(_hostDocument, DocumentState.EmptyLoader); - var (originalOutput, originalInputVersion) = await GetOutputAsync(original, _hostDocument); + var (originalOutput, originalInputVersion) = await GetOutputAsync(original, _hostDocument, DisposalToken); // Act var state = original.WithAddedHostDocument(TestProjectData.AnotherProjectFile1, DocumentState.EmptyLoader); // Assert - var (actualOutput, actualInputVersion) = await GetOutputAsync(state, _hostDocument); + var (actualOutput, actualInputVersion) = await GetOutputAsync(state, _hostDocument, DisposalToken); Assert.Same(originalOutput, actualOutput); Assert.Equal(originalInputVersion, actualInputVersion); } @@ -65,13 +65,13 @@ public async Task HostDocumentAdded_Import_DoesNotCacheOutput() ProjectState.Create(ProjectEngineFactoryProvider, _hostProject, ProjectWorkspaceState.Default) .WithAddedHostDocument(_hostDocument, DocumentState.EmptyLoader); - var (originalOutput, originalInputVersion) = await GetOutputAsync(original, _hostDocument); + var (originalOutput, originalInputVersion) = await GetOutputAsync(original, _hostDocument, DisposalToken); // Act var state = original.WithAddedHostDocument(TestProjectData.SomeProjectImportFile, DocumentState.EmptyLoader); // Assert - var (actualOutput, actualInputVersion) = await GetOutputAsync(state, _hostDocument); + var (actualOutput, actualInputVersion) = await GetOutputAsync(state, _hostDocument, DisposalToken); Assert.NotSame(originalOutput, actualOutput); Assert.NotEqual(originalInputVersion, actualInputVersion); Assert.Equal(state.DocumentCollectionVersion, actualInputVersion); @@ -81,19 +81,19 @@ public async Task HostDocumentAdded_Import_DoesNotCacheOutput() public async Task HostDocumentChanged_DoesNotCacheOutput() { // Arrange - var original = - ProjectState.Create(ProjectEngineFactoryProvider, _hostProject, ProjectWorkspaceState.Default) + var original = ProjectState + .Create(ProjectEngineFactoryProvider, _hostProject, ProjectWorkspaceState.Default) .WithAddedHostDocument(_hostDocument, DocumentState.EmptyLoader) .WithAddedHostDocument(TestProjectData.SomeProjectImportFile, DocumentState.EmptyLoader); - var (originalOutput, originalInputVersion) = await GetOutputAsync(original, _hostDocument); + var (originalOutput, originalInputVersion) = await GetOutputAsync(original, _hostDocument, DisposalToken); // Act var version = VersionStamp.Create(); - var state = original.WithChangedHostDocument(_hostDocument, () => Task.FromResult(TextAndVersion.Create(SourceText.From("@using System"), version))); + var state = original.WithChangedHostDocument(_hostDocument, TestMocks.CreateTextLoader("@using System", version)); // Assert - var (actualOutput, actualInputVersion) = await GetOutputAsync(state, _hostDocument); + var (actualOutput, actualInputVersion) = await GetOutputAsync(state, _hostDocument, DisposalToken); Assert.NotSame(originalOutput, actualOutput); Assert.NotEqual(originalInputVersion, actualInputVersion); Assert.Equal(version, actualInputVersion); @@ -103,19 +103,19 @@ public async Task HostDocumentChanged_DoesNotCacheOutput() public async Task HostDocumentChanged_Import_DoesNotCacheOutput() { // Arrange - var original = - ProjectState.Create(ProjectEngineFactoryProvider, _hostProject, ProjectWorkspaceState.Default) + var original = ProjectState + .Create(ProjectEngineFactoryProvider, _hostProject, ProjectWorkspaceState.Default) .WithAddedHostDocument(_hostDocument, DocumentState.EmptyLoader) .WithAddedHostDocument(TestProjectData.SomeProjectImportFile, DocumentState.EmptyLoader); - var (originalOutput, originalInputVersion) = await GetOutputAsync(original, _hostDocument); + var (originalOutput, originalInputVersion) = await GetOutputAsync(original, _hostDocument, DisposalToken); // Act var version = VersionStamp.Create(); - var state = original.WithChangedHostDocument(TestProjectData.SomeProjectImportFile, () => Task.FromResult(TextAndVersion.Create(SourceText.From("@using System"), version))); + var state = original.WithChangedHostDocument(TestProjectData.SomeProjectImportFile, TestMocks.CreateTextLoader("@using System", version)); // Assert - var (actualOutput, actualInputVersion) = await GetOutputAsync(state, _hostDocument); + var (actualOutput, actualInputVersion) = await GetOutputAsync(state, _hostDocument, DisposalToken); Assert.NotSame(originalOutput, actualOutput); Assert.NotEqual(originalInputVersion, actualInputVersion); Assert.Equal(version, actualInputVersion); @@ -130,13 +130,13 @@ public async Task HostDocumentRemoved_Import_DoesNotCacheOutput() .WithAddedHostDocument(_hostDocument, DocumentState.EmptyLoader) .WithAddedHostDocument(TestProjectData.SomeProjectImportFile, DocumentState.EmptyLoader); - var (originalOutput, originalInputVersion) = await GetOutputAsync(original, _hostDocument); + var (originalOutput, originalInputVersion) = await GetOutputAsync(original, _hostDocument, DisposalToken); // Act var state = original.WithRemovedHostDocument(TestProjectData.SomeProjectImportFile); // Assert - var (actualOutput, actualInputVersion) = await GetOutputAsync(state, _hostDocument); + var (actualOutput, actualInputVersion) = await GetOutputAsync(state, _hostDocument, DisposalToken); Assert.NotSame(originalOutput, actualOutput); Assert.NotEqual(originalInputVersion, actualInputVersion); Assert.Equal(state.DocumentCollectionVersion, actualInputVersion); @@ -150,14 +150,14 @@ public async Task ProjectWorkspaceStateChange_CachesOutput_EvenWhenNewerProjectW ProjectState.Create(ProjectEngineFactoryProvider, _hostProject, ProjectWorkspaceState.Default) .WithAddedHostDocument(_hostDocument, DocumentState.EmptyLoader); - var (originalOutput, originalInputVersion) = await GetOutputAsync(original, _hostDocument); + var (originalOutput, originalInputVersion) = await GetOutputAsync(original, _hostDocument, DisposalToken); var changed = ProjectWorkspaceState.Default; // Act var state = original.WithProjectWorkspaceState(changed); // Assert - var (actualOutput, actualInputVersion) = await GetOutputAsync(state, _hostDocument); + var (actualOutput, actualInputVersion) = await GetOutputAsync(state, _hostDocument, DisposalToken); Assert.Same(originalOutput, actualOutput); Assert.Equal(originalInputVersion, actualInputVersion); } @@ -171,14 +171,14 @@ public async Task ProjectWorkspaceStateChange_WithTagHelperChange_DoesNotCacheOu ProjectState.Create(ProjectEngineFactoryProvider, _hostProject, ProjectWorkspaceState.Default) .WithAddedHostDocument(_hostDocument, DocumentState.EmptyLoader); - var (originalOutput, originalInputVersion) = await GetOutputAsync(original, _hostDocument); + var (originalOutput, originalInputVersion) = await GetOutputAsync(original, _hostDocument, DisposalToken); var changed = ProjectWorkspaceState.Create(_someTagHelpers); // Act var state = original.WithProjectWorkspaceState(changed); // Assert - var (actualOutput, actualInputVersion) = await GetOutputAsync(state, _hostDocument); + var (actualOutput, actualInputVersion) = await GetOutputAsync(state, _hostDocument, DisposalToken); Assert.NotSame(originalOutput, actualOutput); Assert.NotEqual(originalInputVersion, actualInputVersion); Assert.Equal(state.ProjectWorkspaceStateVersion, actualInputVersion); @@ -193,16 +193,16 @@ public async Task ProjectWorkspaceStateChange_WithProjectWorkspaceState_CSharpLa var originalWorkspaceState = ProjectWorkspaceState.Create(_someTagHelpers, LanguageVersion.CSharp7); var original = ProjectState.Create(ProjectEngineFactoryProvider, hostProject, originalWorkspaceState) - .WithAddedHostDocument(_hostDocument, () => Task.FromResult(TextAndVersion.Create(SourceText.From("@DateTime.Now"), VersionStamp.Default))); + .WithAddedHostDocument(_hostDocument, TestMocks.CreateTextLoader("@DateTime.Now", VersionStamp.Default)); var changedWorkspaceState = ProjectWorkspaceState.Create(_someTagHelpers, LanguageVersion.CSharp8); - var (originalOutput, originalInputVersion) = await GetOutputAsync(original, _hostDocument); + var (originalOutput, originalInputVersion) = await GetOutputAsync(original, _hostDocument, DisposalToken); // Act var state = original.WithProjectWorkspaceState(changedWorkspaceState); // Assert - var (actualOutput, actualInputVersion) = await GetOutputAsync(state, _hostDocument); + var (actualOutput, actualInputVersion) = await GetOutputAsync(state, _hostDocument, DisposalToken); Assert.NotSame(originalOutput, actualOutput); Assert.NotEqual(originalInputVersion, actualInputVersion); Assert.Equal(state.ProjectWorkspaceStateVersion, actualInputVersion); @@ -216,29 +216,28 @@ public async Task ConfigurationChange_DoesNotCacheOutput() ProjectState.Create(ProjectEngineFactoryProvider, _hostProject, ProjectWorkspaceState.Default) .WithAddedHostDocument(_hostDocument, DocumentState.EmptyLoader); - var (originalOutput, originalInputVersion) = await GetOutputAsync(original, _hostDocument); + var (originalOutput, originalInputVersion) = await GetOutputAsync(original, _hostDocument, DisposalToken); // Act var state = original.WithHostProject(_hostProjectWithConfigurationChange); // Assert - var (actualOutput, actualInputVersion) = await GetOutputAsync(state, _hostDocument); + var (actualOutput, actualInputVersion) = await GetOutputAsync(state, _hostDocument, DisposalToken); Assert.NotSame(originalOutput, actualOutput); Assert.NotEqual(originalInputVersion, actualInputVersion); Assert.NotEqual(state.ProjectWorkspaceStateVersion, actualInputVersion); } - private static Task<(RazorCodeDocument, VersionStamp)> GetOutputAsync(ProjectState project, HostDocument hostDocument) + private static Task<(RazorCodeDocument, VersionStamp)> GetOutputAsync(ProjectState project, HostDocument hostDocument, CancellationToken cancellationToken) { var document = project.Documents[hostDocument.FilePath]; - return GetOutputAsync(project, document); + return GetOutputAsync(project, document, cancellationToken); } - private static Task<(RazorCodeDocument, VersionStamp)> GetOutputAsync(ProjectState project, DocumentState document) + private static Task<(RazorCodeDocument, VersionStamp)> GetOutputAsync(ProjectState project, DocumentState document, CancellationToken cancellationToken) { - var projectSnapshot = new ProjectSnapshot(project); var documentSnapshot = new DocumentSnapshot(projectSnapshot, document); - return document.GetGeneratedOutputAndVersionAsync(projectSnapshot, documentSnapshot); + return document.GetGeneratedOutputAndVersionAsync(projectSnapshot, documentSnapshot, cancellationToken); } } diff --git a/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/ProjectStateTest.cs b/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/ProjectStateTest.cs index 04036439335..2658d893683 100644 --- a/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/ProjectStateTest.cs +++ b/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/ProjectStateTest.cs @@ -22,7 +22,7 @@ public class ProjectStateTest : WorkspaceTestBase private readonly HostProject _hostProject; private readonly HostProject _hostProjectWithConfigurationChange; private readonly ProjectWorkspaceState _projectWorkspaceState; - private readonly Func> _textLoader; + private readonly TextLoader _textLoader; private readonly SourceText _text; public ProjectStateTest(ITestOutputHelper testOutput) @@ -44,7 +44,7 @@ public ProjectStateTest(ITestOutputHelper testOutput) }; _text = SourceText.From("Hello, world!"); - _textLoader = () => Task.FromResult(TextAndVersion.Create(_text, VersionStamp.Create())); + _textLoader = TestMocks.CreateTextLoader(_text, VersionStamp.Create()); } protected override void ConfigureProjectEngine(RazorProjectEngineBuilder builder) @@ -106,7 +106,7 @@ public async Task ProjectState_AddHostDocument_DocumentIsEmpty() var state = original.WithAddedHostDocument(_documents[0], DocumentState.EmptyLoader); // Assert - var text = await state.Documents[_documents[0].FilePath].GetTextAsync(); + var text = await state.Documents[_documents[0].FilePath].GetTextAsync(DisposalToken); Assert.Equal(0, text.Length); } @@ -278,7 +278,7 @@ public async Task ProjectState_WithChangedHostDocument_Loader() // Assert Assert.NotEqual(original.Version, state.Version); - var text = await state.Documents[_documents[1].FilePath].GetTextAsync(); + var text = await state.Documents[_documents[1].FilePath].GetTextAsync(DisposalToken); Assert.Same(_text, text); Assert.Equal(original.DocumentCollectionVersion, state.DocumentCollectionVersion); @@ -298,7 +298,7 @@ public async Task ProjectState_WithChangedHostDocument_Snapshot() // Assert Assert.NotEqual(original.Version, state.Version); - var text = await state.Documents[_documents[1].FilePath].GetTextAsync(); + var text = await state.Documents[_documents[1].FilePath].GetTextAsync(DisposalToken); Assert.Same(_text, text); Assert.Equal(original.DocumentCollectionVersion, state.DocumentCollectionVersion); @@ -987,7 +987,7 @@ private class TestDocumentState : DocumentState { public static TestDocumentState Create( HostDocument hostDocument, - Func>? loader = null, + TextLoader? loader = null, Action? onTextChange = null, Action? onTextLoaderChange = null, Action? onConfigurationChange = null, @@ -1012,13 +1012,13 @@ public static TestDocumentState Create( private TestDocumentState( HostDocument hostDocument, - Func>? loader, + TextLoader? loader, Action? onTextChange, Action? onTextLoaderChange, Action? onConfigurationChange, Action? onImportsChange, Action? onProjectWorkspaceStateChange) - : base(hostDocument, text: null, textVersion: null, version: 1, loader) + : base(hostDocument, version: 1, loader ?? EmptyLoader) { _onTextChange = onTextChange; _onTextLoaderChange = onTextLoaderChange; @@ -1033,7 +1033,7 @@ public override DocumentState WithText(SourceText sourceText, VersionStamp textV return base.WithText(sourceText, textVersion); } - public override DocumentState WithTextLoader(Func> loader) + public override DocumentState WithTextLoader(TextLoader loader) { _onTextLoaderChange?.Invoke(); return base.WithTextLoader(loader); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostEndpointTestBase.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostEndpointTestBase.cs index 810e9e0f7d3..fac6c663afa 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostEndpointTestBase.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostEndpointTestBase.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Basic.Reference.Assemblies; using Microsoft.AspNetCore.Razor; @@ -71,7 +72,11 @@ private protected void UpdateClientInitializationOptions(Func CreateProjectAndRazorDocumentAsync(string contents, string? fileKind = null, (string fileName, string contents)[]? additionalFiles = null, bool createSeparateRemoteAndLocalWorkspaces = false) + protected Task CreateProjectAndRazorDocumentAsync( + string contents, + string? fileKind = null, + (string fileName, string contents)[]? additionalFiles = null, + bool createSeparateRemoteAndLocalWorkspaces = false) { // Using IsLegacy means null == component, so easier for test authors var isComponent = !FileKinds.IsLegacy(fileKind); @@ -95,7 +100,14 @@ protected Task CreateProjectAndRazorDocumentAsync(string contents, // actual solution syncing set up for testing, and don't really use a service broker, but since we also would // expect to never make changes to a workspace, it should be fine to simply create duplicated solutions as part // of test setup. - return CreateLocalProjectAndRazorDocumentAsync(remoteDocument.Project.Solution, projectId, projectName, documentId, documentFilePath, contents, additionalFiles); + return CreateLocalProjectAndRazorDocumentAsync( + remoteDocument.Project.Solution, + projectId, + projectName, + documentId, + documentFilePath, + contents, + additionalFiles); } // If we're just creating one workspace, then its the remote one and we just return the remote document @@ -104,7 +116,14 @@ protected Task CreateProjectAndRazorDocumentAsync(string contents, return Task.FromResult(remoteDocument); } - private async Task CreateLocalProjectAndRazorDocumentAsync(Solution remoteSolution, ProjectId projectId, string projectName, DocumentId documentId, string documentFilePath, string contents, (string fileName, string contents)[]? additionalFiles) + private async Task CreateLocalProjectAndRazorDocumentAsync( + Solution remoteSolution, + ProjectId projectId, + string projectName, + DocumentId documentId, + string documentFilePath, + string contents, + (string fileName, string contents)[]? additionalFiles) { var exportProvider = TestComposition.Roslyn.ExportProviderFactory.CreateExportProvider(); AddDisposable(exportProvider); @@ -120,7 +139,7 @@ private async Task CreateLocalProjectAndRazorDocumentAsync(Solutio var snapshotManager = _exportProvider.AssumeNotNull().GetExportedValue(); var snapshot = snapshotManager.GetSnapshot(razorDocument); // Compile the Razor file - var codeDocument = await snapshot.GetGeneratedOutputAsync(false); + var codeDocument = await snapshot.GetGeneratedOutputAsync(forceDesignTimeGeneratedOutput: false, DisposalToken); // Update the generated doc contents var generatedDocumentIds = solution.GetDocumentIdsWithFilePath(documentFilePath + CSharpVirtualDocumentSuffix); solution = solution.WithDocumentText(generatedDocumentIds, codeDocument.GetCSharpSourceText()); 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 index 03057cb6e2b..2f4dc0c7b95 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/RazorComponentDefinitionServiceTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/RazorComponentDefinitionServiceTest.cs @@ -83,7 +83,7 @@ private async Task VerifyDefinitionAsync(TestCode input, TestCode expectedDocume var documentMappingService = OOPExportProvider.GetExportedValue(); var documentSnapshot = snapshotManager.GetSnapshot(document); - var codeDocument = await documentSnapshot.GetGeneratedOutputAsync(); + var codeDocument = await documentSnapshot.GetGeneratedOutputAsync(DisposalToken); var positionInfo = documentMappingService.GetPositionInfo(codeDocument, input.Position); var location = await service.GetDefinitionAsync( diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/DocumentGenerator/BackgroundDocumentGeneratorTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/DocumentGenerator/BackgroundDocumentGeneratorTest.cs index 34775f1e8b6..b4d91bec7e6 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/DocumentGenerator/BackgroundDocumentGeneratorTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/DocumentGenerator/BackgroundDocumentGeneratorTest.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.ProjectSystem; using Microsoft.AspNetCore.Razor.Test.Common; +using Microsoft.AspNetCore.Razor.Test.Common.ProjectSystem; using Microsoft.AspNetCore.Razor.Test.Common.VisualStudio; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Razor; @@ -186,8 +187,8 @@ await projectManager.UpdateAsync(updater => { updater.ProjectAdded(s_hostProject1); updater.ProjectAdded(s_hostProject2); - updater.DocumentAdded(s_hostProject1.Key, s_documents[0], null!); - updater.DocumentAdded(s_hostProject1.Key, s_documents[1], null!); + updater.DocumentAdded(s_hostProject1.Key, s_documents[0], s_documents[0].CreateEmptyTextLoader()); + updater.DocumentAdded(s_hostProject1.Key, s_documents[1], s_documents[1].CreateEmptyTextLoader()); }); var project = projectManager.GetLoadedProject(s_hostProject1.Key); @@ -225,8 +226,8 @@ await projectManager.UpdateAsync(updater => { updater.ProjectAdded(hostProject1); updater.ProjectAdded(hostProject2); - updater.DocumentAdded(hostProject1.Key, hostDocument1, null!); - updater.DocumentAdded(hostProject1.Key, hostDocument2, null!); + updater.DocumentAdded(hostProject1.Key, hostDocument1, hostDocument1.CreateEmptyTextLoader()); + updater.DocumentAdded(hostProject1.Key, hostDocument2, hostDocument2.CreateEmptyTextLoader()); }); var project = projectManager.GetLoadedProject(hostProject1.Key); @@ -274,7 +275,7 @@ await projectManager.UpdateAsync(updater => updater.ProjectAdded(s_hostProject1); for (var i = 0; i < documents.Length; i++) { - updater.DocumentAdded(s_hostProject1.Key, documents[i], null!); + updater.DocumentAdded(s_hostProject1.Key, documents[i], documents[i].CreateEmptyTextLoader()); } }); @@ -318,8 +319,8 @@ public async Task DocumentRemoved_ReparsesRelatedFiles() await projectManager.UpdateAsync(updater => { updater.ProjectAdded(s_hostProject1); - updater.DocumentAdded(s_hostProject1.Key, TestProjectData.SomeProjectComponentFile1, null!); - updater.DocumentAdded(s_hostProject1.Key, TestProjectData.SomeProjectImportFile, null!); + updater.DocumentAdded(s_hostProject1.Key, TestProjectData.SomeProjectComponentFile1, TestProjectData.SomeProjectComponentFile1.CreateEmptyTextLoader()); + updater.DocumentAdded(s_hostProject1.Key, TestProjectData.SomeProjectImportFile, TestProjectData.SomeProjectImportFile.CreateEmptyTextLoader()); }); using var generator = new TestBackgroundDocumentGenerator(projectManager, _dynamicFileInfoProvider, LoggerFactory) @@ -407,12 +408,12 @@ public override void Enqueue(IProjectSnapshot project, IDocumentSnapshot documen base.Enqueue(project, document); } - protected override Task ProcessDocumentAsync(IProjectSnapshot project, IDocumentSnapshot document) + protected override Task ProcessDocumentAsync(IProjectSnapshot project, IDocumentSnapshot document, CancellationToken cancellationToken) { var key = GetKey(project, document); PendingWork.Remove(key); - var task = base.ProcessDocumentAsync(project, document); + var task = base.ProcessDocumentAsync(project, document, cancellationToken); CompletedWork.Add(key); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/LanguageClient/CSharpDocumentExcerptServiceTests.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/LanguageClient/CSharpDocumentExcerptServiceTests.cs index 2b693754950..1570ae345e9 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/LanguageClient/CSharpDocumentExcerptServiceTests.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/LanguageClient/CSharpDocumentExcerptServiceTests.cs @@ -35,7 +35,7 @@ public async Task TryGetExcerptInternalAsync_SingleLine_CanClassifyCSharp() "; - var (generatedDocument, razorSourceText, primarySpan, generatedSpan) = await InitializeAsync(razorSource); + var (generatedDocument, razorSourceText, primarySpan, generatedSpan) = await InitializeAsync(razorSource, DisposalToken); #pragma warning disable CS0618 // Type or member is obsolete var excerptService = new CSharpDocumentExcerptService(); @@ -123,7 +123,7 @@ public async Task TryGetExcerptInternalAsync_SingleLine_CanClassifyCSharp_Implic "; - var (generatedDocument, razorSourceText, primarySpan, generatedSpan) = await InitializeAsync(razorSource); + var (generatedDocument, razorSourceText, primarySpan, generatedSpan) = await InitializeAsync(razorSource, DisposalToken); #pragma warning disable CS0618 // Type or member is obsolete var excerptService = new CSharpDocumentExcerptService(); @@ -169,7 +169,7 @@ public async Task TryGetExcerptInternalAsync_SingleLine_CanClassifyCSharp_Comple "; - var (generatedDocument, razorSourceText, primarySpan, generatedSpan) = await InitializeAsync(razorSource); + var (generatedDocument, razorSourceText, primarySpan, generatedSpan) = await InitializeAsync(razorSource, DisposalToken); #pragma warning disable CS0618 // Type or member is obsolete var excerptService = new CSharpDocumentExcerptService(); @@ -216,7 +216,7 @@ public async Task TryGetExcerptInternalAsync_MultiLine_CanClassifyCSharp() "; - var (generatedDocument, razorSourceText, primarySpan, generatedSpan) = await InitializeAsync(razorSource); + var (generatedDocument, razorSourceText, primarySpan, generatedSpan) = await InitializeAsync(razorSource, DisposalToken); #pragma warning disable CS0618 // Type or member is obsolete var excerptService = new CSharpDocumentExcerptService(); @@ -262,7 +262,7 @@ public async Task TryGetExcerptInternalAsync_MultiLine_Boundaries_CanClassifyCSh // Arrange var razorSource = @"@{ var [|foo|] = ""Hello, World!""; }"; - var (generatedDocument, razorSourceText, primarySpan, generatedSpan) = await InitializeAsync(razorSource); + var (generatedDocument, razorSourceText, primarySpan, generatedSpan) = await InitializeAsync(razorSource, DisposalToken); #pragma warning disable CS0618 // Type or member is obsolete var excerptService = new CSharpDocumentExcerptService(); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/LanguageClient/DynamicFiles/RazorDocumentExcerptServiceTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/LanguageClient/DynamicFiles/RazorDocumentExcerptServiceTest.cs index d9565002185..6a104f675a2 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/LanguageClient/DynamicFiles/RazorDocumentExcerptServiceTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/LanguageClient/DynamicFiles/RazorDocumentExcerptServiceTest.cs @@ -28,7 +28,7 @@ public async Task TryGetExcerptInternalAsync_SingleLine_CanClassifyCSharp() "; - var (primary, secondary, secondarySpan) = await InitializeWithSnapshotAsync(razorSource); + var (primary, secondary, secondarySpan) = await InitializeWithSnapshotAsync(razorSource, DisposalToken); var service = CreateExcerptService(primary); @@ -106,7 +106,7 @@ public async Task TryGetExcerptInternalAsync_SingleLine_CanClassifyCSharp_Implic "; - var (primary, secondary, secondarySpan) = await InitializeWithSnapshotAsync(razorSource); + var (primary, secondary, secondarySpan) = await InitializeWithSnapshotAsync(razorSource, DisposalToken); var service = CreateExcerptService(primary); @@ -159,7 +159,7 @@ public async Task TryGetExcerptInternalAsync_SingleLine_CanClassifyCSharp_Comple "; - var (primary, secondary, secondarySpan) = await InitializeWithSnapshotAsync(razorSource); + var (primary, secondary, secondarySpan) = await InitializeWithSnapshotAsync(razorSource, DisposalToken); var service = CreateExcerptService(primary); @@ -266,7 +266,7 @@ than that. """; - var (primary, secondary, secondarySpan) = await InitializeWithSnapshotAsync(razorSource); + var (primary, secondary, secondarySpan) = await InitializeWithSnapshotAsync(razorSource, DisposalToken); var service = CreateExcerptService(primary); @@ -370,7 +370,7 @@ This is a """; - var (primary, secondary, secondarySpan) = await InitializeWithSnapshotAsync(razorSource); + var (primary, secondary, secondarySpan) = await InitializeWithSnapshotAsync(razorSource, DisposalToken); var service = CreateExcerptService(primary); @@ -444,7 +444,7 @@ public async Task TryGetExcerptInternalAsync_MultiLine_CanClassifyCSharp() "; - var (primary, secondary, secondarySpan) = await InitializeWithSnapshotAsync(razorSource); + var (primary, secondary, secondarySpan) = await InitializeWithSnapshotAsync(razorSource, DisposalToken); var service = CreateExcerptService(primary); @@ -553,7 +553,7 @@ public async Task TryGetExcerptInternalAsync_MultiLine_Boundaries_CanClassifyCSh // Arrange var razorSource = @"@{ var [|foo|] = ""Hello, World!""; }"; - var (primary, secondary, secondarySpan) = await InitializeWithSnapshotAsync(razorSource); + var (primary, secondary, secondarySpan) = await InitializeWithSnapshotAsync(razorSource, DisposalToken); var service = CreateExcerptService(primary); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/LanguageClient/DynamicFiles/RazorSpanMappingServiceTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/LanguageClient/DynamicFiles/RazorSpanMappingServiceTest.cs index 40c162f169f..b793ecc1080 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/LanguageClient/DynamicFiles/RazorSpanMappingServiceTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/LanguageClient/DynamicFiles/RazorSpanMappingServiceTest.cs @@ -29,22 +29,21 @@ public async Task TryGetMappedSpans_SpanMatchesSourceMapping_ReturnsTrue() @SomeProperty "); - var project = new ProjectSnapshot( - ProjectState.Create(ProjectEngineFactoryProvider, _hostProject, ProjectWorkspaceState.Default) - .WithAddedHostDocument(_hostDocument, () => Task.FromResult(TextAndVersion.Create(sourceText, VersionStamp.Create())))); + var project = new ProjectSnapshot(ProjectState + .Create(ProjectEngineFactoryProvider, _hostProject, ProjectWorkspaceState.Default) + .WithAddedHostDocument(_hostDocument, TestMocks.CreateTextLoader(sourceText, VersionStamp.Create()))); var document = project.GetDocument(_hostDocument.FilePath); Assert.NotNull(document); - var service = new RazorSpanMappingService(document); - var output = await document.GetGeneratedOutputAsync(); + var output = await document.GetGeneratedOutputAsync(DisposalToken); var generated = output.GetCSharpDocument(); var symbol = "SomeProperty"; var span = new TextSpan(generated.GeneratedCode.IndexOf(symbol, StringComparison.Ordinal), symbol.Length); // Act - var result = RazorSpanMappingService.TryGetMappedSpans(span, await document.GetTextAsync(), generated, out var mappedLinePositionSpan, out var mappedSpan); + var result = RazorSpanMappingService.TryGetMappedSpans(span, await document.GetTextAsync(DisposalToken), generated, out var mappedLinePositionSpan, out var mappedSpan); // Assert Assert.True(result); @@ -62,15 +61,14 @@ public async Task TryGetMappedSpans_SpanMatchesSourceMappingAndPosition_ReturnsT @SomeProperty "); - var project = new ProjectSnapshot( - ProjectState.Create(ProjectEngineFactoryProvider, _hostProject, ProjectWorkspaceState.Default) - .WithAddedHostDocument(_hostDocument, () => Task.FromResult(TextAndVersion.Create(sourceText, VersionStamp.Create())))); + var project = new ProjectSnapshot(ProjectState + .Create(ProjectEngineFactoryProvider, _hostProject, ProjectWorkspaceState.Default) + .WithAddedHostDocument(_hostDocument, TestMocks.CreateTextLoader(sourceText, VersionStamp.Create()))); var document = project.GetDocument(_hostDocument.FilePath); Assert.NotNull(document); - var service = new RazorSpanMappingService(document); - var output = await document.GetGeneratedOutputAsync(); + var output = await document.GetGeneratedOutputAsync(DisposalToken); var generated = output.GetCSharpDocument(); var symbol = "SomeProperty"; @@ -78,7 +76,7 @@ public async Task TryGetMappedSpans_SpanMatchesSourceMappingAndPosition_ReturnsT var span = new TextSpan(generated.GeneratedCode.IndexOf(symbol, generated.GeneratedCode.IndexOf(symbol, StringComparison.Ordinal) + symbol.Length, StringComparison.Ordinal), symbol.Length); // Act - var result = RazorSpanMappingService.TryGetMappedSpans(span, await document.GetTextAsync(), generated, out var mappedLinePositionSpan, out var mappedSpan); + var result = RazorSpanMappingService.TryGetMappedSpans(span, await document.GetTextAsync(DisposalToken), generated, out var mappedLinePositionSpan, out var mappedSpan); // Assert Assert.True(result); @@ -96,22 +94,21 @@ public async Task TryGetMappedSpans_SpanWithinSourceMapping_ReturnsTrue() } "); - var project = new ProjectSnapshot( - ProjectState.Create(ProjectEngineFactoryProvider, _hostProject, ProjectWorkspaceState.Default) - .WithAddedHostDocument(_hostDocument, () => Task.FromResult(TextAndVersion.Create(sourceText, VersionStamp.Create())))); + var project = new ProjectSnapshot(ProjectState + .Create(ProjectEngineFactoryProvider, _hostProject, ProjectWorkspaceState.Default) + .WithAddedHostDocument(_hostDocument, TestMocks.CreateTextLoader(sourceText, VersionStamp.Create()))); var document = project.GetDocument(_hostDocument.FilePath); Assert.NotNull(document); - var service = new RazorSpanMappingService(document); - var output = await document.GetGeneratedOutputAsync(); + var output = await document.GetGeneratedOutputAsync(DisposalToken); var generated = output.GetCSharpDocument(); var symbol = "SomeProperty"; var span = new TextSpan(generated.GeneratedCode.IndexOf(symbol, StringComparison.Ordinal), symbol.Length); // Act - var result = RazorSpanMappingService.TryGetMappedSpans(span, await document.GetTextAsync(), generated, out var mappedLinePositionSpan, out var mappedSpan); + var result = RazorSpanMappingService.TryGetMappedSpans(span, await document.GetTextAsync(DisposalToken), generated, out var mappedLinePositionSpan, out var mappedSpan); // Assert Assert.True(result); @@ -129,22 +126,21 @@ public async Task TryGetMappedSpans_SpanOutsideSourceMapping_ReturnsFalse() } "); - var project = new ProjectSnapshot( - ProjectState.Create(ProjectEngineFactoryProvider, _hostProject, ProjectWorkspaceState.Default) - .WithAddedHostDocument(_hostDocument, () => Task.FromResult(TextAndVersion.Create(sourceText, VersionStamp.Create())))); + var project = new ProjectSnapshot(ProjectState + .Create(ProjectEngineFactoryProvider, _hostProject, ProjectWorkspaceState.Default) + .WithAddedHostDocument(_hostDocument, TestMocks.CreateTextLoader(sourceText, VersionStamp.Create()))); var document = project.GetDocument(_hostDocument.FilePath); Assert.NotNull(document); - var service = new RazorSpanMappingService(document); - var output = await document.GetGeneratedOutputAsync(); + var output = await document.GetGeneratedOutputAsync(DisposalToken); var generated = output.GetCSharpDocument(); var symbol = "ExecuteAsync"; var span = new TextSpan(generated.GeneratedCode.IndexOf(symbol, StringComparison.Ordinal), symbol.Length); // Act - var result = RazorSpanMappingService.TryGetMappedSpans(span, await document.GetTextAsync(), generated, out var mappedLinePositionSpan, out var mappedSpan); + var result = RazorSpanMappingService.TryGetMappedSpans(span, await document.GetTextAsync(DisposalToken), generated, out _, out _); // Assert Assert.False(result); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/LanguageClient/RazorDocumentOptionsServiceTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/LanguageClient/RazorDocumentOptionsServiceTest.cs index 7962574d9e0..9e6c078dd94 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/LanguageClient/RazorDocumentOptionsServiceTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/LanguageClient/RazorDocumentOptionsServiceTest.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.ProjectSystem; +using Microsoft.AspNetCore.Razor.Test.Common; using Microsoft.AspNetCore.Razor.Test.Common.Workspaces; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Formatting; @@ -95,11 +96,12 @@ private Document InitializeDocument(SourceText sourceText) var hostDocument = new HostDocument( Path.Combine(baseDirectory, "SomeProject", "File1.cshtml"), "File1.cshtml", FileKinds.Legacy); - var project = new ProjectSnapshot( - ProjectState.Create(ProjectEngineFactoryProvider, hostProject, ProjectWorkspaceState.Default) - .WithAddedHostDocument(hostDocument, () => Task.FromResult(TextAndVersion.Create(sourceText, VersionStamp.Create())))); + var project = new ProjectSnapshot(ProjectState + .Create(ProjectEngineFactoryProvider, hostProject, ProjectWorkspaceState.Default) + .WithAddedHostDocument(hostDocument, TestMocks.CreateTextLoader(sourceText, VersionStamp.Create()))); var documentSnapshot = project.GetDocument(hostDocument.FilePath); + Assert.NotNull(documentSnapshot); var solution = Workspace.CurrentSolution.AddProject(ProjectInfo.Create( ProjectId.CreateNewId(Path.GetFileNameWithoutExtension(hostDocument.FilePath)), diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/ProjectSnapshotManagerTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/ProjectSnapshotManagerTest.cs index 35d73679c4d..6f127b659dc 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/ProjectSnapshotManagerTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/ProjectSnapshotManagerTest.cs @@ -97,7 +97,7 @@ await _projectManager.UpdateAsync(updater => // Act await _projectManager.UpdateAsync(updater => { - updater.DocumentAdded(s_hostProject.Key, s_documents[0], null!); + updater.DocumentAdded(s_hostProject.Key, s_documents[0], s_documents[0].CreateEmptyTextLoader()); }); // Assert @@ -123,7 +123,7 @@ await _projectManager.UpdateAsync(updater => // Act await _projectManager.UpdateAsync(updater => { - updater.DocumentAdded(s_hostProject.Key, s_documents[0], null!); + updater.DocumentAdded(s_hostProject.Key, s_documents[0], s_documents[0].CreateEmptyTextLoader()); }); // Assert @@ -151,7 +151,7 @@ await _projectManager.UpdateAsync(updater => // Act await _projectManager.UpdateAsync(updater => { - updater.DocumentAdded(s_hostProject.Key, s_documents[3], null!); + updater.DocumentAdded(s_hostProject.Key, s_documents[3], s_documents[3].CreateEmptyTextLoader()); }); // Assert @@ -172,7 +172,7 @@ public async Task DocumentAdded_IgnoresDuplicate() await _projectManager.UpdateAsync(updater => { updater.ProjectAdded(s_hostProject); - updater.DocumentAdded(s_hostProject.Key, s_documents[0], null!); + updater.DocumentAdded(s_hostProject.Key, s_documents[0], s_documents[0].CreateEmptyTextLoader()); }); using var listener = _projectManager.ListenToNotifications(); @@ -180,7 +180,7 @@ await _projectManager.UpdateAsync(updater => // Act await _projectManager.UpdateAsync(updater => { - updater.DocumentAdded(s_hostProject.Key, s_documents[0], null!); + updater.DocumentAdded(s_hostProject.Key, s_documents[0], s_documents[0].CreateEmptyTextLoader()); }); // Assert @@ -210,7 +210,7 @@ await _projectManager.UpdateAsync(updater => } [UIFact] - public async Task DocumentAdded_NullLoader_HasEmptyText() + public async Task DocumentAdded_EmptyLoader_HasEmptyText() { // Arrange await _projectManager.UpdateAsync(updater => @@ -221,7 +221,7 @@ await _projectManager.UpdateAsync(updater => // Act await _projectManager.UpdateAsync(updater => { - updater.DocumentAdded(s_hostProject.Key, s_documents[0], null!); + updater.DocumentAdded(s_hostProject.Key, s_documents[0], s_documents[0].CreateEmptyTextLoader()); }); // Assert @@ -230,12 +230,12 @@ await _projectManager.UpdateAsync(updater => var document = project.GetDocument(documentFilePath); Assert.NotNull(document); - var text = await document.GetTextAsync(); + var text = await document.GetTextAsync(DisposalToken); Assert.Equal(0, text.Length); } [UIFact] - public async Task DocumentAdded_WithLoader_LoadesText() + public async Task DocumentAdded_WithLoader_LoadsText() { // Arrange await _projectManager.UpdateAsync(updater => @@ -257,7 +257,7 @@ await _projectManager.UpdateAsync(updater => var document = project.GetDocument(documentFilePath); Assert.NotNull(document); - var actual = await document.GetTextAsync(); + var actual = await document.GetTextAsync(DisposalToken); Assert.Same(expected, actual); } @@ -276,7 +276,7 @@ await _projectManager.UpdateAsync(updater => // Act await _projectManager.UpdateAsync(updater => { - updater.DocumentAdded(s_hostProject.Key, s_documents[0], null!); + updater.DocumentAdded(s_hostProject.Key, s_documents[0], s_documents[0].CreateEmptyTextLoader()); }); // Assert @@ -304,7 +304,7 @@ await _projectManager.UpdateAsync(updater => // Act await _projectManager.UpdateAsync(updater => { - updater.DocumentAdded(s_hostProject.Key, s_documents[0], null!); + updater.DocumentAdded(s_hostProject.Key, s_documents[0], s_documents[0].CreateEmptyTextLoader()); }); // Assert @@ -319,9 +319,9 @@ public async Task DocumentRemoved_RemovesDocument() await _projectManager.UpdateAsync(updater => { updater.ProjectAdded(s_hostProject); - updater.DocumentAdded(s_hostProject.Key, s_documents[0], null!); - updater.DocumentAdded(s_hostProject.Key, s_documents[1], null!); - updater.DocumentAdded(s_hostProject.Key, s_documents[2], null!); + updater.DocumentAdded(s_hostProject.Key, s_documents[0], s_documents[0].CreateEmptyTextLoader()); + updater.DocumentAdded(s_hostProject.Key, s_documents[1], s_documents[1].CreateEmptyTextLoader()); + updater.DocumentAdded(s_hostProject.Key, s_documents[2], s_documents[2].CreateEmptyTextLoader()); }); using var listener = _projectManager.ListenToNotifications(); @@ -391,9 +391,9 @@ await _projectManager.UpdateAsync(updater => { updater.ProjectAdded(s_hostProject); updater.ProjectWorkspaceStateChanged(s_hostProject.Key, _projectWorkspaceStateWithTagHelpers); - updater.DocumentAdded(s_hostProject.Key, s_documents[0], null!); - updater.DocumentAdded(s_hostProject.Key, s_documents[1], null!); - updater.DocumentAdded(s_hostProject.Key, s_documents[2], null!); + updater.DocumentAdded(s_hostProject.Key, s_documents[0], s_documents[0].CreateEmptyTextLoader()); + updater.DocumentAdded(s_hostProject.Key, s_documents[1], s_documents[1].CreateEmptyTextLoader()); + updater.DocumentAdded(s_hostProject.Key, s_documents[2], s_documents[2].CreateEmptyTextLoader()); }); var originalTagHelpers = await _projectManager.GetLoadedProject(s_hostProject.Key).GetTagHelpersAsync(DisposalToken); @@ -421,9 +421,9 @@ public async Task DocumentRemoved_CachesProjectEngine() await _projectManager.UpdateAsync(updater => { updater.ProjectAdded(s_hostProject); - updater.DocumentAdded(s_hostProject.Key, s_documents[0], null!); - updater.DocumentAdded(s_hostProject.Key, s_documents[1], null!); - updater.DocumentAdded(s_hostProject.Key, s_documents[2], null!); + updater.DocumentAdded(s_hostProject.Key, s_documents[0], s_documents[0].CreateEmptyTextLoader()); + updater.DocumentAdded(s_hostProject.Key, s_documents[1], s_documents[1].CreateEmptyTextLoader()); + updater.DocumentAdded(s_hostProject.Key, s_documents[2], s_documents[2].CreateEmptyTextLoader()); }); var project = _projectManager.GetLoadedProject(s_hostProject.Key); @@ -447,7 +447,7 @@ public async Task DocumentOpened_UpdatesDocument() await _projectManager.UpdateAsync(updater => { updater.ProjectAdded(s_hostProject); - updater.DocumentAdded(s_hostProject.Key, s_documents[0], null!); + updater.DocumentAdded(s_hostProject.Key, s_documents[0], s_documents[0].CreateEmptyTextLoader()); }); using var listener = _projectManager.ListenToNotifications(); @@ -465,7 +465,7 @@ await _projectManager.UpdateAsync(updater => var project = _projectManager.GetLoadedProject(s_hostProject.Key); var document = project.GetDocument(s_documents[0].FilePath); Assert.NotNull(document); - var text = await document.GetTextAsync(); + var text = await document.GetTextAsync(DisposalToken); Assert.Same(_sourceText, text); Assert.True(_projectManager.IsDocumentOpen(s_documents[0].FilePath)); @@ -480,7 +480,7 @@ public async Task DocumentClosed_UpdatesDocument() await _projectManager.UpdateAsync(updater => { updater.ProjectAdded(s_hostProject); - updater.DocumentAdded(s_hostProject.Key, s_documents[0], null!); + updater.DocumentAdded(s_hostProject.Key, s_documents[0], s_documents[0].CreateEmptyTextLoader()); updater.DocumentOpened(s_hostProject.Key, s_documents[0].FilePath, _sourceText); }); @@ -504,7 +504,7 @@ await _projectManager.UpdateAsync(updater => var project = _projectManager.GetLoadedProject(s_hostProject.Key); var document = project.GetDocument(s_documents[0].FilePath); Assert.NotNull(document); - var text = await document.GetTextAsync(); + var text = await document.GetTextAsync(DisposalToken); Assert.Same(expected, text); Assert.False(_projectManager.IsDocumentOpen(s_documents[0].FilePath)); Assert.Equal(3, document.Version); @@ -517,7 +517,7 @@ public async Task DocumentClosed_AcceptsChange() await _projectManager.UpdateAsync(updater => { updater.ProjectAdded(s_hostProject); - updater.DocumentAdded(s_hostProject.Key, s_documents[0], null!); + updater.DocumentAdded(s_hostProject.Key, s_documents[0], s_documents[0].CreateEmptyTextLoader()); }); using var listener = _projectManager.ListenToNotifications(); @@ -538,7 +538,7 @@ await _projectManager.UpdateAsync(updater => var project = _projectManager.GetLoadedProject(s_hostProject.Key); var document = project.GetDocument(s_documents[0].FilePath); Assert.NotNull(document); - var text = await document.GetTextAsync(); + var text = await document.GetTextAsync(DisposalToken); Assert.Same(expected, text); } @@ -549,7 +549,7 @@ public async Task DocumentChanged_Snapshot_UpdatesDocument() await _projectManager.UpdateAsync(updater => { updater.ProjectAdded(s_hostProject); - updater.DocumentAdded(s_hostProject.Key, s_documents[0], null!); + updater.DocumentAdded(s_hostProject.Key, s_documents[0], s_documents[0].CreateEmptyTextLoader()); updater.DocumentOpened(s_hostProject.Key, s_documents[0].FilePath, _sourceText); }); @@ -570,7 +570,7 @@ await _projectManager.UpdateAsync(updater => var project = _projectManager.GetLoadedProject(s_hostProject.Key); var document = project.GetDocument(s_documents[0].FilePath); Assert.NotNull(document); - var text = await document.GetTextAsync(); + var text = await document.GetTextAsync(DisposalToken); Assert.Same(expected, text); Assert.Equal(3, document.Version); } @@ -582,7 +582,7 @@ public async Task DocumentChanged_Loader_UpdatesDocument() await _projectManager.UpdateAsync(updater => { updater.ProjectAdded(s_hostProject); - updater.DocumentAdded(s_hostProject.Key, s_documents[0], null!); + updater.DocumentAdded(s_hostProject.Key, s_documents[0], s_documents[0].CreateEmptyTextLoader()); updater.DocumentOpened(s_hostProject.Key, s_documents[0].FilePath, _sourceText); }); @@ -604,7 +604,7 @@ await _projectManager.UpdateAsync(updater => var project = _projectManager.GetLoadedProject(s_hostProject.Key); var document = project.GetDocument(s_documents[0].FilePath); Assert.NotNull(document); - var text = await document.GetTextAsync(); + var text = await document.GetTextAsync(DisposalToken); Assert.Same(expected, text); Assert.Equal(3, document.Version); } @@ -761,7 +761,7 @@ public async Task ProjectWorkspaceStateChanged_UpdateDocuments() await _projectManager.UpdateAsync(updater => { updater.ProjectAdded(s_hostProject); - updater.DocumentAdded(s_hostProject.Key, s_documents[0], null!); + updater.DocumentAdded(s_hostProject.Key, s_documents[0], s_documents[0].CreateEmptyTextLoader()); }); // Act @@ -854,7 +854,7 @@ await _projectManager.UpdateAsync(updater => // Act await _projectManager.UpdateAsync(updater => { - updater.DocumentAdded(s_hostProject.Key, s_documents[0], null!); + updater.DocumentAdded(s_hostProject.Key, s_documents[0], s_documents[0].CreateEmptyTextLoader()); }); // Assert