From 0d7356cd767a924e5b57e3a93679eef4ca8fae51 Mon Sep 17 00:00:00 2001 From: Ahn <27772165+ahnpnl@users.noreply.github.com> Date: Tue, 19 Apr 2022 08:08:23 +0200 Subject: [PATCH] fix: invoke Babel `processAsync` for `babel-jest` in ESM mode instead of `process` (#3430) --- src/ts-jest-transformer.spec.ts | 26 ++++++- src/ts-jest-transformer.ts | 116 ++++++++++++++++++++++---------- 2 files changed, 103 insertions(+), 39 deletions(-) diff --git a/src/ts-jest-transformer.spec.ts b/src/ts-jest-transformer.spec.ts index 519e687ed4..3068eb4a84 100644 --- a/src/ts-jest-transformer.spec.ts +++ b/src/ts-jest-transformer.spec.ts @@ -463,11 +463,31 @@ describe('TsJestTransformer', () => { const sourceText = 'const foo = 1' const sourcePath = 'foo.ts' const tr = new TsJestTransformer() - tr.process = jest.fn() + // @ts-expect-error `processWithTs` is private + tr.processWithTs = jest.fn().mockReturnValueOnce('var foo = 1') + const transformOptions = { + ...baseTransformOptions, + config: { + ...baseTransformOptions.config, + globals: { + 'ts-jest': { + babelConfig: true, + }, + }, + }, + } + // @ts-expect-error `_configsFor` is private + const babelJest = tr._configsFor(transformOptions).babelJestTransformer! + jest.spyOn(babelJest, 'processAsync').mockResolvedValue('var foo = 1') - await tr.processAsync(sourceText, sourcePath, baseTransformOptions) + const resultFromTs = await tr.processAsync(sourceText, sourcePath, transformOptions) - expect(tr.process).toHaveBeenCalledWith(sourceText, sourcePath, baseTransformOptions) + // @ts-expect-error `processWithTs` is private + expect(tr.processWithTs).toHaveBeenCalledWith(sourceText, sourcePath, transformOptions) + expect(babelJest.processAsync).toHaveBeenCalledWith(resultFromTs, sourcePath, { + ...transformOptions, + instrument: false, + }) }) }) }) diff --git a/src/ts-jest-transformer.ts b/src/ts-jest-transformer.ts index 24d951eb55..f42d90e216 100644 --- a/src/ts-jest-transformer.ts +++ b/src/ts-jest-transformer.ts @@ -137,40 +137,79 @@ export class TsJestTransformer implements SyncTransformer { * @public */ process( - fileContent: string, - filePath: Config.Path, + sourceText: string, + sourcePath: Config.Path, transformOptions: TransformOptionsTsJest, ): TransformedSource | string { - this._logger.debug({ fileName: filePath, transformOptions }, 'processing', filePath) + this._logger.debug({ fileName: sourcePath, transformOptions }, 'processing', sourcePath) - let result: string | TransformedSource const configs = this._configsFor(transformOptions) - const shouldStringifyContent = configs.shouldStringifyContent(filePath) + const shouldStringifyContent = configs.shouldStringifyContent(sourcePath) const babelJest = shouldStringifyContent ? undefined : configs.babelJestTransformer - const isDefinitionFile = filePath.endsWith(DECLARATION_TYPE_EXT) - const isJsFile = JS_JSX_REGEX.test(filePath) - const isTsFile = !isDefinitionFile && TS_TSX_REGEX.test(filePath) - let hooksFile = process.env.TS_JEST_HOOKS - let hooks: TsJestHooksMap | undefined - /* istanbul ignore next (cover by e2e) */ - if (hooksFile) { - hooksFile = path.resolve(configs.cwd, hooksFile) - hooks = importer.tryTheseOr(hooksFile, {}) + let result: TransformedSource | string = this.processWithTs(sourceText, sourcePath, transformOptions) + if (babelJest) { + this._logger.debug({ fileName: sourcePath }, 'calling babel-jest processor') + + // do not instrument here, jest will do it anyway afterwards + result = babelJest.process(result, sourcePath, { + ...transformOptions, + instrument: false, + }) } + result = this.runTsJestHook(sourcePath, sourceText, transformOptions, result) as string + + return result + } + + async processAsync( + sourceText: string, + sourcePath: Config.Path, + transformOptions: TransformOptionsTsJest, + ): Promise { + this._logger.debug({ fileName: sourcePath, transformOptions }, 'processing', sourcePath) + + return new Promise(async (resolve) => { + const configs = this._configsFor(transformOptions) + const shouldStringifyContent = configs.shouldStringifyContent(sourcePath) + const babelJest = shouldStringifyContent ? undefined : configs.babelJestTransformer + let result: TransformedSource | string = this.processWithTs(sourceText, sourcePath, transformOptions) + if (babelJest) { + this._logger.debug({ fileName: sourcePath }, 'calling babel-jest processor') + + // do not instrument here, jest will do it anyway afterwards + result = await babelJest.processAsync(result, sourcePath, { + ...transformOptions, + instrument: false, + }) + } + result = this.runTsJestHook(sourcePath, sourceText, transformOptions, result) as string + + resolve(result) + }) + } + + private processWithTs(sourceText: string, sourcePath: string, transformOptions: TransformOptionsTsJest) { + let result: string | TransformedSource + const configs = this._configsFor(transformOptions) + const shouldStringifyContent = configs.shouldStringifyContent(sourcePath) + const babelJest = shouldStringifyContent ? undefined : configs.babelJestTransformer + const isDefinitionFile = sourcePath.endsWith(DECLARATION_TYPE_EXT) + const isJsFile = JS_JSX_REGEX.test(sourcePath) + const isTsFile = !isDefinitionFile && TS_TSX_REGEX.test(sourcePath) if (shouldStringifyContent) { // handles here what we should simply stringify - result = `module.exports=${stringify(fileContent)}` + result = `module.exports=${stringify(sourceText)}` } else if (isDefinitionFile) { // do not try to compile declaration files result = '' } else if (!configs.parsedTsConfig.options.allowJs && isJsFile) { // we've got a '.js' but the compiler option `allowJs` is not set or set to false - this._logger.warn({ fileName: filePath }, interpolate(Errors.GotJsFileButAllowJsFalse, { path: filePath })) + this._logger.warn({ fileName: sourcePath }, interpolate(Errors.GotJsFileButAllowJsFalse, { path: sourcePath })) - result = fileContent + result = sourceText } else if (isJsFile || isTsFile) { // transpile TS code (source maps are included) - result = this._compiler.getCompiledOutput(fileContent, filePath, { + result = this._compiler.getCompiledOutput(sourceText, sourcePath, { depGraphs: this._depGraphs, supportsStaticESM: transformOptions.supportsStaticESM, watchMode: this._watchMode, @@ -181,36 +220,41 @@ export class TsJestTransformer implements SyncTransformer { // define the transform value with `babel-jest` for this extension instead const message = babelJest ? Errors.GotUnknownFileTypeWithBabel : Errors.GotUnknownFileTypeWithoutBabel - this._logger.warn({ fileName: filePath }, interpolate(message, { path: filePath })) + this._logger.warn({ fileName: sourcePath }, interpolate(message, { path: sourcePath })) - result = fileContent + result = sourceText } - // calling babel-jest transformer - if (babelJest) { - this._logger.debug({ fileName: filePath }, 'calling babel-jest processor') - // do not instrument here, jest will do it anyway afterwards - result = babelJest.process(result, filePath, { ...transformOptions, instrument: false }) + return result + } + + private runTsJestHook( + sourcePath: string, + sourceText: string, + transformOptions: TransformOptionsTsJest, + compiledOutput: TransformedSource | string, + ) { + let hooksFile = process.env.TS_JEST_HOOKS + let hooks: TsJestHooksMap | undefined + /* istanbul ignore next (cover by e2e) */ + if (hooksFile) { + hooksFile = path.resolve(this._configsFor(transformOptions).cwd, hooksFile) + hooks = importer.tryTheseOr(hooksFile, {}) } // This is not supposed to be a public API but we keep it as some people use it if (hooks?.afterProcess) { - this._logger.debug({ fileName: filePath, hookName: 'afterProcess' }, 'calling afterProcess hook') + this._logger.debug({ fileName: sourcePath, hookName: 'afterProcess' }, 'calling afterProcess hook') - const newResult = hooks.afterProcess([fileContent, filePath, transformOptions.config, transformOptions], result) + const newResult = hooks.afterProcess( + [sourceText, sourcePath, transformOptions.config, transformOptions], + compiledOutput, + ) if (newResult) { return newResult } } - return result - } - - async processAsync( - sourceText: string, - sourcePath: Config.Path, - transformOptions: TransformOptionsTsJest, - ): Promise { - return new Promise((resolve) => resolve(this.process(sourceText, sourcePath, transformOptions))) + return compiledOutput } /**