Skip to content

Commit

Permalink
Allow a IDynamicFileInfoProvider to be able to provide LSP diagnostics
Browse files Browse the repository at this point in the history
ASP.NET Razor is prototyping a system where they can add files into
our workspace via IDynamicFileInfoProvider and can then query our LSP
server to get diagnostics about those files. They're normally closed
files but we want them to get analyzed always if they want to query
them via LSP, and we also don't want those diagnostics to end up in the
error list since further processing needs to be done with them with
LSP.

This implements a hook where Razor can specify that diagnostics produced
for their documents are ingored by most of the system, but can still
be queried through their special LSP server.
  • Loading branch information
jasonmalinowski committed Mar 14, 2020
1 parent bb5c4c8 commit aeb8b22
Show file tree
Hide file tree
Showing 8 changed files with 62 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,12 @@ public Task NewSolutionSnapshotAsync(Solution solution, CancellationToken cancel

private static bool AnalysisEnabled(Document document)
{
if (document.Services.GetService<DocumentPropertiesService>()?.DiagnosticsLspClientName != null)
{
// This is a generated Razor document, and they want diagnostics, so let's report it
return true;
}

// change it to check active file (or visible files), not open files if active file tracking is enabled.
// otherwise, use open file.
return document.IsOpen() && document.SupportsDiagnostics();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.LanguageServer;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
Expand All @@ -32,14 +33,15 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService
internal class InProcLanguageServer
{
private readonly IDiagnosticService _diagnosticService;
private readonly string? _clientName;
private readonly JsonRpc _jsonRpc;
private readonly LanguageServerProtocol _protocol;
private readonly Workspace _workspace;

private VSClientCapabilities? _clientCapabilities;

public InProcLanguageServer(Stream inputStream, Stream outputStream, LanguageServerProtocol protocol,
Workspace workspace, IDiagnosticService diagnosticService)
Workspace workspace, IDiagnosticService diagnosticService, string? clientName)
{
_protocol = protocol;
_workspace = workspace;
Expand All @@ -48,6 +50,7 @@ public InProcLanguageServer(Stream inputStream, Stream outputStream, LanguageSer
_jsonRpc.StartListening();

_diagnosticService = diagnosticService;
_clientName = clientName;
_diagnosticService.DiagnosticsUpdated += DiagnosticService_DiagnosticsUpdated;
}

Expand Down Expand Up @@ -237,7 +240,9 @@ protected virtual async Task PublishDiagnosticsAsync(Document document)

private async Task<Dictionary<Uri, LanguageServer.Protocol.Diagnostic[]>> GetDiagnosticsAsync(Document document, CancellationToken cancellationToken)
{
var diagnostics = _diagnosticService.GetDiagnostics(document.Project.Solution.Workspace, document.Project.Id, document.Id, null, false, cancellationToken);
var diagnostics = _diagnosticService.GetDiagnostics(document.Project.Solution.Workspace, document.Project.Id, document.Id, null, false, cancellationToken)
.Where(IncludeDiagnostic);

var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

// Razor documents can import other razor documents
Expand Down Expand Up @@ -274,6 +279,18 @@ static LanguageServer.Protocol.Diagnostic ConvertToLspDiagnostic(DiagnosticData
}
}

private bool IncludeDiagnostic(DiagnosticData diagnostic)
{
if (!diagnostic.Properties.TryGetValue(nameof(DocumentPropertiesService.DiagnosticsLspClientName), out var diagnosticClientName))
{
// This diagnostic is not restricted to a specific LSP client, so just pass it through
return true;
}

// We only include this diagnostic if it directly matches our name.
return diagnosticClientName == _clientName;
}

private static LanguageServer.Protocol.Range? GetDiagnosticRange(DiagnosticDataLocation? diagnosticDataLocation, SourceText text)
{
(var startLine, var endLine) = DiagnosticData.GetLinePositions(diagnosticDataLocation, text, useMapped: true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public LiveShareLanguageServerClient(LanguageServerProtocol languageServerProtoc
public Task<Connection> ActivateAsync(CancellationToken token)
{
var (clientStream, serverStream) = FullDuplexStream.CreatePair();
_ = new InProcLanguageServer(serverStream, serverStream, _languageServerProtocol, _workspace, _diagnosticService);
_ = new InProcLanguageServer(serverStream, serverStream, _languageServerProtocol, _workspace, _diagnosticService, clientName: null);
return Task.FromResult(new Connection(clientStream, clientStream));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,16 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Lsp
/// </summary>
[ContentType(ContentTypeNames.CSharpLspContentTypeName)]
[ContentType(ContentTypeNames.VBLspContentTypeName)]
[ClientName("RazorCSharp")]
[ClientName(ClientName)]
[Export(typeof(ILanguageClient))]
class RazorLanguageClient : ILanguageClient
{
private readonly IDiagnosticService _diagnosticService;
private readonly LanguageServerProtocol _languageServerProtocol;
private readonly Workspace _workspace;

public const string ClientName = "RazorCSharp";

/// <summary>
/// Gets the name of the language client (displayed to the user).
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@

#nullable enable

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.LanguageServer;
Expand All @@ -19,7 +15,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Lsp
class RazorLanguageServer : InProcLanguageServer
{
public RazorLanguageServer(Stream inputStream, Stream outputStream, LanguageServerProtocol protocol, Workspace workspace, IDiagnosticService diagnosticService)
: base(inputStream, outputStream, protocol, workspace, diagnosticService)
: base(inputStream, outputStream, protocol, workspace, diagnosticService, clientName: RazorLanguageClient.ClientName)
{
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Common;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Imaging.Interop;
Expand Down Expand Up @@ -188,6 +189,12 @@ private static bool ShouldInclude(DiagnosticData diagnostic)
return false;
}

// If this diagnostic is for LSP only, then we won't show it here
if (diagnostic.Properties.ContainsKey(nameof(DocumentPropertiesService.DiagnosticsLspClientName)))
{
return false;
}

switch (diagnostic.Severity)
{
case DiagnosticSeverity.Info:
Expand Down
18 changes: 17 additions & 1 deletion src/Workspaces/Core/Portable/Diagnostics/DiagnosticData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
Expand Down Expand Up @@ -348,13 +349,28 @@ public static DiagnosticData Create(Diagnostic diagnostic, Document document)
.WhereNotNull()
.ToReadOnlyCollection();

var additionalProperties = GetAdditionalProperties(document, diagnostic);

var documentPropertiesService = document.Services.GetService<DocumentPropertiesService>();
var diagnosticsLspClientName = documentPropertiesService?.DiagnosticsLspClientName;

if (diagnosticsLspClientName != null)
{
if (additionalProperties == null)
{
additionalProperties = ImmutableDictionary.Create<string, string>();
}

additionalProperties = additionalProperties.Add(nameof(documentPropertiesService.DiagnosticsLspClientName), diagnosticsLspClientName);
}

return Create(diagnostic,
project.Id,
project.Language,
project.Solution.Options,
location,
additionalLocations,
GetAdditionalProperties(document, diagnostic));
additionalProperties);
}

private static DiagnosticData Create(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable enable

namespace Microsoft.CodeAnalysis.Host
{
/// <summary>
Expand All @@ -16,5 +18,11 @@ internal class DocumentPropertiesService : IDocumentService
/// but is not passed to the compiler when the containing project is built.
/// </summary>
public virtual bool DesignTimeOnly => false;

/// <summary>
/// The LSP client name that should get the diagnostics produced by this document; any other source
/// will not show these diagnostics. If null, the diagnostics do not have this special handling.
/// </summary>
public virtual string? DiagnosticsLspClientName => null;
}
}

0 comments on commit aeb8b22

Please sign in to comment.