Skip to content

Commit

Permalink
Merge pull request #109 from Discookie/ericsson/doc-url
Browse files Browse the repository at this point in the history
Add `Go to docs` button to sidebar
  • Loading branch information
vodorok authored Aug 26, 2022
2 parents 6ab1f02 + 39900d7 commit c70546d
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 11 deletions.
2 changes: 1 addition & 1 deletion src/backend/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { DiagnosticsApi, MetadataApi } from './processor';

export class ExtensionApi {
static init(ctx: ExtensionContext): void {
this._metadata = new MetadataApi(ctx);
this._executorManager = new ExecutorManager(ctx);
this._executorBridge = new ExecutorBridge(ctx);
this._metadata = new MetadataApi(ctx);
this._diagnostics = new DiagnosticsApi(ctx);
}

Expand Down
36 changes: 36 additions & 0 deletions src/backend/executor/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,14 @@ export class ExecutorBridge implements Disposable {
return args;
}

public getCheckersCmdArgs(): string[] | undefined {
return [
'checkers',
'--details',
'--output', 'json',
];
}

public getLogCmdArgs(buildCommand?: string): string[] | undefined {
if (!workspace.workspaceFolders?.length) {
return undefined;
Expand Down Expand Up @@ -350,6 +358,34 @@ export class ExecutorBridge implements Disposable {
ExtensionApi.executorManager.addToQueue(process, 'replace');
}

public async reloadCheckerData() {
if (!await this.checkVersion()) {
return;
}

const ccPath = getConfigAndReplaceVariables('codechecker.executor', 'executablePath') || 'CodeChecker';
const commandArgs = this.getCheckersCmdArgs();

if (commandArgs === undefined) {
return;
}

const process = new ScheduledProcess(ccPath, commandArgs, { processType: ProcessType.checkers });

// TODO: Find a better way to collect full process output
let processOutput = '';

process.processStdout((output) => processOutput += output);

process.processStatusChange((status) => {
if (status === ProcessStatus.finished) {
ExtensionApi.metadata.parseCheckerData(processOutput);
}
});

ExtensionApi.executorManager.addToQueue(process, 'replace');
}

public stopAnalysis() {
ExtensionApi.executorManager.clearQueue(ProcessType.analyze);
ExtensionApi.executorManager.clearQueue(ProcessType.log);
Expand Down
7 changes: 5 additions & 2 deletions src/backend/executor/process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export enum ProcessStatus {

export enum ProcessType {
analyze = 'CodeChecker analyze',
checkers = 'CodeChecker checkers',
log = 'CodeChecker log',
parse = 'CodeChecker parse',
version = 'CodeChecker analyzer-version',
Expand Down Expand Up @@ -98,7 +99,7 @@ export class ScheduledProcess implements Disposable {
this.processParameters = parameters ?? {};

const processType = parameters?.processType ?? '';
const forwardDefaults: string[] = [ ProcessType.parse ];
const forwardDefaults: string[] = [ ProcessType.checkers, ProcessType.parse ];

if (this.processParameters.forwardStdoutToLogs === undefined) {
this.processParameters.forwardStdoutToLogs = !forwardDefaults.includes(processType);
Expand Down Expand Up @@ -210,8 +211,10 @@ export class ExecutorManager implements Disposable {

private executionPriority = [
ProcessType.version,
ProcessType.checkers,
ProcessType.parse,
ProcessType.analyze
ProcessType.log,
ProcessType.analyze,
];

/** Map of scheduled processes, indexed by its commonName.
Expand Down
12 changes: 11 additions & 1 deletion src/backend/parser/metadata.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { TextDecoder } from 'util';
import { Uri, workspace } from 'vscode';
import { AnalyzerMetadata, CheckerMetadata, MetadataFile } from '../types';
import { AnalyzerMetadata, CheckerData, CheckerMetadata, MetadataFile } from '../types';
import { v1Types } from '../types/internal/metadata';

export class MetadataParseError extends Error {
Expand Down Expand Up @@ -69,3 +69,13 @@ export async function parseMetadata(path: string): Promise<MetadataFile> {

return metadataFile as MetadataFile;
}

export function parseCheckerData(data: string): CheckerData[] {
const parsedData = JSON.parse(data) as CheckerData[];

if (parsedData === undefined) {
throw new SyntaxError('Invalid output of CodeChecker checkers');
}

return parsedData;
}
42 changes: 39 additions & 3 deletions src/backend/processor/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,24 @@ import {
workspace
} from 'vscode';
import * as path from 'path';
import { parseMetadata } from '../parser';
import { CheckerMetadata } from '../types';
import { ExtensionApi } from '../api';
import { parseCheckerData, parseMetadata } from '../parser';
import { CheckerData, CheckerMetadata } from '../types';
import { shouldShowNotifications } from '../../utils/config';

export class MetadataApi implements Disposable {
private _metadata?: CheckerMetadata;
public get metadata(): CheckerMetadata | undefined {
return this._metadata;
}
private _checkerData: Map<string, CheckerData> = new Map();
/**
* Content based on the CodeChecker checkers command.
* Key: checker name, value: checker descriptor data (incl. doc URL)
*/
public get checkerData(): Map<string, CheckerData>{
return this._checkerData;
}

// Path to the metadata.json file
private metadataPath?: string;
Expand Down Expand Up @@ -50,6 +59,7 @@ export class MetadataApi implements Disposable {

private init() {
this.updateMetadataPath();
ExtensionApi.executorBridge.reloadCheckerData();
}

dispose() {
Expand Down Expand Up @@ -200,9 +210,35 @@ export class MetadataApi implements Disposable {
this._metadataUpdated.fire(this._metadata);
}

parseCheckerData(checkerData: string) {
let parsedData;

try {
parsedData = parseCheckerData(checkerData);
} catch (err: any) {
console.error(err);

if (err instanceof SyntaxError) {
window.showErrorMessage('Failed to read CodeChecker checker data: Invalid format');
} else {
window.showErrorMessage('Failed to read CodeChecker checker data\nCheck console for more details');
}

return;
}

const dataMap = new Map();

for (const entry of parsedData) {
dataMap.set(`${entry.analyzer}/${entry.name}`, entry);
}

this._checkerData = dataMap;
}

onConfigChanged(event: ConfigurationChangeEvent) {
if (event.affectsConfiguration('codechecker.backend')) {
this.updateMetadataPath();
this.init();
}
}
}
7 changes: 7 additions & 0 deletions src/backend/types/checkers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface CheckerData {
readonly status: 'enabled' | 'disabled' | 'unknown';
readonly name: string;
readonly analyzer: string;
readonly description: string;
readonly labels: string[];
}
1 change: 1 addition & 0 deletions src/backend/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './parse_result';
export * from './metadata';
export * from './checkers';
11 changes: 10 additions & 1 deletion src/editor/navigation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ExtensionContext, Position, Range, Uri, commands, window } from 'vscode';
import { ExtensionContext, Position, Range, Uri, commands, env, window } from 'vscode';
import { ExtensionApi } from '../backend/api';
import { DiagnosticReport } from '../backend/types';

Expand All @@ -12,6 +12,15 @@ export class NavigationHandler {
ctx.subscriptions.push(commands.registerCommand('codechecker.editor.jumpToStep', this.jumpToStep, this));
ctx.subscriptions.push(commands.registerCommand('codechecker.editor.nextStep', this.nextStep, this));
ctx.subscriptions.push(commands.registerCommand('codechecker.editor.previousStep', this.previousStep, this));
ctx.subscriptions.push(commands.registerCommand('codechecker.editor.openDocs', this.openDocs, this));
}

openDocs(docUrl: Uri | string) {
if (typeof docUrl === 'string') {
docUrl = Uri.parse(docUrl);
}

void env.openExternal(docUrl);
}

onDiagnosticsUpdated() {
Expand Down
21 changes: 21 additions & 0 deletions src/sidebar/views/reports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,26 @@ export class ReportsView implements TreeDataProvider<ReportTreeItem> {
arguments: [currentFile, entryId, true]
};

const checkerData = ExtensionApi.metadata.checkerData
?.get(`${entry.analyzer_name ?? ''}/${entry.checker_name ?? ''}`);

const docUrl = checkerData?.labels
.find((val) => val.startsWith('doc_url:'))
?.substring(8);

const goToDocsItems = [];

if (docUrl !== undefined) {
const goToDocsItem = new ReportTreeItem('openDocs', 'Go to checker documentation', new ThemeIcon('book'));
goToDocsItem.command = {
title: 'openDocs',
command: 'codechecker.editor.openDocs',
arguments: [docUrl]
};

goToDocsItems.push(goToDocsItem);
}

let toggleStepsItem = null;
if (isActiveReport) {
toggleStepsItem = new ReportTreeItem(
Expand All @@ -202,6 +222,7 @@ export class ReportsView implements TreeDataProvider<ReportTreeItem> {
let indentation = 0;
return [
jumpToReportItem,
...goToDocsItems,
toggleStepsItem,
...entry.bug_path_events.map((event, idx) => {
const filePath = event.file.original_path;
Expand Down
13 changes: 10 additions & 3 deletions src/test/executor/executor.functional.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ suite('Functional Test: Backend - Executor', () => {
await closeAllTabs();
}).timeout(8000);

test('Stop analysis cancels current task and clears the queue', async function() {
test('Stop analysis cancels current long-running task and clears the queue', async function() {
await Promise.all([
executorBridge.analyzeFile(Uri.file(path.join(STATIC_WORKSPACE_PATH, 'file.cpp'))),
executorBridge.analyzeFile(Uri.file(path.join(STATIC_WORKSPACE_PATH, 'file2.cpp'))),
Expand All @@ -221,8 +221,15 @@ suite('Functional Test: Backend - Executor', () => {

executorBridge.stopAnalysis();

assert(executorManager['queue'].get(ProcessType.analyze)!.length === 0, 'Queue not cleared on analysis stop');
assert(executorManager.activeProcess === undefined, 'Running process not killed');
const processType = executorManager.activeProcess?.processParameters.processType;

for (const clearedProcessType of [ProcessType.analyze, ProcessType.log]) {
assert(
executorManager['queue'].get(clearedProcessType)!.length === 0,
'Queue not cleared on analysis stop'
);
assert(processType !== clearedProcessType, 'Running process not killed');
}
});

test('Duplicate tasks are removed from the queue', async function() {
Expand Down

0 comments on commit c70546d

Please sign in to comment.