Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,13 @@
- **On-save linting**: When you save a c/cpp file, `cppcheck` is automatically run on that file.
- **Per-file diagnostics**: Only diagnostics relevant to the saved file are displayed.
- **Configurable severity threshold**: Filter out messages below a chosen severity level (`info`, `warning`, or `error`).
- **Set C/C++ standard**: Easily specify `--std=<id>` (e.g. `c++17`, `c99`, etc.).
- **Diagnostic cleanup**: When you close a file, its diagnostics are automatically cleared.
- **Project file support**: You can feed your project file to cppcheck through the `--project` flag in the `cppcheck-official.arguments` field in the extension settings.
- **Warning notes**: Display notes for warnings when those are available

- **Dynamic config**: The extension supports runing scripts to generate arguments to pass to cppcheck. This can be done by setting the argument to command to run wrapped with \${}, e.g. `--project=${bash path/to/script.sh}`. The script is expected to output the argument wrapped with \${}, so with the argument `--project=${bash path/to/script.sh}` the script will be run and expected to create a compile_commands.json file whose path will be printed out as such: `${path/to/compile_commands.json}`.
## Requirements

**Cppcheck** must be installed on your system.
**Cppcheck** must be installed on your system.
Comment thread
davidramnero marked this conversation as resolved.
Outdated
- By default, this extension looks for `cppcheck` on the system PATH.
- Alternatively, specify a custom executable path using the `cppcheck-official.path` setting.

Expand Down
62 changes: 29 additions & 33 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import * as vscode from 'vscode';
import * as cp from 'child_process';
import * as path from "path";
import * as os from "os";
import * as xml2js from 'xml2js';

import { runScript } from './util/scripts';
import { resolvePath } from './util/path';

enum SeverityNumber {
Info = 0,
Warning = 1,
Error = 2
}

// If a script generates arguments at extension activation they are saved in dynamicArgs
const dynamicArgs : Array<string> = [];

const criticalWarningTypes = [
'cppcheckError',
'cppcheckLimit',
Expand Down Expand Up @@ -54,42 +59,30 @@ function parseMinSeverity(str: string): SeverityNumber {
}
}

export function resolvePath(argPath: string): string {
const folders = vscode.workspace.workspaceFolders;
const workspaceRoot = folders && folders.length > 0
? folders[0].uri.fsPath
: process.cwd();

// Expand ${workspaceFolder}
if (argPath.includes("${workspaceFolder}")) {
argPath = argPath.replace("${workspaceFolder}", workspaceRoot);
}

// Expand tilde (~) to home directory
if (argPath.startsWith("~")) {
argPath = path.join(os.homedir(), argPath.slice(1));
}

// Expand ./ or ../ relative paths (relative to workspace root if available)
if (argPath.startsWith("./") || argPath.startsWith("../")) {
argPath = path.resolve(workspaceRoot, argPath);
}

// If still not absolute, treat it as relative to workspace root
if (!path.isAbsolute(argPath)) {
argPath = path.join(workspaceRoot, argPath);
}
return argPath;
}

// This method is called when your extension is activated.
// Your extension is activated the very first time the command is executed.
export function activate(context: vscode.ExtensionContext) {
export async function activate(context: vscode.ExtensionContext) {

// Create a diagnostic collection.
const diagnosticCollection = vscode.languages.createDiagnosticCollection("Cppcheck");
context.subscriptions.push(diagnosticCollection);

// If an argument requires us to run any scripts we do it here
const config = vscode.workspace.getConfiguration();
const args = config.get<string>("cppcheck-official.arguments", "");
const argsWithScripts = args.split("--").filter((arg) => arg.includes('${'));
for (const arg of argsWithScripts) {
// argType will look like e.g. --project
const argType = arg.split("=")[0];
const argValue = arg.split("=")[1];
Comment thread
danmar marked this conversation as resolved.
Outdated
// Remove ${ from the beginning and slice } away from the end of argValue
const scriptCommand = argValue.split("{")[1].split("}")[0];
const scriptOutput = await runScript(scriptCommand);
// We expect the script output that we are to set the argument to will be wrapped with ${}
const scriptOutputPath = scriptOutput.split("${")[1].split("}")[0];
dynamicArgs.push(`${argType}=${scriptOutputPath}`);
};

// set up a map of timers per document URI for debounce for continuous analysis triggers
// I.e. document has been changed -> DEBOUNCE_MS time passed since last change -> run cppcheck
const debounceTimers: Map<string, NodeJS.Timeout> = new Map();
Expand Down Expand Up @@ -198,11 +191,14 @@ async function runCppcheckOnFileXML(
const filePath = document.fileName.replaceAll('\\', '/');
const minSevNum = parseMinSeverity(minSevString);

// Arguments specified with scripts are replaced with script output (dynamicArgs)
const staticArgs = extraArgs.split("--").filter((arg) => !arg.includes("${"));
const allArgs = staticArgs.concat(dynamicArgs);
// Resolve paths for arguments where applicable
const extraArgsParsed = (extraArgs.split(" ")).map((arg) => {
if (arg.startsWith('--project')) {
const extraArgsParsed = allArgs.map((arg) => {
if (arg.startsWith('project')) {
const splitArg = arg.split('=');
return `${splitArg[0]}=${resolvePath(splitArg[1])}`;
return `--${splitArg[0]}=${resolvePath(splitArg[1])}`;
}
return arg;
});
Expand Down
31 changes: 31 additions & 0 deletions src/util/path.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import * as path from "path";
import * as os from "os";
import * as vscode from 'vscode';

export function resolvePath(argPath: string): string {
const folders = vscode.workspace.workspaceFolders;
const workspaceRoot = folders && folders.length > 0
? folders[0].uri.fsPath
: process.cwd();

// Expand ${workspaceFolder}
if (argPath.includes("${workspaceFolder}")) {
argPath = argPath.replace("${workspaceFolder}", workspaceRoot);
}

// Expand tilde (~) to home directory
if (argPath.startsWith("~")) {
argPath = path.join(os.homedir(), argPath.slice(1));
}

// Expand ./ or ../ relative paths (relative to workspace root if available)
if (argPath.startsWith("./") || argPath.startsWith("../")) {
argPath = path.resolve(workspaceRoot, argPath);
}

// If still not absolute, treat it as relative to workspace root
if (!path.isAbsolute(argPath)) {
argPath = path.join(workspaceRoot, argPath);
}
return argPath;
}
26 changes: 26 additions & 0 deletions src/util/scripts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { execFile } from "child_process";
import { resolvePath } from './path';

function runScript(scriptCommand: string): Promise<string> {
const scriptParts : string[] = scriptCommand.split(" ");
// ASSUMPTION: script path will be the last part of the command
const scriptPath = scriptParts[scriptParts.length -1];
const absoluteScriptPath = resolvePath(scriptPath);
const joinedCommand = scriptParts.slice(0, scriptParts.length -1).join(" ") + " " + absoluteScriptPath;
return new Promise((resolve, reject) => {
execFile(joinedCommand, [], { cwd: resolvePath('${workspaceFolder}') }, (error, stdout, stderr) => {
if (error) {
reject(error);
return;
}
if (stderr) {
console.warn("Script stderr:", stderr);
}
const result = stdout.trim();
resolve(result);
});
});
}


export { runScript };
Loading