From 0f58452b1b2bf897237066670069183ea36bf2c1 Mon Sep 17 00:00:00 2001 From: davidramnero Date: Tue, 23 Jun 2026 16:31:52 +0200 Subject: [PATCH 1/3] feature / #77 show warnings for included header files along with source files --- src/extension.ts | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index e7a2b02..4ef71ea 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -272,7 +272,7 @@ async function runCppcheckOnFileXML( return arg; }); - let proc; + let usingProjectFile = false; const args = [ '--enable=all', '--inline-suppr', @@ -283,10 +283,13 @@ async function runCppcheckOnFileXML( ...argsParsed, ].filter(Boolean); if (processedArgs.includes("--project")) { + usingProjectFile = true; args.push(`--file-filter=${filePath}`); } else { args.push(filePath); } + + let proc; const cwd = findWorkspaceRoot(); proc = cp.spawn(commandPath, args, { cwd, @@ -312,14 +315,14 @@ async function runCppcheckOnFileXML( vscode.window.showErrorMessage(errorMessage); } const parser = new xml2js.Parser({ explicitArray: true }); - parser.parseString(xmlOutput, (err, result) => { + parser.parseString(xmlOutput, async (err, result) => { if (err) { console.error("XML parse error:", err); return; } const errors = result.results?.errors?.[0]?.error || []; - const diagnostics: vscode.Diagnostic[] = []; + const diagnostics: Record = {}; for (const e of errors) { const isCriticalError = criticalWarningTypes.includes(e.$.id); @@ -330,8 +333,8 @@ async function runCppcheckOnFileXML( const mainLoc = locations[locations.length - 1].$; - // If main location is not current file, then skip displaying warning unless it is critical - if (!isCriticalError && !filePath.endsWith(mainLoc.file)) { + // If main location is not current file, we are not using a project file and warning is not critical then skip displaying warning + if (!isCriticalError && usingProjectFile && !filePath.endsWith(mainLoc.file)) { continue; } @@ -386,10 +389,10 @@ async function runCppcheckOnFileXML( lLine, lCol, lLine, document.lineAt(lLine).text.length ); - + const relatedDocument = await vscode.workspace.openTextDocument(loc.file); relatedInfos.push( new vscode.DiagnosticRelatedInformation( - new vscode.Location(document.uri, relatedRange), + new vscode.Location(relatedDocument?.uri ?? '', relatedRange), msg ) ); @@ -397,9 +400,25 @@ async function runCppcheckOnFileXML( if (relatedInfos.length > 0) { diagnostic.relatedInformation = relatedInfos; } - diagnostics.push(diagnostic); + const diagnosticFile = mainLoc.file; + if (diagnosticFile === document.fileName) { + const uri = document.uri.toString(); + if (diagnostics[uri] === null || diagnostics[uri] === undefined) { + diagnostics[uri] = []; + } + diagnostics[uri].push(diagnostic); + } else { + const relatedDocument = await vscode.workspace.openTextDocument(mainLoc.file); + const uri = relatedDocument.uri.toString(); + if (diagnostics[uri] === null || diagnostics[uri] === undefined) { + diagnostics[uri] = []; + } + diagnostics[uri].push(diagnostic); + } + } + for (const uri of Object.keys(diagnostics)) { + diagnosticCollection.set(vscode.Uri.parse(uri), diagnostics[uri]); } - diagnosticCollection.set(document.uri, diagnostics); }); // If checks have run without error, save hashed document content to memory From 6dceff4549d66d71a5439d0cac60a26ad01bbf96 Mon Sep 17 00:00:00 2001 From: davidramnero Date: Tue, 23 Jun 2026 23:43:03 +0200 Subject: [PATCH 2/3] keep track of header errors generated from source file to decide when to clear them from diagnostics --- src/extension.ts | 43 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 4ef71ea..536dfe2 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -9,6 +9,8 @@ import { looksLikePath, resolvePath, findWorkspaceRoot } from './util/path'; // To keep track of document changes we save hashed versions of their content to this record let documentHashMemory : Record = {}; +// To keep track of header warnings created from analysis of source file we save their relations to headerSourceFileRelationMap +let headerSourceFileRelationMap: Record> = {}; let previewAnalysisTimer: NodeJS.Timeout | undefined; let previewedDocument: vscode.TextDocument | undefined; @@ -114,6 +116,21 @@ export async function activate(context: vscode.ExtensionContext) { const diagnosticCollection = vscode.languages.createDiagnosticCollection("Cppcheck"); context.subscriptions.push(diagnosticCollection); + function clearDiagnosticForDoc(doc: vscode.TextDocument): void { + diagnosticCollection.delete(doc.uri); + // If we are displaying header errors generated from analysing doc (and only from this), clear these + for (const headerUri of Object.keys(headerSourceFileRelationMap)) { + if (headerSourceFileRelationMap[headerUri].has(doc.uri.toString())) { + if (headerSourceFileRelationMap[headerUri].size === 1) { + diagnosticCollection.delete(vscode.Uri.parse(headerUri)); + } else { + headerSourceFileRelationMap[headerUri].delete(doc.uri.toString()); + } + } + } + documentHashMemory[doc.fileName] = ''; + } + async function handleDocument(document: vscode.TextDocument) { // Only process C/C++ files. if (!["c", "cpp"].includes(document.languageId)) { @@ -164,8 +181,7 @@ export async function activate(context: vscode.ExtensionContext) { // If disabled, clear any existing diagnostics for this doc. if (!isEnabled) { - diagnosticCollection.delete(document.uri); - documentHashMemory[document.fileName] = ''; + clearDiagnosticForDoc(document); return; } @@ -217,13 +233,22 @@ export async function activate(context: vscode.ExtensionContext) { } } } + for (const tab of e.closed) { + if (tab.input instanceof vscode.TabInputText) { + const uri = tab.input.uri; + const document = + vscode.workspace.textDocuments.find( + doc => doc.uri.toString() === uri.toString() + ) ?? await vscode.workspace.openTextDocument(uri); + clearDiagnosticForDoc(document); + } + } }, null, context.subscriptions); // Clear diagnostics of previewed files when no longer viewed vscode.window.onDidChangeActiveTextEditor(() => { if (previewedDocument) { - diagnosticCollection.delete(previewedDocument.uri); - documentHashMemory[previewedDocument.fileName] = ''; + clearDiagnosticForDoc(previewedDocument); previewedDocument = undefined; } }); @@ -238,8 +263,7 @@ export async function activate(context: vscode.ExtensionContext) { // Clean up diagnostics when a file is closed vscode.workspace.onDidCloseTextDocument((document: vscode.TextDocument) => { - diagnosticCollection.delete(document.uri); - documentHashMemory[document.fileName] = ''; + clearDiagnosticForDoc(document); }, null, context.subscriptions); } @@ -416,8 +440,15 @@ async function runCppcheckOnFileXML( diagnostics[uri].push(diagnostic); } } + const sourceDocumentUri = document.uri.toString(); for (const uri of Object.keys(diagnostics)) { diagnosticCollection.set(vscode.Uri.parse(uri), diagnostics[uri]); + if (uri !== sourceDocumentUri) { + if (headerSourceFileRelationMap[uri] === null ||headerSourceFileRelationMap[uri] === undefined) { + headerSourceFileRelationMap[uri] = new Set; + } + headerSourceFileRelationMap[uri].add(sourceDocumentUri); + } } }); From 5cd3e8acf722466c8e876ce8a1d5bde8621e84e7 Mon Sep 17 00:00:00 2001 From: davidramnero Date: Wed, 24 Jun 2026 11:21:15 +0200 Subject: [PATCH 3/3] fixed cases where diagnostics were cleared when it should not have been --- src/extension.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 536dfe2..aa988b6 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -9,8 +9,8 @@ import { looksLikePath, resolvePath, findWorkspaceRoot } from './util/path'; // To keep track of document changes we save hashed versions of their content to this record let documentHashMemory : Record = {}; -// To keep track of header warnings created from analysis of source file we save their relations to headerSourceFileRelationMap -let headerSourceFileRelationMap: Record> = {}; +// To keep track of warnings for files created from analysis of other files we save their relations to fileRelationMap +let fileRelationMap: Record> = {}; let previewAnalysisTimer: NodeJS.Timeout | undefined; let previewedDocument: vscode.TextDocument | undefined; @@ -117,14 +117,15 @@ export async function activate(context: vscode.ExtensionContext) { context.subscriptions.push(diagnosticCollection); function clearDiagnosticForDoc(doc: vscode.TextDocument): void { - diagnosticCollection.delete(doc.uri); - // If we are displaying header errors generated from analysing doc (and only from this), clear these - for (const headerUri of Object.keys(headerSourceFileRelationMap)) { - if (headerSourceFileRelationMap[headerUri].has(doc.uri.toString())) { - if (headerSourceFileRelationMap[headerUri].size === 1) { - diagnosticCollection.delete(vscode.Uri.parse(headerUri)); + // 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 + for (const fileUri of Object.keys(fileRelationMap)) { + if (fileRelationMap[fileUri].has(doc.uri.toString())) { + if (fileRelationMap[fileUri].size <= 1) { + diagnosticCollection.delete(vscode.Uri.parse(fileUri)); + fileRelationMap[fileUri].clear(); } else { - headerSourceFileRelationMap[headerUri].delete(doc.uri.toString()); + fileRelationMap[fileUri].delete(doc.uri.toString()); } } } @@ -443,12 +444,11 @@ async function runCppcheckOnFileXML( const sourceDocumentUri = document.uri.toString(); for (const uri of Object.keys(diagnostics)) { diagnosticCollection.set(vscode.Uri.parse(uri), diagnostics[uri]); - if (uri !== sourceDocumentUri) { - if (headerSourceFileRelationMap[uri] === null ||headerSourceFileRelationMap[uri] === undefined) { - headerSourceFileRelationMap[uri] = new Set; - } - headerSourceFileRelationMap[uri].add(sourceDocumentUri); + if (fileRelationMap[uri] === null ||fileRelationMap[uri] === undefined) { + fileRelationMap[uri] = new Set; } + // NOTE: uri can be the same as sourceDocumentUri + fileRelationMap[uri].add(sourceDocumentUri); } });