Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dedupe Build and Live Diagnostics #6543

Merged
merged 17 commits into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from 11 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
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@
"name": "Microsoft.CodeAnalysis.LanguageClient.SolutionSnapshotProvider",
"version": "0.1"
}
},
{
"moniker": {
"name": "Microsoft.VisualStudio.CSharpExtension.BuildResultService",
"version": "0.1"
}
}
],
"scripts": {
Expand Down Expand Up @@ -5635,4 +5641,4 @@
}
]
}
}
}
146 changes: 146 additions & 0 deletions src/lsptoolshost/buildDiagnosticsService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';

export enum AnalysisSetting {
FullSolution = 'fullSolution',
OpenFiles = 'openFiles',
None = 'none',
}

export class BuildDiagnosticsService {
/** All the build results sent by the DevKit extension. */
private _allBuildDiagnostics: Array<[vscode.Uri, vscode.Diagnostic[]]> = [];

/** The diagnostic results from build displayed by VS Code. When live diagnostics are available for a file, these are errors that only build knows about.
* When live diagnostics aren't loaded for a file, then these are all of the diagnostics reported by the build.*/
private _diagnosticsReportedByBuild: vscode.DiagnosticCollection;

constructor(buildDiagnostics: vscode.DiagnosticCollection) {
this._diagnosticsReportedByBuild = buildDiagnostics;
}

public clearDiagnostics() {
this._diagnosticsReportedByBuild.clear();
}

public async setBuildDiagnostics(
buildDiagnostics: Array<[vscode.Uri, vscode.Diagnostic[]]>,
buildOnlyIds: string[]
) {
this._allBuildDiagnostics = buildDiagnostics;
const displayedBuildDiagnostics = new Array<[vscode.Uri, vscode.Diagnostic[]]>();
const allDocuments = vscode.workspace.textDocuments;

this._allBuildDiagnostics.forEach((fileDiagnostics) => {
const uri = fileDiagnostics[0];
const diagnosticList = fileDiagnostics[1];

// Check if the document is open
const document = allDocuments.find((d) => this.compareUri(d.uri, uri));
const isDocumentOpen = document !== undefined ? !document.isClosed : false;

// Show the build-only diagnostics
displayedBuildDiagnostics.push([
uri,
BuildDiagnosticsService.filerDiagnosticsFromBuild(diagnosticList, buildOnlyIds, isDocumentOpen),
]);
});

this._diagnosticsReportedByBuild.set(displayedBuildDiagnostics);
}

private compareUri(a: vscode.Uri, b: vscode.Uri): boolean {
return a.fsPath.localeCompare(b.fsPath) === 0;
}

public async _onFileOpened(document: vscode.TextDocument, buildOnlyIds: string[]) {
const uri = document.uri;
const currentFileBuildDiagnostics = this._allBuildDiagnostics?.find(([u]) => this.compareUri(u, uri));

// The document is now open in the editor and live diagnostics are being shown. Filter diagnostics
// reported by the build to show build-only problems.
if (currentFileBuildDiagnostics) {
const buildDiagnostics = BuildDiagnosticsService.filerDiagnosticsFromBuild(
currentFileBuildDiagnostics[1],
buildOnlyIds,
true
);
this._diagnosticsReportedByBuild.set(uri, buildDiagnostics);
}
}

public static filerDiagnosticsFromBuild(
beccamc marked this conversation as resolved.
Show resolved Hide resolved
diagnosticList: vscode.Diagnostic[],
buildOnlyIds: string[],
isDocumentOpen: boolean
): vscode.Diagnostic[] {
const configuration = vscode.workspace.getConfiguration();
beccamc marked this conversation as resolved.
Show resolved Hide resolved
const analyzerDiagnosticScope = configuration.get(
'dotnet.backgroundAnalysis.analyzerDiagnosticsScope'
) as AnalysisSetting;
const compilerDiagnosticScope = configuration.get(
'dotnet.backgroundAnalysis.compilerDiagnosticsScope'
) as AnalysisSetting;

// If compiler and analyzer diagnostics are set to "none", show everything reported by the build
if (analyzerDiagnosticScope === AnalysisSetting.None && compilerDiagnosticScope === AnalysisSetting.None) {
return diagnosticList;
}

// Filter the diagnostics reported by the build. Some may already be shown by live diagnostics.
const buildOnlyDiagnostics: vscode.Diagnostic[] = [];
diagnosticList.forEach((d) => {
if (d.code) {
// If it is a project system diagnostic (e.g. "Target framework out of support")
// then always show it. It cannot be reported by live.
if (this.isProjectSystemDiagnostic(d)) {
buildOnlyDiagnostics.push(d);
}
// If it is a "build-only"diagnostics (i.e. it can only be found by building)
// then always show it. It cannot be reported by live.
else if (buildOnlyIds.find((b_id) => b_id === d.code)) {
buildOnlyDiagnostics.push(d);
} else {
const isAnalyzerDiagnostic = BuildDiagnosticsService.isAnalyzerDiagnostic(d);
const isCompilerDiagnostic = BuildDiagnosticsService.isCompilerDiagnostic(d);

if (
(isAnalyzerDiagnostic && analyzerDiagnosticScope === AnalysisSetting.None) ||
(isCompilerDiagnostic && compilerDiagnosticScope === AnalysisSetting.None)
) {
// If live diagnostics are completely turned off for this type, then show the build diagnostic
buildOnlyDiagnostics.push(d);
} else if (isDocumentOpen) {
// no-op. The document is open and live diagnostis are on. This diagnostic is already being shown.
} else if (
beccamc marked this conversation as resolved.
Show resolved Hide resolved
(isAnalyzerDiagnostic && analyzerDiagnosticScope === AnalysisSetting.FullSolution) ||
(isCompilerDiagnostic && compilerDiagnosticScope === AnalysisSetting.FullSolution)
) {
// no-op. Full solution analysis is on for this diagnostic type. All diagnostics are already being shown.
} else {
// The document is closed, and the analysis setting is to only show for open files.
// Show the diagnostic reported by build.
buildOnlyDiagnostics.push(d);
}
}
}
});

return buildOnlyDiagnostics;
}

private static isCompilerDiagnostic(d: vscode.Diagnostic): boolean {
return d.code ? d.code.toString().startsWith('CS') : false;
beccamc marked this conversation as resolved.
Show resolved Hide resolved
}

private static isAnalyzerDiagnostic(d: vscode.Diagnostic): boolean {
return d.code ? !d.code.toString().startsWith('CS') : false;
beccamc marked this conversation as resolved.
Show resolved Hide resolved
}

private static isProjectSystemDiagnostic(d: vscode.Diagnostic): boolean {
return d.code ? d.code.toString().startsWith('NETSDK') : false;
}
}
20 changes: 19 additions & 1 deletion src/lsptoolshost/roslynLanguageServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
SocketMessageReader,
MessageTransports,
RAL,
CancellationToken,
} from 'vscode-languageclient/node';
import { PlatformInformation } from '../shared/platform';
import { readConfigurations } from './configurationMiddleware';
Expand Down Expand Up @@ -56,6 +57,7 @@ import { registerCodeActionFixAllCommands } from './fixAllCodeAction';
import { commonOptions, languageServerOptions, omnisharpOptions } from '../shared/options';
import { NamedPipeInformation } from './roslynProtocol';
import { IDisposable } from '../disposable';
import { BuildDiagnosticsService } from './buildDiagnosticsService';

