|
| 1 | +// Copyright (c) .NET Foundation. All rights reserved. |
| 2 | +// Licensed under the MIT license. See License.txt in the project root for license information. |
| 3 | + |
| 4 | +using System; |
| 5 | +using System.Threading; |
| 6 | +using System.Threading.Tasks; |
| 7 | +using Microsoft.AspNetCore.Razor.Language; |
| 8 | +using Microsoft.AspNetCore.Razor.LanguageServer.Diagnostics; |
| 9 | +using Microsoft.AspNetCore.Razor.LanguageServer.EndpointContracts; |
| 10 | +using Microsoft.AspNetCore.Razor.LanguageServer.Hosting; |
| 11 | +using Microsoft.AspNetCore.Razor.PooledObjects; |
| 12 | +using Microsoft.AspNetCore.Razor.ProjectSystem; |
| 13 | +using Microsoft.AspNetCore.Razor.Telemetry; |
| 14 | +using Microsoft.CodeAnalysis.Razor.Diagnostics; |
| 15 | +using Microsoft.CodeAnalysis.Razor.Protocol; |
| 16 | +using Microsoft.CodeAnalysis.Razor.Workspaces.Telemetry; |
| 17 | +using Microsoft.VisualStudio.LanguageServer.Protocol; |
| 18 | + |
| 19 | +namespace Microsoft.AspNetCore.Razor.LanguageServer.Hosting.Diagnostics; |
| 20 | + |
| 21 | +[RazorLanguageServerEndpoint(Methods.TextDocumentDiagnosticName)] |
| 22 | +internal sealed class DocumentDiagnosticsEndpoint( |
| 23 | + RazorTranslateDiagnosticsService translateDiagnosticsService, |
| 24 | + IClientConnection clientConnection, |
| 25 | + ITelemetryReporter? telemetryReporter) |
| 26 | + : IRazorRequestHandler<DocumentDiagnosticParams, FullDocumentDiagnosticReport?>, ICapabilitiesProvider |
| 27 | +{ |
| 28 | + private readonly RazorTranslateDiagnosticsService _translateDiagnosticsService = translateDiagnosticsService; |
| 29 | + private readonly IClientConnection _clientConnection = clientConnection; |
| 30 | + private readonly ITelemetryReporter? _telemetryReporter = telemetryReporter; |
| 31 | + private readonly MissingTagHelperTelemetryReporter? _missingTagHelperTelemetryReporter = telemetryReporter is null ? null : new(telemetryReporter); |
| 32 | + |
| 33 | + public bool MutatesSolutionState => false; |
| 34 | + |
| 35 | + public TextDocumentIdentifier GetTextDocumentIdentifier(DocumentDiagnosticParams request) |
| 36 | + => request.TextDocument; |
| 37 | + |
| 38 | + public void ApplyCapabilities(VSInternalServerCapabilities serverCapabilities, VSInternalClientCapabilities clientCapabilities) |
| 39 | + { |
| 40 | + serverCapabilities.SupportsDiagnosticRequests = true; |
| 41 | + serverCapabilities.DiagnosticOptions = new() |
| 42 | + { |
| 43 | + InterFileDependencies = false, |
| 44 | + WorkspaceDiagnostics = false, |
| 45 | + WorkDoneProgress = false |
| 46 | + }; |
| 47 | + } |
| 48 | + |
| 49 | + public async Task<FullDocumentDiagnosticReport?> HandleRequestAsync(DocumentDiagnosticParams request, RazorRequestContext context, CancellationToken cancellationToken) |
| 50 | + { |
| 51 | + var documentContext = context.DocumentContext; |
| 52 | + if (documentContext is null) |
| 53 | + { |
| 54 | + return null; |
| 55 | + } |
| 56 | + |
| 57 | + var correlationId = Guid.NewGuid(); |
| 58 | + using var __ = _telemetryReporter?.TrackLspRequest(Methods.TextDocumentDiagnosticName, LanguageServerConstants.RazorLanguageServerName, TelemetryThresholds.DiagnosticsRazorTelemetryThreshold, correlationId); |
| 59 | + |
| 60 | + var documentSnapshot = documentContext.Snapshot; |
| 61 | + var razorDiagnostics = await RazorDiagnosticHelper.GetRazorDiagnosticsAsync(documentSnapshot, cancellationToken).ConfigureAwait(false); |
| 62 | + var csharpDiagnostics = await GetCSharpDiagnosticsAsync(documentSnapshot, request.TextDocument, correlationId, cancellationToken).ConfigureAwait(false); |
| 63 | + |
| 64 | + var diagnosticCount = |
| 65 | + (razorDiagnostics?.Length ?? 0) + |
| 66 | + (csharpDiagnostics?.Length ?? 0); |
| 67 | + |
| 68 | + using var _ = ListPool<Diagnostic>.GetPooledObject(out var allDiagnostics); |
| 69 | + allDiagnostics.SetCapacityIfLarger(diagnosticCount); |
| 70 | + |
| 71 | + if (razorDiagnostics is not null) |
| 72 | + { |
| 73 | + // No extra work to do for Razor diagnostics |
| 74 | + allDiagnostics.AddRange(razorDiagnostics); |
| 75 | + |
| 76 | + if (_missingTagHelperTelemetryReporter is not null) |
| 77 | + { |
| 78 | + await _missingTagHelperTelemetryReporter.ReportRZ10012TelemetryAsync(documentContext, razorDiagnostics, cancellationToken).ConfigureAwait(false); |
| 79 | + } |
| 80 | + } |
| 81 | + |
| 82 | + if (csharpDiagnostics is not null) |
| 83 | + { |
| 84 | + var mappedDiagnostics = await _translateDiagnosticsService |
| 85 | + .TranslateAsync(RazorLanguageKind.CSharp, csharpDiagnostics, documentSnapshot, cancellationToken) |
| 86 | + .ConfigureAwait(false); |
| 87 | + allDiagnostics.AddRange(mappedDiagnostics); |
| 88 | + } |
| 89 | + |
| 90 | + return new() |
| 91 | + { |
| 92 | + Items = [.. allDiagnostics] |
| 93 | + }; |
| 94 | + } |
| 95 | + |
| 96 | + private async Task<Diagnostic[]?> GetCSharpDiagnosticsAsync(IDocumentSnapshot documentSnapshot, TextDocumentIdentifier razorDocumentIdentifier, Guid correlationId, CancellationToken cancellationToken) |
| 97 | + { |
| 98 | + var delegatedParams = new DelegatedDiagnosticParams( |
| 99 | + new(razorDocumentIdentifier, documentSnapshot.Version), |
| 100 | + correlationId |
| 101 | + ); |
| 102 | + |
| 103 | + var delegatedResponse = await _clientConnection |
| 104 | + .SendRequestAsync<DelegatedDiagnosticParams, SumType<FullDocumentDiagnosticReport, UnchangedDocumentDiagnosticReport>?>( |
| 105 | + CustomMessageNames.RazorCSharpPullDiagnosticsEndpointName, |
| 106 | + delegatedParams, |
| 107 | + cancellationToken) |
| 108 | + .ConfigureAwait(false); |
| 109 | + |
| 110 | + return delegatedResponse.HasValue |
| 111 | + ? delegatedResponse.Value.TryGetFirst(out var fullReport) |
| 112 | + ? fullReport.Items |
| 113 | + : null |
| 114 | + : null; |
| 115 | + } |
| 116 | +} |
0 commit comments