Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 running a script to generate arguments to pass to cppcheck. This can be done by including the command in the argument field wrapped with \${}, e.g. `--suppress=memleak:src/file1.cpp ${bash path/to/script.sh}`. The script is expected to output the argument(s) wrapped with \${}. If the script e.g. creates a project file it should print out as `${--project=path/to/projectfile.json}`. This output will be spliced into the argument string as such: `--suppress=memleak:src/file1.cpp --project=path/to/projectfile.json`.
## Requirements

**Cppcheck** must be installed on your system.
**Cppcheck** must be installed on your system.
- 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: 24 additions & 38 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
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 { runCommand } from './util/scripts';
import { resolvePath } from './util/path';

enum SeverityNumber {
Info = 0,
Warning = 1,
Expand Down Expand Up @@ -54,42 +56,14 @@ 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);

// 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 All @@ -112,11 +86,23 @@ export function activate(context: vscode.ExtensionContext) {

const config = vscode.workspace.getConfiguration();
const isEnabled = config.get<boolean>("cppcheck-official.enable", true);
const extraArgs = config.get<string>("cppcheck-official.arguments", "");
const minSevString = config.get<string>("cppcheck-official.minSeverity", "info");
const userPath = config.get<string>("cppcheck-official.path")?.trim() || "";
const commandPath = userPath ? resolvePath(userPath) : "cppcheck";

var args = config.get<string>("cppcheck-official.arguments", "");
var processedArgs = '';
// If argument field contains command to run script we do so here
if (args.includes('${')) {
const scriptCommand = args.split("${")[1].split("}")[0];
const scriptOutput = await runCommand(scriptCommand);
// We expect that the script output that is to be used as arguments will be wrapped with ${}
const scriptOutputTrimmed = scriptOutput.split("${")[1].split("}")[0];
processedArgs = args.split("${")[0] + scriptOutputTrimmed + args.split("}")?.[1];
} else {
processedArgs = args;
}

// If disabled, clear any existing diagnostics for this doc.
if (!isEnabled) {
diagnosticCollection.delete(document.uri);
Expand All @@ -137,7 +123,7 @@ export function activate(context: vscode.ExtensionContext) {
await runCppcheckOnFileXML(
document,
commandPath,
extraArgs,
processedArgs,
minSevString,
diagnosticCollection
);
Expand Down Expand Up @@ -187,7 +173,7 @@ export function activate(context: vscode.ExtensionContext) {
async function runCppcheckOnFileXML(
document: vscode.TextDocument,
commandPath: string,
extraArgs: string,
processedArgs: string,
minSevString: string,
diagnosticCollection: vscode.DiagnosticCollection
): Promise<void> {
Expand All @@ -199,7 +185,7 @@ async function runCppcheckOnFileXML(
const minSevNum = parseMinSeverity(minSevString);

// Resolve paths for arguments where applicable
const extraArgsParsed = (extraArgs.split(" ")).map((arg) => {
const argsParsed = processedArgs.split(" ").map((arg) => {
if (arg.startsWith('--project')) {
const splitArg = arg.split('=');
return `${splitArg[0]}=${resolvePath(splitArg[1])}`;
Expand All @@ -208,7 +194,7 @@ async function runCppcheckOnFileXML(
});

let proc;
if (extraArgs.includes("--project")) {
if (processedArgs.includes("--project")) {
const args = [
'--enable=all',
'--inline-suppr',
Expand All @@ -217,7 +203,7 @@ async function runCppcheckOnFileXML(
'--suppress=missingInclude',
'--suppress=missingIncludeSystem',
`--file-filter=${filePath}`,
...extraArgsParsed,
...argsParsed,
].filter(Boolean);
proc = cp.spawn(commandPath, args, {
cwd: path.dirname(document.fileName),
Expand All @@ -230,7 +216,7 @@ async function runCppcheckOnFileXML(
'--suppress=unusedFunction',
'--suppress=missingInclude',
'--suppress=missingIncludeSystem',
...extraArgsParsed,
...argsParsed,
filePath,
].filter(Boolean);
proc = cp.spawn(commandPath, args, {
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;
}
22 changes: 22 additions & 0 deletions src/util/scripts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { exec } from "child_process";
import { resolvePath } from './path';
import util from 'util';

const execAsync = util.promisify(exec);

async function runCommand(command : string) {
try {
const { stdout, stderr } = await execAsync(command, {
cwd: resolvePath('${workspaceFolder}'),
});

if (stderr) {
throw new Error(stderr);
}
return stdout;
} catch (error) {
throw error;
}
}

export { runCommand };
Loading