namespace tsp { const activeProgramTransformers = new Set(); const { dirname } = require('path'); /* ********************************************************* */ // region: Helpers /* ********************************************************* */ function getProjectDir(compilerOptions: tsShim.CompilerOptions) { return compilerOptions.configFilePath && dirname(compilerOptions.configFilePath); } function getProjectConfig(compilerOptions: tsShim.CompilerOptions, rootFileNames: ReadonlyArray) { let configFilePath = compilerOptions.configFilePath; let projectDir = getProjectDir(compilerOptions); if (configFilePath === undefined) { const baseDir = (rootFileNames.length > 0) ? dirname(rootFileNames[0]) : projectDir ?? process.cwd(); configFilePath = tsShim.findConfigFile(baseDir, tsShim.sys.fileExists); if (configFilePath) { const config = readConfig(configFilePath); compilerOptions = { ...config.options, ...compilerOptions }; projectDir = getProjectDir(compilerOptions); } } return ({ projectDir, compilerOptions }); } function readConfig(configFileNamePath: string) { const projectDir = dirname(configFileNamePath); const result = tsShim.readConfigFile(configFileNamePath, tsShim.sys.readFile); if (result.error) throw new TsPatchError('Error in tsconfig.json: ' + result.error.messageText); return tsShim.parseJsonConfigFileContent(result.config, tsShim.sys, projectDir, undefined, configFileNamePath); } function preparePluginsFromCompilerOptions(plugins: any): PluginConfig[] { if (!plugins) return []; // Old transformers system if ((plugins.length === 1) && plugins[0].customTransformers) { const { before = [], after = [] } = (plugins[0].customTransformers as { before: string[]; after: string[] }); return [ ...before.map((item: string) => ({ transform: item })), ...after.map((item: string) => ({ transform: item, after: true })), ]; } return plugins; } // endregion /* ********************************************************* * * Patched createProgram() * ********************************************************* */ export function createProgram( rootNamesOrOptions: ReadonlyArray | tsShim.CreateProgramOptions, options?: tsShim.CompilerOptions, host?: tsShim.CompilerHost, oldProgram?: tsShim.Program, configFileParsingDiagnostics?: ReadonlyArray ): tsShim.Program { let rootNames; /* Determine options */ const createOpts = !Array.isArray(rootNamesOrOptions) ? rootNamesOrOptions : void 0; if (createOpts) { rootNames = createOpts.rootNames; options = createOpts.options; host = createOpts.host; oldProgram = createOpts.oldProgram; configFileParsingDiagnostics = createOpts.configFileParsingDiagnostics; } else { options = options!; rootNames = rootNamesOrOptions as ReadonlyArray; } /* Get Config */ const projectConfig = getProjectConfig(options, rootNames); if ([ 'tsc', 'tsserver', 'tsserverlibrary' ].includes(tsp.currentLibrary)) { options = projectConfig.compilerOptions; if (createOpts) createOpts.options = options; } /* Prepare Plugins */ const plugins = preparePluginsFromCompilerOptions(options.plugins); const pluginCreator = new PluginCreator(plugins, { resolveBaseDir: projectConfig.projectDir ?? process.cwd() }); /* Handle JSDoc parsing in v5.3+ */ if (tsp.currentLibrary === 'tsc' && tsShim.JSDocParsingMode && pluginCreator.needsTscJsDocParsing) { host!.jsDocParsingMode = tsShim.JSDocParsingMode.ParseAll; } /* Invoke TS createProgram */ let program: tsShim.Program & { originalEmit?: tsShim.Program['emit'] } = createOpts ? tsShim.originalCreateProgram(createOpts) : tsShim.originalCreateProgram(rootNames, options, host, oldProgram, configFileParsingDiagnostics); /* Prevent recursion in Program transformers */ const programTransformers = pluginCreator.createProgramTransformers(); /* Transform Program */ for (const [ transformerKey, [ programTransformer, config ] ] of programTransformers) { if (activeProgramTransformers.has(transformerKey)) continue; activeProgramTransformers.add(transformerKey); const newProgram: any = programTransformer(program, host, config, { ts: tsp.getTsInstance() }); if (typeof newProgram?.['emit'] === 'function') program = newProgram; activeProgramTransformers.delete(transformerKey); } /* Hook emit method */ if (!program.originalEmit) { program.originalEmit = program.emit; program.emit = newEmit; } function newEmit( targetSourceFile?: tsShim.SourceFile, writeFile?: tsShim.WriteFileCallback, cancellationToken?: tsShim.CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: tsShim.CustomTransformers, ...additionalArgs: any ): tsShim.EmitResult { /* Merge in our transformers */ const transformers = pluginCreator.createSourceTransformers({ program }, customTransformers); /* Invoke TS emit */ const result: tsShim.EmitResult = program.originalEmit!( targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, transformers, // @ts-ignore ...additionalArgs ); /* Merge in transformer diagnostics */ for (const diagnostic of tsp.diagnosticMap.get(program) || []) if (!result.diagnostics.includes(diagnostic)) (result.diagnostics).push(diagnostic) return result; } return program; } }