From a782f9e8e3fd31678e13c85ea6b6ce3bea40a9cd Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 11 Sep 2024 16:50:50 +1000 Subject: [PATCH 01/14] Create basic service and endpoints to gather diagnostics and pass them over to OOP Wanted to validate my Roslyn code would work first, before moving our services around, in case the order of this work seems backwards to usual :) --- eng/targets/Services.props | 1 + .../RazorLanguageServer.cs | 4 +- .../Remote/IRemoteDiagnosticsService.cs | 20 ++ .../Remote/RazorServices.cs | 1 + .../Diagnostics/RemoteDiagnosticsService.cs | 43 ++++ .../CohostDocumentPullDiagnosticsEndpoint.cs | 205 ++++++++++++++++++ 6 files changed, 273 insertions(+), 1 deletion(-) create mode 100644 src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteDiagnosticsService.cs create mode 100644 src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Diagnostics/RemoteDiagnosticsService.cs create mode 100644 src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostDocumentPullDiagnosticsEndpoint.cs diff --git a/eng/targets/Services.props b/eng/targets/Services.props index fc2acc27dfa..0501df0e116 100644 --- a/eng/targets/Services.props +++ b/eng/targets/Services.props @@ -32,5 +32,6 @@ + diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs index be86fe01409..70d4fcd1351 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.cs @@ -131,7 +131,6 @@ protected override ILspServices ConstructLspServices() services.AddLifeCycleServices(this, _clientConnection, _lspServerActivationTracker); - services.AddDiagnosticServices(); services.AddSemanticTokensServices(featureOptions); services.AddDocumentManagementServices(featureOptions); services.AddCompletionServices(); @@ -143,6 +142,9 @@ protected override ILspServices ConstructLspServices() if (!featureOptions.UseRazorCohostServer) { + // Diagnostics + services.AddDiagnosticServices(); + // Auto insert services.AddSingleton(); services.AddSingleton(); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteDiagnosticsService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteDiagnosticsService.cs new file mode 100644 index 00000000000..65d6cc876c6 --- /dev/null +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteDiagnosticsService.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// 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.CodeAnalysis.ExternalAccess.Razor; +using RoslynLspDiagnostic = Roslyn.LanguageServer.Protocol.Diagnostic; + +namespace Microsoft.CodeAnalysis.Razor.Remote; + +internal interface IRemoteDiagnosticsService : IRemoteJsonService +{ + ValueTask> GetDiagnosticsAsync( + JsonSerializableRazorPinnedSolutionInfoWrapper solutionInfo, + JsonSerializableDocumentId documentId, + ImmutableArray csharpDiagnostics, + ImmutableArray htmlDiagnostics, + CancellationToken cancellationToken); +} diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/RazorServices.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/RazorServices.cs index 4ede4292572..2e4257cdb3c 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/RazorServices.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/RazorServices.cs @@ -35,6 +35,7 @@ internal static class RazorServices (typeof(IRemoteDocumentSymbolService), null), (typeof(IRemoteRenameService), null), (typeof(IRemoteGoToImplementationService), null), + (typeof(IRemoteDiagnosticsService), null), ]; private const string ComponentName = "Razor"; diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Diagnostics/RemoteDiagnosticsService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Diagnostics/RemoteDiagnosticsService.cs new file mode 100644 index 00000000000..cb95569cd29 --- /dev/null +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Diagnostics/RemoteDiagnosticsService.cs @@ -0,0 +1,43 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// 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.CodeAnalysis.ExternalAccess.Razor; +using Microsoft.CodeAnalysis.Razor.Remote; +using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; +using LspDiagnostic = Roslyn.LanguageServer.Protocol.Diagnostic; + +namespace Microsoft.CodeAnalysis.Remote.Razor; + +internal sealed class RemoteDiagnosticsService(in ServiceArgs args) : RazorDocumentServiceBase(in args), IRemoteDiagnosticsService +{ + internal sealed class Factory : FactoryBase + { + protected override IRemoteDiagnosticsService CreateService(in ServiceArgs args) + => new RemoteDiagnosticsService(in args); + } + + public ValueTask> GetDiagnosticsAsync( + JsonSerializableRazorPinnedSolutionInfoWrapper solutionInfo, + JsonSerializableDocumentId documentId, + ImmutableArray csharpDiagnostics, + ImmutableArray htmlDiagnostics, + CancellationToken cancellationToken) + => RunServiceAsync( + solutionInfo, + documentId, + context => GetDiagnosticsAsync(context, csharpDiagnostics, htmlDiagnostics, cancellationToken), + cancellationToken); + + private async ValueTask> GetDiagnosticsAsync( + RemoteDocumentContext context, + ImmutableArray csharpDiagnostics, + ImmutableArray htmlDiagnostics, + CancellationToken cancellationToken) + { + // TODO: More work! + return htmlDiagnostics; + } +} diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostDocumentPullDiagnosticsEndpoint.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostDocumentPullDiagnosticsEndpoint.cs new file mode 100644 index 00000000000..37493d18731 --- /dev/null +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostDocumentPullDiagnosticsEndpoint.cs @@ -0,0 +1,205 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor; +using Microsoft.AspNetCore.Razor.PooledObjects; +using Microsoft.AspNetCore.Razor.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.ExternalAccess.Razor; +using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; +using Microsoft.CodeAnalysis.Razor.Logging; +using Microsoft.CodeAnalysis.Razor.ProjectSystem; +using Microsoft.CodeAnalysis.Razor.Remote; +using Microsoft.CodeAnalysis.Razor.Workspaces; +using Microsoft.VisualStudio.LanguageServer.ContainedLanguage; +using Microsoft.VisualStudio.LanguageServer.Protocol; +using ExternalHandlers = Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers; +using RoslynLspDiagnostic = Roslyn.LanguageServer.Protocol.Diagnostic; +using RoslynVSInternalDiagnosticReport = Roslyn.LanguageServer.Protocol.VSInternalDiagnosticReport; + +namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost; + +#pragma warning disable RS0030 // Do not use banned APIs +[Shared] +[CohostEndpoint(VSInternalMethods.DocumentPullDiagnosticName)] +[Export(typeof(IDynamicRegistrationProvider))] +[ExportCohostStatelessLspService(typeof(CohostDocumentPullDiagnosticsEndpoint))] +[method: ImportingConstructor] +#pragma warning restore RS0030 // Do not use banned APIs +internal class CohostDocumentPullDiagnosticsEndpoint( + IRemoteServiceInvoker remoteServiceInvoker, + IHtmlDocumentSynchronizer htmlDocumentSynchronizer, + LSPRequestInvoker requestInvoker, + IFilePathService filePathService, + ILoggerFactory loggerFactory) + : AbstractRazorCohostDocumentRequestHandler, IDynamicRegistrationProvider +{ + private readonly IRemoteServiceInvoker _remoteServiceInvoker = remoteServiceInvoker; + private readonly IHtmlDocumentSynchronizer _htmlDocumentSynchronizer = htmlDocumentSynchronizer; + private readonly LSPRequestInvoker _requestInvoker = requestInvoker; + private readonly IFilePathService _filePathService = filePathService; + private readonly ILogger _logger = loggerFactory.GetOrCreateLogger(); + + protected override bool MutatesSolutionState => false; + + protected override bool RequiresLSPSolution => true; + + public Registration? GetRegistration(VSInternalClientCapabilities clientCapabilities, DocumentFilter[] filter, RazorCohostRequestContext requestContext) + { + // TODO: if (clientCapabilities.TextDocument?.Diagnostic?.DynamicRegistration is true) + { + return new Registration() + { + Method = VSInternalMethods.DocumentPullDiagnosticName, + RegisterOptions = new VSInternalDiagnosticRegistrationOptions() + { + DocumentSelector = filter, + DiagnosticKinds = [VSInternalDiagnosticKind.Syntax] + } + }; + } + + // return null; + } + + protected override RazorTextDocumentIdentifier? GetRazorTextDocumentIdentifier(VSInternalDocumentDiagnosticsParams request) + => request.TextDocument?.ToRazorTextDocumentIdentifier(); + + protected override Task HandleRequestAsync(VSInternalDocumentDiagnosticsParams request, RazorCohostRequestContext context, CancellationToken cancellationToken) + => HandleRequestAsync(context.TextDocument.AssumeNotNull(), cancellationToken); + + private async Task HandleRequestAsync(TextDocument razorDocument, CancellationToken cancellationToken) + { + // Diagnostics is a little different, because Roslyn is not designed to run diagnostics in OOP. Their system will transition to OOP + // as it needs, but we have to start here in devenv. This is not as big a problem as it sounds, specifically for diagnostics, because + // we only need to tell Roslyn the document we need diagnostics for. If we had to map positions or ranges etc. it would be worse + // because we'd have to transition to our OOP to find out that info, then back here to get the diagnostics, then back to OOP to process. + _logger.LogDebug($"Getting diagnostics for {razorDocument.FilePath}"); + + var csharpTask = GetCSharpDiagnosticsAsync(razorDocument, cancellationToken); + var htmlTask = GetHtmlDiagnosticsAsync(razorDocument, cancellationToken); + + try + { + await Task.WhenAll(htmlTask, csharpTask).ConfigureAwait(false); + } + catch (Exception e) + { + if (e is not OperationCanceledException) + { + _logger.LogError(e, $"Exception thrown in PullDiagnostic delegation"); + } + // Return null if any of the tasks getting diagnostics results in an error + return null; + } + + var csharpDiagnostics = await csharpTask.ConfigureAwait(false); + var htmlDiagnostics = await htmlTask.ConfigureAwait(false); + + _logger.LogDebug($"Calling OOP with the {csharpDiagnostics.Length} C# and {htmlDiagnostics.Length} Html diagnostics"); + var diagnostics = await _remoteServiceInvoker.TryInvokeAsync>( + razorDocument.Project.Solution, + (service, solutionInfo, cancellationToken) => service.GetDiagnosticsAsync(solutionInfo, razorDocument.Id, csharpDiagnostics, htmlDiagnostics, cancellationToken), + cancellationToken).ConfigureAwait(false); + + if (diagnostics.IsDefaultOrEmpty) + { + return null; + } + + return + [ + new() + { + Diagnostics = diagnostics.ToArray(), + ResultId = Guid.NewGuid().ToString() + } + ]; + } + + private Task> GetCSharpDiagnosticsAsync(TextDocument razorDocument, CancellationToken cancellationToken) + { + // TODO: This code will not work when the source generator is hooked up. + // How do we get the source generated C# document without OOP? Can we reverse engineer a file path? + var projectKey = razorDocument.Project.ToProjectKey(); + var csharpFilePath = _filePathService.GetRazorCSharpFilePath(projectKey, razorDocument.FilePath.AssumeNotNull()); + // We put the project Id in the generated document path, so there can only be one document + if (razorDocument.Project.Solution.GetDocumentIdsWithFilePath(csharpFilePath) is not [{ } generatedDocumentId] || + razorDocument.Project.GetDocument(generatedDocumentId) is not { } generatedDocument) + { + return SpecializedTasks.EmptyImmutableArray(); + } + + _logger.LogDebug($"Getting C# diagnostics for {generatedDocument.FilePath}"); + return ExternalHandlers.Diagnostics.GetDocumentDiagnosticsAsync(generatedDocument, supportsVisualStudioExtensions: true, cancellationToken); + } + + private async Task> GetHtmlDiagnosticsAsync(TextDocument razorDocument, CancellationToken cancellationToken) + { + var htmlDocument = await _htmlDocumentSynchronizer.TryGetSynchronizedHtmlDocumentAsync(razorDocument, cancellationToken).ConfigureAwait(false); + if (htmlDocument is null) + { + return []; + } + + var diagnosticsParams = new VSInternalDocumentDiagnosticsParams + { + TextDocument = new TextDocumentIdentifier { Uri = htmlDocument.Uri } + }; + + _logger.LogDebug($"Getting Html diagnostics for {htmlDocument.Uri}"); + + var result = await _requestInvoker.ReinvokeRequestOnServerAsync( + htmlDocument.Buffer, + VSInternalMethods.DocumentPullDiagnosticName, + RazorLSPConstants.HtmlLanguageServerName, + diagnosticsParams, + cancellationToken).ConfigureAwait(false); + + if (result?.Response is null) + { + return []; + } + + // This is, to say the least, not ideal. In future we're going to normalize on to Roslyn LSP types, and this can go. + var options = new JsonSerializerOptions(); + foreach (var converter in RazorServiceDescriptorsWrapper.GetLspConverters()) + { + options.Converters.Add(converter); + } + + var hmlDiagnostics = JsonSerializer.Deserialize(JsonSerializer.SerializeToDocument(result.Response), options); + if (hmlDiagnostics is not { } convertedHtmlDiagnostics) + { + return []; + } + + using var allDiagnostics = new PooledArrayBuilder(); + foreach (var report in convertedHtmlDiagnostics) + { + if (report.Diagnostics is not null) + { + allDiagnostics.AddRange(report.Diagnostics); + } + } + + return allDiagnostics.ToImmutable(); + } + + internal TestAccessor GetTestAccessor() => new(this); + + internal readonly struct TestAccessor(CohostDocumentPullDiagnosticsEndpoint instance) + { + public Task HandleRequestAsync(TextDocument razorDocument, CancellationToken cancellationToken) + => instance.HandleRequestAsync(razorDocument, cancellationToken); + } +} + From 4736846c586230b1f42095fe31c9d68fef3fa669 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 11 Sep 2024 20:59:45 +1000 Subject: [PATCH 02/14] Move RazorDocumentConverter and RazorTranslateDiagnosticsService down to workspaces layer --- .../RazorDiagnosticsBenchmark.cs | 1 + .../DocumentPullDiagnosticsEndpoint.cs | 17 +++-- .../Diagnostics/RazorDiagnosticsPublisher.cs | 3 +- .../IServiceCollectionExtensions.cs | 1 + .../Diagnostics}/CSSErrorCodes.cs | 2 +- .../Diagnostics}/HTMLErrorCodes.cs | 2 +- .../Diagnostics/RazorDiagnosticConverter.cs | 22 +++--- .../RazorTranslateDiagnosticsService.cs | 73 +++++++++---------- .../CSharpDiagnosticsEndToEndTest.cs | 1 + .../DocumentPullDiagnosticsEndpointTest.cs | 1 + .../RazorDiagnosticConverterTest.cs | 1 + .../RazorDiagnosticsPublisherTest.cs | 1 + 12 files changed, 68 insertions(+), 57 deletions(-) rename src/Razor/src/{Microsoft.AspNetCore.Razor.LanguageServer => Microsoft.CodeAnalysis.Razor.Workspaces/Diagnostics}/CSSErrorCodes.cs (89%) rename src/Razor/src/{Microsoft.AspNetCore.Razor.LanguageServer => Microsoft.CodeAnalysis.Razor.Workspaces/Diagnostics}/HTMLErrorCodes.cs (97%) rename src/Razor/src/{Microsoft.AspNetCore.Razor.LanguageServer => Microsoft.CodeAnalysis.Razor.Workspaces}/Diagnostics/RazorDiagnosticConverter.cs (71%) rename src/Razor/src/{Microsoft.AspNetCore.Razor.LanguageServer => Microsoft.CodeAnalysis.Razor.Workspaces}/Diagnostics/RazorTranslateDiagnosticsService.cs (87%) diff --git a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorDiagnosticsBenchmark.cs b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorDiagnosticsBenchmark.cs index 1733e5058fa..7071c1986f1 100644 --- a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorDiagnosticsBenchmark.cs +++ b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorDiagnosticsBenchmark.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Razor.LanguageServer.Diagnostics; using Microsoft.AspNetCore.Razor.LanguageServer.EndpointContracts; using Microsoft.AspNetCore.Razor.LanguageServer.Hosting; +using Microsoft.CodeAnalysis.Razor.Diagnostics; using Microsoft.CodeAnalysis.Razor.DocumentMapping; using Microsoft.CodeAnalysis.Razor.Logging; using Microsoft.CodeAnalysis.Razor.ProjectSystem; 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 eba0c7da011..5e39d98c2f4 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Diagnostics/DocumentPullDiagnosticsEndpoint.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Diagnostics/DocumentPullDiagnosticsEndpoint.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Razor.LanguageServer.Hosting; using Microsoft.AspNetCore.Razor.PooledObjects; using Microsoft.AspNetCore.Razor.Telemetry; +using Microsoft.CodeAnalysis.Razor.Diagnostics; using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Razor.Protocol; using Microsoft.CodeAnalysis.Razor.Protocol.Diagnostics; @@ -72,7 +73,9 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(VSInternalDocumentDiagno return null; } - var razorDiagnostics = await GetRazorDiagnosticsAsync(documentContext, cancellationToken).ConfigureAwait(false); + var documentSnapshot = documentContext.Snapshot; + + var razorDiagnostics = await GetRazorDiagnosticsAsync(documentSnapshot).ConfigureAwait(false); var (csharpDiagnostics, htmlDiagnostics) = await GetHtmlCSharpDiagnosticsAsync(documentContext, correlationId, cancellationToken).ConfigureAwait(false); @@ -96,7 +99,7 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(VSInternalDocumentDiagno { if (report.Diagnostics is not null) { - var mappedDiagnostics = await _translateDiagnosticsService.TranslateAsync(RazorLanguageKind.CSharp, report.Diagnostics, documentContext, cancellationToken).ConfigureAwait(false); + var mappedDiagnostics = await _translateDiagnosticsService.TranslateAsync(RazorLanguageKind.CSharp, report.Diagnostics, documentSnapshot).ConfigureAwait(false); report.Diagnostics = mappedDiagnostics; } @@ -110,7 +113,7 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(VSInternalDocumentDiagno { if (report.Diagnostics is not null) { - var mappedDiagnostics = await _translateDiagnosticsService.TranslateAsync(RazorLanguageKind.Html, report.Diagnostics, documentContext, cancellationToken).ConfigureAwait(false); + var mappedDiagnostics = await _translateDiagnosticsService.TranslateAsync(RazorLanguageKind.Html, report.Diagnostics, documentSnapshot).ConfigureAwait(false); report.Diagnostics = mappedDiagnostics; } @@ -121,10 +124,10 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(VSInternalDocumentDiagno return allDiagnostics.ToArray(); } - private static async Task GetRazorDiagnosticsAsync(DocumentContext documentContext, CancellationToken cancellationToken) + private static async Task GetRazorDiagnosticsAsync(IDocumentSnapshot documentSnapshot) { - var codeDocument = await documentContext.GetCodeDocumentAsync(cancellationToken).ConfigureAwait(false); - var sourceText = await documentContext.GetSourceTextAsync(cancellationToken).ConfigureAwait(false); + var codeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); + var sourceText = codeDocument.Source.Text; var csharpDocument = codeDocument.GetCSharpDocument(); var diagnostics = csharpDocument.Diagnostics; @@ -133,7 +136,7 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(VSInternalDocumentDiagno return null; } - var convertedDiagnostics = RazorDiagnosticConverter.Convert(diagnostics, sourceText, documentContext.Snapshot); + var convertedDiagnostics = RazorDiagnosticConverter.Convert(diagnostics, sourceText, documentSnapshot); return [ 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 1f3320294e5..204b03658af 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Diagnostics/RazorDiagnosticsPublisher.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Diagnostics/RazorDiagnosticsPublisher.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Razor.PooledObjects; using Microsoft.AspNetCore.Razor.Utilities; using Microsoft.CodeAnalysis.Razor; +using Microsoft.CodeAnalysis.Razor.Diagnostics; using Microsoft.CodeAnalysis.Razor.Logging; using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Razor.Protocol; @@ -187,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, token) + .TranslateAsync(RazorLanguageKind.CSharp, fullDiagnostics.Items, documentContext.Snapshot) .ConfigureAwait(false); } } diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Extensions/IServiceCollectionExtensions.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Extensions/IServiceCollectionExtensions.cs index 8f12aff65d8..df9b7b61097 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Extensions/IServiceCollectionExtensions.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Extensions/IServiceCollectionExtensions.cs @@ -22,6 +22,7 @@ using Microsoft.AspNetCore.Razor.LanguageServer.Tooltip; using Microsoft.AspNetCore.Razor.ProjectEngineHost; using Microsoft.CodeAnalysis.Razor.Completion; +using Microsoft.CodeAnalysis.Razor.Diagnostics; using Microsoft.CodeAnalysis.Razor.DocumentMapping; using Microsoft.CodeAnalysis.Razor.Formatting; using Microsoft.CodeAnalysis.Razor.ProjectSystem; diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CSSErrorCodes.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Diagnostics/CSSErrorCodes.cs similarity index 89% rename from src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CSSErrorCodes.cs rename to src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Diagnostics/CSSErrorCodes.cs index bafb8675ab1..658f918272f 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CSSErrorCodes.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Diagnostics/CSSErrorCodes.cs @@ -1,7 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. -namespace Microsoft.AspNetCore.Razor.LanguageServer; +namespace Microsoft.CodeAnalysis.Razor.Diagnostics; // Note: This type should be kept in sync with WTE's ErrorCodes.cs internal static class CSSErrorCodes diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/HTMLErrorCodes.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Diagnostics/HTMLErrorCodes.cs similarity index 97% rename from src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/HTMLErrorCodes.cs rename to src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Diagnostics/HTMLErrorCodes.cs index 015feacef97..dc0042a9dd7 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/HTMLErrorCodes.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Diagnostics/HTMLErrorCodes.cs @@ -1,7 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. -namespace Microsoft.AspNetCore.Razor.LanguageServer; +namespace Microsoft.CodeAnalysis.Razor.Diagnostics; // Note: This type should be kept in sync with WTE's ErrorCodes.cs internal static class HtmlErrorCodes diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Diagnostics/RazorDiagnosticConverter.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Diagnostics/RazorDiagnosticConverter.cs similarity index 71% rename from src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Diagnostics/RazorDiagnosticConverter.cs rename to src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Diagnostics/RazorDiagnosticConverter.cs index 1ee9556ff92..bbbacbee1d5 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Diagnostics/RazorDiagnosticConverter.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Diagnostics/RazorDiagnosticConverter.cs @@ -2,15 +2,17 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System; -using System.Collections.Generic; +using System.Collections.Immutable; using System.Globalization; using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; -using Range = Microsoft.VisualStudio.LanguageServer.Protocol.Range; +using LspDiagnostic = Microsoft.VisualStudio.LanguageServer.Protocol.Diagnostic; +using LspDiagnosticSeverity = Microsoft.VisualStudio.LanguageServer.Protocol.DiagnosticSeverity; +using LspRange = Microsoft.VisualStudio.LanguageServer.Protocol.Range; -namespace Microsoft.AspNetCore.Razor.LanguageServer.Diagnostics; +namespace Microsoft.CodeAnalysis.Razor.Diagnostics; internal static class RazorDiagnosticConverter { @@ -46,9 +48,9 @@ public static VSDiagnosticProjectInformation[] GetProjectInformation(IDocumentSn }]; } - internal static Diagnostic[] Convert(IReadOnlyList diagnostics, SourceText sourceText, IDocumentSnapshot documentSnapshot) + internal static LspDiagnostic[] Convert(ImmutableArray diagnostics, SourceText sourceText, IDocumentSnapshot documentSnapshot) { - var convertedDiagnostics = new Diagnostic[diagnostics.Count]; + var convertedDiagnostics = new LspDiagnostic[diagnostics.Length]; var i = 0; foreach (var diagnostic in diagnostics) @@ -60,18 +62,18 @@ internal static Diagnostic[] Convert(IReadOnlyList diagnostics, } // Internal for testing - internal static DiagnosticSeverity ConvertSeverity(RazorDiagnosticSeverity severity) + internal static LspDiagnosticSeverity ConvertSeverity(RazorDiagnosticSeverity severity) { return severity switch { - RazorDiagnosticSeverity.Error => DiagnosticSeverity.Error, - RazorDiagnosticSeverity.Warning => DiagnosticSeverity.Warning, - _ => DiagnosticSeverity.Information, + RazorDiagnosticSeverity.Error => LspDiagnosticSeverity.Error, + RazorDiagnosticSeverity.Warning => LspDiagnosticSeverity.Warning, + _ => LspDiagnosticSeverity.Information, }; } // Internal for testing - internal static Range? ConvertSpanToRange(SourceSpan sourceSpan, SourceText sourceText) + internal static LspRange? ConvertSpanToRange(SourceSpan sourceSpan, SourceText sourceText) { if (sourceSpan == SourceSpan.Undefined) { diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Diagnostics/RazorTranslateDiagnosticsService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Diagnostics/RazorTranslateDiagnosticsService.cs similarity index 87% rename from src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Diagnostics/RazorTranslateDiagnosticsService.cs rename to src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Diagnostics/RazorTranslateDiagnosticsService.cs index 11fc9b5f5aa..b662d2ca2b3 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Diagnostics/RazorTranslateDiagnosticsService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Diagnostics/RazorTranslateDiagnosticsService.cs @@ -6,7 +6,6 @@ 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; @@ -18,12 +17,14 @@ using Microsoft.CodeAnalysis.Razor.Protocol; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; -using Diagnostic = Microsoft.VisualStudio.LanguageServer.Protocol.Diagnostic; -using DiagnosticSeverity = Microsoft.VisualStudio.LanguageServer.Protocol.DiagnosticSeverity; -using Range = Microsoft.VisualStudio.LanguageServer.Protocol.Range; -using SyntaxNode = Microsoft.AspNetCore.Razor.Language.Syntax.SyntaxNode; +using LspDiagnostic = Microsoft.VisualStudio.LanguageServer.Protocol.Diagnostic; +using LspDiagnosticSeverity = Microsoft.VisualStudio.LanguageServer.Protocol.DiagnosticSeverity; +using LspRange = Microsoft.VisualStudio.LanguageServer.Protocol.Range; -namespace Microsoft.AspNetCore.Razor.LanguageServer.Diagnostics; +namespace Microsoft.CodeAnalysis.Razor.Diagnostics; + +using RazorDiagnosticFactory = AspNetCore.Razor.Language.RazorDiagnosticFactory; +using SyntaxNode = AspNetCore.Razor.Language.Syntax.SyntaxNode; /// /// Contains several methods for mapping and filtering Razor and C# diagnostics. It allows for @@ -48,16 +49,14 @@ internal class RazorTranslateDiagnosticsService(IDocumentMappingService document /// /// 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. - /// A `CancellationToken` to observe while waiting for the task to complete. + /// The `DocumentContext` for the code document associated with the diagnostics. /// An array of translated diagnostics - internal async Task TranslateAsync( + internal async Task TranslateAsync( RazorLanguageKind diagnosticKind, - Diagnostic[] diagnostics, - DocumentContext documentContext, - CancellationToken cancellationToken) + LspDiagnostic[] diagnostics, + IDocumentSnapshot documentSnapshot) { - var codeDocument = await documentContext.GetCodeDocumentAsync(cancellationToken).ConfigureAwait(false); + var codeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); if (codeDocument.IsUnsupported() != false) { _logger.LogInformation($"Unsupported code document."); @@ -78,20 +77,20 @@ internal async Task TranslateAsync( var mappedDiagnostics = MapDiagnostics( diagnosticKind, filteredDiagnostics, - documentContext.Snapshot, + documentSnapshot, codeDocument); return mappedDiagnostics; } - private Diagnostic[] FilterCSharpDiagnostics(Diagnostic[] unmappedDiagnostics, RazorCodeDocument codeDocument) + private LspDiagnostic[] FilterCSharpDiagnostics(LspDiagnostic[] unmappedDiagnostics, RazorCodeDocument codeDocument) { return unmappedDiagnostics.Where(d => !ShouldFilterCSharpDiagnosticBasedOnErrorCode(d, codeDocument)).ToArray(); } - private static Diagnostic[] FilterHTMLDiagnostics( - Diagnostic[] unmappedDiagnostics, + private static LspDiagnostic[] FilterHTMLDiagnostics( + LspDiagnostic[] unmappedDiagnostics, RazorCodeDocument codeDocument) { var syntaxTree = codeDocument.GetSyntaxTree(); @@ -110,14 +109,14 @@ private static Diagnostic[] FilterHTMLDiagnostics( return filteredDiagnostics; } - private Diagnostic[] MapDiagnostics( + private LspDiagnostic[] MapDiagnostics( RazorLanguageKind languageKind, - Diagnostic[] diagnostics, + LspDiagnostic[] diagnostics, IDocumentSnapshot documentSnapshot, RazorCodeDocument codeDocument) { var projects = RazorDiagnosticConverter.GetProjectInformation(documentSnapshot); - using var mappedDiagnostics = new PooledArrayBuilder(); + using var mappedDiagnostics = new PooledArrayBuilder(); foreach (var diagnostic in diagnostics) { @@ -146,7 +145,7 @@ private Diagnostic[] MapDiagnostics( } private static bool InCSharpLiteral( - Diagnostic d, + LspDiagnostic d, SourceText sourceText, RazorSyntaxTree syntaxTree) { @@ -177,7 +176,7 @@ or SyntaxKind.CSharpStatementLiteral or SyntaxKind.CSharpEphemeralTextLiteral; } - private static bool AppliesToTagHelperTagName(Diagnostic diagnostic, SourceText sourceText, RazorSyntaxTree syntaxTree) + private static bool AppliesToTagHelperTagName(LspDiagnostic diagnostic, SourceText sourceText, RazorSyntaxTree syntaxTree) { // Goal of this method is to filter diagnostics that touch TagHelper tag names. Reason being is TagHelpers can output anything. Meaning // If you have a TagHelper like: @@ -214,7 +213,7 @@ private static bool AppliesToTagHelperTagName(Diagnostic diagnostic, SourceText return true; } - private static bool ShouldFilterHtmlDiagnosticBasedOnErrorCode(Diagnostic diagnostic, SourceText sourceText, RazorSyntaxTree syntaxTree) + private static bool ShouldFilterHtmlDiagnosticBasedOnErrorCode(LspDiagnostic diagnostic, SourceText sourceText, RazorSyntaxTree syntaxTree) { if (!diagnostic.Code.HasValue) { @@ -235,7 +234,7 @@ private static bool ShouldFilterHtmlDiagnosticBasedOnErrorCode(Diagnostic diagno _ => false, }; - static bool IsCSharpInStyleBlock(Diagnostic diagnostic, SourceText sourceText, RazorSyntaxTree syntaxTree) + static bool IsCSharpInStyleBlock(LspDiagnostic diagnostic, SourceText sourceText, RazorSyntaxTree syntaxTree) { // C# in a style block causes diagnostics because the HTML background document replaces C# with "~" var owner = syntaxTree.FindInnermostNode(sourceText, diagnostic.Range.Start); @@ -253,7 +252,7 @@ static bool IsCSharpInStyleBlock(Diagnostic diagnostic, SourceText sourceText, R // Ideally this would be solved instead by not emitting the "!" at the HTML backing file, // but we don't currently have a system to accomplish that - static bool IsAnyFilteredTooFewElementsError(Diagnostic diagnostic, SourceText sourceText, RazorSyntaxTree syntaxTree) + static bool IsAnyFilteredTooFewElementsError(LspDiagnostic diagnostic, SourceText sourceText, RazorSyntaxTree syntaxTree) { var owner = syntaxTree.FindInnermostNode(sourceText, diagnostic.Range.Start); if (owner is null) @@ -282,7 +281,7 @@ static bool IsAnyFilteredTooFewElementsError(Diagnostic diagnostic, SourceText s // Ideally this would be solved instead by not emitting the "!" at the HTML backing file, // but we don't currently have a system to accomplish that - static bool IsHtmlWithBangAndMatchingTags(Diagnostic diagnostic, SourceText sourceText, RazorSyntaxTree syntaxTree) + static bool IsHtmlWithBangAndMatchingTags(LspDiagnostic diagnostic, SourceText sourceText, RazorSyntaxTree syntaxTree) { var owner = syntaxTree.FindInnermostNode(sourceText, diagnostic.Range.Start); if (owner is null) @@ -306,11 +305,11 @@ static bool IsHtmlWithBangAndMatchingTags(Diagnostic diagnostic, SourceText sour return haveBang && namesEquivalent; } - static bool IsAnyFilteredInvalidNestingError(Diagnostic diagnostic, SourceText sourceText, RazorSyntaxTree syntaxTree) + static bool IsAnyFilteredInvalidNestingError(LspDiagnostic diagnostic, SourceText sourceText, RazorSyntaxTree syntaxTree) => IsInvalidNestingWarningWithinComponent(diagnostic, sourceText, syntaxTree) || IsInvalidNestingFromBody(diagnostic, sourceText, syntaxTree); - static bool IsInvalidNestingWarningWithinComponent(Diagnostic diagnostic, SourceText sourceText, RazorSyntaxTree syntaxTree) + static bool IsInvalidNestingWarningWithinComponent(LspDiagnostic diagnostic, SourceText sourceText, RazorSyntaxTree syntaxTree) { var owner = syntaxTree.FindInnermostNode(sourceText, diagnostic.Range.Start); if (owner is null) @@ -325,7 +324,7 @@ static bool IsInvalidNestingWarningWithinComponent(Diagnostic diagnostic, Source // Ideally this would be solved instead by not emitting the "!" at the HTML backing file, // but we don't currently have a system to accomplish that - static bool IsInvalidNestingFromBody(Diagnostic diagnostic, SourceText sourceText, RazorSyntaxTree syntaxTree) + static bool IsInvalidNestingFromBody(LspDiagnostic diagnostic, SourceText sourceText, RazorSyntaxTree syntaxTree) { var owner = syntaxTree.FindInnermostNode(sourceText, diagnostic.Range.Start); if (owner is null) @@ -350,7 +349,7 @@ static bool IsInvalidNestingFromBody(Diagnostic diagnostic, SourceText sourceTex } private static bool InAttributeContainingCSharp( - Diagnostic diagnostic, + LspDiagnostic diagnostic, SourceText sourceText, RazorSyntaxTree syntaxTree, Dictionary processedAttributes) @@ -400,7 +399,7 @@ n is GenericBlockSyntax || } } - private bool ShouldFilterCSharpDiagnosticBasedOnErrorCode(Diagnostic diagnostic, RazorCodeDocument codeDocument) + private bool ShouldFilterCSharpDiagnosticBasedOnErrorCode(LspDiagnostic diagnostic, RazorCodeDocument codeDocument) { if (diagnostic.Code is not { } code || !code.TryGetSecond(out var str) || @@ -413,10 +412,10 @@ private bool ShouldFilterCSharpDiagnosticBasedOnErrorCode(Diagnostic diagnostic, { "CS1525" => ShouldIgnoreCS1525(diagnostic, codeDocument), _ => s_cSharpDiagnosticsToIgnore.Contains(str) && - diagnostic.Severity != DiagnosticSeverity.Error + diagnostic.Severity != LspDiagnosticSeverity.Error }; - bool ShouldIgnoreCS1525(Diagnostic diagnostic, RazorCodeDocument codeDocument) + bool ShouldIgnoreCS1525(LspDiagnostic diagnostic, RazorCodeDocument codeDocument) { if (CheckIfDocumentHasRazorDiagnostic(codeDocument, RazorDiagnosticFactory.TagHelper_EmptyBoundAttribute.Id) && TryGetOriginalDiagnosticRange(diagnostic, codeDocument, out var originalRange) && @@ -440,7 +439,7 @@ private static bool CheckIfDocumentHasRazorDiagnostic(RazorCodeDocument codeDocu return codeDocument.GetSyntaxTree().Diagnostics.Any(razorDiagnosticCode, static (d, code) => d.Id == code); } - private bool TryGetOriginalDiagnosticRange(Diagnostic diagnostic, RazorCodeDocument codeDocument, [NotNullWhen(true)] out Range? originalRange) + private bool TryGetOriginalDiagnosticRange(LspDiagnostic diagnostic, RazorCodeDocument codeDocument, [NotNullWhen(true)] out LspRange? originalRange) { if (IsRudeEditDiagnostic(diagnostic)) { @@ -460,7 +459,7 @@ private bool TryGetOriginalDiagnosticRange(Diagnostic diagnostic, RazorCodeDocum { // Couldn't remap the range correctly. // If this isn't an `Error` Severity Diagnostic we can discard it. - if (diagnostic.Severity != DiagnosticSeverity.Error) + if (diagnostic.Severity != LspDiagnosticSeverity.Error) { return false; } @@ -474,14 +473,14 @@ private bool TryGetOriginalDiagnosticRange(Diagnostic diagnostic, RazorCodeDocum return true; } - private static bool IsRudeEditDiagnostic(Diagnostic diagnostic) + private static bool IsRudeEditDiagnostic(LspDiagnostic diagnostic) { return diagnostic.Code.HasValue && diagnostic.Code.Value.TryGetSecond(out var str) && str.StartsWith("ENC"); } - private bool TryRemapRudeEditRange(Range diagnosticRange, RazorCodeDocument codeDocument, [NotNullWhen(true)] out Range? remappedRange) + private bool TryRemapRudeEditRange(LspRange diagnosticRange, RazorCodeDocument codeDocument, [NotNullWhen(true)] out LspRange? remappedRange) { // This is a rude edit diagnostic that has already been mapped to the Razor document. The mapping isn't absolutely correct though, // it's based on the runtime code generation of the Razor document therefore we need to re-map the already mapped diagnostic in a diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Diagnostics/CSharpDiagnosticsEndToEndTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Diagnostics/CSharpDiagnosticsEndToEndTest.cs index 8122d21ac0e..6a650134e75 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Diagnostics/CSharpDiagnosticsEndToEndTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Diagnostics/CSharpDiagnosticsEndToEndTest.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.LanguageServer.EndpointContracts; +using Microsoft.CodeAnalysis.Razor.Diagnostics; using Microsoft.CodeAnalysis.Testing; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Diagnostics/DocumentPullDiagnosticsEndpointTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Diagnostics/DocumentPullDiagnosticsEndpointTest.cs index ecbadccdd23..c930ee00dd7 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Diagnostics/DocumentPullDiagnosticsEndpointTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Diagnostics/DocumentPullDiagnosticsEndpointTest.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Razor.LanguageServer.Hosting; using Microsoft.AspNetCore.Razor.Test.Common.LanguageServer; using Microsoft.AspNetCore.Razor.Test.Common.Workspaces; +using Microsoft.CodeAnalysis.Razor.Diagnostics; using Microsoft.VisualStudio.LanguageServer.Protocol; using Moq; using Xunit; diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Diagnostics/RazorDiagnosticConverterTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Diagnostics/RazorDiagnosticConverterTest.cs index 4e670005541..b62496963d5 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Diagnostics/RazorDiagnosticConverterTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Diagnostics/RazorDiagnosticConverterTest.cs @@ -6,6 +6,7 @@ using System.Globalization; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Test.Common.LanguageServer; +using Microsoft.CodeAnalysis.Razor.Diagnostics; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; using Xunit; diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Diagnostics/RazorDiagnosticsPublisherTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Diagnostics/RazorDiagnosticsPublisherTest.cs index 869786c9280..c3f9cf2eb0a 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Diagnostics/RazorDiagnosticsPublisherTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Diagnostics/RazorDiagnosticsPublisherTest.cs @@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Razor.Test.Common.ProjectSystem; using Microsoft.AspNetCore.Razor.Test.Common.Workspaces; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Razor.Diagnostics; using Microsoft.CodeAnalysis.Razor.DocumentMapping; using Microsoft.CodeAnalysis.Razor.Logging; using Microsoft.CodeAnalysis.Razor.ProjectSystem; From 37ca60742972f7c3eacfb9502f1550a46b573202 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 11 Sep 2024 21:50:06 +1000 Subject: [PATCH 03/14] Combine, translate and report diagnostics from the OOP service Also switch from Roslyn LSP types to VS LSP types so we can call the shared code --- .../Remote/IRemoteDiagnosticsService.cs | 6 +- .../Diagnostics/RemoteDiagnosticsService.cs | 30 +++++++--- .../RemoteRazorTranslateDiagnosticsService.cs | 17 ++++++ .../CohostDocumentPullDiagnosticsEndpoint.cs | 56 +++++++++---------- 4 files changed, 71 insertions(+), 38 deletions(-) create mode 100644 src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Diagnostics/RemoteRazorTranslateDiagnosticsService.cs diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteDiagnosticsService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteDiagnosticsService.cs index 65d6cc876c6..e8f841af660 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteDiagnosticsService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Remote/IRemoteDiagnosticsService.cs @@ -5,7 +5,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.ExternalAccess.Razor; -using RoslynLspDiagnostic = Roslyn.LanguageServer.Protocol.Diagnostic; +using RoslynLspDiagnostic = Microsoft.VisualStudio.LanguageServer.Protocol.Diagnostic; namespace Microsoft.CodeAnalysis.Razor.Remote; @@ -14,7 +14,7 @@ internal interface IRemoteDiagnosticsService : IRemoteJsonService ValueTask> GetDiagnosticsAsync( JsonSerializableRazorPinnedSolutionInfoWrapper solutionInfo, JsonSerializableDocumentId documentId, - ImmutableArray csharpDiagnostics, - ImmutableArray htmlDiagnostics, + RoslynLspDiagnostic[] csharpDiagnostics, + RoslynLspDiagnostic[] htmlDiagnostics, CancellationToken cancellationToken); } 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 cb95569cd29..cd907a8f1b4 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Diagnostics/RemoteDiagnosticsService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Diagnostics/RemoteDiagnosticsService.cs @@ -4,10 +4,14 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.AspNetCore.Razor.PooledObjects; using Microsoft.CodeAnalysis.ExternalAccess.Razor; +using Microsoft.CodeAnalysis.Razor.Diagnostics; +using Microsoft.CodeAnalysis.Razor.Protocol; using Microsoft.CodeAnalysis.Razor.Remote; using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; -using LspDiagnostic = Roslyn.LanguageServer.Protocol.Diagnostic; +using LspDiagnostic = Microsoft.VisualStudio.LanguageServer.Protocol.Diagnostic; namespace Microsoft.CodeAnalysis.Remote.Razor; @@ -19,11 +23,13 @@ protected override IRemoteDiagnosticsService CreateService(in ServiceArgs args) => new RemoteDiagnosticsService(in args); } + private readonly RazorTranslateDiagnosticsService _translateDiagnosticsService = args.ExportProvider.GetExportedValue(); + public ValueTask> GetDiagnosticsAsync( JsonSerializableRazorPinnedSolutionInfoWrapper solutionInfo, JsonSerializableDocumentId documentId, - ImmutableArray csharpDiagnostics, - ImmutableArray htmlDiagnostics, + LspDiagnostic[] csharpDiagnostics, + LspDiagnostic[] htmlDiagnostics, CancellationToken cancellationToken) => RunServiceAsync( solutionInfo, @@ -33,11 +39,21 @@ public ValueTask> GetDiagnosticsAsync( private async ValueTask> GetDiagnosticsAsync( RemoteDocumentContext context, - ImmutableArray csharpDiagnostics, - ImmutableArray htmlDiagnostics, + LspDiagnostic[] csharpDiagnostics, + LspDiagnostic[] htmlDiagnostics, CancellationToken cancellationToken) { - // TODO: More work! - return htmlDiagnostics; + // We've got C# and Html, lets get Razor diagnostics + var codeDocument = await context.GetCodeDocumentAsync(cancellationToken).ConfigureAwait(false); + // Yes, CSharpDocument.Documents are the Razor diagnostics. Don't ask. + var razorDiagnostics = codeDocument.GetCSharpDocument().Diagnostics; + + using var allDiagnostics = new PooledArrayBuilder(capacity: razorDiagnostics.Length + csharpDiagnostics.Length + htmlDiagnostics.Length); + + allDiagnostics.AddRange(RazorDiagnosticConverter.Convert(razorDiagnostics, codeDocument.Source.Text, context.Snapshot)); + allDiagnostics.AddRange(await _translateDiagnosticsService.TranslateAsync(RazorLanguageKind.CSharp, csharpDiagnostics, context.Snapshot)); + allDiagnostics.AddRange(await _translateDiagnosticsService.TranslateAsync(RazorLanguageKind.Html, htmlDiagnostics, context.Snapshot)); + + return allDiagnostics.DrainToImmutable(); } } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Diagnostics/RemoteRazorTranslateDiagnosticsService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Diagnostics/RemoteRazorTranslateDiagnosticsService.cs new file mode 100644 index 00000000000..78ae0833889 --- /dev/null +++ b/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Diagnostics/RemoteRazorTranslateDiagnosticsService.cs @@ -0,0 +1,17 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Composition; +using Microsoft.CodeAnalysis.Razor.Diagnostics; +using Microsoft.CodeAnalysis.Razor.DocumentMapping; +using Microsoft.CodeAnalysis.Razor.Logging; + +namespace Microsoft.CodeAnalysis.Remote.Razor.Diagnostics; + +[Export(typeof(RazorTranslateDiagnosticsService)), Shared] +[method: ImportingConstructor] +internal sealed class RemoteRazorTranslateDiagnosticsService( + IDocumentMappingService documentMappingService, + ILoggerFactory loggerFactory) : RazorTranslateDiagnosticsService(documentMappingService, loggerFactory) +{ +} diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostDocumentPullDiagnosticsEndpoint.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostDocumentPullDiagnosticsEndpoint.cs index 37493d18731..7fbff3e7b8f 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostDocumentPullDiagnosticsEndpoint.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostDocumentPullDiagnosticsEndpoint.cs @@ -11,7 +11,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Razor; using Microsoft.AspNetCore.Razor.PooledObjects; -using Microsoft.AspNetCore.Razor.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.ExternalAccess.Razor; using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; @@ -22,8 +21,7 @@ using Microsoft.VisualStudio.LanguageServer.ContainedLanguage; using Microsoft.VisualStudio.LanguageServer.Protocol; using ExternalHandlers = Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers; -using RoslynLspDiagnostic = Roslyn.LanguageServer.Protocol.Diagnostic; -using RoslynVSInternalDiagnosticReport = Roslyn.LanguageServer.Protocol.VSInternalDiagnosticReport; +using LspDiagnostic = Microsoft.VisualStudio.LanguageServer.Protocol.Diagnostic; namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost; @@ -40,7 +38,7 @@ internal class CohostDocumentPullDiagnosticsEndpoint( LSPRequestInvoker requestInvoker, IFilePathService filePathService, ILoggerFactory loggerFactory) - : AbstractRazorCohostDocumentRequestHandler, IDynamicRegistrationProvider + : AbstractRazorCohostDocumentRequestHandler, IDynamicRegistrationProvider { private readonly IRemoteServiceInvoker _remoteServiceInvoker = remoteServiceInvoker; private readonly IHtmlDocumentSynchronizer _htmlDocumentSynchronizer = htmlDocumentSynchronizer; @@ -73,10 +71,10 @@ internal class CohostDocumentPullDiagnosticsEndpoint( protected override RazorTextDocumentIdentifier? GetRazorTextDocumentIdentifier(VSInternalDocumentDiagnosticsParams request) => request.TextDocument?.ToRazorTextDocumentIdentifier(); - protected override Task HandleRequestAsync(VSInternalDocumentDiagnosticsParams request, RazorCohostRequestContext context, CancellationToken cancellationToken) + protected override Task HandleRequestAsync(VSInternalDocumentDiagnosticsParams request, RazorCohostRequestContext context, CancellationToken cancellationToken) => HandleRequestAsync(context.TextDocument.AssumeNotNull(), cancellationToken); - private async Task HandleRequestAsync(TextDocument razorDocument, CancellationToken cancellationToken) + private async Task HandleRequestAsync(TextDocument razorDocument, CancellationToken cancellationToken) { // Diagnostics is a little different, because Roslyn is not designed to run diagnostics in OOP. Their system will transition to OOP // as it needs, but we have to start here in devenv. This is not as big a problem as it sounds, specifically for diagnostics, because @@ -105,7 +103,7 @@ internal class CohostDocumentPullDiagnosticsEndpoint( var htmlDiagnostics = await htmlTask.ConfigureAwait(false); _logger.LogDebug($"Calling OOP with the {csharpDiagnostics.Length} C# and {htmlDiagnostics.Length} Html diagnostics"); - var diagnostics = await _remoteServiceInvoker.TryInvokeAsync>( + var diagnostics = await _remoteServiceInvoker.TryInvokeAsync>( razorDocument.Project.Solution, (service, solutionInfo, cancellationToken) => service.GetDiagnosticsAsync(solutionInfo, razorDocument.Id, csharpDiagnostics, htmlDiagnostics, cancellationToken), cancellationToken).ConfigureAwait(false); @@ -115,6 +113,7 @@ internal class CohostDocumentPullDiagnosticsEndpoint( return null; } + _logger.LogDebug($"Reporting {diagnostics.Length} diagnostics back to the client"); return [ new() @@ -125,7 +124,7 @@ internal class CohostDocumentPullDiagnosticsEndpoint( ]; } - private Task> GetCSharpDiagnosticsAsync(TextDocument razorDocument, CancellationToken cancellationToken) + private async Task GetCSharpDiagnosticsAsync(TextDocument razorDocument, CancellationToken cancellationToken) { // TODO: This code will not work when the source generator is hooked up. // How do we get the source generated C# document without OOP? Can we reverse engineer a file path? @@ -135,14 +134,28 @@ private Task> GetCSharpDiagnosticsAsync(Text if (razorDocument.Project.Solution.GetDocumentIdsWithFilePath(csharpFilePath) is not [{ } generatedDocumentId] || razorDocument.Project.GetDocument(generatedDocumentId) is not { } generatedDocument) { - return SpecializedTasks.EmptyImmutableArray(); + return []; } _logger.LogDebug($"Getting C# diagnostics for {generatedDocument.FilePath}"); - return ExternalHandlers.Diagnostics.GetDocumentDiagnosticsAsync(generatedDocument, supportsVisualStudioExtensions: true, cancellationToken); + var csharpDiagnostics = await ExternalHandlers.Diagnostics.GetDocumentDiagnosticsAsync(generatedDocument, supportsVisualStudioExtensions: true, cancellationToken).ConfigureAwait(false); + + // This is, to say the least, not ideal. In future we're going to normalize on to Roslyn LSP types, and this can go. + var options = new JsonSerializerOptions(); + foreach (var converter in RazorServiceDescriptorsWrapper.GetLspConverters()) + { + options.Converters.Add(converter); + } + + if (JsonSerializer.Deserialize(JsonSerializer.SerializeToDocument(csharpDiagnostics), options) is not { } convertedDiagnostics) + { + return []; + } + + return convertedDiagnostics; } - private async Task> GetHtmlDiagnosticsAsync(TextDocument razorDocument, CancellationToken cancellationToken) + private async Task GetHtmlDiagnosticsAsync(TextDocument razorDocument, CancellationToken cancellationToken) { var htmlDocument = await _htmlDocumentSynchronizer.TryGetSynchronizedHtmlDocumentAsync(razorDocument, cancellationToken).ConfigureAwait(false); if (htmlDocument is null) @@ -169,21 +182,8 @@ private async Task> GetHtmlDiagnosticsAsync( return []; } - // This is, to say the least, not ideal. In future we're going to normalize on to Roslyn LSP types, and this can go. - var options = new JsonSerializerOptions(); - foreach (var converter in RazorServiceDescriptorsWrapper.GetLspConverters()) - { - options.Converters.Add(converter); - } - - var hmlDiagnostics = JsonSerializer.Deserialize(JsonSerializer.SerializeToDocument(result.Response), options); - if (hmlDiagnostics is not { } convertedHtmlDiagnostics) - { - return []; - } - - using var allDiagnostics = new PooledArrayBuilder(); - foreach (var report in convertedHtmlDiagnostics) + using var allDiagnostics = new PooledArrayBuilder(); + foreach (var report in result.Response) { if (report.Diagnostics is not null) { @@ -191,14 +191,14 @@ private async Task> GetHtmlDiagnosticsAsync( } } - return allDiagnostics.ToImmutable(); + return allDiagnostics.ToArray(); } internal TestAccessor GetTestAccessor() => new(this); internal readonly struct TestAccessor(CohostDocumentPullDiagnosticsEndpoint instance) { - public Task HandleRequestAsync(TextDocument razorDocument, CancellationToken cancellationToken) + public Task HandleRequestAsync(TextDocument razorDocument, CancellationToken cancellationToken) => instance.HandleRequestAsync(razorDocument, cancellationToken); } } From 462d9a4ff417dc0669f2185a1b37ba2b58317080 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Thu, 12 Sep 2024 22:08:17 +1000 Subject: [PATCH 04/14] Add diagnostics test Writing the code: Easy Writing the test: Easy Getting the MEF stuff to work: I've lost even more hair --- .../CohostDocumentPullDiagnosticsEndpoint.cs | 3 +- .../CSharpTestLspServerHelpers.cs | 2 +- .../Mef/ExportProviderExtensions.cs | 72 +++++++++------- .../TestCode.cs | 3 + .../CohostDocumentPullDiagnosticsTest.cs | 60 ++++++++++++++ .../Cohost/CohostEndpointTestBase.cs | 83 +++++++++++++++---- .../Cohost/TestBrokeredServiceInterceptor.cs | 14 ++++ .../Cohost/TestRemoteServiceInvoker.cs | 5 ++ .../LanguageClient/TestLSPRequestInvoker.cs | 3 +- 9 files changed, 197 insertions(+), 48 deletions(-) create mode 100644 src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostDocumentPullDiagnosticsTest.cs diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostDocumentPullDiagnosticsEndpoint.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostDocumentPullDiagnosticsEndpoint.cs index 7fbff3e7b8f..2959ef1a0b9 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostDocumentPullDiagnosticsEndpoint.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostDocumentPullDiagnosticsEndpoint.cs @@ -94,9 +94,8 @@ internal class CohostDocumentPullDiagnosticsEndpoint( if (e is not OperationCanceledException) { _logger.LogError(e, $"Exception thrown in PullDiagnostic delegation"); + throw; } - // Return null if any of the tasks getting diagnostics results in an error - return null; } var csharpDiagnostics = await csharpTask.ConfigureAwait(false); diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/LanguageServer/CSharpTestLspServerHelpers.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/LanguageServer/CSharpTestLspServerHelpers.cs index ecf351fe1e8..83a05dc1abb 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/LanguageServer/CSharpTestLspServerHelpers.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/LanguageServer/CSharpTestLspServerHelpers.cs @@ -172,7 +172,7 @@ private static AdhocWorkspace CreateCSharpTestWorkspace( return workspace; } - private static void AddAnalyzersToWorkspace(Workspace workspace, ExportProvider exportProvider) + public static void AddAnalyzersToWorkspace(Workspace workspace, ExportProvider exportProvider) { var analyzerLoader = RazorTestAnalyzerLoader.CreateAnalyzerAssemblyLoader(); diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Mef/ExportProviderExtensions.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Mef/ExportProviderExtensions.cs index 05540f3862b..18d5c5dd654 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Mef/ExportProviderExtensions.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Mef/ExportProviderExtensions.cs @@ -3,12 +3,12 @@ using System; using System.Collections.Generic; -using System.Composition.Hosting.Core; using System.Composition; +using System.Composition.Hosting.Core; using System.Diagnostics.CodeAnalysis; using System.Linq; -using Microsoft.VisualStudio.Composition; using System.Reflection; +using Microsoft.VisualStudio.Composition; namespace Microsoft.AspNetCore.Razor.Test.Common.Mef; @@ -31,43 +31,55 @@ public CompositionContextShim(ExportProvider exportProvider) public override bool TryGetExport(CompositionContract contract, [NotNullWhen(true)] out object? export) { var importMany = contract.MetadataConstraints.Contains(new KeyValuePair("IsImportMany", true)); - var (contractType, metadataType) = GetContractType(contract.ContractType, importMany); + var (contractType, metadataType, isLazy) = GetContractType(contract.ContractType, importMany); - if (metadataType != null) + var method = (metadataType, isLazy) switch { - var methodInfo = (from method in _exportProvider.GetType().GetTypeInfo().GetMethods() - where method.Name == nameof(ExportProvider.GetExports) - where method.IsGenericMethod && method.GetGenericArguments().Length == 2 - where method.GetParameters().Length == 1 && method.GetParameters()[0].ParameterType == typeof(string) - select method).Single(); - var parameterizedMethod = methodInfo.MakeGenericMethod(contractType, metadataType); - export = parameterizedMethod.Invoke(_exportProvider, new[] { contract.ContractName }); - Assumes.NotNull(export); + (not null, true) => GetExportProviderGenericMethod(nameof(ExportProvider.GetExports), contractType, metadataType), + (null, true) => GetExportProviderGenericMethod(nameof(ExportProvider.GetExports), contractType), + (null, false) => GetExportProviderGenericMethod(nameof(ExportProvider.GetExportedValues), contractType), + _ => null + }; + + if (method is null) + { + export = null; + return false; } - else + + export = method.Invoke(_exportProvider, [contract.ContractName]); + Assumes.NotNull(export); + + return true; + + static MethodInfo GetExportProviderGenericMethod(string methodName, params Type[] typeArguments) { - var methodInfo = (from method in _exportProvider.GetType().GetTypeInfo().GetMethods() - where method.Name == nameof(ExportProvider.GetExports) - where method.IsGenericMethod && method.GetGenericArguments().Length == 1 + var methodInfo = (from method in typeof(ExportProvider).GetTypeInfo().GetMethods() + where method.Name == methodName + where method.IsGenericMethod && method.GetGenericArguments().Length == typeArguments.Length where method.GetParameters().Length == 1 && method.GetParameters()[0].ParameterType == typeof(string) select method).Single(); - var parameterizedMethod = methodInfo.MakeGenericMethod(contractType); - export = parameterizedMethod.Invoke(_exportProvider, new[] { contract.ContractName }); - Assumes.NotNull(export); - } - return true; + return methodInfo.MakeGenericMethod(typeArguments); + } } - private static (Type exportType, Type? metadataType) GetContractType(Type contractType, bool importMany) + private static (Type exportType, Type? metadataType, bool isLazy) GetContractType(Type contractType, bool importMany) { - if (importMany && contractType.IsConstructedGenericType) + if (importMany) { - if (contractType.GetGenericTypeDefinition() == typeof(IList<>) - || contractType.GetGenericTypeDefinition() == typeof(ICollection<>) - || contractType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + if (contractType.IsConstructedGenericType) + { + if (contractType.GetGenericTypeDefinition() == typeof(IList<>) + || contractType.GetGenericTypeDefinition() == typeof(ICollection<>) + || contractType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + { + contractType = contractType.GenericTypeArguments[0]; + } + } + else if (contractType.IsArray) { - contractType = contractType.GenericTypeArguments[0]; + contractType = contractType.GetElementType().AssumeNotNull(); } } @@ -75,11 +87,11 @@ private static (Type exportType, Type? metadataType) GetContractType(Type contra { if (contractType.GetGenericTypeDefinition() == typeof(Lazy<>)) { - return (contractType.GenericTypeArguments[0], null); + return (contractType.GenericTypeArguments[0], null, true); } else if (contractType.GetGenericTypeDefinition() == typeof(Lazy<,>)) { - return (contractType.GenericTypeArguments[0], contractType.GenericTypeArguments[1]); + return (contractType.GenericTypeArguments[0], contractType.GenericTypeArguments[1], true); } else { @@ -87,7 +99,7 @@ private static (Type exportType, Type? metadataType) GetContractType(Type contra } } - throw new NotSupportedException(); + return (contractType, null, false); } } } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/TestCode.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/TestCode.cs index 6930bda0d33..9374bd108ab 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/TestCode.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/TestCode.cs @@ -47,6 +47,9 @@ public TextSpan Span public ImmutableArray Spans => GetNamedSpans(string.Empty); + public ImmutableDictionary> NamedSpans + => _nameToSpanMap; + public ImmutableArray GetNamedSpans(string name) => _nameToSpanMap[name]; diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostDocumentPullDiagnosticsTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostDocumentPullDiagnosticsTest.cs new file mode 100644 index 00000000000..84b1d414ffd --- /dev/null +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostDocumentPullDiagnosticsTest.cs @@ -0,0 +1,60 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor.Test.Common; +using Microsoft.AspNetCore.Razor.Test.Common.Mef; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.LanguageServer.Protocol; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost; + +[UseExportProvider] +public class CohostDocumentPullDiagnosticsTest(ITestOutputHelper testOutputHelper) : CohostEndpointTestBase(testOutputHelper) +{ + [Fact] + public Task IfStatements() + => VerifyDiagnosticsAsync(""" +
+ + @code + { + public void IJustMetYou() + { + {|CS0103:CallMeMaybe|}(); + } + } + """); + + private async Task VerifyDiagnosticsAsync(TestCode input) + { + var document = await CreateProjectAndRazorDocumentAsync(input.Text, createSeparateRemoteAndLocalWorkspaces: true); + var inputText = await document.GetTextAsync(DisposalToken); + + var requestInvoker = new TestLSPRequestInvoker(); + + var endpoint = new CohostDocumentPullDiagnosticsEndpoint(RemoteServiceInvoker, TestHtmlDocumentSynchronizer.Instance, requestInvoker, FilePathService, LoggerFactory); + + var result = await endpoint.GetTestAccessor().HandleRequestAsync(document, DisposalToken); + var actual = result!.SelectMany(d => d.Diagnostics!).ToArray(); + + if (input.NamedSpans.Count == 0) + { + Assert.Null(result); + return; + } + + Assert.Equal(input.NamedSpans.Count, actual.Length); + + foreach (var (code, spans) in input.NamedSpans) + { + var diagnostic = Assert.Single(actual, d => d.Code == code); + Assert.Equal(spans.First(), inputText.GetTextSpan(diagnostic.Range)); + } + } +} 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 3573e1badf6..abd502ca203 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 @@ -9,10 +9,15 @@ using Microsoft.AspNetCore.Razor; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Test.Common; +using Microsoft.AspNetCore.Razor.Test.Common.LanguageServer; +using Microsoft.AspNetCore.Razor.Test.Common.Mef; +using Microsoft.AspNetCore.Razor.Test.Common.Workspaces; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Razor.Remote; using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.CodeAnalysis.Remote.Razor; +using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Composition; using Xunit.Abstractions; @@ -68,7 +73,7 @@ private protected void UpdateClientInitializationOptions(Func 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); @@ -82,20 +87,70 @@ protected TextDocument CreateProjectAndRazorDocument(string contents, string? fi var projectId = ProjectId.CreateNewId(debugName: projectName); var documentId = DocumentId.CreateNewId(projectId, debugName: documentFilePath); + var remoteWorkspace = RemoteWorkspaceAccessor.GetWorkspace(); + var remoteDocument = CreateProjectAndRazorDocument(remoteWorkspace, projectId, projectName, documentId, documentFilePath, contents, additionalFiles); + + if (createSeparateRemoteAndLocalWorkspaces) + { + // Usually its fine to just use the remote workspace, but sometimes we need to also have things available in the + // "devenv" side of Roslyn, which is a different workspace with a different set of services. We don't have any + // 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); + } + + // If we're just creating one workspace, then its the remote one and we just return the remote document + // and assume that the endpoint under test doesn't need to do anything on the devenv side. This makes it + // easier for tests to mutate solutions + 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) + { + var exportProvider = TestComposition.Roslyn.ExportProviderFactory.CreateExportProvider(); + AddDisposable(exportProvider); + var hostServices = MefHostServices.Create(exportProvider.AsCompositionContext()); + var workspace = TestWorkspace.Create(hostServices); + AddDisposable(workspace); + // Adding analyzers modifies the workspace, so important to do it before creating the first project + CSharpTestLspServerHelpers.AddAnalyzersToWorkspace(workspace, exportProvider); + + var razorDocument = CreateProjectAndRazorDocument(workspace, projectId, projectName, documentId, documentFilePath, contents, additionalFiles); + + // Until the source generator is hooked up, the workspace representing "local" projects doesn't have anything + // to actually compile the Razor to C#, so we just do it now at creation + var solution = razorDocument.Project.Solution; + // We're cheating a bit here and using the remote export provider to get something to do the compilation + var factory = _exportProvider.AssumeNotNull().GetExportedValue(); + var snapshot = factory.GetOrCreate(razorDocument); + // Compile the Razor file + var codeDocument = await snapshot.GetGeneratedOutputAsync(false); + // Update the generated doc contents + var generatedDocumentIds = solution.GetDocumentIdsWithFilePath(documentFilePath + CSharpVirtualDocumentSuffix); + solution = solution.WithDocumentText(generatedDocumentIds, codeDocument.GetCSharpSourceText()); + razorDocument = solution.GetAdditionalDocument(documentId).AssumeNotNull(); + + // If we're creating remote and local workspaces, then we'll return the local document, and have to allow + // the remote service invoker to map from the local solution to the remote one. + RemoteServiceInvoker.MapSolutionIdToRemote(razorDocument.Project.Solution.Id, remoteSolution); + + return razorDocument; + } + + private static TextDocument CreateProjectAndRazorDocument(CodeAnalysis.Workspace workspace, ProjectId projectId, string projectName, DocumentId documentId, string documentFilePath, string contents, (string fileName, string contents)[]? additionalFiles) + { var projectInfo = ProjectInfo - .Create( - projectId, - VersionStamp.Create(), - name: projectName, - assemblyName: projectName, - LanguageNames.CSharp, - documentFilePath) - .WithDefaultNamespace(TestProjectData.SomeProject.RootNamespace) - .WithMetadataReferences(AspNet80.ReferenceInfos.All.Select(r => r.Reference)); - - // Importantly, we use Roslyn's remote workspace here so that when our OOP services call into Roslyn, their code - // will be able to access their services. - var workspace = RemoteWorkspaceAccessor.GetWorkspace(); + .Create( + projectId, + VersionStamp.Create(), + name: projectName, + assemblyName: projectName, + LanguageNames.CSharp, + documentFilePath) + .WithDefaultNamespace(TestProjectData.SomeProject.RootNamespace) + .WithMetadataReferences(AspNet80.ReferenceInfos.All.Select(r => r.Reference)); + var solution = workspace.CurrentSolution.AddProject(projectInfo); solution = solution diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/TestBrokeredServiceInterceptor.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/TestBrokeredServiceInterceptor.cs index 7d0da662311..fffa4b394c6 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/TestBrokeredServiceInterceptor.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/TestBrokeredServiceInterceptor.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -14,6 +15,7 @@ namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost; internal sealed class TestBrokeredServiceInterceptor : IRazorBrokeredServiceInterceptor { private readonly TestSolutionStore _solutionStore = new(); + private readonly Dictionary _localToRemoteSolutionMap = []; public Task GetSolutionInfoAsync(Solution solution, CancellationToken cancellationToken) => _solutionStore.AddAsync(solution, cancellationToken); @@ -32,6 +34,18 @@ public ValueTask RunServiceAsync( Assert.NotNull(solution); + // Rather than actually syncing assets, we just let the test author directly map from a local solution + // to a remote solution; + if (_localToRemoteSolutionMap.TryGetValue(solution.Id, out var remoteSolution)) + { + solution = remoteSolution; + } + return implementation(solution); } + + internal void MapSolutionIdToRemote(SolutionId localSolutionId, Solution remoteSolution) + { + _localToRemoteSolutionMap.Add(localSolutionId, remoteSolution); + } } diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/TestRemoteServiceInvoker.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/TestRemoteServiceInvoker.cs index 9e5c8651aac..581d08f35a4 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/TestRemoteServiceInvoker.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/TestRemoteServiceInvoker.cs @@ -57,6 +57,11 @@ private async Task GetOrCreateServiceAsync() return await invocation(service, solutionInfo, cancellationToken); } + public void MapSolutionIdToRemote(SolutionId localSolutionId, Solution remoteSolution) + { + _serviceInterceptor.MapSolutionIdToRemote(localSolutionId, remoteSolution); + } + public void Dispose() { _reentrantSemaphore.Dispose(); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/LanguageClient/TestLSPRequestInvoker.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/LanguageClient/TestLSPRequestInvoker.cs index 3bbf1c56ee0..c4d01769a9f 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/LanguageClient/TestLSPRequestInvoker.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/LanguageClient/TestLSPRequestInvoker.cs @@ -111,7 +111,8 @@ public async override Task> ReinvokeRequestOnServerAs return new ReinvocationResponse(languageClientName: RazorLSPConstants.RazorCSharpLanguageServerName, result); } - if (_htmlResponses.TryGetValue(method, out var response)) + if (_htmlResponses is not null && + _htmlResponses.TryGetValue(method, out var response)) { return new ReinvocationResponse(languageClientName: "html", (TOut)response); } From 8f7e1f8d7f45beca3330226694b097d766ee2d33 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Thu, 12 Sep 2024 22:27:44 +1000 Subject: [PATCH 05/14] Update all of the other tests --- .../Cohost/CohostDocumentFormattingEndpointTest.cs | 2 +- .../Cohost/CohostDocumentHighlightEndpointTest.cs | 2 +- .../Cohost/CohostDocumentSpellCheckEndpointTest.cs | 2 +- .../Cohost/CohostDocumentSymbolEndpointTest.cs | 2 +- .../Cohost/CohostFoldingRangeEndpointTest.cs | 3 +-- .../Cohost/CohostGoToDefinitionEndpointTest.cs | 11 +++++------ .../Cohost/CohostGoToImplementationEndpointTest.cs | 4 ++-- .../Cohost/CohostInlayHintEndpointTest.cs | 2 +- .../Cohost/CohostLinkedEditingRangeEndpointTest.cs | 2 +- .../Cohost/CohostOnAutoInsertEndpointTest.cs | 2 +- .../Cohost/CohostOnTypeFormattingEndpointTest.cs | 2 +- .../Cohost/CohostRangeFormattingEndpointTest.cs | 2 +- .../Cohost/CohostRenameEndpointTest.cs | 2 +- .../Cohost/CohostSemanticTokensRangeEndpointTest.cs | 2 +- .../Cohost/CohostSignatureHelpEndpointTest.cs | 2 +- .../Cohost/CohostTextPresentationEndpointTest.cs | 2 +- .../Cohost/CohostUriPresentationEndpointTest.cs | 2 +- .../Cohost/RazorComponentDefinitionServiceTest.cs | 2 +- 18 files changed, 23 insertions(+), 25 deletions(-) diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostDocumentFormattingEndpointTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostDocumentFormattingEndpointTest.cs index 3c8f9b268b0..bc9bb3a4507 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostDocumentFormattingEndpointTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostDocumentFormattingEndpointTest.cs @@ -108,7 +108,7 @@ private void M(string thisIsMyString) private async Task VerifyDocumentFormattingAsync(string input, string expected) { - var document = CreateProjectAndRazorDocument(input); + var document = await CreateProjectAndRazorDocumentAsync(input); var inputText = await document.GetTextAsync(DisposalToken); var htmlDocumentPublisher = new HtmlDocumentPublisher(RemoteServiceInvoker, StrictMock.Of(), StrictMock.Of(), LoggerFactory); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostDocumentHighlightEndpointTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostDocumentHighlightEndpointTest.cs index ce044474c00..65de7ed10a9 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostDocumentHighlightEndpointTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostDocumentHighlightEndpointTest.cs @@ -147,7 +147,7 @@ @inject [|IDis$$posable|] Disposable private async Task VerifyDocumentHighlightsAsync(string input, DocumentHighlight[]? htmlResponse = null) { TestFileMarkupParser.GetPositionAndSpans(input, out var source, out int cursorPosition, out ImmutableArray spans); - var document = CreateProjectAndRazorDocument(source); + var document = await CreateProjectAndRazorDocumentAsync(source); var inputText = await document.GetTextAsync(DisposalToken); var position = inputText.GetPosition(cursorPosition); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostDocumentSpellCheckEndpointTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostDocumentSpellCheckEndpointTest.cs index 6711a4bdc2b..51c5d3d28a9 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostDocumentSpellCheckEndpointTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostDocumentSpellCheckEndpointTest.cs @@ -57,7 +57,7 @@ Eat more chickin. private async Task VerifySemanticTokensAsync(TestCode input) { - var document = CreateProjectAndRazorDocument(input.Text); + var document = await CreateProjectAndRazorDocumentAsync(input.Text); var sourceText = await document.GetTextAsync(DisposalToken); var endpoint = new CohostDocumentSpellCheckEndpoint(RemoteServiceInvoker); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostDocumentSymbolEndpointTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostDocumentSymbolEndpointTest.cs index 0f008bc6ef2..2cc98c4c923 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostDocumentSymbolEndpointTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostDocumentSymbolEndpointTest.cs @@ -69,7 +69,7 @@ public Task DocumentSymbols_CSharpMethods(bool hierarchical) private async Task VerifyDocumentSymbolsAsync(string input, bool hierarchical = false) { TestFileMarkupParser.GetSpans(input, out input, out ImmutableDictionary> spansDict); - var document = CreateProjectAndRazorDocument(input); + var document = await CreateProjectAndRazorDocumentAsync(input); var endpoint = new CohostDocumentSymbolEndpoint(RemoteServiceInvoker); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostFoldingRangeEndpointTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostFoldingRangeEndpointTest.cs index 08ad6977496..1bfb8b299cd 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostFoldingRangeEndpointTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostFoldingRangeEndpointTest.cs @@ -7,7 +7,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Razor; using Microsoft.AspNetCore.Razor.Language; -using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.CodeAnalysis.Testing; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; @@ -218,7 +217,7 @@ public void M() {[| private async Task VerifyFoldingRangesAsync(string input, string? fileKind = null) { TestFileMarkupParser.GetSpans(input, out var source, out ImmutableDictionary> spans); - var document = CreateProjectAndRazorDocument(source, fileKind); + var document = await CreateProjectAndRazorDocumentAsync(source, fileKind); var inputText = await document.GetTextAsync(DisposalToken); var htmlSpans = spans.GetValueOrDefault("html").NullToEmpty(); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostGoToDefinitionEndpointTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostGoToDefinitionEndpointTest.cs index b97fe4ef07a..f5b8b6e77e3 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostGoToDefinitionEndpointTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostGoToDefinitionEndpointTest.cs @@ -8,7 +8,6 @@ using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Test.Common; using Microsoft.CodeAnalysis.ExternalAccess.Razor; -using Microsoft.CodeAnalysis.Remote.Razor; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; using Xunit; @@ -313,7 +312,7 @@ public async Task Html() """; - var document = CreateProjectAndRazorDocument(input.Text); + var document = await CreateProjectAndRazorDocumentAsync(input.Text); var inputText = await document.GetTextAsync(DisposalToken); var htmlResponse = new SumType?(new Location[] @@ -333,7 +332,7 @@ private static string FileName(string projectRelativeFileName) private async Task VerifyGoToDefinitionAsync(TestCode input, string? fileKind = null, SumType? htmlResponse = null) { - var document = CreateProjectAndRazorDocument(input.Text, fileKind); + var document = await CreateProjectAndRazorDocumentAsync(input.Text, fileKind); var result = await GetGoToDefinitionResultAsync(document, input, htmlResponse); Assumes.NotNull(result); @@ -349,11 +348,11 @@ private async Task VerifyGoToDefinitionAsync(TestCode input, string? fileKind = Assert.Equal(document.CreateUri(), location.Uri); } - private Task?> GetGoToDefinitionResultAsync( + private async Task?> GetGoToDefinitionResultAsync( TestCode input, string? fileKind = null, params (string fileName, string contents)[]? additionalFiles) { - var document = CreateProjectAndRazorDocument(input.Text, fileKind, additionalFiles); - return GetGoToDefinitionResultAsync(document, input, htmlResponse: null); + var document = await CreateProjectAndRazorDocumentAsync(input.Text, fileKind, additionalFiles); + return await GetGoToDefinitionResultAsync(document, input, htmlResponse: null); } private async Task?> GetGoToDefinitionResultAsync( diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostGoToImplementationEndpointTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostGoToImplementationEndpointTest.cs index 4ee5dab2c8d..b6c91a29bea 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostGoToImplementationEndpointTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostGoToImplementationEndpointTest.cs @@ -100,7 +100,7 @@ public async Task Html() """; - var document = CreateProjectAndRazorDocument(input.Text); + var document = await CreateProjectAndRazorDocumentAsync(input.Text); var inputText = await document.GetTextAsync(DisposalToken); var htmlResponse = new SumType?(new LspLocation[] @@ -119,7 +119,7 @@ public async Task Html() private async Task VerifyCSharpGoToImplementationAsync(TestCode input) { - var document = CreateProjectAndRazorDocument(input.Text); + var document = await CreateProjectAndRazorDocumentAsync(input.Text); var requestInvoker = new TestLSPRequestInvoker(); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostInlayHintEndpointTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostInlayHintEndpointTest.cs index f7a3df1d975..05c9b4486ec 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostInlayHintEndpointTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostInlayHintEndpointTest.cs @@ -131,7 +131,7 @@ public Task InlayHints_ComponentAttributes() private async Task VerifyInlayHintsAsync(string input, Dictionary toolTipMap, string output, bool displayAllOverride = false) { TestFileMarkupParser.GetSpans(input, out input, out ImmutableDictionary> spansDict); - var document = CreateProjectAndRazorDocument(input); + var document = await CreateProjectAndRazorDocumentAsync(input); var inputText = await document.GetTextAsync(DisposalToken); var endpoint = new CohostInlayHintEndpoint(RemoteServiceInvoker); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostLinkedEditingRangeEndpointTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostLinkedEditingRangeEndpointTest.cs index 28c05827166..865f5101125 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostLinkedEditingRangeEndpointTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostLinkedEditingRangeEndpointTest.cs @@ -159,7 +159,7 @@ The end. private async Task VerifyLinkedEditingRangeAsync(string input) { TestFileMarkupParser.GetPositionAndSpans(input, out input, out int cursorPosition, out ImmutableArray spans); - var document = CreateProjectAndRazorDocument(input); + var document = await CreateProjectAndRazorDocumentAsync(input); var sourceText = await document.GetTextAsync(DisposalToken); var endpoint = new CohostLinkedEditingRangeEndpoint(RemoteServiceInvoker); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostOnAutoInsertEndpointTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostOnAutoInsertEndpointTest.cs index b7dbb70194a..74d5036d454 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostOnAutoInsertEndpointTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostOnAutoInsertEndpointTest.cs @@ -196,7 +196,7 @@ private async Task VerifyOnAutoInsertAsync( bool formatOnType = true, bool autoClosingTags = true) { - var document = CreateProjectAndRazorDocument(input.Text); + var document = await CreateProjectAndRazorDocumentAsync(input.Text); var sourceText = await document.GetTextAsync(DisposalToken); var clientSettingsManager = new ClientSettingsManager([], null, null); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostOnTypeFormattingEndpointTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostOnTypeFormattingEndpointTest.cs index 7a1e3d913f3..3363c7a2ed6 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostOnTypeFormattingEndpointTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostOnTypeFormattingEndpointTest.cs @@ -103,7 +103,7 @@ await VerifyOnTypeFormattingAsync( private async Task VerifyOnTypeFormattingAsync(TestCode input, string expected, char triggerCharacter, bool html = false) { - var document = CreateProjectAndRazorDocument(input.Text); + var document = await CreateProjectAndRazorDocumentAsync(input.Text); var inputText = await document.GetTextAsync(DisposalToken); var position = inputText.GetPosition(input.Position); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostRangeFormattingEndpointTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostRangeFormattingEndpointTest.cs index 32d03e28002..2b6a6005b8f 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostRangeFormattingEndpointTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostRangeFormattingEndpointTest.cs @@ -102,7 +102,7 @@ private void M(string thisIsMyString) private async Task VerifyRangeFormattingAsync(TestCode input, string expected) { - var document = CreateProjectAndRazorDocument(input.Text); + var document = await CreateProjectAndRazorDocumentAsync(input.Text); var inputText = await document.GetTextAsync(DisposalToken); var htmlDocumentPublisher = new HtmlDocumentPublisher(RemoteServiceInvoker, StrictMock.Of(), StrictMock.Of(), LoggerFactory); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostRenameEndpointTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostRenameEndpointTest.cs index 9328744f2ae..6e1af1d4f06 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostRenameEndpointTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostRenameEndpointTest.cs @@ -198,7 +198,7 @@ public class Component : Microsoft.AspNetCore.Components.ComponentBase private async Task VerifyRenamesAsync(string input, string newName, string expected, string? fileKind = null, (string fileName, string contents)[]? additionalFiles = null, (string oldName, string newName)[]? renames = null) { TestFileMarkupParser.GetPosition(input, out var source, out var cursorPosition); - var document = CreateProjectAndRazorDocument(source, fileKind, additionalFiles); + var document = await CreateProjectAndRazorDocumentAsync(source, fileKind, additionalFiles); var inputText = await document.GetTextAsync(DisposalToken); var position = inputText.GetPosition(cursorPosition); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostSemanticTokensRangeEndpointTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostSemanticTokensRangeEndpointTest.cs index 42b792b7ad5..7f841c9b12d 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostSemanticTokensRangeEndpointTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostSemanticTokensRangeEndpointTest.cs @@ -90,7 +90,7 @@ @section MySection { private async Task VerifySemanticTokensAsync(string input, bool colorBackground, bool precise, string? fileKind = null, [CallerMemberName] string? testName = null) { - var document = CreateProjectAndRazorDocument(input, fileKind); + var document = await CreateProjectAndRazorDocumentAsync(input, fileKind); var sourceText = await document.GetTextAsync(DisposalToken); var legend = TestRazorSemanticTokensLegendService.Instance; diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostSignatureHelpEndpointTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostSignatureHelpEndpointTest.cs index 7c599b0a3ed..8facef4127a 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostSignatureHelpEndpointTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostSignatureHelpEndpointTest.cs @@ -91,7 +91,7 @@ void Act() private async Task VerifySignatureHelpAsync(string input, string expected, bool autoListParams = true, SignatureHelpTriggerKind? triggerKind = null) { TestFileMarkupParser.GetPosition(input, out input, out var cursorPosition); - var document = CreateProjectAndRazorDocument(input); + var document = await CreateProjectAndRazorDocumentAsync(input); var sourceText = await document.GetTextAsync(DisposalToken); var clientSettingsManager = new ClientSettingsManager([], null, null); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostTextPresentationEndpointTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostTextPresentationEndpointTest.cs index f946a642581..f2a4acc5b67 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostTextPresentationEndpointTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostTextPresentationEndpointTest.cs @@ -55,7 +55,7 @@ private static string File(string projectRelativeFileName) private async Task VerifyUriPresentationAsync(string input, string text, string? expected, WorkspaceEdit? htmlResponse = null) { TestFileMarkupParser.GetSpan(input, out input, out var span); - var document = CreateProjectAndRazorDocument(input); + var document = await CreateProjectAndRazorDocumentAsync(input); var sourceText = await document.GetTextAsync(DisposalToken); var requestInvoker = new TestLSPRequestInvoker([(VSInternalMethods.TextDocumentTextPresentationName, htmlResponse)]); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostUriPresentationEndpointTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostUriPresentationEndpointTest.cs index d1f965f0b76..68b3be9565a 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostUriPresentationEndpointTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostUriPresentationEndpointTest.cs @@ -286,7 +286,7 @@ private static string File(string projectRelativeFileName) private async Task VerifyUriPresentationAsync(string input, Uri[] uris, string? expected, WorkspaceEdit? htmlResponse = null, (string fileName, string contents)[]? additionalFiles = null) { TestFileMarkupParser.GetSpan(input, out input, out var span); - var document = CreateProjectAndRazorDocument(input, additionalFiles: additionalFiles); + var document = await CreateProjectAndRazorDocumentAsync(input, additionalFiles: additionalFiles); var sourceText = await document.GetTextAsync(DisposalToken); var requestInvoker = new TestLSPRequestInvoker([(VSInternalMethods.TextDocumentUriPresentationName, htmlResponse)]); 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 041b95c6eee..c7b1fd79b4c 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 @@ -76,7 +76,7 @@ await VerifyDefinitionAsync(input, surveyPrompt, (FileName("SurveyPrompt.razor") private async Task VerifyDefinitionAsync(TestCode input, TestCode expectedDocument, params (string fileName, string contents)[]? additionalFiles) { - var document = CreateProjectAndRazorDocument(input.Text, FileKinds.Component, additionalFiles); + var document = await CreateProjectAndRazorDocumentAsync(input.Text, FileKinds.Component, additionalFiles); var service = OOPExportProvider.GetExportedValue(); var documentSnapshotFactory = OOPExportProvider.GetExportedValue(); From 923c016bff1ccd3a899719af181b04f4ea5fea8b Mon Sep 17 00:00:00 2001 From: David Wengier Date: Fri, 13 Sep 2024 09:38:00 +1000 Subject: [PATCH 06/14] Convert HtmlFormatting to a service, have it own its export provider, and create a fixture so we can still share an instance --- ...SharpStatementBlockOnTypeFormattingTest.cs | 5 +++- .../CodeActionFormattingTest.cs | 5 +++- .../CodeDirectiveFormattingTest.cs | 5 +++- .../CodeDirectiveOnTypeFormattingTest.cs | 5 +++- .../FormattingLanguageServerClient.cs | 7 ++--- .../Formatting_NetFx/FormattingTestBase.cs | 12 ++++++--- .../HtmlFormattingCollection.cs | 13 ++++++++++ .../Formatting_NetFx/HtmlFormattingTest.cs | 4 ++- .../Formatting_NetFx/RazorFormattingTest.cs | 4 ++- .../Formatting_NetFx/HtmlFormattingFixture.cs | 18 +++++++++++++ ...Formatting.cs => HtmlFormattingService.cs} | 26 ++++++++++++++----- .../CohostDocumentFormattingEndpointTest.cs | 8 +++--- .../CohostOnTypeFormattingEndpointTest.cs | 8 +++--- .../CohostRangeFormattingEndpointTest.cs | 8 +++--- .../Cohost/HtmlFormattingCollection.cs | 13 ++++++++++ 15 files changed, 109 insertions(+), 32 deletions(-) create mode 100644 src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/HtmlFormattingCollection.cs create mode 100644 src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Formatting_NetFx/HtmlFormattingFixture.cs rename src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Formatting_NetFx/{HtmlFormatting.cs => HtmlFormattingService.cs} (78%) create mode 100644 src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/HtmlFormattingCollection.cs diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/CSharpStatementBlockOnTypeFormattingTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/CSharpStatementBlockOnTypeFormattingTest.cs index 9e769c37366..9354fecb02c 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/CSharpStatementBlockOnTypeFormattingTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/CSharpStatementBlockOnTypeFormattingTest.cs @@ -4,12 +4,15 @@ #nullable disable using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Razor.Formatting; using Xunit; using Xunit.Abstractions; namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting; -public class CSharpStatementBlockOnTypeFormattingTest(ITestOutputHelper testOutput) : FormattingTestBase(testOutput) +[Collection(HtmlFormattingCollection.Name)] +public class CSharpStatementBlockOnTypeFormattingTest(HtmlFormattingFixture fixture, ITestOutputHelper testOutput) + : FormattingTestBase(fixture.Service, testOutput) { [Fact] public async Task CloseCurly_IfBlock_SingleLineAsync() diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/CodeActionFormattingTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/CodeActionFormattingTest.cs index c7067eae067..b5f33ec5aa6 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/CodeActionFormattingTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/CodeActionFormattingTest.cs @@ -4,12 +4,15 @@ #nullable disable using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Razor.Formatting; using Xunit; using Xunit.Abstractions; namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting; -public class CodeActionFormattingTest(ITestOutputHelper testOutput) : FormattingTestBase(testOutput) +[Collection(HtmlFormattingCollection.Name)] +public class CodeActionFormattingTest(HtmlFormattingFixture fixture, ITestOutputHelper testOutput) + : FormattingTestBase(fixture.Service, testOutput) { [Fact] public async Task AddDebuggerDisplay() diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/CodeDirectiveFormattingTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/CodeDirectiveFormattingTest.cs index 24ca9cf007a..fbccac31ee2 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/CodeDirectiveFormattingTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/CodeDirectiveFormattingTest.cs @@ -5,12 +5,15 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Test.Common; +using Microsoft.CodeAnalysis.Razor.Formatting; using Xunit; using Xunit.Abstractions; namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting; -public class CodeDirectiveFormattingTest(ITestOutputHelper testOutput) : FormattingTestBase(testOutput) +[Collection(HtmlFormattingCollection.Name)] +public class CodeDirectiveFormattingTest(HtmlFormattingFixture fixture, ITestOutputHelper testOutput) + : FormattingTestBase(fixture.Service, testOutput) { internal override bool UseTwoPhaseCompilation => true; diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/CodeDirectiveOnTypeFormattingTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/CodeDirectiveOnTypeFormattingTest.cs index e879f2b59cd..37992612a07 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/CodeDirectiveOnTypeFormattingTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/CodeDirectiveOnTypeFormattingTest.cs @@ -5,12 +5,15 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Test.Common; +using Microsoft.CodeAnalysis.Razor.Formatting; using Xunit; using Xunit.Abstractions; namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting; -public class CodeDirectiveOnTypeFormattingTest(ITestOutputHelper testOutput) : FormattingTestBase(testOutput) +[Collection(HtmlFormattingCollection.Name)] +public class CodeDirectiveOnTypeFormattingTest(HtmlFormattingFixture fixture, ITestOutputHelper testOutput) + : FormattingTestBase(fixture.Service, testOutput) { [Fact] public async Task FormatsIfStatementInComponent() diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingLanguageServerClient.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingLanguageServerClient.cs index f647e7d31d6..5779ad5aa30 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingLanguageServerClient.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingLanguageServerClient.cs @@ -18,8 +18,9 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting; -internal class FormattingLanguageServerClient(ILoggerFactory loggerFactory) : IClientConnection +internal class FormattingLanguageServerClient(HtmlFormattingService htmlFormattingService, ILoggerFactory loggerFactory) : IClientConnection { + private readonly HtmlFormattingService _htmlFormattingService = htmlFormattingService; private readonly Dictionary _documents = []; private readonly ILoggerFactory _loggerFactory = loggerFactory; @@ -36,7 +37,7 @@ private async Task FormatAsync(DocumentOnTypeFo { var generatedHtml = GetGeneratedHtml(@params.TextDocument.Uri); - var edits = await HtmlFormatting.GetOnTypeFormattingEditsAsync(_loggerFactory, @params.TextDocument.Uri, generatedHtml, @params.Position, @params.Options.InsertSpaces, @params.Options.TabSize); + var edits = await _htmlFormattingService.GetOnTypeFormattingEditsAsync(_loggerFactory, @params.TextDocument.Uri, generatedHtml, @params.Position, @params.Options.InsertSpaces, @params.Options.TabSize); return new() { @@ -48,7 +49,7 @@ private async Task FormatAsync(DocumentFormatti { var generatedHtml = GetGeneratedHtml(@params.TextDocument.Uri); - var edits = await HtmlFormatting.GetDocumentFormattingEditsAsync(_loggerFactory, @params.TextDocument.Uri, generatedHtml, @params.Options.InsertSpaces, @params.Options.TabSize); + var edits = await _htmlFormattingService.GetDocumentFormattingEditsAsync(_loggerFactory, @params.TextDocument.Uri, generatedHtml, @params.Options.InsertSpaces, @params.Options.TabSize); return new() { 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 6c3b299990b..8e070db10f5 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 @@ -34,10 +34,14 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting; public class FormattingTestBase : RazorToolingIntegrationTestBase { - public FormattingTestBase(ITestOutputHelper testOutput) + private readonly HtmlFormattingService _htmlFormattingService; + + internal FormattingTestBase(HtmlFormattingService htmlFormattingService, ITestOutputHelper testOutput) : base(testOutput) { ITestOnlyLoggerExtensions.TestOnlyLoggingEnabled = true; + + _htmlFormattingService = htmlFormattingService; } private protected async Task RunFormattingTestAsync( @@ -94,7 +98,7 @@ private async Task RunFormattingTestInternalAsync(string input, string expected, var formattingService = await TestRazorFormattingService.CreateWithFullSupportAsync(LoggerFactory, codeDocument, razorLSPOptions); var documentContext = new DocumentContext(uri, documentSnapshot, projectContext: null); - var client = new FormattingLanguageServerClient(LoggerFactory); + var client = new FormattingLanguageServerClient(_htmlFormattingService, LoggerFactory); client.AddCodeDocument(codeDocument); var htmlFormatter = new HtmlFormatter(client); @@ -159,7 +163,7 @@ private protected async Task RunOnTypeFormattingTestAsync( } else { - var client = new FormattingLanguageServerClient(LoggerFactory); + var client = new FormattingLanguageServerClient(_htmlFormattingService, LoggerFactory); client.AddCodeDocument(codeDocument); var htmlFormatter = new HtmlFormatter(client); @@ -168,7 +172,7 @@ private protected async Task RunOnTypeFormattingTestAsync( } // Assert - var edited = razorSourceText.WithChanges( changes); + var edited = razorSourceText.WithChanges(changes); var actual = edited.ToString(); AssertEx.EqualOrDiff(expected, actual); diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/HtmlFormattingCollection.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/HtmlFormattingCollection.cs new file mode 100644 index 00000000000..c8eacf6f87d --- /dev/null +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/HtmlFormattingCollection.cs @@ -0,0 +1,13 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.Razor.Formatting; +using Xunit; + +namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting; + +[CollectionDefinition(Name)] +public class HtmlFormattingCollection : ICollectionFixture +{ + public const string Name = nameof(HtmlFormattingCollection); +} diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/HtmlFormattingTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/HtmlFormattingTest.cs index 300f2fc42c1..593b32dd64e 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/HtmlFormattingTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/HtmlFormattingTest.cs @@ -14,7 +14,9 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting; -public class HtmlFormattingTest(ITestOutputHelper testOutput) : FormattingTestBase(testOutput) +[Collection(HtmlFormattingCollection.Name)] +public class HtmlFormattingTest(HtmlFormattingFixture fixture, ITestOutputHelper testOutput) + : FormattingTestBase(fixture.Service, testOutput) { internal override bool UseTwoPhaseCompilation => true; diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/RazorFormattingTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/RazorFormattingTest.cs index 27bb6dc6e7c..6a3cd641b89 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/RazorFormattingTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/RazorFormattingTest.cs @@ -7,12 +7,14 @@ using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.LanguageServer.Hosting; using Microsoft.AspNetCore.Razor.Test.Common; +using Microsoft.CodeAnalysis.Razor.Formatting; using Xunit; using Xunit.Abstractions; namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting; -public class RazorFormattingTest(ITestOutputHelper testOutput) : FormattingTestBase(testOutput) +[Collection(HtmlFormattingCollection.Name)] +public class RazorFormattingTest(HtmlFormattingFixture fixture, ITestOutputHelper testOutput) : FormattingTestBase(fixture.Service, testOutput) { [Fact] public async Task Section_BraceOnNextLine() diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Formatting_NetFx/HtmlFormattingFixture.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Formatting_NetFx/HtmlFormattingFixture.cs new file mode 100644 index 00000000000..13881a19c93 --- /dev/null +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Formatting_NetFx/HtmlFormattingFixture.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.CodeAnalysis.Razor.Formatting; + +public class HtmlFormattingFixture : IDisposable +{ + private readonly HtmlFormattingService _htmlFormattingService = new(); + + internal HtmlFormattingService Service => _htmlFormattingService; + + public void Dispose() + { + _htmlFormattingService.Dispose(); + } +} diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Formatting_NetFx/HtmlFormatting.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Formatting_NetFx/HtmlFormattingService.cs similarity index 78% rename from src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Formatting_NetFx/HtmlFormatting.cs rename to src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Formatting_NetFx/HtmlFormattingService.cs index 1879dfadf1d..7c082b276cc 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Formatting_NetFx/HtmlFormatting.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Formatting_NetFx/HtmlFormattingService.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Razor.Test.Common.Mef; using Microsoft.CodeAnalysis.Razor.Logging; using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.Composition; using Microsoft.VisualStudio.LanguageServer.Protocol; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Utilities; @@ -17,9 +18,21 @@ namespace Microsoft.CodeAnalysis.Razor.Formatting; -internal static class HtmlFormatting +internal sealed class HtmlFormattingService : IDisposable { - public static Task GetDocumentFormattingEditsAsync(ILoggerFactory loggerFactory, Uri uri, string generatedHtml, bool insertSpaces, int tabSize) + private ExportProvider? _exportProvider; + + private ExportProvider ExportProvider => _exportProvider ?? (_exportProvider = TestComposition.Editor.ExportProviderFactory.CreateExportProvider()); + + public void Dispose() + { + if (_exportProvider is not null) + { + _exportProvider.Dispose(); + } + } + + public Task GetDocumentFormattingEditsAsync(ILoggerFactory loggerFactory, Uri uri, string generatedHtml, bool insertSpaces, int tabSize) { var request = $$""" { @@ -37,7 +50,7 @@ internal static class HtmlFormatting return CallWebToolsApplyFormattedEditsHandlerAsync(loggerFactory, request, uri, generatedHtml); } - public static Task GetOnTypeFormattingEditsAsync(ILoggerFactory loggerFactory, Uri uri, string generatedHtml, Position position, bool insertSpaces, int tabSize) + public Task GetOnTypeFormattingEditsAsync(ILoggerFactory loggerFactory, Uri uri, string generatedHtml, Position position, bool insertSpaces, int tabSize) { var generatedHtmlSource = SourceText.From(generatedHtml, Encoding.UTF8); var absoluteIndex = generatedHtmlSource.GetRequiredAbsoluteIndex(position); @@ -64,10 +77,9 @@ internal static class HtmlFormatting return CallWebToolsApplyFormattedEditsHandlerAsync(loggerFactory, request, uri, generatedHtml); } - private static async Task CallWebToolsApplyFormattedEditsHandlerAsync(ILoggerFactory loggerFactory, string serializedValue, Uri documentUri, string generatedHtml) + private async Task CallWebToolsApplyFormattedEditsHandlerAsync(ILoggerFactory loggerFactory, string serializedValue, Uri documentUri, string generatedHtml) { - var exportProvider = TestComposition.Editor.ExportProviderFactory.CreateExportProvider(); - var contentTypeService = exportProvider.GetExportedValue(); + var contentTypeService = ExportProvider.GetExportedValue(); lock (contentTypeService) { @@ -77,7 +89,7 @@ internal static class HtmlFormatting } } - var textBufferFactoryService = (ITextBufferFactoryService3)exportProvider.GetExportedValue(); + var textBufferFactoryService = (ITextBufferFactoryService3)ExportProvider.GetExportedValue(); var bufferManager = WebTools.BufferManager.New(contentTypeService, textBufferFactoryService, []); var logger = loggerFactory.GetOrCreateLogger("ApplyFormattedEditsHandler"); var applyFormatEditsHandler = WebTools.ApplyFormatEditsHandler.New(textBufferFactoryService, bufferManager, logger); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostDocumentFormattingEndpointTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostDocumentFormattingEndpointTest.cs index bc9bb3a4507..7370b711fc1 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostDocumentFormattingEndpointTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostDocumentFormattingEndpointTest.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Test.Common; -using Microsoft.AspNetCore.Razor.Test.Common.Mef; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.ExternalAccess.Razor; using Microsoft.CodeAnalysis.Razor.Formatting; @@ -19,8 +18,9 @@ namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost; -[UseExportProvider] -public class CohostDocumentFormattingEndpointTest(ITestOutputHelper testOutputHelper) : CohostEndpointTestBase(testOutputHelper) +[Collection(HtmlFormattingCollection.Name)] +public class CohostDocumentFormattingEndpointTest(HtmlFormattingFixture htmlFormattingFixture, ITestOutputHelper testOutputHelper) + : CohostEndpointTestBase(testOutputHelper) { // All of the formatting tests in the language server exercise the formatting engine and cover various edge cases // and provide regression prevention. The tests here are not exhaustive, but they validate the the cohost endpoints @@ -116,7 +116,7 @@ private async Task VerifyDocumentFormattingAsync(string input, string expected) Assert.NotNull(generatedHtml); var uri = new Uri(document.CreateUri(), $"{document.FilePath}{FeatureOptions.HtmlVirtualDocumentSuffix}"); - var htmlEdits = await HtmlFormatting.GetDocumentFormattingEditsAsync(LoggerFactory, uri, generatedHtml, insertSpaces: true, tabSize: 4); + var htmlEdits = await htmlFormattingFixture.Service.GetDocumentFormattingEditsAsync(LoggerFactory, uri, generatedHtml, insertSpaces: true, tabSize: 4); var requestInvoker = new TestLSPRequestInvoker([(Methods.TextDocumentFormattingName, htmlEdits)]); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostOnTypeFormattingEndpointTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostOnTypeFormattingEndpointTest.cs index 3363c7a2ed6..1919956dad4 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostOnTypeFormattingEndpointTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostOnTypeFormattingEndpointTest.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Test.Common; -using Microsoft.AspNetCore.Razor.Test.Common.Mef; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.ExternalAccess.Razor; using Microsoft.CodeAnalysis.Razor.Formatting; @@ -19,8 +18,9 @@ namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost; -[UseExportProvider] -public class CohostOnTypeFormattingEndpointTest(ITestOutputHelper testOutputHelper) : CohostEndpointTestBase(testOutputHelper) +[Collection(HtmlFormattingCollection.Name)] +public class CohostOnTypeFormattingEndpointTest(HtmlFormattingFixture htmlFormattingFixture, ITestOutputHelper testOutputHelper) + : CohostEndpointTestBase(testOutputHelper) { [Fact] public async Task InvalidTrigger() @@ -115,7 +115,7 @@ private async Task VerifyOnTypeFormattingAsync(TestCode input, string expected, Assert.NotNull(generatedHtml); var uri = new Uri(document.CreateUri(), $"{document.FilePath}{FeatureOptions.HtmlVirtualDocumentSuffix}"); - var htmlEdits = await HtmlFormatting.GetOnTypeFormattingEditsAsync(LoggerFactory, uri, generatedHtml, position, insertSpaces: true, tabSize: 4); + var htmlEdits = await htmlFormattingFixture.Service.GetOnTypeFormattingEditsAsync(LoggerFactory, uri, generatedHtml, position, insertSpaces: true, tabSize: 4); requestInvoker = new TestLSPRequestInvoker([(Methods.TextDocumentOnTypeFormattingName, htmlEdits)]); } diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostRangeFormattingEndpointTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostRangeFormattingEndpointTest.cs index 2b6a6005b8f..97b3db3fd79 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostRangeFormattingEndpointTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostRangeFormattingEndpointTest.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Test.Common; -using Microsoft.AspNetCore.Razor.Test.Common.Mef; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.ExternalAccess.Razor; using Microsoft.CodeAnalysis.Razor.Formatting; @@ -19,8 +18,9 @@ namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost; -[UseExportProvider] -public class CohostRangeFormattingEndpointTest(ITestOutputHelper testOutputHelper) : CohostEndpointTestBase(testOutputHelper) +[Collection(HtmlFormattingCollection.Name)] +public class CohostRangeFormattingEndpointTest(HtmlFormattingFixture htmlFormattingFixture, ITestOutputHelper testOutputHelper) + : CohostEndpointTestBase(testOutputHelper) { [Fact] public Task RangeFormatting() @@ -110,7 +110,7 @@ private async Task VerifyRangeFormattingAsync(TestCode input, string expected) Assert.NotNull(generatedHtml); var uri = new Uri(document.CreateUri(), $"{document.FilePath}{FeatureOptions.HtmlVirtualDocumentSuffix}"); - var htmlEdits = await HtmlFormatting.GetDocumentFormattingEditsAsync(LoggerFactory, uri, generatedHtml, insertSpaces: true, tabSize: 4); + var htmlEdits = await htmlFormattingFixture.Service.GetDocumentFormattingEditsAsync(LoggerFactory, uri, generatedHtml, insertSpaces: true, tabSize: 4); var requestInvoker = new TestLSPRequestInvoker([(Methods.TextDocumentFormattingName, htmlEdits)]); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/HtmlFormattingCollection.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/HtmlFormattingCollection.cs new file mode 100644 index 00000000000..0380b2d5675 --- /dev/null +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/HtmlFormattingCollection.cs @@ -0,0 +1,13 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.Razor.Formatting; +using Xunit; + +namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost; + +[CollectionDefinition(Name)] +public class HtmlFormattingCollection : ICollectionFixture +{ + public const string Name = nameof(HtmlFormattingCollection); +} From f3062e7106c6c3e6e6e8fbaf2c5b77d0f7724034 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Fri, 13 Sep 2024 09:50:31 +1000 Subject: [PATCH 07/14] Have our test service dispose of its export provider --- .../LanguageServer/CSharpTestLspServer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/LanguageServer/CSharpTestLspServer.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/LanguageServer/CSharpTestLspServer.cs index 2b20c842cf4..f095947c978 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/LanguageServer/CSharpTestLspServer.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/LanguageServer/CSharpTestLspServer.cs @@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Razor.Test.Common.LanguageServer; public sealed class CSharpTestLspServer : IAsyncDisposable { private readonly AdhocWorkspace _testWorkspace; - private readonly IRazorLanguageServerTarget _languageServer; + private readonly ExportProvider _exportProvider; private readonly JsonRpc _clientRpc; private readonly JsonRpc _serverRpc; @@ -41,6 +41,7 @@ private CSharpTestLspServer( CancellationToken cancellationToken) { _testWorkspace = testWorkspace; + _exportProvider = exportProvider; _cancellationToken = cancellationToken; var (clientStream, serverStream) = FullDuplexStream.CreatePair(); @@ -67,7 +68,7 @@ private CSharpTestLspServer( _clientRpc.StartListening(); - _languageServer = CreateLanguageServer(_serverRpc, _serverMessageFormatter.JsonSerializerOptions, testWorkspace, languageServerFactory, exportProvider, serverCapabilities); + _ = CreateLanguageServer(_serverRpc, _serverMessageFormatter.JsonSerializerOptions, testWorkspace, languageServerFactory, exportProvider, serverCapabilities); static SystemTextJsonFormatter CreateSystemTextJsonMessageFormatter(AbstractRazorLanguageServerFactoryWrapper languageServerFactory) { @@ -146,6 +147,7 @@ internal Task ExecuteRequestAsync( public async ValueTask DisposeAsync() { _testWorkspace.Dispose(); + _exportProvider.Dispose(); _clientRpc.Dispose(); _clientMessageFormatter.Dispose(); From 851405f3270f88ed341378184e9aec3fa1d6ace9 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Fri, 13 Sep 2024 11:42:33 +1000 Subject: [PATCH 08/14] Remove static caches for export providers Also delete all of the if-def-ed out and redundant code as a result. If we need to do any caching of things, lets just use xunit features and not odd static stuff with custom test attributes etc. --- ...legatedCompletionItemResolverTest.NetFx.cs | 2 - .../DelegatedCompletionListProviderTest.cs | 2 - .../DocumentHighlightEndpointTest.cs | 2 - .../Hover/HoverServiceTest.cs | 2 - .../MapCode/MapCodeTest.cs | 2 - .../RenameEndpointDelegationTest.cs | 2 - .../Refactoring/RenameEndpointTest.cs | 2 - .../Semantic/SemanticTokensTest.cs | 2 - .../SingleServerDelegatingEndpointTestBase.cs | 2 - .../RazorToolingIntegrationTestBase.cs | 2 - .../Mef/ExportProviderCache.cs | 207 ++-------------- .../Mef/TestComposition.cs | 31 +-- .../Mef/UseExportProviderAttribute.cs | 232 ------------------ .../CohostDocumentPullDiagnosticsTest.cs | 2 - 14 files changed, 29 insertions(+), 463 deletions(-) delete mode 100644 src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Mef/UseExportProviderAttribute.cs diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Completion/Delegation/DelegatedCompletionItemResolverTest.NetFx.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Completion/Delegation/DelegatedCompletionItemResolverTest.NetFx.cs index 06fcc14d264..8aeefbf6e87 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Completion/Delegation/DelegatedCompletionItemResolverTest.NetFx.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Completion/Delegation/DelegatedCompletionItemResolverTest.NetFx.cs @@ -12,7 +12,6 @@ using Microsoft.AspNetCore.Razor.LanguageServer.Formatting; using Microsoft.AspNetCore.Razor.LanguageServer.Test; using Microsoft.AspNetCore.Razor.Test.Common.LanguageServer; -using Microsoft.AspNetCore.Razor.Test.Common.Mef; using Microsoft.CodeAnalysis.Razor.Formatting; using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Razor.Protocol; @@ -26,7 +25,6 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Completion.Delegation; -[UseExportProvider] public class DelegatedCompletionItemResolverTest : LanguageServerTestBase { private readonly VSInternalClientCapabilities _clientCapabilities; diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Completion/Delegation/DelegatedCompletionListProviderTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Completion/Delegation/DelegatedCompletionListProviderTest.cs index cc82c40eb4d..faf9392f1f1 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Completion/Delegation/DelegatedCompletionListProviderTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Completion/Delegation/DelegatedCompletionListProviderTest.cs @@ -10,7 +10,6 @@ using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.LanguageServer.Hosting; using Microsoft.AspNetCore.Razor.Test.Common.LanguageServer; -using Microsoft.AspNetCore.Razor.Test.Common.Mef; using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Razor.Protocol; using Microsoft.CodeAnalysis.Testing; @@ -21,7 +20,6 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Completion.Delegation; -[UseExportProvider] public class DelegatedCompletionListProviderTest : LanguageServerTestBase { private readonly TestDelegatedCompletionListProvider _provider; diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/DocumentHighlighting/DocumentHighlightEndpointTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/DocumentHighlighting/DocumentHighlightEndpointTest.cs index 655f2796130..6510f2dd7ee 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/DocumentHighlighting/DocumentHighlightEndpointTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/DocumentHighlighting/DocumentHighlightEndpointTest.cs @@ -11,7 +11,6 @@ using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.LanguageServer.Hosting; using Microsoft.AspNetCore.Razor.Test.Common.LanguageServer; -using Microsoft.AspNetCore.Razor.Test.Common.Mef; using Microsoft.CodeAnalysis.Razor.Protocol; using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.CodeAnalysis.Testing; @@ -23,7 +22,6 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.DocumentHighlighting; -[UseExportProvider] public class DocumentHighlightEndpointTest(ITestOutputHelper testOutput) : LanguageServerTestBase(testOutput) { [Fact] 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 246335632f0..c884946e106 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 @@ -13,7 +13,6 @@ using Microsoft.AspNetCore.Razor.ProjectSystem; using Microsoft.AspNetCore.Razor.Test.Common; using Microsoft.AspNetCore.Razor.Test.Common.LanguageServer; -using Microsoft.AspNetCore.Razor.Test.Common.Mef; using Microsoft.AspNetCore.Razor.Test.Common.ProjectSystem; using Microsoft.CodeAnalysis.Razor.DocumentMapping; using Microsoft.CodeAnalysis.Razor.ProjectSystem; @@ -29,7 +28,6 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Test.Hover; -[UseExportProvider] public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTestBase(testOutput) { private static VSInternalClientCapabilities CreateMarkDownCapabilities() diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/MapCode/MapCodeTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/MapCode/MapCodeTest.cs index 31ea7e1b81e..0fa73622a5a 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/MapCode/MapCodeTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/MapCode/MapCodeTest.cs @@ -10,7 +10,6 @@ using Microsoft.AspNetCore.Razor.LanguageServer.MapCode; using Microsoft.AspNetCore.Razor.Telemetry; using Microsoft.AspNetCore.Razor.Test.Common.LanguageServer; -using Microsoft.AspNetCore.Razor.Test.Common.Mef; using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Razor.Protocol; using Microsoft.CodeAnalysis.Testing; @@ -22,7 +21,6 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Test.MapCode; -[UseExportProvider] public class MapCodeTest(ITestOutputHelper testOutput) : LanguageServerTestBase(testOutput) { private const string RazorFilePath = "C:/path/to/file.razor"; diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Refactoring/RenameEndpointDelegationTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Refactoring/RenameEndpointDelegationTest.cs index e96dbb8b141..0a06da92a94 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Refactoring/RenameEndpointDelegationTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Refactoring/RenameEndpointDelegationTest.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; -using Microsoft.AspNetCore.Razor.Test.Common.Mef; using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Razor.Rename; using Microsoft.CodeAnalysis.Razor.Workspaces; @@ -19,7 +18,6 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Refactoring; -[UseExportProvider] public class RenameEndpointDelegationTest(ITestOutputHelper testOutput) : SingleServerDelegatingEndpointTestBase(testOutput) { [Fact] diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Refactoring/RenameEndpointTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Refactoring/RenameEndpointTest.cs index 1ff45c926a5..2bb699fdf80 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Refactoring/RenameEndpointTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Refactoring/RenameEndpointTest.cs @@ -17,7 +17,6 @@ using Microsoft.AspNetCore.Razor.ProjectSystem; using Microsoft.AspNetCore.Razor.Test.Common; using Microsoft.AspNetCore.Razor.Test.Common.LanguageServer; -using Microsoft.AspNetCore.Razor.Test.Common.Mef; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Razor.DocumentMapping; using Microsoft.CodeAnalysis.Razor.ProjectSystem; @@ -33,7 +32,6 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Refactoring; -[UseExportProvider] public class RenameEndpointTest(ITestOutputHelper testOutput) : LanguageServerTestBase(testOutput) { private static readonly string s_project1BasePath = PathUtilities.CreateRootedPath("First"); 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 455da63ae60..3f0365de187 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 @@ -19,7 +19,6 @@ using Microsoft.AspNetCore.Razor.PooledObjects; using Microsoft.AspNetCore.Razor.Test.Common; using Microsoft.AspNetCore.Razor.Test.Common.LanguageServer; -using Microsoft.AspNetCore.Razor.Test.Common.Mef; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Razor.Protocol; @@ -35,7 +34,6 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Semantic; -[UseExportProvider] public partial class SemanticTokensTest(ITestOutputHelper testOutput) : TagHelperServiceTestBase(testOutput) { private readonly Mock _clientConnection = new(MockBehavior.Strict); diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/SingleServerDelegatingEndpointTestBase.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/SingleServerDelegatingEndpointTestBase.cs index 49c4fbb7423..c2c8405e394 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/SingleServerDelegatingEndpointTestBase.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/SingleServerDelegatingEndpointTestBase.cs @@ -7,7 +7,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Test.Common.LanguageServer; -using Microsoft.AspNetCore.Razor.Test.Common.Mef; using Microsoft.AspNetCore.Razor.Test.Common.ProjectSystem; using Microsoft.CodeAnalysis.Razor.DocumentMapping; using Microsoft.CodeAnalysis.Razor.ProjectSystem; @@ -19,7 +18,6 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer; -[UseExportProvider] public abstract partial class SingleServerDelegatingEndpointTestBase(ITestOutputHelper testOutput) : LanguageServerTestBase(testOutput) { private protected IDocumentContextFactory? DocumentContextFactory { get; private set; } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Language/IntegrationTests/RazorToolingIntegrationTestBase.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Language/IntegrationTests/RazorToolingIntegrationTestBase.cs index 8d4d088e7d9..c7ad99dc1b7 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Language/IntegrationTests/RazorToolingIntegrationTestBase.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Language/IntegrationTests/RazorToolingIntegrationTestBase.cs @@ -12,7 +12,6 @@ using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Razor.PooledObjects; using Microsoft.AspNetCore.Razor.Test.Common; -using Microsoft.AspNetCore.Razor.Test.Common.Mef; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Razor; @@ -22,7 +21,6 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests; -[UseExportProvider] public class RazorToolingIntegrationTestBase : ToolingTestBase { internal const string ArbitraryWindowsPath = "x:\\dir\\subdir\\Test"; diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Mef/ExportProviderCache.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Mef/ExportProviderCache.cs index 2b2a62815a7..e8937fd4a96 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Mef/ExportProviderCache.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Mef/ExportProviderCache.cs @@ -2,14 +2,10 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System; -using System.Collections.Concurrent; using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; using System.Reflection; -using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.VisualStudio.Composition; namespace Microsoft.AspNetCore.Razor.Test.Common.Mef; @@ -18,60 +14,6 @@ public static class ExportProviderCache { private static readonly PartDiscovery s_partDiscovery = CreatePartDiscovery(Resolver.DefaultInstance); - private static readonly TestComposition s_defaultHostExportProviderComposition = TestComposition.Empty - .AddAssemblies(MefHostServices.DefaultAssemblies); - private static readonly ConcurrentDictionary s_scopes = new(); - private const string DefaultScope = "default"; - - private static readonly object s_lock = new(); - - internal static bool Enabled { get; private set; } - - internal static ExportProvider[] ExportProvidersForCleanup - { - get - { - var scopes = s_scopes.Values.ToArray(); - var defaultScope = scopes.Where(scope => scope.Name == DefaultScope); - var allButDefault = scopes.Where(scope => scope.Name != DefaultScope); - - // Make sure to return the default scope as the last element - return allButDefault.Concat(defaultScope) - .Where(scope => scope._currentExportProvider is { }) - .Select(scope => scope._currentExportProvider!) - .ToArray(); - } - } - - internal static void SetEnabled_OnlyUseExportProviderAttributeCanCall(bool value) - { - lock (s_lock) - { - Enabled = value; - if (!Enabled) - { - foreach (var scope in s_scopes.Values.ToArray()) - { - scope.Clear(); - } - } - } - } - - /// - /// Use to create for default instances of . - /// - public static IExportProviderFactory GetOrCreateExportProviderFactory(IEnumerable assemblies) - { - if (assemblies is ImmutableArray assembliesArray && - assembliesArray == MefHostServices.DefaultAssemblies) - { - return s_defaultHostExportProviderComposition.ExportProviderFactory; - } - - return CreateExportProviderFactory(CreateAssemblyCatalog(assemblies), scopeName: DefaultScope); - } - public static ComposableCatalog CreateAssemblyCatalog(IEnumerable assemblies, Resolver? resolver = null) { var discovery = resolver is null ? s_partDiscovery : CreatePartDiscovery(resolver); @@ -125,151 +67,48 @@ bool IsExcludedPart(ComposablePartDefinition part) } } - public static IExportProviderFactory CreateExportProviderFactory(ComposableCatalog catalog, string? scopeName = null) + public static IExportProviderFactory CreateExportProviderFactory(ComposableCatalog catalog) { - var scope = s_scopes.GetOrAdd(scopeName ?? DefaultScope, scopeName => new Scope(scopeName)); var configuration = CompositionConfiguration.Create(catalog.WithCompositionService()); + ValidateConfiguration(configuration); + var runtimeComposition = RuntimeComposition.CreateRuntimeComposition(configuration); var exportProviderFactory = runtimeComposition.CreateExportProviderFactory(); - return new SingleExportProviderFactory(scope, catalog, configuration, exportProviderFactory); + return exportProviderFactory; } - private sealed class SingleExportProviderFactory : IExportProviderFactory + private static void ValidateConfiguration(CompositionConfiguration configuration) { - private readonly Scope _scope; - private readonly ComposableCatalog _catalog; - private readonly CompositionConfiguration _configuration; - private readonly IExportProviderFactory _exportProviderFactory; - - public SingleExportProviderFactory(Scope scope, ComposableCatalog catalog, CompositionConfiguration configuration, IExportProviderFactory exportProviderFactory) + foreach (var errorCollection in configuration.CompositionErrors) { - _scope = scope; - _catalog = catalog; - _configuration = configuration; - _exportProviderFactory = exportProviderFactory; - } - - private ExportProvider GetOrCreateExportProvider() - { - if (!Enabled) - { - // The [UseExportProvider] attribute on tests ensures that the pre- and post-conditions of methods - // in this type are met during test conditions. - throw new InvalidOperationException($"{nameof(ExportProviderCache)} may only be used from tests marked with {nameof(UseExportProviderAttribute)}"); - } - - var expectedCatalog = Interlocked.CompareExchange(ref _scope._expectedCatalog, _catalog, null) ?? _catalog; - RequireForSingleExportProvider(expectedCatalog == _catalog); - - var expected = _scope._expectedProviderForCatalog; - if (expected is null) + foreach (var error in errorCollection) { - foreach (var errorCollection in _configuration.CompositionErrors) + foreach (var part in error.Parts) { - foreach (var error in errorCollection) + foreach (var pair in part.SatisfyingExports) { - foreach (var part in error.Parts) + var (importBinding, exportBindings) = (pair.Key, pair.Value); + if (exportBindings.Count <= 1) { - foreach (var pair in part.SatisfyingExports) - { - var (importBinding, exportBindings) = (pair.Key, pair.Value); - if (exportBindings.Count <= 1) - { - // Ignore composition errors for missing parts - continue; - } + // Ignore composition errors for missing parts + continue; + } - if (importBinding.ImportDefinition.Cardinality != ImportCardinality.ZeroOrMore) - { - // This failure occurs when a binding fails because multiple exports were - // provided but only a single one (at most) is expected. This typically occurs - // when a test ExportProvider is created with a mock implementation without - // first removing a value provided by default. - throw new InvalidOperationException( - "Failed to construct the MEF catalog for testing. Multiple exports were found for a part for which only one export is expected:" + Environment.NewLine - + error.Message); - } - } + if (importBinding.ImportDefinition.Cardinality != ImportCardinality.ZeroOrMore) + { + // This failure occurs when a binding fails because multiple exports were + // provided but only a single one (at most) is expected. This typically occurs + // when a test ExportProvider is created with a mock implementation without + // first removing a value provided by default. + throw new InvalidOperationException( + "Failed to construct the MEF catalog for testing. Multiple exports were found for a part for which only one export is expected:" + Environment.NewLine + + error.Message); } } } - - expected = _exportProviderFactory.CreateExportProvider(); - expected = Interlocked.CompareExchange(ref _scope._expectedProviderForCatalog, expected, null) ?? expected; - Interlocked.CompareExchange(ref _scope._currentExportProvider, expected, null); - } - - var exportProvider = _scope._currentExportProvider; - RequireForSingleExportProvider(exportProvider == expected); - - return exportProvider!; - } - - ExportProvider IExportProviderFactory.CreateExportProvider() - { - // Currently this implementation deviates from the typical behavior of IExportProviderFactory. For the - // duration of a single test, an instance of SingleExportProviderFactory will continue returning the - // same ExportProvider instance each time this method is called. - // - // It may be clearer to refactor the implementation to only allow one call to CreateExportProvider in - // the context of a single test. https://github.com/dotnet/roslyn/issues/25863 - lock (s_lock) - { - return GetOrCreateExportProvider(); } } - - private void RequireForSingleExportProvider(bool condition) - { - if (!condition) - { - // The ExportProvider provides services that act as singleton instances in the context of an - // application (this include cases of multiple exports, where the 'singleton' is the list of all - // exports matching the contract). When reasoning about the behavior of test code, it is valuable to - // know service instances will be used in a consistent manner throughout the execution of a test, - // regardless of whether they are passed as arguments or obtained through requests to the - // ExportProvider. - // - // Restricting a test to a single ExportProvider guarantees that objects that *look* like singletons - // will *behave* like singletons for the duration of the test. Each test is expected to create and - // use its ExportProvider in a consistent manner. - // - // A test that validates remote services is allowed to create a couple of ExportProviders: - // one for local workspace and the other for the remote one. - // - // When this exception is thrown by a test, it typically means one of the following occurred: - // - // * A test failed to pass an ExportProvider via an optional argument to a method, resulting in the - // method attempting to create a default ExportProvider which did not match the one assigned to - // the test. - // * A test attempted to perform multiple test sequences in the context of a single test method, - // rather than break up the test into distinct tests for each case. - // * A test referenced different predefined ExportProvider instances within the context of a test. - // Each test is expected to use the same ExportProvider throughout the test. - throw new InvalidOperationException($"Only one {_scope.Name} {nameof(ExportProvider)} can be created in the context of a single test."); - } - } - } - - private sealed class Scope - { - public readonly string Name; - public ExportProvider? _currentExportProvider; - public ComposableCatalog? _expectedCatalog; - public ExportProvider? _expectedProviderForCatalog; - - public Scope(string name) - { - Name = name; - } - - public void Clear() - { - _currentExportProvider = null; - _expectedCatalog = null; - _expectedProviderForCatalog = null; - } } private sealed class SimpleAssemblyLoader : IAssemblyLoader diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Mef/TestComposition.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Mef/TestComposition.cs index 4144af3544b..d73f291ca9c 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Mef/TestComposition.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Mef/TestComposition.cs @@ -22,8 +22,7 @@ public sealed partial class TestComposition public static readonly TestComposition Empty = new( ImmutableHashSet.Empty, ImmutableHashSet.Empty, - ImmutableHashSet.Empty, - scope: null); + ImmutableHashSet.Empty); public static readonly TestComposition Roslyn = Empty .AddAssemblies(MefHostServices.DefaultAssemblies) @@ -102,32 +101,17 @@ public override int GetHashCode() /// public readonly ImmutableHashSet Parts; - /// - /// The scope in which to create the export provider, or to use the default scope. - /// - public readonly string? Scope; - private readonly Lazy _exportProviderFactory; - private TestComposition(ImmutableHashSet assemblies, ImmutableHashSet parts, ImmutableHashSet excludedPartTypes, string? scope) + private TestComposition(ImmutableHashSet assemblies, ImmutableHashSet parts, ImmutableHashSet excludedPartTypes) { Assemblies = assemblies; Parts = parts; ExcludedPartTypes = excludedPartTypes; - Scope = scope; _exportProviderFactory = new Lazy(GetOrCreateFactory); } -#if false -/// -/// Returns a new instance of for the composition. This will either be a MEF composition or VS MEF composition host, -/// depending on what layer the composition is for. Editor Features and VS layers use VS MEF composition while anything else uses System.Composition. -/// -public HostServices GetHostServices() - => VisualStudioMefHostServices.Create(ExportProviderFactory.CreateExportProvider()); -#endif - /// /// VS MEF . /// @@ -145,7 +129,7 @@ private IExportProviderFactory GetOrCreateFactory() } } - var newFactory = ExportProviderCache.CreateExportProviderFactory(GetCatalog(), Scope); + var newFactory = ExportProviderCache.CreateExportProviderFactory(GetCatalog()); lock (s_factoryCache) { @@ -215,7 +199,7 @@ public TestComposition WithAssemblies(ImmutableHashSet assemblies) var testAssembly = assemblies.FirstOrDefault(IsTestAssembly); Verify.Operation(testAssembly is null, $"Test assemblies are not allowed in test composition: {testAssembly}. Specify explicit test parts instead."); - return new TestComposition(assemblies, Parts, ExcludedPartTypes, Scope); + return new TestComposition(assemblies, Parts, ExcludedPartTypes); static bool IsTestAssembly(Assembly assembly) { @@ -230,13 +214,10 @@ static bool IsTestAssembly(Assembly assembly) } public TestComposition WithParts(ImmutableHashSet parts) - => parts == Parts ? this : new TestComposition(Assemblies, parts, ExcludedPartTypes, Scope); + => parts == Parts ? this : new TestComposition(Assemblies, parts, ExcludedPartTypes); public TestComposition WithExcludedPartTypes(ImmutableHashSet excludedPartTypes) - => excludedPartTypes == ExcludedPartTypes ? this : new TestComposition(Assemblies, Parts, excludedPartTypes, Scope); - - public TestComposition WithScope(string? scope) - => scope == Scope ? this : new TestComposition(Assemblies, Parts, ExcludedPartTypes, scope); + => excludedPartTypes == ExcludedPartTypes ? this : new TestComposition(Assemblies, Parts, excludedPartTypes); /// /// Use for VS MEF composition troubleshooting. diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Mef/UseExportProviderAttribute.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Mef/UseExportProviderAttribute.cs deleted file mode 100644 index 8f2d7ba28f3..00000000000 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Mef/UseExportProviderAttribute.cs +++ /dev/null @@ -1,232 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root for license information. - -using System; -using System.Reflection; -using Microsoft.CodeAnalysis.Host; -using Microsoft.VisualStudio.Composition; -using Xunit.Sdk; - -namespace Microsoft.AspNetCore.Razor.Test.Common.Mef; - -/// -/// This attribute supports tests that need to use a MEF container () directly or -/// indirectly during the test sequence. It ensures production code uniformly handles the export provider created -/// during a test, and cleans up the state before the test completes. -/// -/// -/// This attribute serves several important functions for tests that use state variables which are otherwise -/// shared at runtime: -/// -/// Ensures implementations all use the same , which is -/// the one created by the test. -/// Clears static cached values in production code holding instances of , or any -/// object obtained from it or one of its related interfaces such as . -/// Isolates tests by waiting for asynchronous operations to complete before a test is considered -/// complete. -/// When required, provides a separate for the -/// executing in the test process. If this provider is created during testing, it is cleaned up with the primary -/// export provider during test teardown. -/// -/// -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] -public class UseExportProviderAttribute : BeforeAfterTestAttribute -{ - /// - /// Asynchronous operations are expected to be cancelled at the end of the test that started them. Operations - /// cancelled by the test are cleaned up immediately. The remaining operations are given an opportunity to run - /// to completion. If this timeout is exceeded by the asynchronous operations running after a test completes, - /// the test is failed. - /// - private static readonly TimeSpan s_cleanupTimeout = TimeSpan.FromMinutes(1); - -#if false -private MefHostServices? _hostServices; -#endif - - public override void Before(MethodInfo? methodUnderTest) - { -#if false - MefHostServices.TestAccessor.HookServiceCreation(CreateMefHostServices); - - // make sure we enable this for all unit tests - AsynchronousOperationListenerProvider.Enable(enable: true, diagnostics: true); -#endif - - ExportProviderCache.SetEnabled_OnlyUseExportProviderAttributeCanCall(true); - } - - /// - /// To the extent reasonably possible, this method resets the state of the test environment to the same state as - /// it started, ensuring that tests running in sequence cannot influence the outcome of later tests. - /// - /// - /// The test cleanup runs in two primary steps: - /// - /// Waiting for asynchronous operations started by the test to complete. - /// Disposing of mutable resources created by the test. - /// Clearing static state variables related to the use of MEF during a test. - /// - /// - public override void After(MethodInfo? methodUnderTest) - { - try - { - foreach (var exportProvider in ExportProviderCache.ExportProvidersForCleanup) - { - DisposeExportProvider(exportProvider); - } - } - finally - { -#if false - // Replace hooks with ones that always throw exceptions. These hooks detect cases where code executing - // after the end of a test attempts to create an ExportProvider. - MefHostServices.TestAccessor.HookServiceCreation(DenyMefHostServicesCreationBetweenTests); -#endif - - // Reset static state variables. -#if false - _hostServices = null; -#endif - ExportProviderCache.SetEnabled_OnlyUseExportProviderAttributeCanCall(false); - } - } - - private static void DisposeExportProvider(ExportProvider? exportProvider) - { - if (exportProvider is null) - { - return; - } - - // Dispose of the export provider, including calling Dispose for any IDisposable services created during the test. - using var _ = exportProvider; - -#if false - if (exportProvider.GetExportedValues().SingleOrDefault() is { } listenerProvider) - { - if (exportProvider.GetExportedValues().SingleOrDefault()?.HasMainThread ?? false) - { - // Immediately clear items from the foreground notification service for which cancellation is - // requested. This service maintains a queue separately from Tasks, and work items scheduled for - // execution after a delay are not immediately purged when cancellation is requested. This code - // instructs the service to walk the list of queued work items and immediately cancel and purge any - // which are already cancelled. - var foregroundNotificationService = exportProvider.GetExportedValues().SingleOrDefault() as ForegroundNotificationService; - foregroundNotificationService?.ReleaseCancelledItems(); - } - - // Verify the synchronization context was not used incorrectly - var testExportJoinableTaskContext = exportProvider.GetExportedValues().SingleOrDefault(); - var denyExecutionSynchronizationContext = testExportJoinableTaskContext?.SynchronizationContext as TestExportJoinableTaskContext.DenyExecutionSynchronizationContext; - - // Join remaining operations with a timeout - using (var timeoutTokenSource = new CancellationTokenSource(s_cleanupTimeout)) - { - if (denyExecutionSynchronizationContext is object) - { - // Immediately cancel the test if the synchronization context is improperly used - denyExecutionSynchronizationContext.InvalidSwitch += delegate { timeoutTokenSource.CancelAfter(0); }; - denyExecutionSynchronizationContext.ThrowIfSwitchOccurred(); - } - - try - { - // This attribute cleans up the in-process and out-of-process export providers separately, so we - // don't need to provide a workspace when waiting for operations to complete. - var waiter = ((AsynchronousOperationListenerProvider)listenerProvider).WaitAllDispatcherOperationAndTasksAsync(workspace: null); - waiter.JoinUsingDispatcher(timeoutTokenSource.Token); - } - catch (OperationCanceledException ex) when (timeoutTokenSource.IsCancellationRequested) - { - // If the failure was caused by an invalid thread change, throw that exception - denyExecutionSynchronizationContext?.ThrowIfSwitchOccurred(); - - var messageBuilder = new StringBuilder("Failed to clean up listeners in a timely manner."); - foreach (var token in ((AsynchronousOperationListenerProvider)listenerProvider).GetTokens()) - { - messageBuilder.AppendLine().Append($" {token}"); - } - - throw new TimeoutException(messageBuilder.ToString(), ex); - } - } - - denyExecutionSynchronizationContext?.ThrowIfSwitchOccurred(); - - foreach (var testErrorHandler in exportProvider.GetExportedValues()) - { - var exceptions = testErrorHandler.Exceptions; - if (exceptions.Count > 0) - { - throw new AggregateException("Tests threw unexpected exceptions", exceptions); - } - } - } -#endif - } - -#if false -private MefHostServices CreateMefHostServices(IEnumerable assemblies) -{ - ExportProvider exportProvider; - - if (assemblies is ImmutableArray array && - array == MefHostServices.DefaultAssemblies && - ExportProviderCache.LocalExportProviderForCleanup != null) - { - if (_hostServices != null) - { - return _hostServices; - } - - exportProvider = ExportProviderCache.LocalExportProviderForCleanup; - } - else - { - exportProvider = ExportProviderCache.GetOrCreateExportProviderFactory(assemblies).CreateExportProvider(); - } - - Interlocked.CompareExchange( - ref _hostServices, - new ExportProviderMefHostServices(exportProvider), - null); - - return _hostServices; -} - -private static MefHostServices DenyMefHostServicesCreationBetweenTests(IEnumerable assemblies) -{ - // If you hit this, one of three situations occurred: - // - // 1. A test method that uses ExportProvider is not marked with UseExportProviderAttribute (can also be - // applied to the containing type or a base type. - // 2. A test attempted to create an ExportProvider during the test cleanup operations after the - // ExportProvider was already disposed. - // 3. A test attempted to use an ExportProvider in the constructor of the test, or during the initialization - // of a field in the test class. - throw new InvalidOperationException("Cannot create host services after test tear down."); -} - -private class ExportProviderMefHostServices : MefHostServices, IMefHostExportProvider -{ - private readonly VisualStudioMefHostServices _vsHostServices; - - public ExportProviderMefHostServices(ExportProvider exportProvider) - : base(new ContainerConfiguration().CreateContainer()) - { - _vsHostServices = VisualStudioMefHostServices.Create(exportProvider); - } - - protected internal override HostWorkspaceServices CreateWorkspaceServices(Workspace workspace) - => _vsHostServices.CreateWorkspaceServices(workspace); - - IEnumerable> IMefHostExportProvider.GetExports() - => _vsHostServices.GetExports(); - - IEnumerable> IMefHostExportProvider.GetExports() - => _vsHostServices.GetExports(); -} -#endif -} diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostDocumentPullDiagnosticsTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostDocumentPullDiagnosticsTest.cs index 84b1d414ffd..3895dc088fc 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostDocumentPullDiagnosticsTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostDocumentPullDiagnosticsTest.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Test.Common; -using Microsoft.AspNetCore.Razor.Test.Common.Mef; using Microsoft.CodeAnalysis.Testing; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; @@ -14,7 +13,6 @@ namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost; -[UseExportProvider] public class CohostDocumentPullDiagnosticsTest(ITestOutputHelper testOutputHelper) : CohostEndpointTestBase(testOutputHelper) { [Fact] From b36d507968ac380353cc18aebce1d50d92ac8465 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Fri, 13 Sep 2024 13:36:30 +1000 Subject: [PATCH 09/14] More tests and better validation --- .../CohostDocumentPullDiagnosticsTest.cs | 95 +++++++++++++++---- 1 file changed, 79 insertions(+), 16 deletions(-) diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostDocumentPullDiagnosticsTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostDocumentPullDiagnosticsTest.cs index 3895dc088fc..1bc8fd332cc 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostDocumentPullDiagnosticsTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostDocumentPullDiagnosticsTest.cs @@ -4,10 +4,11 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor; using Microsoft.AspNetCore.Razor.Test.Common; -using Microsoft.CodeAnalysis.Testing; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; +using Roslyn.Test.Utilities; using Xunit; using Xunit.Abstractions; @@ -16,10 +17,10 @@ namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost; public class CohostDocumentPullDiagnosticsTest(ITestOutputHelper testOutputHelper) : CohostEndpointTestBase(testOutputHelper) { [Fact] - public Task IfStatements() + public Task CSharp() => VerifyDiagnosticsAsync("""
- + @code { public void IJustMetYou() @@ -29,30 +30,92 @@ public void IJustMetYou() } """); - private async Task VerifyDiagnosticsAsync(TestCode input) + [Fact] + public Task Razor() + => VerifyDiagnosticsAsync(""" +
+ + {|RZ10012:|} + +
+ """); + + [Fact] + public Task Html() + { + TestCode input = """ +
+ + {|HTM1337:|} + +
+ """; + + return VerifyDiagnosticsAsync(input, + htmlResponse: [new VSInternalDiagnosticReport + { + Diagnostics = + [ + new Diagnostic + { + Code = "HTM1337", + Range = SourceText.From(input.Text).GetRange(input.NamedSpans.First().Value.First()) + } + ] + }]); + } + + [Fact] + public Task CombinedAndNestedDiagnostics() + => VerifyDiagnosticsAsync(""" + @using System.Threading.Tasks; + +
+ + {|RZ10012:|} + + @code + { + public void IJustMetYou() + { + {|CS0103:CallMeMaybe|}(); + } + } + +
+ @{ + {|CS4033:await Task.{|CS1501:Delay|}()|}; + } + + {|RZ9980:

|} +

+ +
+ """); + + private async Task VerifyDiagnosticsAsync(TestCode input, VSInternalDiagnosticReport[]? htmlResponse = null) { var document = await CreateProjectAndRazorDocumentAsync(input.Text, createSeparateRemoteAndLocalWorkspaces: true); var inputText = await document.GetTextAsync(DisposalToken); - var requestInvoker = new TestLSPRequestInvoker(); + var requestInvoker = new TestLSPRequestInvoker([(VSInternalMethods.DocumentPullDiagnosticName, htmlResponse)]); var endpoint = new CohostDocumentPullDiagnosticsEndpoint(RemoteServiceInvoker, TestHtmlDocumentSynchronizer.Instance, requestInvoker, FilePathService, LoggerFactory); var result = await endpoint.GetTestAccessor().HandleRequestAsync(document, DisposalToken); - var actual = result!.SelectMany(d => d.Diagnostics!).ToArray(); - if (input.NamedSpans.Count == 0) - { - Assert.Null(result); - return; - } + var markers = result!.SelectMany(d => d.Diagnostics.AssumeNotNull()).SelectMany(d => + new[] { + (index: inputText.GetTextSpan(d.Range).Start, text: $"{{|{d.Code!.Value.Second}:"), + (index: inputText.GetTextSpan(d.Range).End, text:"|}") + }); - Assert.Equal(input.NamedSpans.Count, actual.Length); - - foreach (var (code, spans) in input.NamedSpans) + var testOutput = input.Text; + foreach (var (index, text) in markers.OrderByDescending(i => i.index)) { - var diagnostic = Assert.Single(actual, d => d.Code == code); - Assert.Equal(spans.First(), inputText.GetTextSpan(diagnostic.Range)); + testOutput = testOutput.Insert(index, text); } + + AssertEx.EqualOrDiff(input.OriginalInput, testOutput); } } From bce408a66db744d0c2c8064195d474750d55e108 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Fri, 13 Sep 2024 14:03:43 +1000 Subject: [PATCH 10/14] Move helper method to somewhere central --- ...ft.AspNetCore.Razor.Microbenchmarks.csproj | 3 ++ .../CSharpTestLspServerHelpers.cs | 32 +------------- ...spNetCore.Razor.Test.Common.Tooling.csproj | 4 -- .../Workspaces/TestWorkspace.cs | 44 +++++++++++++++++++ .../Cohost/CohostEndpointTestBase.cs | 7 +-- 5 files changed, 49 insertions(+), 41 deletions(-) diff --git a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks.csproj b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks.csproj index 1010350dd29..d3179687681 100644 --- a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks.csproj +++ b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks.csproj @@ -43,6 +43,9 @@ TestServices\%(FileName)%(Extension) + + TestServices\%(FileName)%(Extension) + diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/LanguageServer/CSharpTestLspServerHelpers.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/LanguageServer/CSharpTestLspServerHelpers.cs index 83a05dc1abb..52a5b146126 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/LanguageServer/CSharpTestLspServerHelpers.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/LanguageServer/CSharpTestLspServerHelpers.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -14,9 +13,7 @@ using Microsoft.AspNetCore.Razor.Test.Common.Workspaces; using Microsoft.AspNetCore.Razor.Threading; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.ExternalAccess.Razor; -using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Testing; using Microsoft.CodeAnalysis.Text; @@ -109,8 +106,7 @@ private static AdhocWorkspace CreateCSharpTestWorkspace( IRazorSpanMappingService razorSpanMappingService, bool multiTargetProject) { - var hostServices = MefHostServices.Create(exportProvider.AsCompositionContext()); - var workspace = TestWorkspace.Create(hostServices); + var workspace = TestWorkspace.CreateWithDiagnosticAnalyzers(exportProvider); // Add project and solution to workspace var projectInfoNet60 = ProjectInfo.Create( @@ -142,8 +138,6 @@ private static AdhocWorkspace CreateCSharpTestWorkspace( workspace.AddSolution(solutionInfo); - AddAnalyzersToWorkspace(workspace, exportProvider); - // Add document to workspace. We use an IVT method to create the DocumentInfo variable because there's // a special constructor in Roslyn that will help identify the document as belonging to Razor. var languageServerFactory = exportProvider.GetExportedValue(); @@ -172,30 +166,6 @@ private static AdhocWorkspace CreateCSharpTestWorkspace( return workspace; } - public static void AddAnalyzersToWorkspace(Workspace workspace, ExportProvider exportProvider) - { - var analyzerLoader = RazorTestAnalyzerLoader.CreateAnalyzerAssemblyLoader(); - - var analyzerPaths = new DirectoryInfo(AppContext.BaseDirectory).GetFiles("*.dll") - .Where(f => f.Name.StartsWith("Microsoft.CodeAnalysis.", StringComparison.Ordinal) && !f.Name.Contains("LanguageServer") && !f.Name.Contains("Test.Utilities")) - .Select(f => f.FullName) - .ToImmutableArray(); - var references = new List(); - foreach (var analyzerPath in analyzerPaths) - { - if (File.Exists(analyzerPath)) - { - references.Add(new AnalyzerFileReference(analyzerPath, analyzerLoader)); - } - } - - workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences(references)); - - // Make sure Roslyn is producing diagnostics for our workspace - var razorTestAnalyzerLoader = exportProvider.GetExportedValue(); - razorTestAnalyzerLoader.InitializeDiagnosticsServices(workspace); - } - private record CSharpFile(Uri DocumentUri, SourceText CSharpSourceText); private class EmptyMappingService : IRazorSpanMappingService diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Microsoft.AspNetCore.Razor.Test.Common.Tooling.csproj b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Microsoft.AspNetCore.Razor.Test.Common.Tooling.csproj index 89019499fa8..6683bf95cdb 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Microsoft.AspNetCore.Razor.Test.Common.Tooling.csproj +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Microsoft.AspNetCore.Razor.Test.Common.Tooling.csproj @@ -89,10 +89,6 @@ - - - - diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Workspaces/TestWorkspace.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Workspaces/TestWorkspace.cs index cae18632e56..ffb3c8de509 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Workspaces/TestWorkspace.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Workspaces/TestWorkspace.cs @@ -2,8 +2,17 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using Microsoft.AspNetCore.Razor.Test.Common.Mef; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.ExternalAccess.Razor; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.VisualStudio.Composition; namespace Microsoft.AspNetCore.Razor.Test.Common.Workspaces; @@ -14,6 +23,17 @@ public static class TestWorkspace public static Workspace Create(Action? configure = null) => Create(services: null, configure: configure); + public static AdhocWorkspace CreateWithDiagnosticAnalyzers(ExportProvider exportProvider) + { + var hostServices = MefHostServices.Create(exportProvider.AsCompositionContext()); + + var workspace = Create(hostServices); + + AddAnalyzersToWorkspace(workspace, exportProvider); + + return workspace; + } + public static AdhocWorkspace Create(HostServices? services, Action? configure = null) { lock (s_workspaceLock) @@ -27,4 +47,28 @@ public static AdhocWorkspace Create(HostServices? services, Action f.Name.StartsWith("Microsoft.CodeAnalysis.", StringComparison.Ordinal) && !f.Name.Contains("LanguageServer") && !f.Name.Contains("Test.Utilities")) + .Select(f => f.FullName) + .ToImmutableArray(); + var references = new List(); + foreach (var analyzerPath in analyzerPaths) + { + if (File.Exists(analyzerPath)) + { + references.Add(new AnalyzerFileReference(analyzerPath, analyzerLoader)); + } + } + + workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences(references)); + + // Make sure Roslyn is producing diagnostics for our workspace + var razorTestAnalyzerLoader = exportProvider.GetExportedValue(); + razorTestAnalyzerLoader.InitializeDiagnosticsServices(workspace); + } } 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 abd502ca203..bb50863d647 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 @@ -9,11 +9,9 @@ using Microsoft.AspNetCore.Razor; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Test.Common; -using Microsoft.AspNetCore.Razor.Test.Common.LanguageServer; using Microsoft.AspNetCore.Razor.Test.Common.Mef; using Microsoft.AspNetCore.Razor.Test.Common.Workspaces; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Razor.Remote; using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.CodeAnalysis.Remote.Razor; @@ -110,11 +108,8 @@ private async Task CreateLocalProjectAndRazorDocumentAsync(Solutio { var exportProvider = TestComposition.Roslyn.ExportProviderFactory.CreateExportProvider(); AddDisposable(exportProvider); - var hostServices = MefHostServices.Create(exportProvider.AsCompositionContext()); - var workspace = TestWorkspace.Create(hostServices); + var workspace = TestWorkspace.CreateWithDiagnosticAnalyzers(exportProvider); AddDisposable(workspace); - // Adding analyzers modifies the workspace, so important to do it before creating the first project - CSharpTestLspServerHelpers.AddAnalyzersToWorkspace(workspace, exportProvider); var razorDocument = CreateProjectAndRazorDocument(workspace, projectId, projectName, documentId, documentFilePath, contents, additionalFiles); From c349de7362e5b4b1f0226edb3d1726ef917d559f Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 18 Sep 2024 07:20:51 +1000 Subject: [PATCH 11/14] Bump Roslyn to 4.12.0-3.24466.4 --- eng/Version.Details.xml | 76 ++++++++++++++++++++--------------------- eng/Versions.props | 38 ++++++++++----------- 2 files changed, 57 insertions(+), 57 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 7ea417466f4..9937b0aea87 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -11,82 +11,82 @@ ad3c9aa85596f42c6a483233c50fab8cee8c412a - + https://github.com/dotnet/roslyn - 9f86520c46f67d2a8a59af189f8fd87e35c574bb + 7b7951aa13c50ad768538e58ed3805898b058928 - + https://github.com/dotnet/roslyn - 9f86520c46f67d2a8a59af189f8fd87e35c574bb + 7b7951aa13c50ad768538e58ed3805898b058928 - + https://github.com/dotnet/roslyn - 9f86520c46f67d2a8a59af189f8fd87e35c574bb + 7b7951aa13c50ad768538e58ed3805898b058928 - + https://github.com/dotnet/roslyn - 9f86520c46f67d2a8a59af189f8fd87e35c574bb + 7b7951aa13c50ad768538e58ed3805898b058928 - + https://github.com/dotnet/roslyn - 9f86520c46f67d2a8a59af189f8fd87e35c574bb + 7b7951aa13c50ad768538e58ed3805898b058928 - + https://github.com/dotnet/roslyn - 9f86520c46f67d2a8a59af189f8fd87e35c574bb + 7b7951aa13c50ad768538e58ed3805898b058928 - + https://github.com/dotnet/roslyn - 9f86520c46f67d2a8a59af189f8fd87e35c574bb + 7b7951aa13c50ad768538e58ed3805898b058928 - + https://github.com/dotnet/roslyn - 9f86520c46f67d2a8a59af189f8fd87e35c574bb + 7b7951aa13c50ad768538e58ed3805898b058928 - + https://github.com/dotnet/roslyn - 9f86520c46f67d2a8a59af189f8fd87e35c574bb + 7b7951aa13c50ad768538e58ed3805898b058928 - + https://github.com/dotnet/roslyn - 9f86520c46f67d2a8a59af189f8fd87e35c574bb + 7b7951aa13c50ad768538e58ed3805898b058928 - + https://github.com/dotnet/roslyn - 9f86520c46f67d2a8a59af189f8fd87e35c574bb + 7b7951aa13c50ad768538e58ed3805898b058928 - + https://github.com/dotnet/roslyn - 9f86520c46f67d2a8a59af189f8fd87e35c574bb + 7b7951aa13c50ad768538e58ed3805898b058928 - + https://github.com/dotnet/roslyn - 9f86520c46f67d2a8a59af189f8fd87e35c574bb + 7b7951aa13c50ad768538e58ed3805898b058928 - + https://github.com/dotnet/roslyn - 9f86520c46f67d2a8a59af189f8fd87e35c574bb + 7b7951aa13c50ad768538e58ed3805898b058928 - + https://github.com/dotnet/roslyn - 9f86520c46f67d2a8a59af189f8fd87e35c574bb + 7b7951aa13c50ad768538e58ed3805898b058928 - + https://github.com/dotnet/roslyn - 9f86520c46f67d2a8a59af189f8fd87e35c574bb + 7b7951aa13c50ad768538e58ed3805898b058928 - + https://github.com/dotnet/roslyn - 9f86520c46f67d2a8a59af189f8fd87e35c574bb + 7b7951aa13c50ad768538e58ed3805898b058928 - + https://github.com/dotnet/roslyn - 9f86520c46f67d2a8a59af189f8fd87e35c574bb + 7b7951aa13c50ad768538e58ed3805898b058928 - + https://github.com/dotnet/roslyn - 9f86520c46f67d2a8a59af189f8fd87e35c574bb + 7b7951aa13c50ad768538e58ed3805898b058928 diff --git a/eng/Versions.props b/eng/Versions.props index 707ebbb49ad..a78ad1ba11c 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -53,25 +53,25 @@ 9.0.0-beta.24453.1 1.0.0-beta.23475.1 1.0.0-beta.23475.1 - 4.12.0-3.24454.5 - 4.12.0-3.24454.5 - 4.12.0-3.24454.5 - 4.12.0-3.24454.5 - 4.12.0-3.24454.5 - 4.12.0-3.24454.5 - 4.12.0-3.24454.5 - 4.12.0-3.24454.5 - 4.12.0-3.24454.5 - 4.12.0-3.24454.5 - 4.12.0-3.24454.5 - 4.12.0-3.24454.5 - 4.12.0-3.24454.5 - 4.12.0-3.24454.5 - 4.12.0-3.24454.5 - 4.12.0-3.24454.5 - 4.12.0-3.24454.5 - 4.12.0-3.24454.5 - 4.12.0-3.24454.5 + 4.12.0-3.24466.4 + 4.12.0-3.24466.4 + 4.12.0-3.24466.4 + 4.12.0-3.24466.4 + 4.12.0-3.24466.4 + 4.12.0-3.24466.4 + 4.12.0-3.24466.4 + 4.12.0-3.24466.4 + 4.12.0-3.24466.4 + 4.12.0-3.24466.4 + 4.12.0-3.24466.4 + 4.12.0-3.24466.4 + 4.12.0-3.24466.4 + 4.12.0-3.24466.4 + 4.12.0-3.24466.4 + 4.12.0-3.24466.4 + 4.12.0-3.24466.4 + 4.12.0-3.24466.4 + 4.12.0-3.24466.4