From 6cae8b2c78fef13e44ae6e6d4ab50a9ec92f0639 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Thu, 8 Oct 2026 11:29:25 +0200 Subject: [PATCH 1/2] feature / shared cppcheck.json settings file Add support for a cppcheck.json file in the workspace root that can be committed to version control so that all developers get the same cppcheck arguments. If the file specifies "arguments" (string or array of strings) it takes precedence over the cppcheck-official.arguments setting. Other settings are still read from the VS Code settings. A JSON schema is contributed for autocomplete and validation. Co-Authored-By: Claude Opus 5.5 --- README.md | 12 +++++++ package.json | 8 ++++- schemas/cppcheck.schema.json | 16 +++++++++ src/extension.ts | 5 +-- src/util/settingsFile.ts | 64 ++++++++++++++++++++++++++++++++++++ walkthrough/arguments.md | 2 +- 6 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 schemas/cppcheck.schema.json create mode 100644 src/util/settingsFile.ts diff --git a/README.md b/README.md index 0c2e6e8..f0351a8 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,18 @@ This extension contributes the following settings under `cppcheck-official.*`: - **`cppcheck-official.arguments`**: (string) Additional [command line arguments](https://cppcheck.sourceforge.io/manual.pdf?#page=5) to pass to `cppcheck`. - **`cppcheck-official.path`**: (string) Path to the `cppcheck` executable (i.e. `/path/to/cppcheck.exe`). If left empty, `cppcheck` from the system PATH is used. Supports paths relative to workspace folder on the formats `./RELATIVE_PATH`, `../RELATIVE_PATH` or `${workspaceFolder}/RELATIVE_PATH`. +## Shared Settings File + +To give all developers in a project the same setup, create a `cppcheck.json` file in the root of the workspace folder and commit it to version control: + +```json +{ + "arguments": "--project=build/compile_commands.json --enable=style --suppress=missingInclude" +} +``` + +`arguments` can be either a string or an array of strings. If the file specifies `arguments`, it takes precedence over the `cppcheck-official.arguments` setting. Relative paths are resolved relative to the workspace folder. All other settings are still read from the VS Code settings, so each developer can adjust them individually. + ## Reporting Issues Please submit any issues or feature requests via the [GitHub Issues page](https://github.com/cppchecksolutions/vscode-cppcheck-official/issues). diff --git a/package.json b/package.json index 07f3680..63089b5 100644 --- a/package.json +++ b/package.json @@ -67,12 +67,18 @@ "cppcheck-official.arguments": { "type": "string", "default": "", - "markdownDescription": "Additional command line arguments for cppcheck. Use spaces to separate multiple arguments. For documentation see: [Cppcheck documentation](https://files.cppchecksolutions.com/manual.pdf)", + "markdownDescription": "Additional command line arguments for cppcheck. Use spaces to separate multiple arguments. For documentation see: [Cppcheck documentation](https://files.cppchecksolutions.com/manual.pdf). Overridden by `arguments` in `cppcheck.json` in the workspace folder, if present.", "description": "Additional command line arguments for cppcheck. Use spaces to separate multiple arguments." } } } ], + "jsonValidation": [ + { + "fileMatch": "/cppcheck.json", + "url": "./schemas/cppcheck.schema.json" + } + ], "walkthroughs": [ { "id": "cppcheck-official.gettingStarted", diff --git a/schemas/cppcheck.schema.json b/schemas/cppcheck.schema.json new file mode 100644 index 0000000..bb84870 --- /dev/null +++ b/schemas/cppcheck.schema.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cppcheck Official settings file", + "description": "Shared settings for the Cppcheck Official VS Code extension. Settings in this file take precedence over the VS Code settings.", + "type": "object", + "properties": { + "arguments": { + "description": "Additional command line arguments for cppcheck. Overrides the 'cppcheck-official.arguments' setting. Paths are resolved relative to the workspace folder.", + "oneOf": [ + { "type": "string" }, + { "type": "array", "items": { "type": "string" } } + ] + } + }, + "additionalProperties": false +} diff --git a/src/extension.ts b/src/extension.ts index 09ff2ed..8c2a308 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -9,6 +9,7 @@ import { DiagnosticMetadataStore, diagnosticsUnion } from './util/diagnostics'; import { CodeActionProvider } from './util/codeActions'; import { ProjectFileStore, writeSuppressionToProjectFile } from './util/files'; import { extractRelatedInformation, setUpDiagnostic } from './helpers/diagnosticHelpers'; +import { getArguments } from './util/settingsFile'; // To keep track of document changes we save hashed versions of their content to this record let documentHashMemory : Record = {}; @@ -441,7 +442,7 @@ export async function activate(context: vscode.ExtensionContext) { const userPath = config.get("cppcheck-official.path")?.trim() || ""; const commandPath = userPath ? resolvePath(userPath) : "cppcheck"; - var args = config.get("cppcheck-official.arguments", ""); + var args = getArguments(config); const processedArgs = await processArguments(args); // Check if cppcheck is available @@ -590,7 +591,7 @@ export async function activate(context: vscode.ExtensionContext) { const userPath = config.get("cppcheck-official.path")?.trim() || ""; const commandPath = userPath ? resolvePath(userPath) : "cppcheck"; - var args = config.get("cppcheck-official.arguments", ""); + var args = getArguments(config); const processedArgs = await processArguments(args); // If disabled, clear any existing diagnostics for this doc. diff --git a/src/util/settingsFile.ts b/src/util/settingsFile.ts new file mode 100644 index 0000000..e112182 --- /dev/null +++ b/src/util/settingsFile.ts @@ -0,0 +1,64 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { findWorkspaceRoot } from './path'; + +// Shared settings file that can be committed to the repository so that all developers get the same settings. +// Settings in this file take precedence over the VS Code settings. +export const settingsFileName = 'cppcheck.json'; + +interface SettingsFile { + arguments?: string; +} + +// Remember the last reported error so that the user is not spammed with the same message on every analysis +let lastReportedError = ''; + +export function getSettingsFilePath(): string { + return path.join(findWorkspaceRoot(), settingsFileName); +} + +function readSettingsFile(): SettingsFile | undefined { + const filePath = getSettingsFilePath(); + if (!fs.existsSync(filePath)) { + lastReportedError = ''; + return undefined; + } + + try { + const content = JSON.parse(fs.readFileSync(filePath, 'utf8')); + if (typeof content !== 'object' || content === null || Array.isArray(content)) { + throw new Error('expected a JSON object'); + } + + const settings: SettingsFile = {}; + if (content.arguments !== undefined) { + // Arguments may be given either as a string or as an array of strings + if (typeof content.arguments === 'string') { + settings.arguments = content.arguments; + } else if (Array.isArray(content.arguments) && content.arguments.every((a: unknown) => typeof a === 'string')) { + settings.arguments = content.arguments.join(' '); + } else { + throw new Error('"arguments" must be a string or an array of strings'); + } + } + lastReportedError = ''; + return settings; + } catch (error) { + const message = `Cppcheck: Failed to read ${filePath}: ${error instanceof Error ? error.message : error}`; + if (message !== lastReportedError) { + lastReportedError = message; + vscode.window.showErrorMessage(message); + } + return undefined; + } +} + +// Get cppcheck arguments, from the settings file if it specifies them, otherwise from the VS Code settings +export function getArguments(config: vscode.WorkspaceConfiguration): string { + const settings = readSettingsFile(); + if (settings?.arguments !== undefined) { + return settings.arguments; + } + return config.get("cppcheck-official.arguments", ""); +} diff --git a/walkthrough/arguments.md b/walkthrough/arguments.md index bdbe99a..efc0d70 100644 --- a/walkthrough/arguments.md +++ b/walkthrough/arguments.md @@ -2,7 +2,7 @@ Cppcheck works best for you if you set it up according to your needs. You may e.g. want to disable certain kinds of warnings, and if you have cppcheck premium you will need to set this up for it to work in this extension. All of this is done through the extension property 'arguments', with the different flags or arguments available being detailed in the [official cppcheck documentation](https://files.cppchecksolutions.com/manual.pdf#page=13). -It is recommended to set up these settings in the workspace settings so that you and your team easily can work with the same set up. Furthermore you are likely to want to have the same settings between VS Code and your CI workflows. This is most easily done through project files, which are referenced from the argument setting (see [documentation](https://files.cppchecksolutions.com/manual.pdf#page=4) for how to create a project file). +To make sure that you and your team work with the same set up, it is recommended to put the arguments in a `cppcheck.json` file in the root of your workspace and commit it to version control, e.g. `{ "arguments": "--project=build/compile_commands.json" }`. Arguments in this file take precedence over the arguments setting. Furthermore you are likely to want to have the same settings between VS Code and your CI workflows. This is most easily done through project files, which are referenced from the argument setting (see [documentation](https://files.cppchecksolutions.com/manual.pdf#page=4) for how to create a project file). ![image showing project file path being set in arguments property](../images/project_file.png) From 25d8b6a307f45a806cb5c0f5d2c5eaee6c0be75a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Thu, 8 Oct 2026 11:37:18 +0200 Subject: [PATCH 2/2] test / add unit tests for cppcheck.json settings file Make the workspace root injectable in getArguments so that tests can use a temporary directory. Co-Authored-By: Claude Opus 5.5 --- src/test/settingsFile.test.ts | 104 ++++++++++++++++++++++++++++++++++ src/util/settingsFile.ts | 12 ++-- 2 files changed, 110 insertions(+), 6 deletions(-) create mode 100644 src/test/settingsFile.test.ts diff --git a/src/test/settingsFile.test.ts b/src/test/settingsFile.test.ts new file mode 100644 index 0000000..5585f4a --- /dev/null +++ b/src/test/settingsFile.test.ts @@ -0,0 +1,104 @@ +import * as assert from 'assert'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { getArguments, getSettingsFilePath } from '../util/settingsFile'; + +suite('Settings file', () => { + let workspaceRoot: string; + let errorMessages: string[]; + const originalShowErrorMessage = vscode.window.showErrorMessage; + + // Fake VS Code configuration that returns the given value for the arguments setting + const config = { + get: (key: string, defaultValue?: unknown) => + key === 'cppcheck-official.arguments' ? '--from-vscode-settings' : defaultValue, + } as vscode.WorkspaceConfiguration; + + function writeSettingsFile(content: string) { + fs.writeFileSync(getSettingsFilePath(workspaceRoot), content); + } + + setup(() => { + workspaceRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'cppcheck-settings-')); + errorMessages = []; + (vscode.window as { showErrorMessage: unknown }).showErrorMessage = (message: string) => { + errorMessages.push(message); + return Promise.resolve(undefined); + }; + // Clear error memory from previous tests + getArguments(config, workspaceRoot); + }); + + teardown(() => { + (vscode.window as { showErrorMessage: unknown }).showErrorMessage = originalShowErrorMessage; + fs.rmSync(workspaceRoot, { recursive: true, force: true }); + }); + + test('Uses VS Code setting when there is no settings file', () => { + assert.strictEqual(getArguments(config, workspaceRoot), '--from-vscode-settings'); + assert.deepStrictEqual(errorMessages, []); + }); + + test('Arguments as string', () => { + writeSettingsFile('{"arguments": "--enable=style --project=build/compile_commands.json"}'); + assert.strictEqual(getArguments(config, workspaceRoot), '--enable=style --project=build/compile_commands.json'); + assert.deepStrictEqual(errorMessages, []); + }); + + test('Arguments as array', () => { + writeSettingsFile('{"arguments": ["--enable=style", "--project=build/compile_commands.json"]}'); + assert.strictEqual(getArguments(config, workspaceRoot), '--enable=style --project=build/compile_commands.json'); + assert.deepStrictEqual(errorMessages, []); + }); + + test('Empty arguments in settings file override VS Code setting', () => { + writeSettingsFile('{"arguments": ""}'); + assert.strictEqual(getArguments(config, workspaceRoot), ''); + }); + + test('Uses VS Code setting when settings file has no arguments', () => { + writeSettingsFile('{}'); + assert.strictEqual(getArguments(config, workspaceRoot), '--from-vscode-settings'); + assert.deepStrictEqual(errorMessages, []); + }); + + test('Changes to settings file are picked up', () => { + writeSettingsFile('{"arguments": "--first"}'); + assert.strictEqual(getArguments(config, workspaceRoot), '--first'); + writeSettingsFile('{"arguments": "--second"}'); + assert.strictEqual(getArguments(config, workspaceRoot), '--second'); + }); + + test('Invalid JSON reports error once and falls back to VS Code setting', () => { + writeSettingsFile('{"arguments": '); + assert.strictEqual(getArguments(config, workspaceRoot), '--from-vscode-settings'); + assert.strictEqual(getArguments(config, workspaceRoot), '--from-vscode-settings'); + assert.strictEqual(errorMessages.length, 1); + assert.ok(errorMessages[0].includes('cppcheck.json')); + }); + + test('Invalid arguments type reports error', () => { + writeSettingsFile('{"arguments": 5}'); + assert.strictEqual(getArguments(config, workspaceRoot), '--from-vscode-settings'); + assert.strictEqual(errorMessages.length, 1); + assert.ok(errorMessages[0].includes('"arguments" must be a string or an array of strings')); + }); + + test('Settings file that is not an object reports error', () => { + writeSettingsFile('["--enable=style"]'); + assert.strictEqual(getArguments(config, workspaceRoot), '--from-vscode-settings'); + assert.strictEqual(errorMessages.length, 1); + }); + + test('Error is reported again after settings file has been fixed and broken again', () => { + writeSettingsFile('{"arguments": 5}'); + getArguments(config, workspaceRoot); + writeSettingsFile('{"arguments": "--fixed"}'); + assert.strictEqual(getArguments(config, workspaceRoot), '--fixed'); + writeSettingsFile('{"arguments": 5}'); + getArguments(config, workspaceRoot); + assert.strictEqual(errorMessages.length, 2); + }); +}); diff --git a/src/util/settingsFile.ts b/src/util/settingsFile.ts index e112182..3bf09fc 100644 --- a/src/util/settingsFile.ts +++ b/src/util/settingsFile.ts @@ -14,12 +14,12 @@ interface SettingsFile { // Remember the last reported error so that the user is not spammed with the same message on every analysis let lastReportedError = ''; -export function getSettingsFilePath(): string { - return path.join(findWorkspaceRoot(), settingsFileName); +export function getSettingsFilePath(workspaceRoot: string = findWorkspaceRoot()): string { + return path.join(workspaceRoot, settingsFileName); } -function readSettingsFile(): SettingsFile | undefined { - const filePath = getSettingsFilePath(); +function readSettingsFile(workspaceRoot: string): SettingsFile | undefined { + const filePath = getSettingsFilePath(workspaceRoot); if (!fs.existsSync(filePath)) { lastReportedError = ''; return undefined; @@ -55,8 +55,8 @@ function readSettingsFile(): SettingsFile | undefined { } // Get cppcheck arguments, from the settings file if it specifies them, otherwise from the VS Code settings -export function getArguments(config: vscode.WorkspaceConfiguration): string { - const settings = readSettingsFile(); +export function getArguments(config: vscode.WorkspaceConfiguration, workspaceRoot: string = findWorkspaceRoot()): string { + const settings = readSettingsFile(workspaceRoot); if (settings?.arguments !== undefined) { return settings.arguments; }