diff --git a/package.json b/package.json index 8e8e5cb..c3f84b3 100644 --- a/package.json +++ b/package.json @@ -31,9 +31,7 @@ "code analysis" ], "activationEvents": [ - "onCommand:codechecker.backend.reloadMetadata", - "onView:codechecker.views.overview", - "onFileSystem:file" + "onStartupFinished" ], "main": "./dist/extension.js", "contributes": { diff --git a/src/backend/executor/bridge.ts b/src/backend/executor/bridge.ts index 9bbc236..21969db 100644 --- a/src/backend/executor/bridge.ts +++ b/src/backend/executor/bridge.ts @@ -25,6 +25,12 @@ import { NotificationType } from '../../editor/notifications'; import { Editor } from '../../editor'; import { SidebarContainer } from '../../sidebar'; import { ReportTreeItem } from '../../sidebar/views'; +import { + COMPILE_COMMANDS_JSON, + getCompilationDatabasePath, + getOutputFolder, + isSupportedFile } from '../../utils/files'; +import { state } from '../../utils/state'; // Structure: // CodeChecker analyzer version: \n {"base_package_version": "M.m.p", ...} @@ -116,8 +122,10 @@ export class ExecutorBridge implements Disposable { ExtensionApi.executorManager )); - this.updateCompilationDatabasePaths(); - this.checkVersion(); + if (state.workspaceSupported) { + this.updateCompilationDatabasePaths(); + this.checkVersion(); + } } dispose() { @@ -250,7 +258,18 @@ export class ExecutorBridge implements Disposable { } else if (files.length === 0) { // FIXME: Add a way to analyze all open workspaces, or a selected one this._bridgeMessages.fire('>>> Using CodeChecker\'s built-in compilation database resolver\n'); - args.push(workspace.workspaceFolders[0].uri.fsPath); + let analysisPath; + const ccDbFolder = getCompilationDatabasePath(); + const outputFolder = getOutputFolder(); + if (ccDbFolder !== undefined) { + analysisPath = path.join(ccDbFolder, COMPILE_COMMANDS_JSON); + } else if (outputFolder !== undefined) { + analysisPath = path.join(outputFolder, COMPILE_COMMANDS_JSON); + } else { + const workspaceFolder = workspace.workspaceFolders[0].uri.fsPath; + analysisPath = path.join(workspaceFolder, '.codechecker'); + } + args.push(analysisPath); } else if (files.length === 1) { this._bridgeMessages.fire('>>> Using CodeChecker\'s built-in compilation database resolver\n'); args.push(files[0].fsPath); @@ -387,13 +406,13 @@ export class ExecutorBridge implements Disposable { public async analyzeCurrentFile() { const currentFile = window.activeTextEditor?.document.uri; - if (currentFile !== undefined) { + if (currentFile !== undefined && isSupportedFile(currentFile)) { await this.analyzeFile(currentFile); } } public async analyzeFile(file: Uri) { - if (!await this.checkVersion()) { + if (!state.workspaceSupported || !await this.checkVersion()) { return; } @@ -411,7 +430,7 @@ export class ExecutorBridge implements Disposable { } public async analyzeProject() { - if (!await this.checkVersion()) { + if (!state.workspaceSupported || !await this.checkVersion()) { return; } @@ -431,9 +450,15 @@ export class ExecutorBridge implements Disposable { } public async getFileAnalysisStatus() { - if (!await this.checkVersion()) { + if (!state.workspaceSupported || !await this.checkVersion()) { + return; + } + + const fileUri = window.activeTextEditor?.document.uri; + if (!isSupportedFile(fileUri)) { return; } + if (this.checkedVersion < [ 6, 27, 0 ]) { const statusNode = SidebarContainer.reportsView.getNodeById('statusItem'); statusNode?.setLabelAndIcon('Status report requires CodeChecker 6.27.0 or higher.'); @@ -442,7 +467,6 @@ export class ExecutorBridge implements Disposable { const ccPath = getConfigAndReplaceVariables('codechecker.executor', 'executablePath') || 'CodeChecker'; const reportsFolder = this.getReportsFolder(); - const fileUri = window.activeTextEditor?.document.uri; const fsPath = fileUri?.fsPath; const statusArgs = [ @@ -516,6 +540,7 @@ export class ExecutorBridge implements Disposable { analyzerStatuses.push(existingStatusNode); } else { existingStatusNode.iconPath = new ThemeIcon(iconname); + analyzerStatuses.push(existingStatusNode); } } } @@ -529,6 +554,9 @@ export class ExecutorBridge implements Disposable { } else if (failed > 0) { statusNode?.setLabelAndIcon('Analysis failed', new ThemeIcon('error', new ThemeColor('charts.red'))); + } else if (missing > 0 && outdated === 0 && uptodate === 0) { + statusNode?.setLabelAndIcon('Analysis is missing', + new ThemeIcon('error', new ThemeColor('charts.red'))); } else if (outdated === 0 && failed === 0) { statusNode?.setLabelAndIcon('Analysis is up-to-date', new ThemeIcon('check', new ThemeColor('charts.green'))); @@ -579,7 +607,7 @@ export class ExecutorBridge implements Disposable { } public async runLog(buildCommand?: string) { - if (!await this.checkVersion()) { + if (!state.workspaceSupported || !await this.checkVersion()) { return; } @@ -599,7 +627,7 @@ export class ExecutorBridge implements Disposable { } public async reloadCheckerData() { - if (!await this.checkVersion()) { + if (!state.workspaceSupported || !await this.checkVersion()) { return; } @@ -642,7 +670,12 @@ export class ExecutorBridge implements Disposable { } public async parseMetadata(...files: Uri[]) { - if (!await this.checkVersion()) { + if (!state.workspaceSupported || files.length === 0 || !await this.checkVersion()) { + return; + } + + if (!files.find(uri => isSupportedFile(uri))) { + ExtensionApi.diagnostics.fireDiagnosticsUpdate(); return; } diff --git a/src/backend/processor/diagnostics.ts b/src/backend/processor/diagnostics.ts index 925f463..5f2e3dd 100644 --- a/src/backend/processor/diagnostics.ts +++ b/src/backend/processor/diagnostics.ts @@ -41,6 +41,10 @@ export class DiagnosticsApi { return undefined; } + public fireDiagnosticsUpdate() { + this._diagnosticsUpdated.fire(); + } + public setSelectedEntry(position?: {file: string, idx: number}) { if (position) { const activeFile = this.getFileDiagnostics(Uri.file(position.file)) ?? []; @@ -88,7 +92,8 @@ export class DiagnosticsApi { return; } - ExtensionApi.executorBridge.parseMetadata(...filesToLoad.map(file => Uri.file(file))) + ExtensionApi.executorBridge.parseMetadata(...filesToLoad + .map(file => Uri.file(file))) .catch((err: any) => console.log(`Internal error in reloadDiagnostics: ${err}`)); } diff --git a/src/editor/diagnostics.ts b/src/editor/diagnostics.ts index d657812..5ef509f 100644 --- a/src/editor/diagnostics.ts +++ b/src/editor/diagnostics.ts @@ -157,7 +157,7 @@ export class DiagnosticRenderer { if (this.customSeverities && this.customSeverities[severity]) { const severityString = this.customSeverities[severity]; - if (typeof severityString === 'string' && this._severityMap[severityString.toLowerCase()]) { + if (typeof severityString === 'string' && this._severityMap[severityString.toLowerCase()] !== undefined) { return this._severityMap[severityString.toLowerCase()]; } else { Editor.loggerPanel.window.appendLine( diff --git a/src/editor/initialize.ts b/src/editor/initialize.ts index 033563e..9e2de08 100644 --- a/src/editor/initialize.ts +++ b/src/editor/initialize.ts @@ -2,6 +2,7 @@ import { ExtensionContext, Uri, commands, window, workspace } from 'vscode'; import { ExtensionApi } from '../backend'; import { Editor } from './editor'; import { NotificationType } from './notifications'; +import { state } from '../utils/state'; export class FolderInitializer { constructor(_ctx: ExtensionContext) { @@ -27,7 +28,7 @@ export class FolderInitializer { async showDialog() { const workspaceFolder = workspace.workspaceFolders?.length && workspace.workspaceFolders[0].uri; - if (!workspaceFolder) { + if (!workspaceFolder || !state.workspaceSupported) { return; } diff --git a/src/extension.ts b/src/extension.ts index 1f92754..7dbcd94 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -2,6 +2,7 @@ import * as vscode from 'vscode'; import { ExtensionApi } from './backend'; import { Editor } from './editor'; import { SidebarContainer } from './sidebar'; +import { checkWorkspace } from './utils/files'; export interface CodeCheckerExtension { extensionApi: typeof ExtensionApi, @@ -9,11 +10,14 @@ export interface CodeCheckerExtension { editor: typeof Editor } -export function activate(context: vscode.ExtensionContext): CodeCheckerExtension { +export async function activate(context: vscode.ExtensionContext): Promise { // Backend must be initialized before the frontend - ExtensionApi.init(context); - Editor.init(context); - SidebarContainer.init(context); + + if (await checkWorkspace()) { + ExtensionApi.init(context); + Editor.init(context); + SidebarContainer.init(context); + } console.log('Extension "codechecker" activated'); diff --git a/src/sidebar/views/reports.ts b/src/sidebar/views/reports.ts index e9cb739..08cfcad 100644 --- a/src/sidebar/views/reports.ts +++ b/src/sidebar/views/reports.ts @@ -19,6 +19,7 @@ import { import { ExtensionApi } from '../../backend'; import { DiagnosticReport } from '../../backend/types'; import { SidebarContainer } from '../sidebar_container'; +import { isSupportedFile } from '../../utils/files'; export class ReportTreeItem extends TreeItem { parent: ReportTreeItem | undefined; @@ -378,13 +379,25 @@ export class ReportsView implements TreeDataProvider { // Get root level items. getRootItems(): ReportTreeItem[] | undefined { - if (!this.currentEntryList?.length) { + if (!isSupportedFile(this.currentFile)) { const statusNode = SidebarContainer.reportsView.getNodeById('statusItem'); - statusNode?.setLabelAndIcon('Not in compilation database', - new ThemeIcon('question', new ThemeColor('charts.orange'))); + if (statusNode !== undefined) { + statusNode.setLabelAndIcon('Not supported by codechecker', + new ThemeIcon('question', new ThemeColor('charts.orange'))); + // statusNode.children = []; + statusNode.collapsibleState = TreeItemCollapsibleState.None; + const rootNode = statusNode.parent; + if (rootNode !== undefined) { + rootNode.children = [ statusNode ]; + } + } return statusNode ? [ statusNode ] : undefined; } + if (this.currentEntryList === undefined) { + return undefined; + } + const severityItems: { [key: string]: TreeDiagnosticReport[] } = {}; for (const [idx, entry] of this.currentEntryList.entries()) { const severity = entry.severity || 'UNSPECIFIED'; diff --git a/src/utils/files.ts b/src/utils/files.ts new file mode 100644 index 0000000..c7d2fdf --- /dev/null +++ b/src/utils/files.ts @@ -0,0 +1,54 @@ +import { Uri, workspace } from 'vscode'; +import { state } from './state'; +import { getConfigAndReplaceVariables } from './config'; + +const EXTENSIONS = [ + '.c', + '.cc', + '.cpp', + '.cxx' +]; + +export const COMPILE_COMMANDS_JSON = 'compile_commands.json'; + +export function isSupportedFile(uri: Uri | undefined): boolean { + if (uri === undefined) { + return false; + } + + return EXTENSIONS.find(ext => uri.path.endsWith(ext)) !== undefined; +} + +export async function isSupportedWorkspace() { + const supported = await workspace.findFiles( + `**/*{${EXTENSIONS.join(',')}}`, + '**/{node_modules,.git}/**', + 1 + ); + + return supported.length > 0; +} + +export async function checkWorkspace() { + if (workspace.name === undefined) { + state.workspaceSupported = false; + console.log('No open workspace.'); + return false; + } + const supported = await isSupportedWorkspace(); + if (supported) { + state.workspaceSupported = true; + } else { + state.workspaceSupported = false; + console.log(`workspace ${workspace.name} does not contain supported files.`); + } + return state.workspaceSupported; +} + +export function getOutputFolder() { + return getConfigAndReplaceVariables('codechecker.backend', 'outputFolder'); +} + +export function getCompilationDatabasePath() { + return getConfigAndReplaceVariables('codechecker.backend', 'compilationDatabasePath'); +} \ No newline at end of file diff --git a/src/utils/state.ts b/src/utils/state.ts new file mode 100644 index 0000000..f389cef --- /dev/null +++ b/src/utils/state.ts @@ -0,0 +1,5 @@ +export class ExtensionState { + public workspaceSupported = false; +} + +export const state = new ExtensionState(); \ No newline at end of file