11import * as vscode from 'vscode' ;
22import { ScanResult , FlowStep } from '../services/codeqlService' ;
3+ import { LoggerService } from '../services/loggerService' ;
34
45export class ResultsProvider implements vscode . TreeDataProvider < ResultItem > {
56 private _onDidChangeTreeData : vscode . EventEmitter < ResultItem | undefined | null | void > = new vscode . EventEmitter < ResultItem | undefined | null | void > ( ) ;
@@ -8,32 +9,57 @@ export class ResultsProvider implements vscode.TreeDataProvider<ResultItem> {
89 private results : ScanResult [ ] = [ ] ;
910 private diagnosticCollection : vscode . DiagnosticCollection ;
1011 private hasBeenScanned : boolean = false ;
12+ private logger : LoggerService ;
1113
1214 constructor ( ) {
1315 // Create a diagnostic collection for CodeQL security issues
1416 this . diagnosticCollection = vscode . languages . createDiagnosticCollection ( 'codeql-security' ) ;
17+ this . logger = LoggerService . getInstance ( ) ;
1518 }
1619
1720 refresh ( ) : void {
21+ this . logger . debug ( 'ResultsProvider' , 'Refreshing tree view' ) ;
1822 this . _onDidChangeTreeData . fire ( ) ;
1923 }
2024
2125 setResults ( results : ScanResult [ ] ) : void {
26+ this . logger . logServiceCall ( 'ResultsProvider' , 'setResults' , 'started' , { count : results . length } ) ;
27+
28+ // Count results by severity
29+ const severityCounts : { [ severity : string ] : number } = { } ;
30+ results . forEach ( result => {
31+ const severity = result . severity || 'unknown' ;
32+ severityCounts [ severity ] = ( severityCounts [ severity ] || 0 ) + 1 ;
33+ } ) ;
34+
2235 this . results = results ;
2336 this . hasBeenScanned = true ;
2437 this . updateDiagnostics ( results ) ;
2538 this . refresh ( ) ;
39+
40+ // Generate comprehensive statistics if we have results
41+ if ( results . length > 0 ) {
42+ this . logResultsStatistics ( ) ;
43+ }
44+
45+ this . logger . logServiceCall ( 'ResultsProvider' , 'setResults' , 'completed' , {
46+ totalCount : results . length ,
47+ severityCounts : severityCounts
48+ } ) ;
2649 }
2750
2851 getResults ( ) : ScanResult [ ] {
52+ this . logger . debug ( 'ResultsProvider' , 'Getting results' , { count : this . results . length } ) ;
2953 return this . results ;
3054 }
3155
3256 clearResults ( ) : void {
57+ this . logger . logServiceCall ( 'ResultsProvider' , 'clearResults' , 'started' ) ;
3358 this . results = [ ] ;
3459 this . hasBeenScanned = false ;
3560 this . diagnosticCollection . clear ( ) ;
3661 this . refresh ( ) ;
62+ this . logger . logServiceCall ( 'ResultsProvider' , 'clearResults' , 'completed' ) ;
3763 }
3864
3965 getTreeItem ( element : ResultItem ) : vscode . TreeItem {
@@ -43,6 +69,8 @@ export class ResultsProvider implements vscode.TreeDataProvider<ResultItem> {
4369 getChildren ( element ?: ResultItem ) : Thenable < ResultItem [ ] > {
4470 if ( ! element ) {
4571 // Root level - group by language
72+ this . logger . debug ( 'ResultsProvider' , 'Getting root level tree items' ) ;
73+
4674 if ( ! this . results || this . results . length === 0 ) {
4775 // Show different messages based on whether a scan has been run
4876 const message = this . hasBeenScanned
@@ -52,6 +80,8 @@ export class ResultsProvider implements vscode.TreeDataProvider<ResultItem> {
5280 ? 'No security vulnerabilities were found in the scanned code'
5381 : 'Click "CodeQL: Run Scan" to analyze your code for security vulnerabilities' ;
5482
83+ this . logger . debug ( 'ResultsProvider' , `Showing empty state: ${ message } ` ) ;
84+
5585 return Promise . resolve ( [
5686 new ResultItem (
5787 message ,
@@ -68,6 +98,12 @@ export class ResultsProvider implements vscode.TreeDataProvider<ResultItem> {
6898 }
6999
70100 const groups = this . groupByLanguage ( this . results ) ;
101+
102+ this . logger . debug ( 'ResultsProvider' , 'Grouping results by language' , {
103+ languages : Object . keys ( groups ) ,
104+ counts : Object . fromEntries ( Object . entries ( groups ) . map ( ( [ lang , results ] ) => [ lang , results . length ] ) )
105+ } ) ;
106+
71107 return Promise . resolve (
72108 Object . entries ( groups ) . map ( ( [ language , results ] ) =>
73109 new ResultItem (
@@ -81,8 +117,14 @@ export class ResultsProvider implements vscode.TreeDataProvider<ResultItem> {
81117 ) ;
82118 } else if ( element . type === 'language' ) {
83119 // Second level - group by severity within language
120+ this . logger . debug ( 'ResultsProvider' , `Getting issues for language: ${ element . language } ` , {
121+ language : element . language ,
122+ count : element . results ?. length || 0
123+ } ) ;
124+
84125 if ( ! element . results || element . results . length === 0 ) {
85126 // Show "no results" for this language
127+ this . logger . debug ( 'ResultsProvider' , `No results for language: ${ element . language } ` ) ;
86128 return Promise . resolve ( [
87129 new ResultItem (
88130 '✅ No security alerts found' ,
@@ -100,6 +142,13 @@ export class ResultsProvider implements vscode.TreeDataProvider<ResultItem> {
100142
101143 const severityGroups = this . groupBySeverity ( element . results ) ;
102144 const sortedSeverities = this . sortSeverityGroups ( severityGroups ) ;
145+
146+ this . logger . debug ( 'ResultsProvider' , `Grouped issues by severity for ${ element . language } ` , {
147+ language : element . language ,
148+ severities : Object . keys ( severityGroups ) ,
149+ counts : Object . fromEntries ( Object . entries ( severityGroups ) . map ( ( [ sev , results ] ) => [ sev , results . length ] ) )
150+ } ) ;
151+
103152 return Promise . resolve (
104153 sortedSeverities . map ( ( [ severity , results ] ) =>
105154 new ResultItem (
@@ -115,6 +164,12 @@ export class ResultsProvider implements vscode.TreeDataProvider<ResultItem> {
115164 ) ;
116165 } else if ( element . type === 'severity' ) {
117166 // Third level - individual results
167+ this . logger . debug ( 'ResultsProvider' , `Getting individual results for ${ element . language } /${ element . severity } ` , {
168+ language : element . language ,
169+ severity : element . severity ,
170+ count : element . results ?. length || 0
171+ } ) ;
172+
118173 return Promise . resolve (
119174 element . results ! . map ( result =>
120175 new ResultItem (
@@ -132,6 +187,14 @@ export class ResultsProvider implements vscode.TreeDataProvider<ResultItem> {
132187 } else if ( element . type === 'result' && element . result ?. flowSteps ) {
133188 // Fourth level - flow steps (hidden by default)
134189 const flowSteps = element . result . flowSteps ;
190+
191+ this . logger . debug ( 'ResultsProvider' , `Expanding flow steps for result: ${ element . result . ruleId } ` , {
192+ ruleId : element . result . ruleId ,
193+ steps : flowSteps . length ,
194+ file : element . result . location . file ,
195+ line : element . result . location . startLine
196+ } ) ;
197+
135198 return Promise . resolve (
136199 flowSteps . map ( ( step , index ) => {
137200 const isSource = index === 0 ;
@@ -162,6 +225,8 @@ export class ResultsProvider implements vscode.TreeDataProvider<ResultItem> {
162225 }
163226
164227 private groupByLanguage ( results : ScanResult [ ] ) : { [ language : string ] : ScanResult [ ] } {
228+ this . logger . logServiceCall ( 'ResultsProvider' , 'groupByLanguage' , 'started' , { count : results . length } ) ;
229+
165230 // Get configured languages from settings
166231 const config = vscode . workspace . getConfiguration ( "codeql-scanner" ) ;
167232 const configuredLanguages = config . get < string [ ] > ( "languages" , [ ] ) ;
@@ -183,18 +248,33 @@ export class ResultsProvider implements vscode.TreeDataProvider<ResultItem> {
183248 }
184249 } ) ;
185250
251+ this . logger . logServiceCall ( 'ResultsProvider' , 'groupByLanguage' , 'completed' , {
252+ languages : Object . keys ( groups ) ,
253+ configuredLanguages : configuredLanguages ,
254+ counts : Object . fromEntries ( Object . entries ( groups ) . map ( ( [ lang , results ] ) => [ lang , results . length ] ) )
255+ } ) ;
256+
186257 return groups ;
187258 }
188259
189260 private groupBySeverity ( results : ScanResult [ ] ) : { [ severity : string ] : ScanResult [ ] } {
190- return results . reduce ( ( groups , result ) => {
261+ this . logger . debug ( 'ResultsProvider' , 'Grouping results by severity' , { count : results . length } ) ;
262+
263+ const groups = results . reduce ( ( groups , result ) => {
191264 const severity = result . severity || 'unknown' ;
192265 if ( ! groups [ severity ] ) {
193266 groups [ severity ] = [ ] ;
194267 }
195268 groups [ severity ] . push ( result ) ;
196269 return groups ;
197270 } , { } as { [ severity : string ] : ScanResult [ ] } ) ;
271+
272+ this . logger . debug ( 'ResultsProvider' , 'Severity grouping completed' , {
273+ severities : Object . keys ( groups ) ,
274+ counts : Object . fromEntries ( Object . entries ( groups ) . map ( ( [ sev , results ] ) => [ sev , results . length ] ) )
275+ } ) ;
276+
277+ return groups ;
198278 }
199279
200280 private getSeverityDisplayName ( severity : string ) : string {
@@ -212,9 +292,13 @@ export class ResultsProvider implements vscode.TreeDataProvider<ResultItem> {
212292 }
213293
214294 private sortSeverityGroups ( severityGroups : { [ severity : string ] : ScanResult [ ] } ) : [ string , ScanResult [ ] ] [ ] {
295+ this . logger . debug ( 'ResultsProvider' , 'Sorting severity groups' , {
296+ severities : Object . keys ( severityGroups )
297+ } ) ;
298+
215299 const severityOrder = [ 'critical' , 'high' , 'error' , 'medium' , 'warning' , 'low' , 'info' , 'unknown' ] ;
216300
217- return Object . entries ( severityGroups ) . sort ( ( [ a ] , [ b ] ) => {
301+ const sorted = Object . entries ( severityGroups ) . sort ( ( [ a ] , [ b ] ) => {
218302 const aIndex = severityOrder . indexOf ( a ) ;
219303 const bIndex = severityOrder . indexOf ( b ) ;
220304
@@ -225,13 +309,22 @@ export class ResultsProvider implements vscode.TreeDataProvider<ResultItem> {
225309
226310 return aIndex - bIndex ;
227311 } ) ;
312+
313+ this . logger . debug ( 'ResultsProvider' , 'Severity groups sorted' , {
314+ sortedOrder : sorted . map ( ( [ severity ] ) => severity )
315+ } ) ;
316+
317+ return sorted ;
228318 }
229319
230320 private updateDiagnostics ( results : ScanResult [ ] ) : void {
321+ this . logger . logServiceCall ( 'ResultsProvider' , 'updateDiagnostics' , 'started' , { count : results . length } ) ;
322+
231323 // Clear existing diagnostics
232324 this . diagnosticCollection . clear ( ) ;
233325
234326 if ( ! results || results . length === 0 ) {
327+ this . logger . debug ( 'ResultsProvider' , 'No diagnostics to display' ) ;
235328 return ;
236329 }
237330
@@ -323,26 +416,120 @@ export class ResultsProvider implements vscode.TreeDataProvider<ResultItem> {
323416 diagnosticsMap . forEach ( ( diagnostics , uriString ) => {
324417 this . diagnosticCollection . set ( vscode . Uri . parse ( uriString ) , diagnostics ) ;
325418 } ) ;
419+
420+ this . logger . logServiceCall ( 'ResultsProvider' , 'updateDiagnostics' , 'completed' , {
421+ fileCount : diagnosticsMap . size ,
422+ diagnosticCount : Array . from ( diagnosticsMap . values ( ) ) . reduce ( ( sum , diags ) => sum + diags . length , 0 )
423+ } ) ;
326424 }
327425
328426 private mapToVSCodeSeverity ( severity : string ) : vscode . DiagnosticSeverity {
427+ const originalSeverity = severity ;
428+ let vscSeverity : vscode . DiagnosticSeverity ;
429+
329430 switch ( severity ?. toLowerCase ( ) ) {
330431 case 'critical' :
331432 case 'high' :
332433 case 'error' :
333- return vscode . DiagnosticSeverity . Error ;
434+ vscSeverity = vscode . DiagnosticSeverity . Error ;
435+ break ;
334436 case 'medium' :
335437 case 'warning' :
336- return vscode . DiagnosticSeverity . Warning ;
438+ vscSeverity = vscode . DiagnosticSeverity . Warning ;
439+ break ;
337440 case 'low' :
338441 case 'info' :
339- return vscode . DiagnosticSeverity . Information ;
442+ vscSeverity = vscode . DiagnosticSeverity . Information ;
443+ break ;
340444 default :
341- return vscode . DiagnosticSeverity . Warning ;
445+ vscSeverity = vscode . DiagnosticSeverity . Warning ;
446+ break ;
342447 }
448+
449+ this . logger . debug ( 'ResultsProvider' , 'Mapped severity' , {
450+ from : originalSeverity ,
451+ to : vscode . DiagnosticSeverity [ vscSeverity ]
452+ } ) ;
453+
454+ return vscSeverity ;
455+ }
456+
457+ /**
458+ * Logs comprehensive statistics about the scan results
459+ * This provides a detailed breakdown of issues by language and severity
460+ */
461+ private logResultsStatistics ( ) : void {
462+ if ( ! this . results || this . results . length === 0 ) {
463+ this . logger . info ( 'ResultsStatistics' , 'No scan results to analyze' ) ;
464+ return ;
465+ }
466+
467+ // Overall statistics
468+ const totalAlerts = this . results . length ;
469+
470+ // Group by language
471+ const languageGroups = this . groupByLanguage ( this . results ) ;
472+ const languageStats = Object . entries ( languageGroups ) . map ( ( [ lang , results ] ) => ( {
473+ language : lang ,
474+ count : results . length ,
475+ percentage : Math . round ( ( results . length / totalAlerts ) * 100 )
476+ } ) ) ;
477+
478+ // Group by severity
479+ const severityCounts : { [ severity : string ] : number } = { } ;
480+ this . results . forEach ( result => {
481+ const severity = result . severity || 'unknown' ;
482+ severityCounts [ severity ] = ( severityCounts [ severity ] || 0 ) + 1 ;
483+ } ) ;
484+
485+ // Count rules
486+ const ruleMap : { [ ruleId : string ] : number } = { } ;
487+ this . results . forEach ( result => {
488+ const ruleId = result . ruleId || 'unknown' ;
489+ ruleMap [ ruleId ] = ( ruleMap [ ruleId ] || 0 ) + 1 ;
490+ } ) ;
491+
492+ // Find top rules
493+ const topRules = Object . entries ( ruleMap )
494+ . sort ( ( [ , a ] , [ , b ] ) => b - a )
495+ . slice ( 0 , 5 )
496+ . map ( ( [ rule , count ] ) => ( { rule, count, percentage : Math . round ( ( count / totalAlerts ) * 100 ) } ) ) ;
497+
498+ // Count files with issues
499+ const fileMap : { [ file : string ] : number } = { } ;
500+ this . results . forEach ( result => {
501+ if ( result . location && result . location . file ) {
502+ const fileName = result . location . file . split ( '/' ) . pop ( ) || 'unknown' ;
503+ fileMap [ fileName ] = ( fileMap [ fileName ] || 0 ) + 1 ;
504+ }
505+ } ) ;
506+
507+ // Find files with most issues
508+ const topFiles = Object . entries ( fileMap )
509+ . sort ( ( [ , a ] , [ , b ] ) => b - a )
510+ . slice ( 0 , 5 )
511+ . map ( ( [ file , count ] ) => ( { file, count, percentage : Math . round ( ( count / totalAlerts ) * 100 ) } ) ) ;
512+
513+ // Count data flow results
514+ const dataFlowCount = this . results . filter ( r => r . flowSteps && r . flowSteps . length > 0 ) . length ;
515+ const dataFlowPercentage = Math . round ( ( dataFlowCount / totalAlerts ) * 100 ) ;
516+
517+ // Log the comprehensive statistics using the specialized method
518+ this . logger . logScanStatistics ( 'ResultsProvider' , {
519+ totalCount : totalAlerts ,
520+ byLanguage : languageStats ,
521+ bySeverity : severityCounts ,
522+ topRules,
523+ topFiles,
524+ dataFlow : {
525+ count : dataFlowCount ,
526+ percentage : dataFlowPercentage
527+ }
528+ } ) ;
343529 }
344530
345531 dispose ( ) : void {
532+ this . logger . info ( 'ResultsProvider' , 'Disposing ResultsProvider' ) ;
346533 this . diagnosticCollection . dispose ( ) ;
347534 }
348535}
@@ -485,11 +672,14 @@ export class ResultItem extends vscode.TreeItem {
485672 }
486673
487674 private getCommand ( ) : vscode . Command | undefined {
675+ const logger = LoggerService . getInstance ( ) ;
676+
488677 if ( this . type === 'result' && this . result ) {
489678 return {
490- command : 'vscode.open ' ,
679+ command : 'codeql-scanner.resultSelected ' ,
491680 title : 'Open File' ,
492681 arguments : [
682+ this ,
493683 vscode . Uri . file ( this . result . location . file ) ,
494684 {
495685 selection : new vscode . Range (
@@ -503,9 +693,10 @@ export class ResultItem extends vscode.TreeItem {
503693 } ;
504694 } else if ( this . type === 'flowStep' && this . flowStep ) {
505695 return {
506- command : 'vscode.open ' ,
696+ command : 'codeql-scanner.flowStepSelected ' ,
507697 title : 'Open Flow Step' ,
508698 arguments : [
699+ this ,
509700 vscode . Uri . file ( this . flowStep . file ) ,
510701 {
511702 selection : new vscode . Range (
0 commit comments