Skip to content

Commit

Permalink
fix: invoke Babel processAsync for babel-jest in ESM mode instead…
Browse files Browse the repository at this point in the history
… of `process` (#3430)
  • Loading branch information
ahnpnl authored Apr 19, 2022
1 parent 3dba4ec commit 0d7356c
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 39 deletions.
26 changes: 23 additions & 3 deletions src/ts-jest-transformer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
})
})
})
116 changes: 80 additions & 36 deletions src/ts-jest-transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<TransformedSource | string> {
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,
Expand All @@ -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<TransformedSource | string> {
return new Promise((resolve) => resolve(this.process(sourceText, sourcePath, transformOptions)))
return compiledOutput
}

/**
Expand Down

0 comments on commit 0d7356c

Please sign in to comment.