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
This commit is contained in:
David Wengier 2024-09-11 21:50:06 +10:00
Родитель 4736846c58
Коммит 37ca607429
4 изменённых файлов: 71 добавлений и 38 удалений

Просмотреть файл

@ -5,7 +5,7 @@ using System.Collections.Immutable;
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<ImmutableArray<RoslynLspDiagnostic>> GetDiagnosticsAsync(
JsonSerializableRazorPinnedSolutionInfoWrapper solutionInfo,
JsonSerializableDocumentId documentId,
ImmutableArray<RoslynLspDiagnostic> csharpDiagnostics,
ImmutableArray<RoslynLspDiagnostic> htmlDiagnostics,
RoslynLspDiagnostic[] csharpDiagnostics,
RoslynLspDiagnostic[] htmlDiagnostics,
CancellationToken cancellationToken);
}

Просмотреть файл

@ -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 @@ internal sealed class RemoteDiagnosticsService(in ServiceArgs args) : RazorDocum
=> new RemoteDiagnosticsService(in args);
}
private readonly RazorTranslateDiagnosticsService _translateDiagnosticsService = args.ExportProvider.GetExportedValue<RazorTranslateDiagnosticsService>();
public ValueTask<ImmutableArray<LspDiagnostic>> GetDiagnosticsAsync(
JsonSerializableRazorPinnedSolutionInfoWrapper solutionInfo,
JsonSerializableDocumentId documentId,
ImmutableArray<LspDiagnostic> csharpDiagnostics,
ImmutableArray<LspDiagnostic> htmlDiagnostics,
LspDiagnostic[] csharpDiagnostics,
LspDiagnostic[] htmlDiagnostics,
CancellationToken cancellationToken)
=> RunServiceAsync(
solutionInfo,
@ -33,11 +39,21 @@ internal sealed class RemoteDiagnosticsService(in ServiceArgs args) : RazorDocum
private async ValueTask<ImmutableArray<LspDiagnostic>> GetDiagnosticsAsync(
RemoteDocumentContext context,
ImmutableArray<LspDiagnostic> csharpDiagnostics,
ImmutableArray<LspDiagnostic> 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<LspDiagnostic>(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();
}
}

Просмотреть файл

@ -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)
{
}

Просмотреть файл

@ -11,7 +11,6 @@ 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;
@ -22,8 +21,7 @@ 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;
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<VSInternalDocumentDiagnosticsParams, RoslynVSInternalDiagnosticReport[]?>, IDynamicRegistrationProvider
: AbstractRazorCohostDocumentRequestHandler<VSInternalDocumentDiagnosticsParams, VSInternalDiagnosticReport[]?>, 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<RoslynVSInternalDiagnosticReport[]?> HandleRequestAsync(VSInternalDocumentDiagnosticsParams request, RazorCohostRequestContext context, CancellationToken cancellationToken)
protected override Task<VSInternalDiagnosticReport[]?> HandleRequestAsync(VSInternalDocumentDiagnosticsParams request, RazorCohostRequestContext context, CancellationToken cancellationToken)
=> HandleRequestAsync(context.TextDocument.AssumeNotNull(), cancellationToken);
private async Task<RoslynVSInternalDiagnosticReport[]?> HandleRequestAsync(TextDocument razorDocument, CancellationToken cancellationToken)
private async Task<VSInternalDiagnosticReport[]?> 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<IRemoteDiagnosticsService, ImmutableArray<RoslynLspDiagnostic>>(
var diagnostics = await _remoteServiceInvoker.TryInvokeAsync<IRemoteDiagnosticsService, ImmutableArray<LspDiagnostic>>(
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<ImmutableArray<RoslynLspDiagnostic>> GetCSharpDiagnosticsAsync(TextDocument razorDocument, CancellationToken cancellationToken)
private async Task<LspDiagnostic[]> 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 @@ internal class CohostDocumentPullDiagnosticsEndpoint(
if (razorDocument.Project.Solution.GetDocumentIdsWithFilePath(csharpFilePath) is not [{ } generatedDocumentId] ||
razorDocument.Project.GetDocument(generatedDocumentId) is not { } generatedDocument)
{
return SpecializedTasks.EmptyImmutableArray<RoslynLspDiagnostic>();
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);
}
private async Task<ImmutableArray<RoslynLspDiagnostic>> GetHtmlDiagnosticsAsync(TextDocument razorDocument, CancellationToken cancellationToken)
if (JsonSerializer.Deserialize<LspDiagnostic[]>(JsonSerializer.SerializeToDocument(csharpDiagnostics), options) is not { } convertedDiagnostics)
{
return [];
}
return convertedDiagnostics;
}
private async Task<LspDiagnostic[]> GetHtmlDiagnosticsAsync(TextDocument razorDocument, CancellationToken cancellationToken)
{
var htmlDocument = await _htmlDocumentSynchronizer.TryGetSynchronizedHtmlDocumentAsync(razorDocument, cancellationToken).ConfigureAwait(false);
if (htmlDocument is null)
@ -169,21 +182,8 @@ internal class CohostDocumentPullDiagnosticsEndpoint(
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<RoslynVSInternalDiagnosticReport[]>(JsonSerializer.SerializeToDocument(result.Response), options);
if (hmlDiagnostics is not { } convertedHtmlDiagnostics)
{
return [];
}
using var allDiagnostics = new PooledArrayBuilder<RoslynLspDiagnostic>();
foreach (var report in convertedHtmlDiagnostics)
using var allDiagnostics = new PooledArrayBuilder<LspDiagnostic>();
foreach (var report in result.Response)
{
if (report.Diagnostics is not null)
{
@ -191,14 +191,14 @@ internal class CohostDocumentPullDiagnosticsEndpoint(
}
}
return allDiagnostics.ToImmutable();
return allDiagnostics.ToArray();
}
internal TestAccessor GetTestAccessor() => new(this);
internal readonly struct TestAccessor(CohostDocumentPullDiagnosticsEndpoint instance)
{
public Task<RoslynVSInternalDiagnosticReport[]?> HandleRequestAsync(TextDocument razorDocument, CancellationToken cancellationToken)
public Task<VSInternalDiagnosticReport[]?> HandleRequestAsync(TextDocument razorDocument, CancellationToken cancellationToken)
=> instance.HandleRequestAsync(razorDocument, cancellationToken);
}
}