Skip to content

Commit

Permalink
Merge pull request #3 from jasonmalinowski/razor-client-diagnostics-f…
Browse files Browse the repository at this point in the history
…iltering

Allow a IDynamicFileInfoProvider to be able to provide LSP diagnostics
  • Loading branch information
dibarbet authored Mar 16, 2020
2 parents f465fd9 + aeb8b22 commit 9c7071d
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 26 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 @@ -2,17 +2,21 @@
// 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

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
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 @@ -90,7 +94,7 @@ protected override object GetOrUpdateAggregationKey(object data)
return key;
}

private bool CheckAggregateKey(AggregatedKey key, DiagnosticsUpdatedArgs args)
private bool CheckAggregateKey(AggregatedKey? key, DiagnosticsUpdatedArgs? args)
{
if (key == null)
{
Expand Down Expand Up @@ -185,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 Expand Up @@ -216,12 +226,12 @@ private class TableEntriesSource : DiagnosticTableEntriesSource
{
private readonly LiveTableDataSource _source;
private readonly Workspace _workspace;
private readonly ProjectId _projectId;
private readonly DocumentId _documentId;
private readonly ProjectId? _projectId;
private readonly DocumentId? _documentId;
private readonly object _id;
private readonly string _buildTool;

public TableEntriesSource(LiveTableDataSource source, Workspace workspace, ProjectId projectId, DocumentId documentId, object id)
public TableEntriesSource(LiveTableDataSource source, Workspace workspace, ProjectId? projectId, DocumentId? documentId, object id)
{
_source = source;
_workspace = workspace;
Expand All @@ -234,7 +244,7 @@ public TableEntriesSource(LiveTableDataSource source, Workspace workspace, Proje
public override object Key => _id;
public override string BuildTool => _buildTool;
public override bool SupportSpanTracking => _documentId != null;
public override DocumentId TrackingDocumentId => _documentId;
public override DocumentId? TrackingDocumentId => _documentId;

public override ImmutableArray<DiagnosticTableItem> GetItems()
{
Expand All @@ -255,7 +265,7 @@ public override ImmutableArray<ITrackingPoint> GetTrackingPoints(ImmutableArray<
private class TableEntriesSnapshot : AbstractTableEntriesSnapshot<DiagnosticTableItem>, IWpfTableEntriesSnapshot
{
private readonly DiagnosticTableEntriesSource _source;
private FrameworkElement[] _descriptions;
private FrameworkElement[]? _descriptions;

public TableEntriesSnapshot(
DiagnosticTableEntriesSource source,
Expand All @@ -267,7 +277,7 @@ public TableEntriesSnapshot(
_source = source;
}

public override bool TryGetValue(int index, string columnName, out object content)
public override bool TryGetValue(int index, string columnName, [NotNullWhen(returnValue: true)] out object? content)
{
// REVIEW: this method is too-chatty to make async, but otherwise, how one can implement it async?
// also, what is cancellation mechanism?
Expand Down Expand Up @@ -425,7 +435,7 @@ public bool CanCreateDetailsContent(int index)
return !string.IsNullOrWhiteSpace(item.Description);
}

public bool TryCreateDetailsContent(int index, out FrameworkElement expandedContent)
public bool TryCreateDetailsContent(int index, [NotNullWhen(returnValue: true)] out FrameworkElement? expandedContent)
{
var item = GetItem(index)?.Data;
if (item == null)
Expand All @@ -438,7 +448,7 @@ public bool TryCreateDetailsContent(int index, out FrameworkElement expandedCont
return true;
}

public bool TryCreateDetailsStringContent(int index, out string content)
public bool TryCreateDetailsStringContent(int index, [NotNullWhen(returnValue: true)] out string? content)
{
var item = GetItem(index)?.Data;
if (item == null)
Expand All @@ -454,7 +464,7 @@ public bool TryCreateDetailsStringContent(int index, out string content)
}

content = item.Description;
return true;
return content != null;
}

private static FrameworkElement GetDescriptionTextBlock(DiagnosticData item)
Expand All @@ -469,7 +479,7 @@ private static FrameworkElement GetDescriptionTextBlock(DiagnosticData item)
}

private static FrameworkElement GetOrCreateTextBlock(
ref FrameworkElement[] caches, int count, int index, DiagnosticData item, Func<DiagnosticData, FrameworkElement> elementCreator)
[NotNull] ref FrameworkElement[]? caches, int count, int index, DiagnosticData item, Func<DiagnosticData, FrameworkElement> elementCreator)
{
if (caches == null)
{
Expand All @@ -485,7 +495,7 @@ private static FrameworkElement GetOrCreateTextBlock(
}

// unused ones
public bool TryCreateColumnContent(int index, string columnName, bool singleColumnView, out FrameworkElement content)
public bool TryCreateColumnContent(int index, string columnName, bool singleColumnView, [NotNullWhen(returnValue: true)] out FrameworkElement? content)
{
content = default;
return false;
Expand All @@ -497,20 +507,20 @@ public bool TryCreateImageContent(int index, string columnName, bool singleColum
return false;
}

public bool TryCreateStringContent(int index, string columnName, bool truncatedText, bool singleColumnView, out string content)
public bool TryCreateStringContent(int index, string columnName, bool truncatedText, bool singleColumnView, [NotNullWhen(returnValue: true)] out string? content)
{
content = default;
return false;
}

public bool TryCreateToolTip(int index, string columnName, out object toolTip)
public bool TryCreateToolTip(int index, string columnName, [NotNullWhen(returnValue: true)] out object? toolTip)
{
toolTip = default;
return false;
}

// remove this once we moved to new drop
public bool TryCreateStringContent(int index, string columnName, bool singleColumnView, out string content)
public bool TryCreateStringContent(int index, string columnName, bool singleColumnView, [NotNullWhen(returnValue: true)] out string? content)
{
content = default;
return false;
Expand All @@ -531,7 +541,7 @@ private static string GetDiagnosticUpdatedMessage(DiagnosticsUpdatedArgs e)
id = analyzer.Analyzer.ToString();
}

return $"Kind:{e.Workspace.Kind}, Analyzer:{id}, Update:{e.Kind}, {(object)e.DocumentId ?? e.ProjectId}, ({string.Join(Environment.NewLine, e.Diagnostics)})";
return $"Kind:{e.Workspace.Kind}, Analyzer:{id}, Update:{e.Kind}, {(object?)e.DocumentId ?? e.ProjectId}, ({string.Join(Environment.NewLine, e.Diagnostics)})";
}
}
}
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 9c7071d

Please sign in to comment.