@@ -9,6 +9,8 @@ import { looksLikePath, resolvePath, findWorkspaceRoot } from './util/path';
99
1010// To keep track of document changes we save hashed versions of their content to this record
1111let documentHashMemory : Record < string , string > = { } ;
12+ // To keep track of warnings for files created from analysis of other files we save their relations to fileRelationMap
13+ let fileRelationMap : Record < string , Set < string > > = { } ;
1214
1315let previewAnalysisTimer : NodeJS . Timeout | undefined ;
1416let previewedDocument : vscode . TextDocument | undefined ;
@@ -114,6 +116,22 @@ export async function activate(context: vscode.ExtensionContext) {
114116 const diagnosticCollection = vscode . languages . createDiagnosticCollection ( "Cppcheck" ) ;
115117 context . subscriptions . push ( diagnosticCollection ) ;
116118
119+ function clearDiagnosticForDoc ( doc : vscode . TextDocument ) : void {
120+ // Any file who was warnings generated from (and only from) the closed doc have their diagnostics cleared
121+ // NOTE: This includes the closed doc - its diagnostics will only be cleared if its warnings only come from analysis of it itself
122+ for ( const fileUri of Object . keys ( fileRelationMap ) ) {
123+ if ( fileRelationMap [ fileUri ] . has ( doc . uri . toString ( ) ) ) {
124+ if ( fileRelationMap [ fileUri ] . size <= 1 ) {
125+ diagnosticCollection . delete ( vscode . Uri . parse ( fileUri ) ) ;
126+ fileRelationMap [ fileUri ] . clear ( ) ;
127+ } else {
128+ fileRelationMap [ fileUri ] . delete ( doc . uri . toString ( ) ) ;
129+ }
130+ }
131+ }
132+ documentHashMemory [ doc . fileName ] = '' ;
133+ }
134+
117135 async function handleDocument ( document : vscode . TextDocument ) {
118136 // Only process C/C++ files.
119137 if ( ! [ "c" , "cpp" ] . includes ( document . languageId ) ) {
@@ -164,8 +182,7 @@ export async function activate(context: vscode.ExtensionContext) {
164182
165183 // If disabled, clear any existing diagnostics for this doc.
166184 if ( ! isEnabled ) {
167- diagnosticCollection . delete ( document . uri ) ;
168- documentHashMemory [ document . fileName ] = '' ;
185+ clearDiagnosticForDoc ( document ) ;
169186 return ;
170187 }
171188
@@ -217,13 +234,22 @@ export async function activate(context: vscode.ExtensionContext) {
217234 }
218235 }
219236 }
237+ for ( const tab of e . closed ) {
238+ if ( tab . input instanceof vscode . TabInputText ) {
239+ const uri = tab . input . uri ;
240+ const document =
241+ vscode . workspace . textDocuments . find (
242+ doc => doc . uri . toString ( ) === uri . toString ( )
243+ ) ?? await vscode . workspace . openTextDocument ( uri ) ;
244+ clearDiagnosticForDoc ( document ) ;
245+ }
246+ }
220247 } , null , context . subscriptions ) ;
221248
222249 // Clear diagnostics of previewed files when no longer viewed
223250 vscode . window . onDidChangeActiveTextEditor ( ( ) => {
224251 if ( previewedDocument ) {
225- diagnosticCollection . delete ( previewedDocument . uri ) ;
226- documentHashMemory [ previewedDocument . fileName ] = '' ;
252+ clearDiagnosticForDoc ( previewedDocument ) ;
227253 previewedDocument = undefined ;
228254 }
229255 } ) ;
@@ -238,8 +264,7 @@ export async function activate(context: vscode.ExtensionContext) {
238264
239265 // Clean up diagnostics when a file is closed
240266 vscode . workspace . onDidCloseTextDocument ( ( document : vscode . TextDocument ) => {
241- diagnosticCollection . delete ( document . uri ) ;
242- documentHashMemory [ document . fileName ] = '' ;
267+ clearDiagnosticForDoc ( document ) ;
243268 } , null , context . subscriptions ) ;
244269}
245270
@@ -272,7 +297,7 @@ async function runCppcheckOnFileXML(
272297 return arg ;
273298 } ) ;
274299
275- let proc ;
300+ let usingProjectFile = false ;
276301 const args = [
277302 '--enable=all' ,
278303 '--inline-suppr' ,
@@ -283,10 +308,13 @@ async function runCppcheckOnFileXML(
283308 ...argsParsed ,
284309 ] . filter ( Boolean ) ;
285310 if ( processedArgs . includes ( "--project" ) ) {
311+ usingProjectFile = true ;
286312 args . push ( `--file-filter=${ filePath } ` ) ;
287313 } else {
288314 args . push ( filePath ) ;
289315 }
316+
317+ let proc ;
290318 const cwd = findWorkspaceRoot ( ) ;
291319 proc = cp . spawn ( commandPath , args , {
292320 cwd,
@@ -313,14 +341,14 @@ async function runCppcheckOnFileXML(
313341 vscode . window . showErrorMessage ( errorMessage ) ;
314342 }
315343 const parser = new xml2js . Parser ( { explicitArray : true } ) ;
316- parser . parseString ( xmlOutput , ( err , result ) => {
344+ parser . parseString ( xmlOutput , async ( err , result ) => {
317345 if ( err ) {
318346 console . error ( "XML parse error:" , err ) ;
319347 return ;
320348 }
321349
322350 const errors = result . results ?. errors ?. [ 0 ] ?. error || [ ] ;
323- const diagnostics : vscode . Diagnostic [ ] = [ ] ;
351+ const diagnostics : Record < string , vscode . Diagnostic [ ] > = { } ;
324352
325353 for ( const e of errors ) {
326354 const isCriticalError = criticalWarningTypes . includes ( e . $ . id ) ;
@@ -331,8 +359,8 @@ async function runCppcheckOnFileXML(
331359
332360 const mainLoc = locations [ locations . length - 1 ] . $ ;
333361
334- // If main location is not current file, then skip displaying warning unless it is critical
335- if ( ! isCriticalError && ! filePath . endsWith ( mainLoc . file ) ) {
362+ // If main location is not current file, we are not using a project file and warning is not critical then skip displaying warning
363+ if ( ! isCriticalError && usingProjectFile && ! filePath . endsWith ( mainLoc . file ) ) {
336364 continue ;
337365 }
338366
@@ -387,20 +415,42 @@ async function runCppcheckOnFileXML(
387415 lLine , lCol ,
388416 lLine , document . lineAt ( lLine ) . text . length
389417 ) ;
390-
418+ const relatedDocument = await vscode . workspace . openTextDocument ( loc . file ) ;
391419 relatedInfos . push (
392420 new vscode . DiagnosticRelatedInformation (
393- new vscode . Location ( document . uri , relatedRange ) ,
421+ new vscode . Location ( relatedDocument ? .uri ?? '' , relatedRange ) ,
394422 msg
395423 )
396424 ) ;
397425 }
398426 if ( relatedInfos . length > 0 ) {
399427 diagnostic . relatedInformation = relatedInfos ;
400428 }
401- diagnostics . push ( diagnostic ) ;
429+ const diagnosticFile = mainLoc . file ;
430+ if ( diagnosticFile === document . fileName ) {
431+ const uri = document . uri . toString ( ) ;
432+ if ( diagnostics [ uri ] === null || diagnostics [ uri ] === undefined ) {
433+ diagnostics [ uri ] = [ ] ;
434+ }
435+ diagnostics [ uri ] . push ( diagnostic ) ;
436+ } else {
437+ const relatedDocument = await vscode . workspace . openTextDocument ( mainLoc . file ) ;
438+ const uri = relatedDocument . uri . toString ( ) ;
439+ if ( diagnostics [ uri ] === null || diagnostics [ uri ] === undefined ) {
440+ diagnostics [ uri ] = [ ] ;
441+ }
442+ diagnostics [ uri ] . push ( diagnostic ) ;
443+ }
444+ }
445+ const sourceDocumentUri = document . uri . toString ( ) ;
446+ for ( const uri of Object . keys ( diagnostics ) ) {
447+ diagnosticCollection . set ( vscode . Uri . parse ( uri ) , diagnostics [ uri ] ) ;
448+ if ( fileRelationMap [ uri ] === null || fileRelationMap [ uri ] === undefined ) {
449+ fileRelationMap [ uri ] = new Set ;
450+ }
451+ // NOTE: uri can be the same as sourceDocumentUri
452+ fileRelationMap [ uri ] . add ( sourceDocumentUri ) ;
402453 }
403- diagnosticCollection . set ( document . uri , diagnostics ) ;
404454 } ) ;
405455
406456 // If checks have run without error, save hashed document content to memory
0 commit comments