let _channel: vscode.OutputChannel;
let _traceChannel: vscode.OutputChannel;
Expand Down Expand Up @@ -94,6 +96,8 @@ export class RoslynLanguageServer {
/** The project files previously opened; we hold onto this for the same reason as _solutionFile. */
private _projectFiles: vscode.Uri[] = new Array<vscode.Uri>();

public _buildDiagnosticService: BuildDiagnosticsService;

constructor(
private _languageClient: RoslynLanguageClient,
private _platformInfo: PlatformInformation,
Expand All @@ -108,6 +112,10 @@ export class RoslynLanguageServer {
this.registerExtensionsChanged();
this.registerTelemetryChanged();

const diagnosticsReportedByBuild = vscode.languages.createDiagnosticCollection('csharp-build');
this._buildDiagnosticService = new BuildDiagnosticsService(diagnosticsReportedByBuild);
this.registerDocumentOpenForDiagnostics();

// Register Razor dynamic file info handling
this.registerDynamicFileInfo();

Expand Down Expand Up @@ -569,7 +577,7 @@ export class RoslynLanguageServer {
});
});

// The server process will create the named pipe used for communcation. Wait for it to be created,
// The server process will create the named pipe used for communication. Wait for it to be created,
// and listen for the server to pass back the connection information via stdout.
const namedPipeConnectionPromise = new Promise<NamedPipeInformation>((resolve) => {
_channel.appendLine('waiting for named pipe information from server...');
Expand Down Expand Up @@ -695,6 +703,16 @@ export class RoslynLanguageServer {
);
}

private registerDocumentOpenForDiagnostics() {
// When a file is opened process any build diagnostics that may be shown
this._languageClient.addDisposable(
vscode.workspace.onDidOpenTextDocument(async (event) => {
const buildIds = await this.getBuildOnlyDiagnosticIds(CancellationToken.None);
this._buildDiagnosticService._onFileOpened(event, buildIds);
})
);
}

private registerExtensionsChanged() {
// subscribe to extension change events so that we can get notified if C# Dev Kit is added/removed later.
this._languageClient.addDisposable(
Expand Down
30 changes: 30 additions & 0 deletions src/lsptoolshost/services/buildResultReporterService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Diagnostic, Uri } from 'vscode';
import { RoslynLanguageServer } from '../roslynLanguageServer';
import { CancellationToken } from 'vscode-jsonrpc';

interface IBuildResultDiagnostics {
buildStarted(cancellationToken?: CancellationToken): Promise<void>;
reportBuildResult(
buildDiagnostics: Array<[Uri, Diagnostic[]]>,
cancellationToken?: CancellationToken
): Promise<void>;
}

export class BuildResultDiagnostics implements IBuildResultDiagnostics {
constructor(private _languageServerPromise: Promise<RoslynLanguageServer>) {}

public async buildStarted(): Promise<void> {
const langServer = await this._languageServerPromise;
langServer._buildDiagnosticService.clearDiagnostics();
}

public async reportBuildResult(buildDiagnostics: Array<[Uri, Diagnostic[]]>): Promise<void> {
dibarbet marked this conversation as resolved.
Show resolved Hide resolved
const langServer = await this._languageServerPromise;
const buildOnlyIds = await langServer.getBuildOnlyDiagnosticIds(CancellationToken.None);
langServer._buildDiagnosticService.setBuildDiagnostics(buildDiagnostics, buildOnlyIds);
}
}
9 changes: 9 additions & 0 deletions src/lsptoolshost/services/descriptors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,13 @@ export default class Descriptors {
protocolMajorVersion: 3,
}
);

static readonly csharpExtensionBuildResultService: ServiceRpcDescriptor = new ServiceJsonRpcDescriptor(
ServiceMoniker.create('Microsoft.VisualStudio.CSharpExtension.BuildResultService', '0.1'),
Formatters.MessagePack,
MessageDelimiters.BigEndianInt32LengthHeader,
{
protocolMajorVersion: 3,
}
);
}
5 changes: 5 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import { ServerStateChange } from './lsptoolshost/serverStateChange';
import { SolutionSnapshotProvider } from './lsptoolshost/services/solutionSnapshotProvider';
import { RazorTelemetryDownloader } from './razor/razorTelemetryDownloader';
import { commonOptions, omnisharpOptions, razorOptions } from './shared/options';
import { BuildResultDiagnostics } from './lsptoolshost/services/buildResultReporterService';
import { debugSessionTracker } from './coreclrDebug/provisionalDebugSessionTracker';

export async function activate(
Expand Down Expand Up @@ -430,6 +431,10 @@ function profferBrokeredServices(
serviceContainer.profferServiceFactory(
Descriptors.solutionSnapshotProviderRegistration,
(_mk, _op, _sb) => new SolutionSnapshotProvider(languageServerPromise)
),
serviceContainer.profferServiceFactory(
Descriptors.csharpExtensionBuildResultService,
(_mk, _op, _sb) => new BuildResultDiagnostics(languageServerPromise)
)
);
}
Expand Down
Loading