Skip to content
73 changes: 67 additions & 6 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ import * as crypto from 'crypto';
import { documentationLinkMap, getPremiumCertLink } from './util/documentation';
import { runCommand } from './util/scripts';
import { looksLikePath, resolvePath, findWorkspaceRoot } from './util/path';
import { diagnosticsUnion } from './util/diagnostics';
import { DiagnosticMetadataStore, 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> = {};
// To keep track of warnings for files created from analysis of other files we save their relations to fileRelationMap
let fileRelationMap: Record<string, Set<string>> = {};
// Some diagnostics have symbol names associated with them, which we keep track of in diagnosticMetadataStore
const diagnosticMetadataStore = new DiagnosticMetadataStore();

let previewAnalysisTimer: NodeJS.Timeout | undefined;
let previewedDocument: vscode.TextDocument | undefined;
Expand Down Expand Up @@ -129,7 +131,7 @@ export async function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.languages.registerCodeActionsProvider(
{ pattern: "**/*" },
new CodeActionProvider(),
new CodeActionProvider(diagnosticMetadataStore),
{
providedCodeActionKinds: [
vscode.CodeActionKind.QuickFix
Expand All @@ -155,12 +157,21 @@ export async function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand(
"cppcheck-official.suppressWarningAll",
async (diagnosticCode : string) => {
async (diagnosticCode : string, file? : string, symbolName? : string) => {
if (cppcheckProjectFileUri) {
const success = await writeSuppressionToProjectFile(cppcheckProjectFileUri, diagnosticCode);
const success = await writeSuppressionToProjectFile(cppcheckProjectFileUri, diagnosticCode, file, symbolName);
if (success) {
vscode.window.showInformationMessage(`Suppression of ${diagnosticCode} added to project file ${cppcheckProjectFileUri.toString()}`);
await vscode.commands.executeCommand('cppcheck-official.hideWarningType', diagnosticCode);
// Construct information message to display to the user
const fileMessagePart = file ? ` for file ${file}` : '';
const symbolNameMessagePart = symbolName ? ` for symbol name ${symbolName}` : '';
const fileOrSymbolExtraMessage = file || symbolName ? '. Note: you have to re-analyze the file for the suppression to take effect.' : '';
const completeInformationMessageText = `Suppression of ${diagnosticCode} added to project file ${cppcheckProjectFileUri.toString()}${fileMessagePart}${symbolNameMessagePart}${fileOrSymbolExtraMessage}`;
vscode.window.showInformationMessage(completeInformationMessageText);

// Only hide warnings if suppression is global, since hide command does not support file or symbol filter for now
if (!file && !symbolName) {
await vscode.commands.executeCommand('cppcheck-official.hideWarningType', diagnosticCode);
}
} else {
vscode.window.showErrorMessage(`Failed to add suppression of ${diagnosticCode} to project file ${cppcheckProjectFileUri.toString()}`);
}
Expand Down Expand Up @@ -214,6 +225,51 @@ export async function activate(context: vscode.ExtensionContext) {
)
);

context.subscriptions.push(
vscode.commands.registerCommand(
"cppcheck-official.suppressWarningAdvanced",
async (diagnosticCode : string, doc : vscode.TextDocument, diagnostic : vscode.Diagnostic) => {
const storedSymbolName = diagnosticMetadataStore.get(diagnostic)?.symbolName;
const symbolExistsForDiagnostic = !!storedSymbolName;
const file = await vscode.window.showInputBox(
{
value: doc.fileName,
title: symbolExistsForDiagnostic
? "Create Cppcheck Suppression by file and / or symbol (leave blank to skip file filter) (1/2)"
: "Create Cppcheck Suppression by file"
}
);
// User presses ESC -> file === undefined
if (file === undefined) {
return;
}
let symbolName = null;
if (symbolExistsForDiagnostic) {
symbolName = await vscode.window.showQuickPick(
[
{
label: `Symbol: ${storedSymbolName}`,
value: storedSymbolName
},
{
label: 'Not symbol specific',
value: null
},
],
{
title: "Create Cppcheck Suppression by file and / or symbol (2/2)"
}
);
// User presses ESC -> symbolName === undefined
if (symbolName === undefined) {
return;
}
}
await vscode.commands.executeCommand('cppcheck-official.suppressWarningAll', diagnosticCode, file, symbolName?.value);
}
)
);

context.subscriptions.push(
vscode.commands.registerCommand(
"cppcheck-official.selectMinSeverity",
Expand Down Expand Up @@ -579,6 +635,11 @@ async function runCppcheckOnFileXML(
target: vscode.Uri.parse(getPremiumCertLink(e.$.id))
} : e.$.id;

// If warning has a symbol we keep track of it
if (e.symbol?.[0]) {
diagnosticMetadataStore.set(diagnostic, {symbolName: e.symbol?.[0]});
}

// Related Information
const relatedInfos: vscode.DiagnosticRelatedInformation[] = [];
for (let i = 1; i <= locations.length; i++) {
Expand Down
25 changes: 23 additions & 2 deletions src/util/codeActions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import * as vscode from 'vscode';

import { DiagnosticMetadataStore } from './diagnostics';
export class CodeActionProvider implements vscode.CodeActionProvider {
constructor(
private readonly metadataStore: DiagnosticMetadataStore
) {}
provideCodeActions(
document: vscode.TextDocument,
range: vscode.Range,
Expand Down Expand Up @@ -61,7 +64,25 @@ export class CodeActionProvider implements vscode.CodeActionProvider {

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


// Set up an action for suppressing warning based on file or symbol name
const symbol = this.metadataStore.get(diagnostic)?.symbolName;
const suppressAdvancedAction = new vscode.CodeAction(
symbol
? `Suppress warning ${diagnosticCode} based on file and / or symbol`
:`Suppress warning ${diagnosticCode} based on file`,
vscode.CodeActionKind.QuickFix
);

suppressAdvancedAction.command = {
command: "cppcheck-official.suppressWarningAdvanced",
title: "Suppress warning advanced",
arguments: [diagnosticCode, document, diagnostic]
};

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

// Set up an action for hiding a warning
const hideAction = new vscode.CodeAction(
`Hide this ${diagnosticCode} warning`,
Expand Down
16 changes: 16 additions & 0 deletions src/util/diagnostics.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
import * as vscode from 'vscode';

interface DiagnosticMetadata {
symbolName?: string;
}

export class DiagnosticMetadataStore {
private readonly map = new WeakMap<vscode.Diagnostic, DiagnosticMetadata>();

set(diagnostic: vscode.Diagnostic, metadata: DiagnosticMetadata) {
this.map.set(diagnostic, metadata);
}

get(diagnostic: vscode.Diagnostic) {
return this.map.get(diagnostic);
}
}

export function diagnosticsUnion(diagnosticsA : vscode.Diagnostic[], diagnosticB : vscode.Diagnostic[]) : vscode.Diagnostic[] {
const diagnosticsUnion = new Array<vscode.Diagnostic>;
// Add all elements from diagnosticsA to result array
Expand Down
15 changes: 12 additions & 3 deletions src/util/files.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as vscode from 'vscode';

export async function writeSuppressionToProjectFile(projectFileUri : vscode.Uri, warningType : String) : Promise<boolean> {
export async function writeSuppressionToProjectFile(projectFileUri : vscode.Uri, warningType : String, file? : String, symbolName? : 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}`);
Expand All @@ -13,6 +13,15 @@ export async function writeSuppressionToProjectFile(projectFileUri : vscode.Uri,
var textToInsert = '';
var positionToInsertAt = 0;

// If file or symbolName is specified we set up that part of the suppression block
let options = '';
if (file) {
options = ` file="${file}"`;
}
if (symbolName) {
options += ` symbolName="${symbolName}"`;
}

// Search for suppressions section
const match = /<suppressions\b[^>]*>([\s\S]*?)<\/suppressions>/m.exec(text);
if (match !== null) {
Expand All @@ -22,7 +31,7 @@ export async function writeSuppressionToProjectFile(projectFileUri : vscode.Uri,
// 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}`;
const newSuppressionLine = `${indentation}<suppression${options}>${warningType}</suppression>\n${indentation}`;

// We splice in the new line just before the end of the suppressions block
textToInsert = newSuppressionLine;
Expand All @@ -34,7 +43,7 @@ export async function writeSuppressionToProjectFile(projectFileUri : vscode.Uri,
// 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`;
const suppressionsBlock = `${indentation}<suppressions>\n${indentation} <suppression${options}>${warningType}</suppression>\n${indentation}</suppressions>\n`;

// Splice in the new suppressions block just before the end of the project-file
textToInsert = suppressionsBlock;
Expand Down
Loading