diff --git a/docs/user/config/astTransformers.md b/docs/user/config/astTransformers.md index a2c58eadd2..a077787920 100644 --- a/docs/user/config/astTransformers.md +++ b/docs/user/config/astTransformers.md @@ -7,13 +7,15 @@ TypeScript AST transformers and provide them to `ts-jest` to include into compil The option is `astTransformers` and it allows ones to specify which 3 types of TypeScript AST transformers to use with `ts-jest`: -- `before` means your transformers get run before TS ones, which means your transformers will get raw TS syntax -instead of transpiled syntax (e.g `import` instead of `require` or `define` ). +- `before` means your transformers get run before TS ones, which means your transformers will get raw TS syntax + instead of transpiled syntax (e.g `import` instead of `require` or `define` ). - `after` means your transformers get run after TS ones, which gets transpiled syntax. - `afterDeclarations` means your transformers get run during `d.ts` generation phase, allowing you to transform output type declarations. ### Examples +#### Basic Transformers +
```js @@ -25,9 +27,52 @@ module.exports = { astTransformers: { before: ['my-custom-transformer'], }, + }, + }, +} +``` + +
+ +```js +// OR package.json +{ + // [...] + "jest": { + "globals": { + "ts-jest": { + astTransformers: { + "before": ["my-custom-transformer"] + } + } } } -}; +} +``` + +
+ +#### Configuring transformers with options + +
+ +```js +// jest.config.js +module.exports = { + // [...] + globals: { + 'ts-jest': { + astTransformers: { + before: [ + { + path: 'my-custom-transformer-that-needs-extra-opts', + options: {}, // extra options to pass to transformers here + }, + ], + }, + }, + }, +} ```
@@ -40,7 +85,10 @@ module.exports = { "globals": { "ts-jest": { astTransformers: { - "before": ["my-custom-transformer"] + "before": [{ + path: 'my-custom-transformer-that-needs-extra-opts', + options: {} // extra options to pass to transformers here + }] } } } @@ -55,9 +103,9 @@ module.exports = { `ts-jest` is able to expose transformers for public usage to provide the possibility to opt-in/out for users. Currently the exposed transformers are: -- `path-mapping` convert alias import/export to relative import/export path base on `paths` in `tsconfig`. -This transformer works similar to `moduleNameMapper` in `jest.config.js`. When using this transformer, one might not need -`moduleNameMapper` anymore. +- `path-mapping` convert alias import/export to relative import/export path base on `paths` in `tsconfig`. + This transformer works similar to `moduleNameMapper` in `jest.config.js`. When using this transformer, one might not need + `moduleNameMapper` anymore. #### Example of opt-in transformers @@ -72,9 +120,9 @@ module.exports = { astTransformers: { before: ['ts-jest/dist/transformers/path-mapping'], }, - } - } -}; + }, + }, +} ```
@@ -97,7 +145,6 @@ module.exports = {
- ### Writing custom TypeScript AST transformers To write a custom TypeScript AST transformers, one can take a look at [the one](https://github.com/kulshekhar/ts-jest/tree/master/src/transformers) that `ts-jest` is using. diff --git a/e2e/__cases__/ast-transformers/with-extra-options/foo.js b/e2e/__cases__/ast-transformers/with-extra-options/foo.js new file mode 100644 index 0000000000..2fae9f1050 --- /dev/null +++ b/e2e/__cases__/ast-transformers/with-extra-options/foo.js @@ -0,0 +1,18 @@ +const { LogContexts, LogLevels } = require('bs-logger') + +function factory(cs, extraOpts = Object.create(null)) { + const logger = cs.logger.child({ namespace: 'dummy-transformer' }) + const ts = cs.compilerModule + logger.debug('Dummy transformer with extra options', JSON.stringify(extraOpts)) + + function createVisitor(_ctx, _sf) { + return (node) => node + } + + return (ctx) => + logger.wrap({ [LogContexts.logLevel]: LogLevels.debug, call: null }, 'visitSourceFileNode(): dummy', (sf) => + ts.visitNode(sf, createVisitor(ctx, sf)) + ) +} + +exports.factory = factory diff --git a/e2e/__cases__/ast-transformers/with-extra-options/with-extra-options.spec.ts b/e2e/__cases__/ast-transformers/with-extra-options/with-extra-options.spec.ts new file mode 100644 index 0000000000..11634b90f3 --- /dev/null +++ b/e2e/__cases__/ast-transformers/with-extra-options/with-extra-options.spec.ts @@ -0,0 +1,5 @@ +const a = 1; + +it('should pass', () => { + expect(a).toEqual(1); +}) diff --git a/e2e/__tests__/__snapshots__/ast-transformers.test.ts.snap b/e2e/__tests__/__snapshots__/ast-transformers.test.ts.snap new file mode 100644 index 0000000000..0ed37052b1 --- /dev/null +++ b/e2e/__tests__/__snapshots__/ast-transformers.test.ts.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AST transformers with extra options should pass using template "default" 1`] = ` +Array [ + "[level:20] Dummy transformer with extra options {\\"foo\\":\\"bar\\"}", +] +`; + +exports[`AST transformers with extra options should pass using template "with-babel-7" 1`] = ` +Array [ + "[level:20] Dummy transformer with extra options {\\"foo\\":\\"bar\\"}", +] +`; + +exports[`AST transformers with extra options should pass using template "with-babel-7-string-config" 1`] = ` +Array [ + "[level:20] Dummy transformer with extra options {\\"foo\\":\\"bar\\"}", +] +`; diff --git a/e2e/__tests__/ast-transformers.test.ts b/e2e/__tests__/ast-transformers.test.ts new file mode 100644 index 0000000000..a744e45cd9 --- /dev/null +++ b/e2e/__tests__/ast-transformers.test.ts @@ -0,0 +1,38 @@ +import { configureTestCase } from '../__helpers__/test-case' +import { allValidPackageSets } from '../__helpers__/templates' +import { existsSync } from "fs" +import { LogContexts, LogLevels } from 'bs-logger' + +describe('AST transformers', () => { + describe('with extra options', () => { + const testCase = configureTestCase('ast-transformers/with-extra-options', { + env: { TS_JEST_LOG: 'ts-jest.log' }, + tsJestConfig: { + astTransformers: { + before: [{ + path: require.resolve('../__cases__/ast-transformers/with-extra-options/foo'), + options: { + foo: 'bar', + }, + }], + }, + }, + }) + + testCase.runWithTemplates(allValidPackageSets, 0, (runTest, { testLabel }) => { + it(testLabel, () => { + const result = runTest() + expect(result.status).toBe(0) + expect(existsSync(result.logFilePath)).toBe(true) + const filteredEntries = result.logFileEntries + // keep only debug and above + .filter(m => (m.context[LogContexts.logLevel] || 0) >= LogLevels.debug) + // simplify entries + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + .map(e => result.normalize(`[level:${e.context[LogContexts.logLevel]}] ${e.message}`)) + .filter(logging => logging.includes('Dummy transformer with extra options')) + expect(filteredEntries).toMatchSnapshot() + }) + }) + }) +}) diff --git a/src/config/__snapshots__/config-set.spec.ts.snap b/src/config/__snapshots__/config-set.spec.ts.snap index e310da8a30..fb0447b0d5 100644 --- a/src/config/__snapshots__/config-set.spec.ts.snap +++ b/src/config/__snapshots__/config-set.spec.ts.snap @@ -355,3 +355,14 @@ exports[`tsJest transformers should display deprecation warning message when con "[level:40] The configuration for astTransformers as string[] is deprecated and will be removed in ts-jest 27. Please define your custom AST transformers in a form of an object. More information you can check online documentation https://kulshekhar.github.io/ts-jest/user/config/astTransformers " `; + +exports[`tsJest transformers should support transformers with options 1`] = ` +Array [ + Object { + "options": Object { + "foo": 1, + }, + "path": Any, + }, +] +`; diff --git a/src/config/config-set.spec.ts b/src/config/config-set.spec.ts index 10a29f8678..ee8451687e 100644 --- a/src/config/config-set.spec.ts +++ b/src/config/config-set.spec.ts @@ -161,6 +161,37 @@ describe('tsJest', () => { expect(logger.target.lines[1]).toMatchSnapshot() }) + it('should support transformers with options', () => { + const cs = createConfigSet({ + jestConfig: { + rootDir: 'src', + cwd: 'src', + globals: { + 'ts-jest': { + astTransformers: { + before: [ + { + path: 'dummy-transformer', + options: { + foo: 1, + }, + }, + ], + }, + }, + }, + } as any, + logger, + resolve: null, + }) + + expect(cs.tsJest.transformers.before).toMatchSnapshot([ + { + path: expect.any(String), + }, + ]) + }) + it.each([ {}, { diff --git a/src/config/config-set.ts b/src/config/config-set.ts index 2d941370aa..f284749656 100644 --- a/src/config/config-set.ts +++ b/src/config/config-set.ts @@ -56,10 +56,15 @@ import { TSError } from '../utils/ts-error' const logger = rootLogger.child({ namespace: 'config' }) +interface AstTransformerObj> { + transformModule: AstTransformerDesc + options?: T +} + interface AstTransformer { - before: AstTransformerDesc[] - after?: AstTransformerDesc[] - afterDeclarations?: AstTransformerDesc[] + before: AstTransformerObj[] + after?: AstTransformerObj[] + afterDeclarations?: AstTransformerObj[] } /** * @internal @@ -184,29 +189,52 @@ export class ConfigSet { this.logger.warn(Deprecations.AstTransformerArrayConfig) transformers = { - before: astTransformers.map((transformerPath) => this.resolvePath(transformerPath, { nodeResolve: true })), + before: astTransformers.map((transformerPath) => ({ + path: this.resolvePath(transformerPath, { nodeResolve: true }), + })), } } else { if (astTransformers.before) { transformers = { - before: astTransformers.before.map((transformerPath: string) => - this.resolvePath(transformerPath, { nodeResolve: true }), + before: astTransformers.before.map((transformer) => + typeof transformer === 'string' + ? { + path: this.resolvePath(transformer, { nodeResolve: true }), + } + : { + ...transformer, + path: this.resolvePath(transformer.path, { nodeResolve: true }), + }, ), } } if (astTransformers.after) { transformers = { ...transformers, - after: astTransformers.after.map((transformerPath: string) => - this.resolvePath(transformerPath, { nodeResolve: true }), + after: astTransformers.after.map((transformer) => + typeof transformer === 'string' + ? { + path: this.resolvePath(transformer, { nodeResolve: true }), + } + : { + ...transformer, + path: this.resolvePath(transformer.path, { nodeResolve: true }), + }, ), } } if (astTransformers.afterDeclarations) { transformers = { ...transformers, - afterDeclarations: astTransformers.afterDeclarations.map((transformerPath: string) => - this.resolvePath(transformerPath, { nodeResolve: true }), + afterDeclarations: astTransformers.afterDeclarations.map((transformer) => + typeof transformer === 'string' + ? { + path: this.resolvePath(transformer, { nodeResolve: true }), + } + : { + ...transformer, + path: this.resolvePath(transformer.path, { nodeResolve: true }), + }, ), } } @@ -402,28 +430,57 @@ export class ConfigSet { @Memoize() private get astTransformers(): AstTransformer { let astTransformers: AstTransformer = { - before: [...internalAstTransformers], + before: [ + ...internalAstTransformers.map((transformer) => ({ + transformModule: transformer, + })), + ], } const { transformers } = this.tsJest if (transformers.before) { astTransformers = { before: [ ...astTransformers.before, - ...transformers.before.map((transformerFilePath: string) => require(transformerFilePath)), + ...transformers.before.map((transformer) => + typeof transformer === 'string' + ? { + transformModule: require(transformer), + } + : { + transformModule: require(transformer.path), + options: transformer.options, + }, + ), ], } } if (transformers.after) { astTransformers = { ...astTransformers, - after: transformers.after.map((transformerFilePath: string) => require(transformerFilePath)), + after: transformers.after.map((transformer) => + typeof transformer === 'string' + ? { + transformModule: require(transformer), + } + : { + transformModule: require(transformer.path), + options: transformer.options, + }, + ), } } if (transformers.afterDeclarations) { astTransformers = { ...astTransformers, - afterDeclarations: transformers.afterDeclarations.map((transformerFilePath: string) => - require(transformerFilePath), + afterDeclarations: transformers.afterDeclarations.map((transformer) => + typeof transformer === 'string' + ? { + transformModule: require(transformer), + } + : { + transformModule: require(transformer.path), + options: transformer.options, + }, ), } } @@ -437,20 +494,24 @@ export class ConfigSet { @Memoize() get tsCustomTransformers(): CustomTransformers { let customTransformers: CustomTransformers = { - before: this.astTransformers.before.map((t) => t.factory(this)) as TransformerFactory[], + before: this.astTransformers.before.map((t) => t.transformModule.factory(this, t.options)) as TransformerFactory< + SourceFile + >[], } if (this.astTransformers.after) { customTransformers = { ...customTransformers, - after: this.astTransformers.after.map((t) => t.factory(this)) as TransformerFactory[], + after: this.astTransformers.after.map((t) => t.transformModule.factory(this, t.options)) as TransformerFactory< + SourceFile + >[], } } if (this.astTransformers.afterDeclarations) { customTransformers = { ...customTransformers, - afterDeclarations: this.astTransformers.afterDeclarations.map((t) => t.factory(this)) as TransformerFactory< - Bundle | SourceFile - >[], + afterDeclarations: this.astTransformers.afterDeclarations.map((t) => + t.transformModule.factory(this, t.options), + ) as TransformerFactory[], } } @@ -660,7 +721,7 @@ export class ConfigSet { digest: this.tsJestDigest, transformers: Object.values(this.astTransformers) .reduce((acc, val) => acc.concat(val), []) - .map((t: AstTransformerDesc) => `${t.name}@${t.version}`), + .map(({ transformModule }: AstTransformerObj) => `${transformModule.name}@${transformModule.version}`), jest, tsJest: this.tsJest, babel: this.babel, diff --git a/src/types.ts b/src/types.ts index ef17b21cc6..882d3980f4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -24,10 +24,15 @@ export type BabelJestTransformer = { */ export type BabelConfig = _babel.TransformOptions +export interface AstTransformer> { + path: string + options?: T +} + export interface ConfigCustomTransformer { - before?: string[] - after?: string[] - afterDeclarations?: string[] + before?: (string | AstTransformer)[] + after?: (string | AstTransformer)[] + afterDeclarations?: (string | AstTransformer)[] } export interface TsJestGlobalOptions { @@ -229,8 +234,11 @@ export interface CompilerInstance { /** * @internal */ -export interface AstTransformerDesc { +export interface AstTransformerDesc> { name: string version: number - factory(cs: ConfigSet): _ts.TransformerFactory<_ts.SourceFile> | _ts.TransformerFactory<_ts.Bundle | _ts.SourceFile> + factory( + cs: ConfigSet, + opts?: T, + ): _ts.TransformerFactory<_ts.SourceFile> | _ts.TransformerFactory<_ts.Bundle | _ts.SourceFile> }