Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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 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 wrapped with \${}. If the script e.g. creates a project file it should print out `\${--project=path/to/projectfile.json}`. This output will be spliced in to the argument string as such: `--suppress=memleak:src/file1.cpp --project=path/to/projectfile.json`.
Comment thread
danmar marked this conversation as resolved.
Outdated
## 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
61 changes: 24 additions & 37 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 { runCommand } from './util/scripts';
import { resolvePath } from './util/path';

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

// The arguments field may contain a script to evaluate on startup. The result of this evaluation is stored in this variable
var processedArgs = '';

const criticalWarningTypes = [
'cppcheckError',
'cppcheckLimit',
Expand Down Expand Up @@ -54,42 +59,27 @@ 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", "");
if (args.includes('${')) {
Comment thread
danmar marked this conversation as resolved.
Outdated
const scriptCommand = args.split("${")[1].split("}")[0];
const scriptOutput = await runCommand(scriptCommand);
// We expect 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;
}

// 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,7 +102,6 @@ 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";
Expand All @@ -137,7 +126,6 @@ export function activate(context: vscode.ExtensionContext) {
await runCppcheckOnFileXML(
document,
commandPath,
extraArgs,
minSevString,
diagnosticCollection
);
Expand Down Expand Up @@ -187,7 +175,6 @@ export function activate(context: vscode.ExtensionContext) {
async function runCppcheckOnFileXML(
document: vscode.TextDocument,
commandPath: string,
extraArgs: string,
minSevString: string,
diagnosticCollection: vscode.DiagnosticCollection
): Promise<void> {
Expand All @@ -199,7 +186,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 +195,7 @@ async function runCppcheckOnFileXML(
});

let proc;
if (extraArgs.includes("--project")) {
if (processedArgs.includes("--project")) {
const args = [
'--enable=all',
'--inline-suppr',
Expand All @@ -217,7 +204,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 +217,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