import * as ts from 'typescript' ; import { Configuration } from '../configuration' ; import { CLI_ERRORS } from '../ui/errors' ; import { getValueOrDefault } from './helpers/get-value-or-default' ; import { TsConfigProvider } from './helpers/tsconfig-provider' ; import { tsconfigPathsBeforeHookFactory } from './hooks/tsconfig-paths.hook' ; import { PluginsLoader } from './plugins-loader' ; import { TypeScriptBinaryLoader } from './typescript-loader' ; export class WatchCompiler { constructor ( private readonly pluginsLoader , private readonly tsConfigProvider , private readonly typescriptLoader ) { } public run ( configuration , configFilename , appName , tsCompilerOptions , onSuccess ? ) { const tsBin = this . typescriptLoader . load ( ) ; const configPath = tsBin . findConfigFile ( process . cwd ( ) , tsBin . sys . fileExists , configFilename ) ; if ( ! configPath ) { throw new Error ( CLI_ERRORS . MISSING_TYPESCRIPT ( configFilename ) ) ; } const { projectReferences } = this . tsConfigProvider . getByConfigFilename ( configFilename ) ; const createProgram = tsBin . createEmitAndSemanticDiagnosticsBuilderProgram ; const origDiagnosticReporter = ( tsBin as any ) . createDiagnosticReporter ( tsBin . sys , true ) ; const origWatchStatusReporter = ( tsBin as any ) . createWatchStatusReporter ( tsBin . sys , true ) ; const host = tsBin . createWatchCompilerHost ( configPath , tsCompilerOptions , tsBin . sys , createProgram , this . createDiagnosticReporter ( origDiagnosticReporter ) , this . createWatchStatusChanged ( origWatchStatusReporter , onSuccess ) ) ; const pluginsConfig = getValueOrDefault ( configuration , 'compilerOptions.plugins' , appName ) ; const plugins = this . pluginsLoader . load ( pluginsConfig ) ; const origCreateProgram = host . createProgram ; ( host as any ) . createProgram = ( rootNames , options , host , oldProgram ) => { const tsconfigPathsPlugin = tsconfigPathsBeforeHookFactory ( options ) ; const program = origCreateProgram ( rootNames , options , host , oldProgram , undefined , projectReferences ) ; const origProgramEmit = program . emit ; program . emit = ( targetSourceFile ? , writeFile ? , cancellationToken ? , emitOnlyDtsFiles ? , customTransformers ? ) => { let transforms = customTransformers ; transforms = typeof transforms !== 'object' ? { } : transforms ; const before = plugins . beforeHooks . map ( ( hook ) => hook ( program . getProgram ( ) ) ) ; const after = plugins . afterHooks . map ( ( hook ) => hook ( program . getProgram ( ) ) ) ; const afterDeclarations = plugins . afterDeclarationsHooks . map ( ( hook ) => hook ( program . getProgram ( ) ) ) ; before . unshift ( tsconfigPathsPlugin ) ; transforms . before = before . concat ( transforms . before || [ ] ) ; transforms . after = after . concat ( transforms . after || [ ] ) ; transforms . afterDeclarations = afterDeclarations . concat ( transforms . afterDeclarations || [ ] ) ; return origProgramEmit ( targetSourceFile , writeFile , cancellationToken , emitOnlyDtsFiles , transforms ) ; } ; return program as any ; } ; tsBin . createWatchProgram ( host ) ; } private createDiagnosticReporter ( diagnosticReporter ) { return function ( this , diagnostic , ... args ) { return diagnosticReporter . call ( this , diagnostic , ... args ) ; } ; } private createWatchStatusChanged ( watchStatusReporter , onSuccess ? ) { return function ( this , diagnostic , ... args ) { const messageText = diagnostic && diagnostic . messageText ; const noErrorsMessage = '0 errors' ; if ( messageText && ( messageText as string ) . includes && ( messageText as string ) . includes ( noErrorsMessage ) && onSuccess ) { onSuccess ( ) ; } return watchStatusReporter . call ( this , diagnostic , ... args ) ; } ; } }