Skip to content
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
- **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`.

- **Warning suppression**: Warnings of a specific type can be supressed with the --supress flag in the argument field in the extension settings. The extension also supports inline suppression for specific lines of code, simply write `// cppcheck-supress >warning id<` (see image below).
- **Warning suppression**: Warnings of a specific type can be supressed with the --suppress flag in the argument field in the extension settings. The extension also supports inline suppression for specific lines of code, simply write `// cppcheck-suppress >warning id<` (see image below). Suppressions can also be added through code actions, either as automatically generated inline suppressions or, if you are using a project file, as universal suppressions specified in your project file.
![Image showing how to suppress warnings](./images/suppression.png)
## Requirements

Expand Down
106 changes: 97 additions & 9 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { documentationLinkMap, getPremiumCertLink } from './util/documentation';
import { runCommand } from './util/scripts';
import { looksLikePath, resolvePath, findWorkspaceRoot } from './util/path';
import { diagnosticsUnion } from './util/diagnostics';
import { CodeActionProvider } from './util/codeActions';
import { writeSuppressionToProjectFile } from './util/files';

// To keep track of document changes we save hashed versions of their content to this record
let documentHashMemory : Record<string, string> = {};
Expand All @@ -17,6 +19,7 @@ let previewAnalysisTimer: NodeJS.Timeout | undefined;
let previewedDocument: vscode.TextDocument | undefined;
let cppcheckProgressIndicator: vscode.StatusBarItem;
let checksRunning = false;
let cppcheckProjectFileUri: vscode.Uri | undefined;

enum SeverityNumber {
Info = 0,
Expand Down Expand Up @@ -94,7 +97,23 @@ function getDocumentSha1(document: vscode.TextDocument): string {

// This method is called when your extension is activated.
// Your extension is activated the very first time the command is executed.
export async 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 code actions provider
context.subscriptions.push(
vscode.languages.registerCodeActionsProvider(
{ pattern: "**/*" },
new CodeActionProvider(),
{
providedCodeActionKinds: [
vscode.CodeActionKind.QuickFix
]
}
)
);

// Register a command to push user to workspace settings from walkthrough
context.subscriptions.push(
Expand All @@ -108,15 +127,74 @@ export async function activate(context: vscode.ExtensionContext) {
}
)
);

// Register a command for suppressing a warning type
context.subscriptions.push(
vscode.commands.registerCommand(
"cppcheck-official.suppressWarningAll",
async (diagnosticCode : string) => {
if (cppcheckProjectFileUri) {
const success = await writeSuppressionToProjectFile(cppcheckProjectFileUri, diagnosticCode);
if (success) {
vscode.window.showInformationMessage(`Suppression of ${diagnosticCode} added to project file ${cppcheckProjectFileUri.toString()}`);
await vscode.commands.executeCommand('cppcheck-official.hideWarningType', diagnosticCode);
} else {
vscode.window.showErrorMessage(`Failed to add suppression of ${diagnosticCode} to project file ${cppcheckProjectFileUri.toString()}`);
}
} else {
vscode.window.showInformationMessage(`Adding suppression is currently only supported for .cppcheck project files`);
}
}
)
);

// Register a command for hiding a warning
context.subscriptions.push(
vscode.commands.registerCommand(
"cppcheck-official.hideWarning",
async (uri : vscode.Uri, diagnosticCode : string, range : vscode.Range) => {
const diagnostics = diagnosticCollection.get(uri);
const filteredDiagnostics = diagnostics?.filter((diagnostic : vscode.Diagnostic) => {
var code = diagnostic.code;
if (typeof(code) === "object" && typeof(code) !== null) {
code = code.value;
}
if (code === diagnosticCode && diagnostic.range.isEqual(range)) {
return false;
}
return true;
});
diagnosticCollection.set(uri, filteredDiagnostics);
}
)
);

// Register a command for hiding all warnings of a given type
context.subscriptions.push(
vscode.commands.registerCommand(
"cppcheck-official.hideWarningType",
async (diagnosticCode : string) => {
diagnosticCollection.forEach((uri : vscode.Uri, diagnostics : readonly vscode.Diagnostic[], collection : vscode.DiagnosticCollection) => {
const filteredDiagnostics = diagnostics?.filter((diagnostic : vscode.Diagnostic) => {
var code = diagnostic.code;
if (typeof(code) === "object" && typeof(code) !== null) {
code = code.value;
}
if (code === diagnosticCode) {
return false;
}
return true;
});
collection.set(uri, filteredDiagnostics);
});
}
)
);

// ProgressIndicator status bar item to show when checks are running
cppcheckProgressIndicator = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 10);
context.subscriptions.push(cppcheckProgressIndicator);

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

function clearDiagnosticForDoc(doc: vscode.TextDocument): void {
// Any file who was warnings generated from (and only from) the closed doc have their diagnostics cleared
// NOTE: This includes the closed doc - its diagnostics will only be cleared if its warnings only come from analysis of it itself
Expand Down Expand Up @@ -299,19 +377,29 @@ async function runCppcheckOnFileXML(
});

let usingProjectFile = false;
cppcheckProjectFileUri = undefined;

