Skip to content

Commit 560478a

Browse files
authored
feature #45/ dynamic argument flags (#46)
* feature #45/ dynamic argument flags * wip working dynamic args * working * updated readme * small readme update * removed specifying language to run script with * made script command parsing a bit more general + updated readme * removed console log * reworked to make command running more generic * removed reference to removed function * updated readme to be a bit clearer * clarified some comments * removed whitespace * rerun script for every analysis
1 parent f65c8c3 commit 560478a

4 files changed

Lines changed: 79 additions & 41 deletions

File tree

README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,13 @@
77
- **On-save linting**: When you save a c/cpp file, `cppcheck` is automatically run on that file.
88
- **Per-file diagnostics**: Only diagnostics relevant to the saved file are displayed.
99
- **Configurable severity threshold**: Filter out messages below a chosen severity level (`info`, `warning`, or `error`).
10-
- **Set C/C++ standard**: Easily specify `--std=<id>` (e.g. `c++17`, `c99`, etc.).
1110
- **Diagnostic cleanup**: When you close a file, its diagnostics are automatically cleared.
1211
- **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.
1312
- **Warning notes**: Display notes for warnings when those are available
14-
13+
- **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`.
1514
## Requirements
1615

17-
**Cppcheck** must be installed on your system.
16+
**Cppcheck** must be installed on your system.
1817
- By default, this extension looks for `cppcheck` on the system PATH.
1918
- Alternatively, specify a custom executable path using the `cppcheck-official.path` setting.
2019

src/extension.ts

Lines changed: 24 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import * as vscode from 'vscode';
22
import * as cp from 'child_process';
33
import * as path from "path";
4-
import * as os from "os";
54
import * as xml2js from 'xml2js';
65

6+
import { runCommand } from './util/scripts';
7+
import { resolvePath } from './util/path';
8+
79
enum SeverityNumber {
810
Info = 0,
911
Warning = 1,
@@ -54,42 +56,14 @@ function parseMinSeverity(str: string): SeverityNumber {
5456
}
5557
}
5658

57-
export function resolvePath(argPath: string): string {
58-
const folders = vscode.workspace.workspaceFolders;
59-
const workspaceRoot = folders && folders.length > 0
60-
? folders[0].uri.fsPath
61-
: process.cwd();
62-
63-
// Expand ${workspaceFolder}
64-
if (argPath.includes("${workspaceFolder}")) {
65-
argPath = argPath.replace("${workspaceFolder}", workspaceRoot);
66-
}
67-
68-
// Expand tilde (~) to home directory
69-
if (argPath.startsWith("~")) {
70-
argPath = path.join(os.homedir(), argPath.slice(1));
71-
}
72-
73-
// Expand ./ or ../ relative paths (relative to workspace root if available)
74-
if (argPath.startsWith("./") || argPath.startsWith("../")) {
75-
argPath = path.resolve(workspaceRoot, argPath);
76-
}
77-
78-
// If still not absolute, treat it as relative to workspace root
79-
if (!path.isAbsolute(argPath)) {
80-
argPath = path.join(workspaceRoot, argPath);
81-
}
82-
return argPath;
83-
}
84-
8559
// This method is called when your extension is activated.
8660
// Your extension is activated the very first time the command is executed.
87-
export function activate(context: vscode.ExtensionContext) {
61+
export async function activate(context: vscode.ExtensionContext) {
8862

8963
// Create a diagnostic collection.
9064
const diagnosticCollection = vscode.languages.createDiagnosticCollection("Cppcheck");
9165
context.subscriptions.push(diagnosticCollection);
92-
66+
9367
// set up a map of timers per document URI for debounce for continuous analysis triggers
9468
// I.e. document has been changed -> DEBOUNCE_MS time passed since last change -> run cppcheck
9569
const debounceTimers: Map<string, NodeJS.Timeout> = new Map();
@@ -112,11 +86,23 @@ export function activate(context: vscode.ExtensionContext) {
11286

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

93+
var args = config.get<string>("cppcheck-official.arguments", "");
94+
var processedArgs = '';
95+
// If argument field contains command to run script we do so here
96+
if (args.includes('${')) {
97+
const scriptCommand = args.split("${")[1].split("}")[0];
98+
const scriptOutput = await runCommand(scriptCommand);
99+
// We expect that the script output that is to be used as arguments will be wrapped with ${}
100+
const scriptOutputTrimmed = scriptOutput.split("${")[1].split("}")[0];
101+
processedArgs = args.split("${")[0] + scriptOutputTrimmed + args.split("}")?.[1];
102+
} else {
103+
processedArgs = args;
104+
}
105+
120106
// If disabled, clear any existing diagnostics for this doc.
121107
if (!isEnabled) {
122108
diagnosticCollection.delete(document.uri);
@@ -137,7 +123,7 @@ export function activate(context: vscode.ExtensionContext) {
137123
await runCppcheckOnFileXML(
138124
document,
139125
commandPath,
140-
extraArgs,
126+
processedArgs,
141127
minSevString,
142128
diagnosticCollection
143129
);
@@ -187,7 +173,7 @@ export function activate(context: vscode.ExtensionContext) {
187173
async function runCppcheckOnFileXML(
188174
document: vscode.TextDocument,
189175
commandPath: string,
190-
extraArgs: string,
176+
processedArgs: string,
191177
minSevString: string,
192178
diagnosticCollection: vscode.DiagnosticCollection
193179
): Promise<void> {
@@ -199,7 +185,7 @@ async function runCppcheckOnFileXML(
199185
const minSevNum = parseMinSeverity(minSevString);
200186

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

210196
let proc;
211-
if (extraArgs.includes("--project")) {
197+
if (processedArgs.includes("--project")) {
212198
const args = [
213199
'--enable=all',
214200
'--inline-suppr',
@@ -217,7 +203,7 @@ async function runCppcheckOnFileXML(
217203
'--suppress=missingInclude',
218204
'--suppress=missingIncludeSystem',
219205
`--file-filter=${filePath}`,
220-
...extraArgsParsed,
206+
...argsParsed,
221207
].filter(Boolean);
222208
proc = cp.spawn(commandPath, args, {
223209
cwd: path.dirname(document.fileName),
@@ -230,7 +216,7 @@ async function runCppcheckOnFileXML(
230216
'--suppress=unusedFunction',
231217
'--suppress=missingInclude',
232218
'--suppress=missingIncludeSystem',
233-
...extraArgsParsed,
219+
...argsParsed,
234220
filePath,
235221
].filter(Boolean);
236222
proc = cp.spawn(commandPath, args, {

src/util/path.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import * as path from "path";
2+
import * as os from "os";
3+
import * as vscode from 'vscode';
4+
5+
export function resolvePath(argPath: string): string {
6+
const folders = vscode.workspace.workspaceFolders;
7+
const workspaceRoot = folders && folders.length > 0
8+
? folders[0].uri.fsPath
9+
: process.cwd();
10+
11+
// Expand ${workspaceFolder}
12+
if (argPath.includes("${workspaceFolder}")) {
13+
argPath = argPath.replace("${workspaceFolder}", workspaceRoot);
14+
}
15+
16+
// Expand tilde (~) to home directory
17+
if (argPath.startsWith("~")) {
18+
argPath = path.join(os.homedir(), argPath.slice(1));
19+
}
20+
21+
// Expand ./ or ../ relative paths (relative to workspace root if available)
22+
if (argPath.startsWith("./") || argPath.startsWith("../")) {
23+
argPath = path.resolve(workspaceRoot, argPath);
24+
}
25+
26+
// If still not absolute, treat it as relative to workspace root
27+
if (!path.isAbsolute(argPath)) {
28+
argPath = path.join(workspaceRoot, argPath);
29+
}
30+
return argPath;
31+
}

src/util/scripts.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { exec } from "child_process";
2+
import { resolvePath } from './path';
3+
import util from 'util';
4+
5+
const execAsync = util.promisify(exec);
6+
7+
async function runCommand(command : string) {
8+
try {
9+
const { stdout, stderr } = await execAsync(command, {
10+
cwd: resolvePath('${workspaceFolder}'),
11+
});
12+
13+
if (stderr) {
14+
throw new Error(stderr);
15+
}
16+
return stdout;
17+
} catch (error) {
18+
throw error;
19+
}
20+
}
21+
22+
export { runCommand };

0 commit comments

Comments
 (0)