@@ -25,7 +25,9 @@ import {Clock, RealClock} from '@salesforce/code-analyzer-engine-api/utils';
2525import { EventEmitter } from "node:events" ;
2626import { CodeAnalyzerConfig , ConfigDescription , EngineOverrides , FIELDS , RuleOverride } from "./config" ;
2727import {
28- EngineProgressAggregator , RuntimeTempFolder ,
28+ EngineProgressAggregator ,
29+ FileSystem ,
30+ RealFileSystem ,
2931 RuntimeUniqueIdGenerator ,
3032 TempFolder ,
3133 toAbsolutePath ,
@@ -99,8 +101,8 @@ const MINIMUM_SUPPORTED_NODE = 20;
99101 */
100102export class CodeAnalyzer {
101103 private readonly config : CodeAnalyzerConfig ;
104+ private readonly tempFolder : TempFolder ;
102105 private clock : Clock = new RealClock ( ) ;
103- private tempFolder : TempFolder = new RuntimeTempFolder ( ) ;
104106 private uniqueIdGenerator : UniqueIdGenerator = new RuntimeUniqueIdGenerator ( ) ;
105107 private readonly eventEmitter : EventEmitter = new EventEmitter ( ) ;
106108 private readonly engines : Map < string , engApi . Engine > = new Map ( ) ;
@@ -110,9 +112,14 @@ export class CodeAnalyzer {
110112 private readonly rulesCache : Map < string , RuleImpl [ ] > = new Map ( ) ;
111113 private readonly engineRuleDiscoveryProgressAggregator : EngineProgressAggregator = new EngineProgressAggregator ( ) ;
112114
113- constructor ( config : CodeAnalyzerConfig , version : string = process . version ) {
114- this . validateEnvironment ( version ) ;
115+ constructor ( config : CodeAnalyzerConfig , fileSystem : FileSystem = new RealFileSystem ( ) , nodeVersion : string = process . version ) {
116+ this . validateEnvironment ( nodeVersion ) ;
115117 this . config = config ;
118+ this . tempFolder = new TempFolder ( fileSystem ) ;
119+ /* istanbul ignore next */
120+ process . addListener ( 'exit' , ( ) => {
121+ this . tempFolder . removeIfNotKept ( ) ;
122+ } ) ;
116123 }
117124
118125 private validateEnvironment ( version : string ) : void {
@@ -131,9 +138,6 @@ export class CodeAnalyzer {
131138 _setUniqueIdGenerator ( uniqueIdGenerator : UniqueIdGenerator ) : void {
132139 this . uniqueIdGenerator = uniqueIdGenerator ;
133140 }
134- _setTempFolder ( tempFolder : TempFolder ) : void {
135- this . tempFolder = tempFolder ;
136- }
137141
138142 /**
139143 * Convenience method to return the same CodeAnalyzerConfig instance that was provided to the constructor
@@ -306,21 +310,37 @@ export class CodeAnalyzer {
306310 // called a second time before the first call to run hasn't finished. This can occur if someone builds
307311 // up a bunch of RunResults promises and then does a Promise.all on them. Otherwise, the progress events may
308312 // override each other.
309- const runWorkingFolderName : string = `code-analyzer-run-${ this . clock . formatToDateTimeString ( ) } ` ;
310313
311314 this . emitLogEvent ( LogLevel . Debug , getMessage ( 'RunningWithWorkspace' , JSON . stringify ( {
312315 filesAndFolders : runOptions . workspace . getRawFilesAndFolders ( ) ,
313316 targets : runOptions . workspace . getRawTargets ( )
314317 } ) ) ) ;
315318
316- const runPromises : Promise < EngineRunResults > [ ] = ruleSelection . getEngineNames ( ) . map (
317- async ( engineName ) => this . runEngineAndValidateResults ( engineName , ruleSelection , {
319+ const engApiWorkspace : engApi . Workspace = toEngApiWorkspace ( runOptions . workspace ) ;
320+ const runWorkingFolderName : string = `run-${ this . clock . formatToDateTimeString ( ) } ` ;
321+ await this . tempFolder . makeSubfolder ( runWorkingFolderName ) ;
322+
323+ const runPromises : Promise < EngineRunResults > [ ] = ruleSelection . getEngineNames ( ) . map ( async ( engineName ) => {
324+ const workingFolder : string = await this . tempFolder . makeSubfolder ( runWorkingFolderName , engineName ) ;
325+ const engineRunOptions : engApi . RunOptions = {
318326 logFolder : this . config . getLogFolder ( ) ,
319- workingFolder : await this . tempFolder . createSubfolder ( runWorkingFolderName , engineName ) ,
320- workspace : toEngApiWorkspace ( runOptions . workspace )
321- } ) ) ;
327+ workingFolder : workingFolder ,
328+ workspace : engApiWorkspace
329+ } ;
330+ const errorCallback : ( ) => void = ( ) => {
331+ if ( ! this . tempFolder . isKept ( runWorkingFolderName , engineName ) ) {
332+ this . emitLogEvent ( LogLevel . Debug , getMessage ( 'EngineWorkingFolderKept' , engineName , workingFolder ) ) ;
333+ this . tempFolder . markToBeKept ( runWorkingFolderName , engineName ) ;
334+ }
335+ } ;
336+ const results : EngineRunResults = await this . runEngineAndValidateResults ( engineName , ruleSelection , engineRunOptions , errorCallback ) ;
337+ await this . tempFolder . removeIfNotKept ( runWorkingFolderName , engineName ) ; // TODO: NEED TO CHECK IF RUNNING ASYNC GIVES PERF BOOST HERE OR NOT
338+ return results ;
339+ } ) ;
322340 const engineRunResultsList : EngineRunResults [ ] = await Promise . all ( runPromises ) ;
323341
342+ await this . tempFolder . removeIfNotKept ( runWorkingFolderName ) ; // TODO: NEED TO CHECK IF RUNNING ASYNC GIVES PERF BOOST HERE OR NOT
343+
324344 const runResults : RunResultsImpl = new RunResultsImpl ( this . clock ) ;
325345 for ( const engineRunResults of engineRunResultsList ) {
326346 runResults . addEngineRunResults ( engineRunResults ) ;
@@ -344,32 +364,62 @@ export class CodeAnalyzer {
344364
345365 private async getAllRules ( workspace ?: Workspace ) : Promise < RuleImpl [ ] > {
346366 const cacheKey : string = workspace ? workspace . getWorkspaceId ( ) : process . cwd ( ) ;
347- const describeWorkingFolderName : string = `code-analyzer-describe-${ this . clock . formatToDateTimeString ( ) } ` ;
348367 if ( ! this . rulesCache . has ( cacheKey ) ) {
349368 this . engineRuleDiscoveryProgressAggregator . reset ( this . getEngineNames ( ) ) ;
350369 const engApiWorkspace : engApi . Workspace | undefined = workspace ? toEngApiWorkspace ( workspace ) : undefined ;
351- const rulePromises : Promise < RuleImpl [ ] > [ ] = this . getEngineNames ( ) . map ( async ( engineName ) =>
352- this . getAllRulesFor ( engineName , {
370+ const rulesWorkingFolderName : string = `rules-${ this . clock . formatToDateTimeString ( ) } ` ;
371+
372+ await this . tempFolder . makeSubfolder ( rulesWorkingFolderName ) ;
373+
374+ const rulePromises : Promise < RuleImpl [ ] > [ ] = this . getEngineNames ( ) . map ( async ( engineName ) => {
375+ const workingFolder : string = await this . tempFolder . makeSubfolder ( rulesWorkingFolderName , engineName ) ;
376+ const describeOptions : engApi . DescribeOptions = {
353377 workspace : engApiWorkspace ,
354- workingFolder : await this . tempFolder . createSubfolder ( describeWorkingFolderName , engineName ) ,
378+ workingFolder : workingFolder ,
355379 logFolder : this . config . getLogFolder ( )
356- } ) ) ;
380+ } ;
381+ const errorCallback : ( ) => void = ( ) => {
382+ if ( ! this . tempFolder . isKept ( rulesWorkingFolderName , engineName ) ) {
383+ this . emitLogEvent ( LogLevel . Debug , getMessage ( 'EngineWorkingFolderKept' , engineName , workingFolder ) ) ;
384+ this . tempFolder . markToBeKept ( rulesWorkingFolderName , engineName ) ;
385+ }
386+ } ;
387+ const rules : RuleImpl [ ] = await this . getAllRulesFor ( engineName , describeOptions , errorCallback ) ;
388+ await this . tempFolder . removeIfNotKept ( rulesWorkingFolderName , engineName ) ; // TODO: NEED TO CHECK IF RUNNING ASYNC GIVES PERF BOOST HERE OR NOT
389+ return rules ;
390+ } ) ;
391+
357392 this . rulesCache . set ( cacheKey , ( await Promise . all ( rulePromises ) ) . flat ( ) ) ;
393+
394+ await this . tempFolder . removeIfNotKept ( rulesWorkingFolderName ) ; // TODO: NEED TO CHECK IF RUNNING ASYNC GIVES PERF BOOST HERE OR NOT
358395 }
359396 return this . rulesCache . get ( cacheKey ) ! ;
360397 }
361398
362- private async getAllRulesFor ( engineName : string , describeOptions : engApi . DescribeOptions ) : Promise < RuleImpl [ ] > {
399+ private async getAllRulesFor ( engineName : string , describeOptions : engApi . DescribeOptions , errorCallback : ( ) => void ) : Promise < RuleImpl [ ] > {
363400 this . emitLogEvent ( LogLevel . Debug , getMessage ( 'GatheringRulesFromEngine' , engineName ) ) ;
401+ const invokeErrorCallbackIfErrorIsLoggedFcn = ( event : engApi . LogEvent ) => {
402+ if ( event . logLevel === engApi . LogLevel . Error ) {
403+ errorCallback ( ) ;
404+ }
405+ } ;
406+
407+ const engine : engApi . Engine = this . getEngine ( engineName ) ;
408+ engine . onEvent ( engApi . EventType . LogEvent , invokeErrorCallbackIfErrorIsLoggedFcn ) ;
409+
364410 let ruleDescriptions : engApi . RuleDescription [ ] = [ ] ;
365411 try {
366- ruleDescriptions = await this . getEngine ( engineName ) . describeRules ( describeOptions ) ;
412+ ruleDescriptions = await engine . describeRules ( describeOptions ) ;
367413 } catch ( err ) {
414+ errorCallback ( ) ;
368415 this . uninstantiableEnginesMap . set ( engineName , err as Error ) ;
369416 this . emitLogEvent ( LogLevel . Error , getMessage ( 'PluginErrorWhenGettingRules' , engineName , ( err as Error ) . message + '\n\n' +
370417 getMessage ( 'InstructionsToIgnoreErrorAndDisableEngine' , engineName ) ) ) ;
371418 return [ ] ;
419+ } finally {
420+ engine . removeEventListener ( engApi . EventType . LogEvent , invokeErrorCallbackIfErrorIsLoggedFcn ) ;
372421 }
422+
373423 this . emitLogEvent ( LogLevel . Debug , getMessage ( 'FinishedGatheringRulesFromEngine' , ruleDescriptions . length , engineName ) ) ;
374424
375425 validateRuleDescriptions ( ruleDescriptions , engineName ) ;
@@ -385,20 +435,29 @@ export class CodeAnalyzer {
385435 this . emitEvent ( { type : EventType . RuleSelectionProgressEvent , timestamp : this . clock . now ( ) , percentComplete : aggregatedPerc } ) ;
386436 }
387437
388- private async runEngineAndValidateResults ( engineName : string , ruleSelection : RuleSelection , engineRunOptions : engApi . RunOptions ) : Promise < EngineRunResults > {
438+ private async runEngineAndValidateResults ( engineName : string , ruleSelection : RuleSelection , engineRunOptions : engApi . RunOptions , errorCallback : ( ) => void ) : Promise < EngineRunResults > {
389439 this . emitEvent < EngineRunProgressEvent > ( {
390440 type : EventType . EngineRunProgressEvent , timestamp : this . clock . now ( ) , engineName : engineName , percentComplete : 0
391441 } ) ;
392-
393442 const rulesToRun : string [ ] = ruleSelection . getRulesFor ( engineName ) . map ( r => r . getName ( ) ) ;
443+
394444 this . emitLogEvent ( LogLevel . Debug , getMessage ( 'RunningEngineWithRules' , engineName , JSON . stringify ( rulesToRun ) ) ) ;
445+ const invokeErrorCallbackIfErrorIsLoggedFcn = ( event : engApi . LogEvent ) => {
446+ if ( event . logLevel === engApi . LogLevel . Error ) {
447+ errorCallback ( ) ;
448+ }
449+ } ;
395450 const engine : engApi . Engine = this . getEngine ( engineName ) ;
451+ engine . onEvent ( engApi . EventType . LogEvent , invokeErrorCallbackIfErrorIsLoggedFcn ) ;
396452
397453 let apiEngineRunResults : engApi . EngineRunResults ;
398454 try {
399455 apiEngineRunResults = await engine . runRules ( rulesToRun , engineRunOptions ) ;
400456 } catch ( error ) {
457+ errorCallback ( ) ;
401458 return new UnexpectedErrorEngineRunResults ( engineName , await engine . getEngineVersion ( ) , error as Error ) ;
459+ } finally {
460+ engine . removeEventListener ( engApi . EventType . LogEvent , invokeErrorCallbackIfErrorIsLoggedFcn ) ;
402461 }
403462
404463 validateEngineRunResults ( engineName , apiEngineRunResults , ruleSelection ) ;
0 commit comments