const args = [
'--enable=all',
'--inline-suppr',
'--xml',
'--suppress=unusedFunction',
'--suppress=missingInclude',
'--suppress=missingIncludeSystem',
...argsParsed,
].filter(Boolean);
if (processedArgs.includes("--project")) {

if (processedArgs.includes("--project=")) {
usingProjectFile = true;
args.push(`--file-filter=${filePath}`);
// If project file is of type .cppcheck we keep track of it
var projectFilePath = processedArgs.split('--project=')[1].split(' ')[0];
var projectFileType = projectFilePath.split('.')[1];
if (projectFileType.toLowerCase() === 'cppcheck') {
cppcheckProjectFileUri = vscode.Uri.file(projectFilePath);
}
} else {
args.push(
'--suppress=unusedFunction',
'--suppress=missingInclude',
'--suppress=missingIncludeSystem');
args.push(filePath);
}

Expand Down
98 changes: 98 additions & 0 deletions src/util/codeActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import * as vscode from 'vscode';

export class CodeActionProvider implements vscode.CodeActionProvider {
provideCodeActions(
document: vscode.TextDocument,
range: vscode.Range,
context: vscode.CodeActionContext,
token: vscode.CancellationToken
): vscode.CodeAction[] {

const actions: vscode.CodeAction[] = [];

for (const diagnostic of context.diagnostics) {
var diagnosticCode = diagnostic.code;
if (typeof(diagnosticCode) === "object" && typeof(diagnosticCode) !== null) {
diagnosticCode = diagnosticCode.value;
}

// Set up one action for suppressing the specific warning on the line targeted by the diagnostic
const suppressAction = new vscode.CodeAction(
`Add comment that suppresses this ${diagnosticCode} warning`,
vscode.CodeActionKind.QuickFix
);

// Copy indentation from line affected by diagnostic
const lineText = document.lineAt(diagnostic.range.start.line).text;
const indent = lineText.match(/^\s*/)?.[0] ?? "";

// Insert suppression comment above affected line
const suppressLineEdit = new vscode.WorkspaceEdit();
suppressLineEdit.insert(
document.uri,
new vscode.Position(
diagnostic.range.start.line,
0,
),
`${indent}// cppcheck-suppress ${diagnosticCode}\n`
);
suppressAction.edit = suppressLineEdit;

// For inline suppression we also hide the warning so user does not have to rerun analysis for it to disappear
suppressAction.command = {
command: "cppcheck-official.hideWarning",
title: "Hide warning",
arguments: [document.uri, diagnosticCode, diagnostic.range]
};
suppressAction.diagnostics = [diagnostic];
actions.push(suppressAction);

// Set up an action for suppressing warning of a given type universally
const suppressTypeAction = new vscode.CodeAction(
`Suppress warning type ${diagnosticCode} universally (in project file)`,
vscode.CodeActionKind.QuickFix
);

suppressTypeAction.command = {
command: "cppcheck-official.suppressWarningAll",
title: "Suppress warning universally",
arguments: [diagnosticCode]
};

suppressTypeAction.diagnostics = [diagnostic];
actions.push(suppressTypeAction);

// Set up an action for hiding a warning
const hideAction = new vscode.CodeAction(
`Hide this ${diagnosticCode} warning`,
vscode.CodeActionKind.QuickFix
);

hideAction.command = {
command: "cppcheck-official.hideWarning",
title: "Hide warning",
arguments: [document.uri, diagnosticCode, diagnostic.range]
};

hideAction.diagnostics = [diagnostic];
actions.push(hideAction);

// Set up an action for hiding all warnings of a given type
const hideTypeAction = new vscode.CodeAction(
`Hide all ${diagnosticCode} warnings`,
vscode.CodeActionKind.QuickFix
);

hideTypeAction.command = {
command: "cppcheck-official.hideWarningType",
title: "Hide warning type",
arguments: [diagnosticCode]
};

hideTypeAction.diagnostics = [diagnostic];
actions.push(hideTypeAction);
}

return actions;
}
}
55 changes: 55 additions & 0 deletions src/util/files.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import * as vscode from 'vscode';

export async function writeSuppressionToProjectFile(projectFileUri : vscode.Uri, warningType : String) : Promise<boolean> {
const fileType = projectFileUri.toString().split('.')[1];
if (fileType !== 'cppcheck') {
throw new Error(`Function writeSuppressionToProjectFile only supports writing to .cppcheck project files! Recieved file is of type .${fileType}`);
}

// Open project file with vscode workspace API
const document = await vscode.workspace.openTextDocument(projectFileUri);
const text = document.getText();
const edit = new vscode.WorkspaceEdit();
var textToInsert = '';
var positionToInsertAt = 0;

// Search for suppressions section
const match = /<suppressions\b[^>]*>([\s\S]*?)<\/suppressions>/m.exec(text);
if (match !== null) {
// match !== null -> Suppressions section exists
const closeIndex = text.indexOf("</suppressions>");

// Determine indentation and construct the new suppression line
const endOfSuppressionsBlockLine = document.lineAt(document.positionAt(closeIndex).line);
const indentation = endOfSuppressionsBlockLine.text.match(/^\s*/)?.[0] ?? " ";
const newSuppressionLine = `${indentation}<suppression>${warningType}</suppression>\n${indentation}`;

// We splice in the new line just before the end of the suppressions block
textToInsert = newSuppressionLine;
positionToInsertAt = closeIndex;
} else {
// match === null -> Suppressions section does not exist
const closeIndex = text.indexOf("</project>");

// Determine indentation and construct the new suppressions block
const line = document.lineAt(document.positionAt(closeIndex).line - 1);
const indentation = line.text.match(/^\s*/)?.[0] ?? " ";
const suppressionsBlock = `${indentation}<suppressions>\n${indentation} <suppression>${warningType}</suppression>\n${indentation}</suppressions>\n`;

// Splice in the new suppressions block just before the end of the project-file
textToInsert = suppressionsBlock;
positionToInsertAt = closeIndex;
}

// Apply edit to document
edit.insert(
document.uri,
document.positionAt(positionToInsertAt),
textToInsert
);
const success = await vscode.workspace.applyEdit(edit);

// Write file
await document.save();
return success;
}
Loading