import * as debug from 'debug' ; import * as pMap from 'p-map' ; import * as convertHrtime from 'convert-hrtime' ; import { Task , TaskListError , TaskName , RegisterableTaskList , ErrorKind , CheckupError , } from '@checkup/core' ; import { Result } from 'sarif' ; import { taskResultComparator } from './task-result-comparator' ; export type TaskFinderResult = { tasksFound : Task [ ] ; tasksNotFound : ( TaskName | string ) [ ] ; } ; class export default class TaskListImpl implements RegisterableTaskList { private _categories : Map < string , Map < TaskName , Task > > ; private _errors : TaskListError [ ] ; private _timings : Record < TaskName , number > ; debug : debug . Debugger ; get categories ( ) { return this . _categories ; } get timings ( ) { return this . _timings ; } get size ( ) { return this . getTasks ( ) . length ; } returns { string [ ] } get fullyQualifiedTaskNames ( ) { return this . getTasks ( ) . map ( ( task ) => task . fullyQualifiedTaskName ) . sort ( ) ; } constructor ( ) { this . _categories = new Map < string , Map < TaskName , Task > > ( ) ; this . _errors = [ ] ; this . _timings = { } ; this . debug = debug ( 'checkup:task' ) ; } method param taskName { TaskName } param task { Task } param taskClassification registerTask ( task ) { if ( task . category === '' ) { throw new CheckupError ( ErrorKind . TaskCategoryRequired , { fullyQualifiedTaskName : task . fullyQualifiedTaskName , } ) ; } let categoryMap = this . getByCategory ( task . category ) ; categoryMap ! . set ( task . taskName , task ) ; } method param taskName hasTask ( taskName ) { return this . findTask ( taskName ) !== undefined ; } method param taskName findTask ( taskName , predicate = ( task ) => task . taskName === taskName ) { return this . getTasks ( ) . find ( ( task ) => predicate ( task ) ) ; } method param taskNames findAllByTaskName ( ... taskNames ) { let tasksFound = [ ] ; let tasksNotFound = [ ] ; let availableTasks = this . getTasks ( ) ; taskNames . forEach ( ( taskName ) => { let taskFound = false ; for ( let availableTask of availableTasks ) { if ( availableTask . fullyQualifiedTaskName === taskName ) { taskFound = true ; tasksFound . push ( availableTask ) ; break ; } } if ( taskFound === false ) { tasksNotFound . push ( taskName ) ; } } ) ; return { tasksFound , tasksNotFound } ; } findAllByCategory ( ... categories ) { let tasksFound = [ ] ; let tasksNotFound = [ ] ; categories . forEach ( ( category ) => { let tasks = this . _categories . get ( category ) ; if ( tasks !== undefined ) { tasksFound = [ ... tasksFound , ... tasks . values ( ) ] ; } else { tasksNotFound . push ( category ) ; } } ) ; return { tasksFound , tasksNotFound } ; } findAllByGroup ( ... groups ) { let tasksFound = [ ] ; let tasksNotFound = [ ] ; let availableTasks = this . getTasks ( ) ; groups . forEach ( ( group ) => { let taskFound = false ; for ( let availableTask of availableTasks ) { if ( availableTask . group === group ) { taskFound = true ; tasksFound . push ( availableTask ) ; } } if ( taskFound === false ) { tasksNotFound . push ( group ) ; } } ) ; return { tasksFound , tasksNotFound } ; } method param { TaskName } taskName returns { Promise < [ Result [ ] , TaskListError [ ] ] > } memberof async runTask ( taskName ) { let result ; let task = this . findTask ( taskName ) ; if ( task === undefined ) { throw new CheckupError ( ErrorKind . TasksNotFound ) ; } this . debug ( 'start %s run' , task . fullyQualifiedTaskName ) ; try { result = await this . _runTask ( task ) ; this . addErrors ( task . fullyQualifiedTaskName , task . nonFatalErrors ) ; } catch ( error ) { this . addErrors ( task . fullyQualifiedTaskName , error ) ; } this . debug ( '%s run done' , task . fullyQualifiedTaskName ) ; return [ result , this . _errors ] ; } method returns { Promise < [ Result [ ] , TaskListError [ ] ] > } memberof async runTasks ( tasks ? ) { let results = await this . eachTask ( async ( task ) => { let result ; this . debug ( 'start %s run' , task . fullyQualifiedTaskName ) ; try { result = await this . _runTask ( task ) ; this . addErrors ( task . fullyQualifiedTaskName , task . nonFatalErrors ) ; } catch ( error ) { this . addErrors ( task . fullyQualifiedTaskName , error ) ; } this . debug ( '%s run done' , task . fullyQualifiedTaskName ) ; return result ; } , tasks ) ; return [ ( results . flat ( ) . filter ( Boolean ) as Result [ ] ) . sort ( taskResultComparator ) , this . _errors ] ; } private async _runTask ( task ) { let t = process . hrtime ( ) ; let result = await task . run ( ) ; t = process . hrtime ( t ) ; this . _timings [ task . fullyQualifiedTaskName ] = convertHrtime ( t ) . seconds ; return result ; } private method param category private getByCategory ( category ) { if ( ! this . _categories . has ( category ) ) { this . _categories . set ( category , new Map < TaskName , Task > ( ) ) ; } return this . _categories . get ( category ) ; } private method param fn { Function } returns { Promise < Result [ ] [ ] > } private eachTask ( fn , tasksToRun ? ) { let availableTasks = tasksToRun || this . getTasks ( ) ; return pMap ( availableTasks . filter ( ( task ) => task . enabled ) , fn ) ; } private method getTasks ( ) { let values = [ ] ; this . _categories . forEach ( ( category ) => { values = [ ... values , ... category . values ( ) ] ; } ) ; return values ; } private addErrors ( taskName , errors ) { errors = Array . isArray ( errors ) ? errors : [ errors ] ; this . _errors . push ( ... errors . map ( ( error ) => { return { error , taskName , } ; } ) ) ; } }