|
| 1 | +/*--------------------------------------------------------------------------------------------- |
| 2 | + * Copyright (c) Microsoft Corporation. All rights reserved. |
| 3 | + * Licensed under the MIT License. See License.txt in the project root for license information. |
| 4 | + *--------------------------------------------------------------------------------------------*/ |
| 5 | +import * as vscode from 'vscode'; |
| 6 | +import { languageServerOptions } from '../shared/options'; |
| 7 | + |
| 8 | +export enum AnalysisSetting { |
| 9 | + FullSolution = 'fullSolution', |
| 10 | + OpenFiles = 'openFiles', |
| 11 | + None = 'none', |
| 12 | +} |
| 13 | + |
| 14 | +export class BuildDiagnosticsService { |
| 15 | + /** All the build results sent by the DevKit extension. */ |
| 16 | + private _allBuildDiagnostics: { [uri: string]: vscode.Diagnostic[] } = {}; |
| 17 | + |
| 18 | + /** 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. |
| 19 | + * When live diagnostics aren't loaded for a file, then these are all of the diagnostics reported by the build.*/ |
| 20 | + private _diagnosticsReportedByBuild: vscode.DiagnosticCollection; |
| 21 | + |
| 22 | + constructor(buildDiagnostics: vscode.DiagnosticCollection) { |
| 23 | + this._diagnosticsReportedByBuild = buildDiagnostics; |
| 24 | + } |
| 25 | + |
| 26 | + public clearDiagnostics() { |
| 27 | + this._diagnosticsReportedByBuild.clear(); |
| 28 | + } |
| 29 | + |
| 30 | + public async setBuildDiagnostics(buildDiagnostics: { [uri: string]: vscode.Diagnostic[] }, buildOnlyIds: string[]) { |
| 31 | + this._allBuildDiagnostics = buildDiagnostics; |
| 32 | + const displayedBuildDiagnostics = new Array<[vscode.Uri, vscode.Diagnostic[]]>(); |
| 33 | + const allDocuments = vscode.workspace.textDocuments; |
| 34 | + |
| 35 | + for (const [uriPath, diagnosticList] of Object.entries(this._allBuildDiagnostics)) { |
| 36 | + // Check if the document is open |
| 37 | + const uri = vscode.Uri.file(uriPath); |
| 38 | + const document = allDocuments.find((d) => this.compareUri(d.uri, uri)); |
| 39 | + const isDocumentOpen = document !== undefined ? !document.isClosed : false; |
| 40 | + |
| 41 | + // Show the build-only diagnostics |
| 42 | + displayedBuildDiagnostics.push([ |
| 43 | + uri, |
| 44 | + BuildDiagnosticsService.filterDiagnosticsFromBuild(diagnosticList, buildOnlyIds, isDocumentOpen), |
| 45 | + ]); |
| 46 | + } |
| 47 | + |
| 48 | + this._diagnosticsReportedByBuild.set(displayedBuildDiagnostics); |
| 49 | + } |
| 50 | + |
| 51 | + private compareUri(a: vscode.Uri, b: vscode.Uri): boolean { |
| 52 | + return a.fsPath.localeCompare(b.fsPath) === 0; |
| 53 | + } |
| 54 | + |
| 55 | + public async _onFileOpened(document: vscode.TextDocument, buildOnlyIds: string[]) { |
| 56 | + const uri = document.uri; |
| 57 | + const currentFileBuildDiagnostics = this._allBuildDiagnostics[uri.fsPath]; |
| 58 | + |
| 59 | + // The document is now open in the editor and live diagnostics are being shown. Filter diagnostics |
| 60 | + // reported by the build to show build-only problems. |
| 61 | + if (currentFileBuildDiagnostics) { |
| 62 | + const buildDiagnostics = BuildDiagnosticsService.filterDiagnosticsFromBuild( |
| 63 | + currentFileBuildDiagnostics, |
| 64 | + buildOnlyIds, |
| 65 | + true |
| 66 | + ); |
| 67 | + this._diagnosticsReportedByBuild.set(uri, buildDiagnostics); |
| 68 | + } |
| 69 | + } |
| 70 | + |
| 71 | + public static filterDiagnosticsFromBuild( |
| 72 | + diagnosticList: vscode.Diagnostic[], |
| 73 | + buildOnlyIds: string[], |
| 74 | + isDocumentOpen: boolean |
| 75 | + ): vscode.Diagnostic[] { |
| 76 | + const analyzerDiagnosticScope = languageServerOptions.analyzerDiagnosticScope as AnalysisSetting; |
| 77 | + const compilerDiagnosticScope = languageServerOptions.compilerDiagnosticScope as AnalysisSetting; |
| 78 | + |
| 79 | + // If compiler and analyzer diagnostics are set to "none", show everything reported by the build |
| 80 | + if (analyzerDiagnosticScope === AnalysisSetting.None && compilerDiagnosticScope === AnalysisSetting.None) { |
| 81 | + return diagnosticList; |
| 82 | + } |
| 83 | + |
| 84 | + // Filter the diagnostics reported by the build. Some may already be shown by live diagnostics. |
| 85 | + const buildDiagnosticsToDisplay: vscode.Diagnostic[] = []; |
| 86 | + |
| 87 | + // If it is a project system diagnostic (e.g. "Target framework out of support") |
| 88 | + // then always show it. It cannot be reported by live. |
| 89 | + const projectSystemDiagnostics = diagnosticList.filter((d) => |
| 90 | + BuildDiagnosticsService.isProjectSystemDiagnostic(d) |
| 91 | + ); |
| 92 | + buildDiagnosticsToDisplay.push(...projectSystemDiagnostics); |
| 93 | + |
| 94 | + // If it is a "build-only"diagnostics (i.e. it can only be found by building) |
| 95 | + // then always show it. It cannot be reported by live. |
| 96 | + const buildOnlyDiagnostics = diagnosticList.filter((d) => |
| 97 | + BuildDiagnosticsService.isBuildOnlyDiagnostic(buildOnlyIds, d) |
| 98 | + ); |
| 99 | + buildDiagnosticsToDisplay.push(...buildOnlyDiagnostics); |
| 100 | + |
| 101 | + // Check the analyzer diagnostic setting. If the setting is "none" or if the file is closed, |
| 102 | + // then no live analyzers are being shown and bulid analyzers should be added. |
| 103 | + // If FSA is on, then this is a no-op as FSA will report all analyzer diagnostics |
| 104 | + if ( |
| 105 | + analyzerDiagnosticScope === AnalysisSetting.None || |
| 106 | + (analyzerDiagnosticScope === AnalysisSetting.OpenFiles && !isDocumentOpen) |
| 107 | + ) { |
| 108 | + const analyzerDiagnostics = diagnosticList.filter( |
| 109 | + // Needs to be analyzer diagnostics and not already reported as "build only" |
| 110 | + (d) => BuildDiagnosticsService.isAnalyzerDiagnostic(d) && !this.isBuildOnlyDiagnostic(buildOnlyIds, d) |
| 111 | + ); |
| 112 | + buildDiagnosticsToDisplay.push(...analyzerDiagnostics); |
| 113 | + } |
| 114 | + |
| 115 | + // Check the compiler diagnostic setting. If the setting is "none" or if the file is closed, |
| 116 | + // then no live compiler diagnostics are being shown and bulid compiler diagnostics should be added. |
| 117 | + // If FSA is on, then this is a no-op as FSA will report all compiler diagnostics |
| 118 | + if ( |
| 119 | + compilerDiagnosticScope === AnalysisSetting.None || |
| 120 | + (compilerDiagnosticScope === AnalysisSetting.OpenFiles && !isDocumentOpen) |
| 121 | + ) { |
| 122 | + const compilerDiagnostics = diagnosticList.filter( |
| 123 | + // Needs to be analyzer diagnostics and not already reported as "build only" |
| 124 | + (d) => BuildDiagnosticsService.isCompilerDiagnostic(d) && !this.isBuildOnlyDiagnostic(buildOnlyIds, d) |
| 125 | + ); |
| 126 | + buildDiagnosticsToDisplay.push(...compilerDiagnostics); |
| 127 | + } |
| 128 | + |
| 129 | + return buildDiagnosticsToDisplay; |
| 130 | + } |
| 131 | + |
| 132 | + private static isBuildOnlyDiagnostic(buildOnlyIds: string[], d: vscode.Diagnostic): boolean { |
| 133 | + return buildOnlyIds.find((b_id) => b_id === d.code) !== undefined; |
| 134 | + } |
| 135 | + |
| 136 | + private static isCompilerDiagnostic(d: vscode.Diagnostic): boolean { |
| 137 | + const regex = '[cC][sS][0-9]{4}'; |
| 138 | + return d.code ? d.code.toString().match(regex) !== null : false; |
| 139 | + } |
| 140 | + |
| 141 | + private static isAnalyzerDiagnostic(d: vscode.Diagnostic): boolean { |
| 142 | + return d.code ? !this.isCompilerDiagnostic(d) : false; |
| 143 | + } |
| 144 | + |
| 145 | + private static isProjectSystemDiagnostic(d: vscode.Diagnostic): boolean { |
| 146 | + return d.code ? d.code.toString().startsWith('NETSDK') : false; |
| 147 | + } |
| 148 | +} |
0 commit comments