Skip to content

Add support for distinct LSP diagnostic categories in LSP Pull Diagnostics.#65985

Merged
CyrusNajmabadi merged 33 commits intodotnet:main-vs-depsfrom
CyrusNajmabadi:lspDiagCategories
Dec 14, 2022
Merged

Add support for distinct LSP diagnostic categories in LSP Pull Diagnostics.#65985
CyrusNajmabadi merged 33 commits intodotnet:main-vs-depsfrom
CyrusNajmabadi:lspDiagCategories

Conversation

@CyrusNajmabadi
Copy link
Copy Markdown
Contributor

This allows us to coordinate with the client to support categories like "Compiler Syntax", "Compiler Semantic", "Task List", etc. categories. This will greatly speed up LSP pull diagnostics as these requests can operate concurrently, and can be displayed to the user once individually complete, as opposed to waiting for us to compute all of them.

@CyrusNajmabadi CyrusNajmabadi requested a review from a team as a code owner December 13, 2022 22:13
@ghost ghost added the Area-IDE label Dec 13, 2022
/// </summary>
Task<ImmutableArray<DiagnosticData>> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId = null, DocumentId? documentId = null, ImmutableHashSet<string>? diagnosticIds = null, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default);
Task<ImmutableArray<DiagnosticData>> GetDiagnosticsForIdsAsync(
Solution solution, ProjectId? projectId = null, DocumentId? documentId = null, ImmutableHashSet<string>? diagnosticIds = null, bool includeSuppressedDiagnostics = false, DiagnosticKind diagnosticKind = DiagnosticKind.All, CancellationToken cancellationToken = default);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mavasani need eyes on htis

AppendDiagnostics(analysisResult.GetDocumentDiagnostics(documentId, AnalysisKind.Semantic));

if (includeAll)
AppendDiagnostics(analysisResult.GetDocumentDiagnostics(documentId, AnalysisKind.NonLocal));
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need eyes on this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, i just realized there's a simpler way to do this most likely.

internal abstract record AbstractDocumentDiagnosticSource<TDocument>(TDocument Document) : IDiagnosticSource
where TDocument : TextDocument
{
private static readonly ImmutableArray<string> s_todoCommentCustomTags = ImmutableArray.Create(PullDiagnosticConstants.TaskItemCustomTag);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these types got much simpler as task-list handling was extracted into a dedicated diagnostic source.

var allSpanDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForSpanAsync(
Document, range: null, diagnosticKind: this.DiagnosticKind, cancellationToken: cancellationToken).ConfigureAwait(false);
return allSpanDiagnostics;
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this part was extremely clean, and fits onto the current diagnostic service API without any issue.


namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics;

internal sealed record class TaskListDiagnosticSource(Document Document) : AbstractDocumentDiagnosticSource<Document>(Document)
Copy link
Copy Markdown
Contributor Author

@CyrusNajmabadi CyrusNajmabadi Dec 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is just extracting the logic for task list diagnostics into a dedicate source. it is otherwise unchanged.

// However we can include them as a part of workspace pull when FSA is on.
var documentDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync(Document.Project.Solution, Document.Project.Id, Document.Id, cancellationToken: cancellationToken).ConfigureAwait(false);
var documentDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync(
Document.Project.Solution, Document.Project.Id, Document.Id, cancellationToken: cancellationToken).ConfigureAwait(false);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this part was a little messy. specifically, GetDiagnosticsForIdsAsync doesn't support fine-grained diagnostic requests (@mavasani ). So for workspace-document-pull requests, we just wait on getting all the diagnostics. this means that there's no speedup on getting diagnostics for closed files. However, i think that's ok as you only get those for FSA, and FSA is notoriously slow anyways.

If we do want to support this, it would be possible. We'd just need GetDiagnosticsForIdsAsync to support passing in a diagnostickind. I initially attempted to do that, but i was uncertain about the precise semantics of how that would work in the impl.

If you are interested in adding support for that @mavasani i can update this side of things.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is fine to wait for adding this support as it does not seem to be a mainline scenario.

protected override async ValueTask<ImmutableArray<IDiagnosticSource>> GetOrderedDiagnosticSourcesAsync(
VSInternalWorkspaceDiagnosticsParams diagnosticsParams,
RequestContext context,
CancellationToken cancellationToken)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

helper to walk projects in priority order. used by both teh normal diagnostic walking code, and the task-list code.

// diagnostics will be handled by razor itself, which will operate by calling into Roslyn and asking for
// document-diagnostics instead.
if (context.ServerKind == WellKnownLspServerKinds.RazorLspServer)
return ImmutableArray<IDiagnosticSource>.Empty;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dibarbet we both check this and below check if (document.IsRazorDocument()) to bail out. Do we need both? Or can we skip the second check if the server is the razor server?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we need both - there are two cases

  1. The razor C# server is being asked for workspace diagnostics (this check here). The razor C# server shouldn't give anything back
  2. The normal C# server is being asked for workspace diagnostics, and it sees a Razor document. It should not return diagnostics for that document because we rely on razor to do a document pull for all razor documents.

}

private static ImmutableArray<IDiagnosticSource> GetTaskListDiagnosticSources(
RequestContext context, IGlobalOptionService globalOptions)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dedicated helpers to get the sources for task-list vs diagnsotics.

{
serverCapabilities.SupportsDiagnosticRequests = true;
serverCapabilities.MultipleContextSupportProvider = new VSInternalMultipleContextFeatures { SupportsMultipleContextsDiagnostics = true };
serverCapabilities.DiagnosticProvider ??= new();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could use initializer syntax here? This should always be null here I believe

protected static (DiagnosticKind diagnosticKind, bool taskList, bool isProject) GetDiagnosticKindInfo(VSInternalDiagnosticKind? queryingDiagnosticKind)
{
if (queryingDiagnosticKind?.Value == PullDiagnosticConstants.Project)
return (default, taskList: false, isProject: true);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe an explicit kind instead of default?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed.

/// </summary>
protected abstract ValueTask<ImmutableArray<IDiagnosticSource>> GetOrderedDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken);
protected abstract ValueTask<ImmutableArray<IDiagnosticSource>> GetOrderedDiagnosticSourcesAsync(
TDiagnosticsParams diagnosticsParams, RequestContext context, CancellationToken cancellationToken);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as discussed offline, we'll need a different cache per kind

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup. has been done.

// diagnostics will be handled by razor itself, which will operate by calling into Roslyn and asking for
// document-diagnostics instead.
if (context.ServerKind == WellKnownLspServerKinds.RazorLspServer)
return ImmutableArray<IDiagnosticSource>.Empty;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we need both - there are two cases

  1. The razor C# server is being asked for workspace diagnostics (this check here). The razor C# server shouldn't give anything back
  2. The normal C# server is being asked for workspace diagnostics, and it sees a Razor document. It should not return diagnostics for that document because we rely on razor to do a document pull for all razor documents.

Assert.Equal("todo: goo", results.Single().Diagnostics.Single().Message);
Assert.Empty(results.Single().Diagnostics);
//Assert.Equal("TODO", results.Single().Diagnostics.Single().Code);
//Assert.Equal("todo: goo", results.Single().Diagnostics.Single().Message);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be removed

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do.

@CyrusNajmabadi CyrusNajmabadi merged commit 38360d1 into dotnet:main-vs-deps Dec 14, 2022
@ghost ghost added this to the Next milestone Dec 14, 2022
@Cosifne Cosifne modified the milestones: Next, 17.5 P3 Jan 4, 2023
@CyrusNajmabadi CyrusNajmabadi deleted the lspDiagCategories branch February 2, 2023 18:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants