Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.LanguageServer;
using Microsoft.AspNetCore.Razor.LanguageServer.Diagnostics;
using Microsoft.AspNetCore.Razor.LanguageServer.EndpointContracts;
using Microsoft.AspNetCore.Razor.LanguageServer.Hosting;
Expand Down Expand Up @@ -91,8 +92,9 @@ public void Setup()
var languageServer = new ClientNotifierService(Diagnostics!);
var documentMappingService = BuildRazorDocumentMappingService();

var optionsMonitor = Mock.Of<RazorLSPOptionsMonitor>(MockBehavior.Strict);
var translateDiagnosticsService = new RazorTranslateDiagnosticsService(documentMappingService, loggerFactory);
DocumentPullDiagnosticsEndpoint = new DocumentPullDiagnosticsEndpoint(languageServerFeatureOptions, translateDiagnosticsService, languageServer, telemetryReporter: null);
DocumentPullDiagnosticsEndpoint = new DocumentPullDiagnosticsEndpoint(languageServerFeatureOptions, translateDiagnosticsService, optionsMonitor, languageServer, telemetryReporter: null);
}

private object BuildDiagnostics()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,33 +25,27 @@
namespace Microsoft.AspNetCore.Razor.LanguageServer.Diagnostics;

[RazorLanguageServerEndpoint(VSInternalMethods.DocumentPullDiagnosticName)]
internal class DocumentPullDiagnosticsEndpoint : IRazorRequestHandler<VSInternalDocumentDiagnosticsParams, IEnumerable<VSInternalDiagnosticReport>?>, ICapabilitiesProvider
internal class DocumentPullDiagnosticsEndpoint(
LanguageServerFeatureOptions languageServerFeatureOptions,
RazorTranslateDiagnosticsService translateDiagnosticsService,
RazorLSPOptionsMonitor razorLSPOptionsMonitor,
IClientConnection clientConnection,
ITelemetryReporter? telemetryReporter) : IRazorRequestHandler<VSInternalDocumentDiagnosticsParams, IEnumerable<VSInternalDiagnosticReport>?>, ICapabilitiesProvider
{
private readonly LanguageServerFeatureOptions _languageServerFeatureOptions;
private readonly IClientConnection _clientConnection;
private readonly RazorTranslateDiagnosticsService _translateDiagnosticsService;
private readonly ITelemetryReporter? _telemetryReporter;
private readonly LanguageServerFeatureOptions _languageServerFeatureOptions = languageServerFeatureOptions;
private readonly IClientConnection _clientConnection = clientConnection;
private readonly RazorTranslateDiagnosticsService _translateDiagnosticsService = translateDiagnosticsService;
private readonly RazorLSPOptionsMonitor _razorLSPOptionsMonitor = razorLSPOptionsMonitor;
private readonly ITelemetryReporter? _telemetryReporter = telemetryReporter;
private ImmutableDictionary<ProjectKey, int> _lastReportedProjectTagHelperCount = ImmutableDictionary<ProjectKey, int>.Empty;

public DocumentPullDiagnosticsEndpoint(
LanguageServerFeatureOptions languageServerFeatureOptions,
RazorTranslateDiagnosticsService translateDiagnosticsService,
IClientConnection clientConnection,
ITelemetryReporter? telemetryReporter)
{
_languageServerFeatureOptions = languageServerFeatureOptions ?? throw new ArgumentNullException(nameof(languageServerFeatureOptions));
_translateDiagnosticsService = translateDiagnosticsService ?? throw new ArgumentNullException(nameof(translateDiagnosticsService));
_clientConnection = clientConnection ?? throw new ArgumentNullException(nameof(clientConnection));
_telemetryReporter = telemetryReporter;
}

public bool MutatesSolutionState => false;

public void ApplyCapabilities(VSInternalServerCapabilities serverCapabilities, VSInternalClientCapabilities clientCapabilities)
{
serverCapabilities.SupportsDiagnosticRequests = true;
serverCapabilities.DiagnosticProvider ??= new();
serverCapabilities.DiagnosticProvider.DiagnosticKinds = [VSInternalDiagnosticKind.Syntax];
serverCapabilities.DiagnosticProvider.DiagnosticKinds = [VSInternalDiagnosticKind.Syntax, VSInternalDiagnosticKind.Task];
}

public TextDocumentIdentifier GetTextDocumentIdentifier(VSInternalDocumentDiagnosticsParams request)
Expand All @@ -71,16 +65,31 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(VSInternalDocumentDiagno
return default;
}

var correlationId = Guid.NewGuid();
using var __ = _telemetryReporter?.TrackLspRequest(VSInternalMethods.DocumentPullDiagnosticName, LanguageServerConstants.RazorLanguageServerName, TelemetryThresholds.DiagnosticsRazorTelemetryThreshold, correlationId);
var documentContext = context.DocumentContext;
if (documentContext is null)
{
return null;
}

var documentSnapshot = documentContext.Snapshot;
// This endpoint is called for regular diagnostics, and Task List items, and they're handled separately.
if (request.QueryingDiagnosticKind?.Value == VSInternalDiagnosticKind.Task.Value)
{
var codeDocument = await documentContext.GetCodeDocumentAsync(cancellationToken).ConfigureAwait(false);
var diagnostics = TaskListDiagnosticProvider.GetTaskListDiagnostics(codeDocument, _razorLSPOptionsMonitor.CurrentValue.TaskListDescriptors);
return
[
new()
{
Diagnostics = [.. diagnostics],
ResultId = Guid.NewGuid().ToString()
}
];
}

var correlationId = Guid.NewGuid();
using var __ = _telemetryReporter?.TrackLspRequest(VSInternalMethods.DocumentPullDiagnosticName, LanguageServerConstants.RazorLanguageServerName, TelemetryThresholds.DiagnosticsRazorTelemetryThreshold, correlationId);

var documentSnapshot = documentContext.Snapshot;
var razorDiagnostics = await GetRazorDiagnosticsAsync(documentSnapshot, cancellationToken).ConfigureAwait(false);

await ReportRZ10012TelemetryAsync(documentContext, razorDiagnostics, cancellationToken).ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
// Licensed under the MIT license. See License.txt in the project root for license information.

using System;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.Razor.Settings;
using Microsoft.Extensions.Internal;

namespace Microsoft.AspNetCore.Razor.LanguageServer.Hosting;

internal record RazorLSPOptions(
internal sealed record RazorLSPOptions(
FormattingFlags Formatting,
bool AutoClosingTags,
bool InsertSpaces,
Expand All @@ -16,7 +19,8 @@ internal record RazorLSPOptions(
bool AutoInsertAttributeQuotes,
bool ColorBackground,
bool CodeBlockBraceOnNextLine,
bool CommitElementsWithSpace)
bool CommitElementsWithSpace,
ImmutableArray<string> TaskListDescriptors)
{
public readonly static RazorLSPOptions Default = new(Formatting: FormattingFlags.All,
AutoClosingTags: true,
Expand All @@ -27,7 +31,15 @@ internal record RazorLSPOptions(
AutoInsertAttributeQuotes: true,
ColorBackground: false,
CodeBlockBraceOnNextLine: false,
CommitElementsWithSpace: true);
CommitElementsWithSpace: true,
TaskListDescriptors: []);

public ImmutableArray<string> TaskListDescriptors
{
get;
init => field = value.NullToEmpty();

} = TaskListDescriptors.NullToEmpty();

/// <summary>
/// Initializes the LSP options with the settings from the passed in client settings, and default values for anything
Expand All @@ -43,7 +55,8 @@ internal static RazorLSPOptions From(ClientSettings settings)
settings.AdvancedSettings.AutoInsertAttributeQuotes,
settings.AdvancedSettings.ColorBackground,
settings.AdvancedSettings.CodeBlockBraceOnNextLine,
settings.AdvancedSettings.CommitElementsWithSpace);
settings.AdvancedSettings.CommitElementsWithSpace,
settings.AdvancedSettings.TaskListDescriptors);

private static FormattingFlags GetFormattingFlags(ClientSettings settings)
{
Expand All @@ -60,4 +73,37 @@ private static FormattingFlags GetFormattingFlags(ClientSettings settings)

return flags;
}

public bool Equals(RazorLSPOptions? other)
{
return other is not null &&
Formatting == other.Formatting &&
AutoClosingTags == other.AutoClosingTags &&
InsertSpaces == other.InsertSpaces &&
TabSize == other.TabSize &&
AutoShowCompletion == other.AutoShowCompletion &&
AutoListParams == other.AutoListParams &&
AutoInsertAttributeQuotes == other.AutoInsertAttributeQuotes &&
ColorBackground == other.ColorBackground &&
CodeBlockBraceOnNextLine == other.CodeBlockBraceOnNextLine &&
CommitElementsWithSpace == other.CommitElementsWithSpace &&
TaskListDescriptors.SequenceEqual(other.TaskListDescriptors);
}

public override int GetHashCode()
{
var hash = HashCodeCombiner.Start();
hash.Add(Formatting);
hash.Add(AutoClosingTags);
hash.Add(InsertSpaces);
hash.Add(TabSize);
hash.Add(AutoShowCompletion);
hash.Add(AutoListParams);
hash.Add(AutoInsertAttributeQuotes);
hash.Add(ColorBackground);
hash.Add(CodeBlockBraceOnNextLine);
hash.Add(CommitElementsWithSpace);
hash.Add(TaskListDescriptors);
return hash;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// 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 Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Syntax;
using Microsoft.AspNetCore.Razor.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using LspDiagnostic = Microsoft.VisualStudio.LanguageServer.Protocol.Diagnostic;
using LspDiagnosticSeverity = Microsoft.VisualStudio.LanguageServer.Protocol.DiagnosticSeverity;

namespace Microsoft.CodeAnalysis.Razor.Diagnostics;

internal static class TaskListDiagnosticProvider
{
private static readonly DiagnosticTag[] s_taskItemTags = [VSDiagnosticTags.TaskItem];

public static ImmutableArray<LspDiagnostic> GetTaskListDiagnostics(RazorCodeDocument codeDocument, ImmutableArray<string> taskListDescriptors)
{
var source = codeDocument.Source.Text;
var tree = codeDocument.GetSyntaxTree();

using var diagnostics = new PooledArrayBuilder<LspDiagnostic>();

foreach (var node in tree.Root.DescendantNodes())
{
if (node is RazorCommentBlockSyntax comment)
{
var i = comment.Comment.SpanStart;

while (char.IsWhiteSpace(source[i]))
{
i++;
}

foreach (var token in taskListDescriptors)
{
if (!CommentMatchesToken(source, comment, i, token))
{
continue;
}

diagnostics.Add(new LspDiagnostic
{
Code = "TODO",
Message = comment.Comment.Content.Trim(),
Source = "Razor",
Severity = LspDiagnosticSeverity.Information,
Range = source.GetRange(comment.Comment.Span),
Tags = s_taskItemTags
});

break;
}
}
}

return diagnostics.ToImmutable();
}

private static bool CommentMatchesToken(SourceText source, RazorCommentBlockSyntax comment, int i, string token)
{
if (i + token.Length + 2 > comment.EndCommentStar.SpanStart)
{
// Not enough room in the comment for the token and some content
return false;
}

for (var j = 0; j < token.Length; j++)
{
if (source.Length < i + j)
{
return false;
}

if (char.ToLowerInvariant(source[i + j]) != char.ToLowerInvariant(token[j]))
{
return false;
}
}

if (char.IsLetter(source[i + token.Length + 1]))
{
// The comment starts with the token, but the next character is a letter, which means it is something like "TODONT"
return false;
}

return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,10 @@ ValueTask<ImmutableArray<RoslynLspDiagnostic>> GetDiagnosticsAsync(
RoslynLspDiagnostic[] csharpDiagnostics,
RoslynLspDiagnostic[] htmlDiagnostics,
CancellationToken cancellationToken);

ValueTask<ImmutableArray<RoslynLspDiagnostic>> GetTaskListDiagnosticsAsync(
JsonSerializableRazorPinnedSolutionInfoWrapper solutionInfo,
JsonSerializableDocumentId documentId,
ImmutableArray<string> taskListDescriptors,
CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT license. See License.txt in the project root for license information.

using System;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.Razor.Logging;

namespace Microsoft.CodeAnalysis.Razor.Settings;
Expand Down Expand Up @@ -32,7 +33,25 @@ internal sealed record ClientSpaceSettings(bool IndentWithTabs, int IndentSize)
public int IndentSize { get; } = IndentSize >= 0 ? IndentSize : throw new ArgumentOutOfRangeException(nameof(IndentSize));
}

internal sealed record ClientAdvancedSettings(bool FormatOnType, bool AutoClosingTags, bool AutoInsertAttributeQuotes, bool ColorBackground, bool CodeBlockBraceOnNextLine, bool CommitElementsWithSpace, SnippetSetting SnippetSetting, LogLevel LogLevel, bool FormatOnPaste)
internal sealed record ClientAdvancedSettings(bool FormatOnType,
bool AutoClosingTags,
bool AutoInsertAttributeQuotes,
bool ColorBackground,
bool CodeBlockBraceOnNextLine,
bool CommitElementsWithSpace,
SnippetSetting SnippetSetting,
LogLevel LogLevel,
bool FormatOnPaste,
ImmutableArray<string> TaskListDescriptors)
{
public static readonly ClientAdvancedSettings Default = new(FormatOnType: true, AutoClosingTags: true, AutoInsertAttributeQuotes: true, ColorBackground: false, CodeBlockBraceOnNextLine: false, CommitElementsWithSpace: true, SnippetSetting.All, LogLevel.Warning, FormatOnPaste: true);
public static readonly ClientAdvancedSettings Default = new(FormatOnType: true,
AutoClosingTags: true,
AutoInsertAttributeQuotes: true,
ColorBackground: false,
CodeBlockBraceOnNextLine: false,
CommitElementsWithSpace: true,
SnippetSetting.All,
LogLevel.Warning,
FormatOnPaste: true,
TaskListDescriptors: []);
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,25 @@ .. await _translateDiagnosticsService.TranslateAsync(RazorLanguageKind.CSharp, c
.. await _translateDiagnosticsService.TranslateAsync(RazorLanguageKind.Html, htmlDiagnostics, context.Snapshot, cancellationToken).ConfigureAwait(false)
];
}

public ValueTask<ImmutableArray<LspDiagnostic>> GetTaskListDiagnosticsAsync(
JsonSerializableRazorPinnedSolutionInfoWrapper solutionInfo,
JsonSerializableDocumentId documentId,
ImmutableArray<string> taskListDescriptors,
CancellationToken cancellationToken)
=> RunServiceAsync(
solutionInfo,
documentId,
context => GetTaskListDiagnosticsAsync(context, taskListDescriptors, cancellationToken),
cancellationToken);

private static async ValueTask<ImmutableArray<LspDiagnostic>> GetTaskListDiagnosticsAsync(
RemoteDocumentContext context,
ImmutableArray<string> taskListDescriptors,
CancellationToken cancellationToken)
{
var codeDocument = await context.GetCodeDocumentAsync(cancellationToken).ConfigureAwait(false);

return TaskListDiagnosticProvider.GetTaskListDiagnostics(codeDocument, taskListDescriptors);
}
}
Loading
Loading