From 3af551e0d5285f110f19c76f2642fe561a69f69d Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Mon, 16 Nov 2020 10:17:42 +0100 Subject: [PATCH 01/16] chore(docs): update babel transformer example --- docs/TutorialReact.md | 6 +++--- website/versioned_docs/version-22.x/TutorialReact.md | 4 ++-- website/versioned_docs/version-23.x/TutorialReact.md | 4 ++-- website/versioned_docs/version-24.x/TutorialReact.md | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/TutorialReact.md b/docs/TutorialReact.md index 491cbd798b85..e07965cfc391 100644 --- a/docs/TutorialReact.md +++ b/docs/TutorialReact.md @@ -304,7 +304,7 @@ The code for this example is available at [examples/enzyme](https://github.com/f ### Custom transformers -If you need more advanced functionality, you can also build your own transformer. Instead of using babel-jest, here is an example of using babel: +If you need more advanced functionality, you can also build your own transformer. Instead of using `babel-jest`, here is an example of using `@babel/core`: ```javascript // custom-transformer.js @@ -320,7 +320,7 @@ module.exports = { presets: [jestPreset], }); - return result ? result.code : src; + return result || src; }, }; ``` @@ -329,7 +329,7 @@ Don't forget to install the `@babel/core` and `babel-preset-jest` packages for t To make this work with Jest you need to update your Jest configuration with this: `"transform": {"\\.js$": "path/to/custom-transformer.js"}`. -If you'd like to build a transformer with babel support, you can also use babel-jest to compose one and pass in your custom configuration options: +If you'd like to build a transformer with babel support, you can also use `babel-jest` to compose one and pass in your custom configuration options: ```javascript const babelJest = require('babel-jest'); diff --git a/website/versioned_docs/version-22.x/TutorialReact.md b/website/versioned_docs/version-22.x/TutorialReact.md index 4e42be51f78c..063203b88a72 100644 --- a/website/versioned_docs/version-22.x/TutorialReact.md +++ b/website/versioned_docs/version-22.x/TutorialReact.md @@ -305,7 +305,7 @@ The code for this example is available at [examples/enzyme](https://github.com/f ### Custom transformers -If you need more advanced functionality, you can also build your own transformer. Instead of using babel-jest, here is an example of using babel: +If you need more advanced functionality, you can also build your own transformer. Instead of using `babel-jest`, here is an example of using `babel-core`: ```javascript // custom-transformer.js @@ -332,7 +332,7 @@ Don't forget to install the `babel-core` and `babel-preset-jest` packages for th To make this work with Jest you need to update your Jest configuration with this: `"transform": {"\\.js$": "path/to/custom-transformer.js"}`. -If you'd like to build a transformer with babel support, you can also use babel-jest to compose one and pass in your custom configuration options: +If you'd like to build a transformer with babel support, you can also use `babel-jest` to compose one and pass in your custom configuration options: ```javascript const babelJest = require('babel-jest'); diff --git a/website/versioned_docs/version-23.x/TutorialReact.md b/website/versioned_docs/version-23.x/TutorialReact.md index 396d67b32b2d..18ab2ae93d1d 100644 --- a/website/versioned_docs/version-23.x/TutorialReact.md +++ b/website/versioned_docs/version-23.x/TutorialReact.md @@ -305,7 +305,7 @@ The code for this example is available at [examples/enzyme](https://github.com/f ### Custom transformers -If you need more advanced functionality, you can also build your own transformer. Instead of using babel-jest, here is an example of using babel: +If you need more advanced functionality, you can also build your own transformer. Instead of using `babel-jest`, here is an example of using `babel-core`: ```javascript // custom-transformer.js @@ -331,7 +331,7 @@ Don't forget to install the `babel-core` and `babel-preset-jest` packages for th To make this work with Jest you need to update your Jest configuration with this: `"transform": {"\\.js$": "path/to/custom-transformer.js"}`. -If you'd like to build a transformer with babel support, you can also use babel-jest to compose one and pass in your custom configuration options: +If you'd like to build a transformer with babel support, you can also use `babel-jest` to compose one and pass in your custom configuration options: ```javascript const babelJest = require('babel-jest'); diff --git a/website/versioned_docs/version-24.x/TutorialReact.md b/website/versioned_docs/version-24.x/TutorialReact.md index 0381c9707be3..96fc597808b7 100644 --- a/website/versioned_docs/version-24.x/TutorialReact.md +++ b/website/versioned_docs/version-24.x/TutorialReact.md @@ -305,7 +305,7 @@ The code for this example is available at [examples/enzyme](https://github.com/f ### Custom transformers -If you need more advanced functionality, you can also build your own transformer. Instead of using babel-jest, here is an example of using babel: +If you need more advanced functionality, you can also build your own transformer. Instead of using `babel-jest`, here is an example of using `@babel/core`: ```javascript // custom-transformer.js @@ -321,7 +321,7 @@ module.exports = { presets: [jestPreset], }); - return result ? result.code : src; + return result || src; }, }; ``` @@ -330,7 +330,7 @@ Don't forget to install the `@babel/core` and `babel-preset-jest` packages for t To make this work with Jest you need to update your Jest configuration with this: `"transform": {"\\.js$": "path/to/custom-transformer.js"}`. -If you'd like to build a transformer with babel support, you can also use babel-jest to compose one and pass in your custom configuration options: +If you'd like to build a transformer with babel support, you can also use `babel-jest` to compose one and pass in your custom configuration options: ```javascript const babelJest = require('babel-jest'); From 3ef146bbb09ed69431075511f4f5ee37f876483b Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Mon, 16 Nov 2020 11:16:59 +0100 Subject: [PATCH 02/16] chore(jest-transform): refactor transformer API to reduce number of arguments --- CHANGELOG.md | 1 + docs/CodeTransformation.md | 94 ++++++++++ docs/Configuration.md | 3 +- docs/TutorialReact.md | 2 + packages/babel-jest/src/index.ts | 33 ++-- packages/jest-repl/src/cli/repl.ts | 10 +- .../src/generateEmptyCoverage.ts | 8 +- .../jest-transform/src/ScriptTransformer.ts | 45 ++--- .../script_transformer.test.ts.snap | 168 ++++++++++-------- .../src/__tests__/script_transformer.test.ts | 12 +- packages/jest-transform/src/index.ts | 1 - packages/jest-transform/src/types.ts | 28 ++- website/sidebars.json | 3 +- 13 files changed, 271 insertions(+), 137 deletions(-) create mode 100644 docs/CodeTransformation.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 288fd495717f..65d8e2fee6a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ - `[jest-runtime]` [**BREAKING**] remove long-deprecated `jest.addMatchers`, `jest.resetModuleRegistry`, and `jest.runTimersToTime` ([#9853](https://github.com/facebook/jest/pull/9853)) - `[jest-transform]` Show enhanced `SyntaxError` message for all `SyntaxError`s ([#10749](https://github.com/facebook/jest/pull/10749)) - `[jest-transform]` [**BREAKING**] Refactor API to pass an options bag around rather than multiple boolean options ([#10753](https://github.com/facebook/jest/pull/10753)) +- `[jest-transform]` [**BREAKING**] Refactor API of transformers to pass an options bag rather than separate `config` and other options ### Chore & Maintenance diff --git a/docs/CodeTransformation.md b/docs/CodeTransformation.md new file mode 100644 index 000000000000..f32ee5051f14 --- /dev/null +++ b/docs/CodeTransformation.md @@ -0,0 +1,94 @@ +--- +id: code-transformation +title: Code Transformation +--- + +Jest runs the code in your project as JavaScript, but if you use some syntax not supported by Node.js out of the box (such as JSX, types from TypeScript, Vue templates etc.) then you'll need to transform that code into plain JavaScript, similar to what you would do when building for browsers. + +Jest supports this via the [`transform` configuration option](Configuration.md#transform-objectstring-pathtotransformer--pathtotransformer-object). + +A transformer is a module that provides a synchronous function for transforming source files. For example, if you wanted to be able to use a new language feature in your modules or tests that aren't yet supported by Node, you might plug in one of many compilers that compile a future version of JavaScript to a current one. + +Jest will cache the result of a transformation and attempt to invalidate that result based on a number of factors, such as the source of the file being transformed and changing configuration. + +## Defaults + +Jest ships with one transformer out of the box - `babel-jest`. It will automatically load your project's Babel configuration and transform any file matching the following RegEx: `/\.[jt]sx?$/` meaning any `.js`, `.jsx`, `.ts` and `.tsx` file. In addition, `babel-jest` will inject the Babel plugin necessary for mock hoisting talked about in [ES Module mocking](ManualMocks.md#using-with-es-module-imports). + +If you override the `transform` configuration option `babel-jest` will no longer be active, and you'll need to add it manually if you wish to use Babel. + +## Writing custom transformers + +You can write you own transformer. The API of a transformer is as follows: + +```ts +interface Transformer { + canInstrument?: boolean; + createTransformer?: (options?: unknown) => Transformer; + + getCacheKey?: ( + sourceText: string, + sourcePath: string, + options: TransformOptions, + ) => string; + + process: ( + sourceText: string, + sourcePath: string, + options: TransformOptions, + ) => TransformedSource; +} + +interface TransformOptions { + config: Config.ProjectConfig; + /** A stringified version of the configuration - useful in cache busting */ + configString: string; + instrument: boolean; + // names are copied from babel: https://babeljs.io/docs/en/options#caller + supportsDynamicImport: boolean; + supportsExportNamespaceFrom: boolean; + supportsStaticESM: boolean; + supportsTopLevelAwait: boolean; +} + +type TransformedSource = + | {code: string; map?: RawSourceMap | string | null} + | string; + +// Config.ProjectConfig can be seen in in code [here](https://github.com/facebook/jest/blob/v26.6.3/packages/jest-types/src/Config.ts#L323) +// RawSourceMap comes from [`source-map`](https://github.com/mozilla/source-map/blob/0.6.1/source-map.d.ts#L6-L12) +``` + +As can be seen, only `process` is mandatory to implement, although we highly recommend implementing `getCacheKey` as well, so we don't waste resources transpiling the same source file when we can read its previous result from disk. + +### Examples + +### TypeScript with type checking + +While `babel-jest` by default will transpile TypeScript files, Babel will not verify the types. If you want that you can use [`ts-jest`](https://github.com/kulshekhar/ts-jest). + +#### Transforming images to their path + +Importing images is a way to include them in your browser bundle, but they are not valid JavaScript. One way of handling it in Jest is to replace the imported value with its filename. + +```js +// fileTransformer.js +const path = require('path'); + +module.exports = { + process(src, filename, config, options) { + return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';'; + }, +}; +``` + +```js +// jest.config.js + +module.exports = { + transform: { + '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': + '/fileTransformer.js', + }, +}; +``` diff --git a/docs/Configuration.md b/docs/Configuration.md index 9a2605816768..1e4d164b125c 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -1263,8 +1263,7 @@ Examples of such compilers include: - [Babel](https://babeljs.io/) - [TypeScript](http://www.typescriptlang.org/) -- [async-to-gen](http://github.com/leebyron/async-to-gen#jest) -- To build your own please visit the [Custom Transformer](TutorialReact.md#custom-transformers) section +- To build your own please visit the [Custom Transformer](CodeTransformation.md#writing-custom-transformers) section You can pass configuration to a transformer like `{filePattern: ['path-to-transformer', {options}]}` For example, to configure babel-jest for non-default behavior, `{"\\.js$": ['babel-jest', {rootMode: "upward"}]}` diff --git a/docs/TutorialReact.md b/docs/TutorialReact.md index e07965cfc391..cca7c1ffd7bf 100644 --- a/docs/TutorialReact.md +++ b/docs/TutorialReact.md @@ -338,3 +338,5 @@ module.exports = babelJest.createTransformer({ presets: ['my-custom-preset'], }); ``` + +See [dedicated docs](CodeTransformation.md#writing-custom-transformers) for more details. diff --git a/packages/babel-jest/src/index.ts b/packages/babel-jest/src/index.ts index 07181095ccc7..fe19c1d9ebf1 100644 --- a/packages/babel-jest/src/index.ts +++ b/packages/babel-jest/src/index.ts @@ -40,10 +40,9 @@ interface BabelJestTransformOptions extends TransformOptions { sourceMaps: 'both'; } -const createTransformer = ( - userOptions?: TransformOptions | null, -): BabelJestTransformer => { - const inputOptions: TransformOptions = userOptions ?? {}; +const createTransformer = (userOptions?: unknown): BabelJestTransformer => { + const inputOptions: TransformOptions = + (userOptions as TransformOptions) ?? {}; const options: BabelJestTransformOptions = { ...inputOptions, caller: { @@ -102,13 +101,13 @@ const createTransformer = ( return { canInstrument: true, - getCacheKey(fileData, filename, configString, cacheKeyOptions) { - const {config, instrument, rootDir} = cacheKeyOptions; + getCacheKey(sourceText, sourcePath, transformOptions) { + const {config, configString, instrument} = transformOptions; const babelOptions = loadBabelConfig( config.cwd, - filename, - cacheKeyOptions, + sourcePath, + transformOptions, ); const configPath = [ babelOptions.config || '', @@ -120,9 +119,9 @@ const createTransformer = ( .update('\0', 'utf8') .update(JSON.stringify(babelOptions.options)) .update('\0', 'utf8') - .update(fileData) + .update(sourceText) .update('\0', 'utf8') - .update(path.relative(rootDir, filename)) + .update(path.relative(config.rootDir, sourcePath)) .update('\0', 'utf8') .update(configString) .update('\0', 'utf8') @@ -135,9 +134,13 @@ const createTransformer = ( .update(process.env.BABEL_ENV || '') .digest('hex'); }, - process(src, filename, config, transformOptions) { + process(sourceText, sourcePath, transformOptions) { const babelOptions = { - ...loadBabelConfig(config.cwd, filename, transformOptions).options, + ...loadBabelConfig( + transformOptions.config.cwd, + sourcePath, + transformOptions, + ).options, }; if (transformOptions?.instrument) { @@ -148,14 +151,14 @@ const createTransformer = ( babelIstanbulPlugin, { // files outside `cwd` will not be instrumented - cwd: config.rootDir, + cwd: transformOptions.config.rootDir, exclude: [], }, ], ]); } - const transformResult = babelTransform(src, babelOptions); + const transformResult = babelTransform(sourceText, babelOptions); if (transformResult) { const {code, map} = transformResult; @@ -164,7 +167,7 @@ const createTransformer = ( } } - return src; + return sourceText; }, }; }; diff --git a/packages/jest-repl/src/cli/repl.ts b/packages/jest-repl/src/cli/repl.ts index 831b2323489a..c5211f5354cd 100644 --- a/packages/jest-repl/src/cli/repl.ts +++ b/packages/jest-repl/src/cli/repl.ts @@ -28,7 +28,15 @@ const evalCommand: repl.REPLEval = ( const transformResult = transformer.process( cmd, jestGlobalConfig.replname || 'jest.js', - jestProjectConfig, + { + config: jestProjectConfig, + configString: JSON.stringify(jestProjectConfig), + instrument: false, + supportsDynamicImport: false, + supportsExportNamespaceFrom: false, + supportsStaticESM: false, + supportsTopLevelAwait: false, + }, ); cmd = typeof transformResult === 'string' diff --git a/packages/jest-reporters/src/generateEmptyCoverage.ts b/packages/jest-reporters/src/generateEmptyCoverage.ts index cc23ba8c0339..443293c9f782 100644 --- a/packages/jest-reporters/src/generateEmptyCoverage.ts +++ b/packages/jest-reporters/src/generateEmptyCoverage.ts @@ -70,7 +70,13 @@ export default function ( const {code} = new ScriptTransformer(config).transformSource( filename, source, - {instrument: true}, + { + instrument: true, + supportsDynamicImport: true, + supportsExportNamespaceFrom: true, + supportsStaticESM: true, + supportsTopLevelAwait: true, + }, ); // TODO: consider passing AST const extracted = readInitialCoverage(code); diff --git a/packages/jest-transform/src/ScriptTransformer.ts b/packages/jest-transform/src/ScriptTransformer.ts index f99310ab3911..61540e9d5a0e 100644 --- a/packages/jest-transform/src/ScriptTransformer.ts +++ b/packages/jest-transform/src/ScriptTransformer.ts @@ -28,7 +28,7 @@ import handlePotentialSyntaxError from './enhanceUnexpectedTokenMessage'; import shouldInstrument from './shouldInstrument'; import type { Options, - TransformOptions, + ReducedTransformOptions, TransformResult, TransformedSource, Transformer, @@ -93,7 +93,7 @@ export default class ScriptTransformer { private _getCacheKey( fileData: string, filename: Config.Path, - options: TransformOptions, + options: ReducedTransformOptions, ): string { const configString = this._cache.configString; const transformer = this._getTransformer(filename); @@ -101,14 +101,10 @@ export default class ScriptTransformer { if (transformer && typeof transformer.getCacheKey === 'function') { return createHash('md5') .update( - transformer.getCacheKey(fileData, filename, configString, { + transformer.getCacheKey(fileData, filename, { + ...options, config: this._config, - instrument: options.instrument, - rootDir: this._config.rootDir, - supportsDynamicImport: options.supportsDynamicImport, - supportsExportNamespaceFrom: options.supportsExportNamespaceFrom, - supportsStaticESM: options.supportsStaticESM, - supportsTopLevelAwait: options.supportsTopLevelAwait, + configString, }), ) .update(CACHE_VERSION) @@ -127,7 +123,7 @@ export default class ScriptTransformer { private _getFileCachePath( filename: Config.Path, content: string, - options: TransformOptions, + options: ReducedTransformOptions, ): Config.Path { const baseCacheDir = HasteMap.getCacheFilePath( this._config.cacheDirectory, @@ -206,7 +202,7 @@ export default class ScriptTransformer { filename: Config.Path, input: TransformedSource, canMapToInput: boolean, - options: TransformOptions, + options: ReducedTransformOptions, ): TransformedSource { const inputCode = typeof input === 'string' ? input : input.code; const inputMap = typeof input === 'string' ? null : input.map; @@ -256,7 +252,7 @@ export default class ScriptTransformer { transformSource( filepath: Config.Path, content: string, - options: TransformOptions, + options: ReducedTransformOptions, ): TransformResult { const filename = tryRealpath(filepath); const transform = this._getTransformer(filename); @@ -290,12 +286,11 @@ export default class ScriptTransformer { }; if (transform && shouldCallTransform) { - const processed = transform.process( - content, - filename, - this._config, - options, - ); + const processed = transform.process(content, filename, { + ...options, + config: this._config, + configString: this._cache.configString, + }); if (typeof processed === 'string') { transformed.code = processed; @@ -376,7 +371,7 @@ export default class ScriptTransformer { private _transformAndBuildScript( filename: Config.Path, options: Options, - transformOptions: TransformOptions, + transformOptions: ReducedTransformOptions, fileSource?: string, ): TransformResult { const {isCoreModule, isInternalModule} = options; @@ -471,17 +466,23 @@ export default class ScriptTransformer { requireAndTranspileModule( moduleName: string, callback?: (module: ModuleType) => void, - transformOptions?: TransformOptions, + transformOptions?: ReducedTransformOptions, ): ModuleType; requireAndTranspileModule( moduleName: string, callback?: (module: ModuleType) => Promise, - transformOptions?: TransformOptions, + transformOptions?: ReducedTransformOptions, ): Promise; requireAndTranspileModule( moduleName: string, callback?: (module: ModuleType) => void | Promise, - transformOptions: TransformOptions = {instrument: false}, + transformOptions: ReducedTransformOptions = { + instrument: false, + supportsDynamicImport: false, + supportsExportNamespaceFrom: false, + supportsStaticESM: false, + supportsTopLevelAwait: false, + }, ): ModuleType | Promise { // Load the transformer to avoid a cycle where we need to load a // transformer in order to transform it in the require hooks diff --git a/packages/jest-transform/src/__tests__/__snapshots__/script_transformer.test.ts.snap b/packages/jest-transform/src/__tests__/__snapshots__/script_transformer.test.ts.snap index c9021bd66fb9..d935cd744c3e 100644 --- a/packages/jest-transform/src/__tests__/__snapshots__/script_transformer.test.ts.snap +++ b/packages/jest-transform/src/__tests__/__snapshots__/script_transformer.test.ts.snap @@ -1,80 +1,98 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ScriptTransformer passes expected transform options to getCacheKey 1`] = ` -Object { - "config": Object { - "automock": false, - "cache": true, - "cacheDirectory": "/cache/", - "clearMocks": false, - "coveragePathIgnorePatterns": Array [], - "cwd": "/test_root_dir/", - "detectLeaks": false, - "detectOpenHandles": false, - "displayName": undefined, - "errorOnDeprecated": false, - "extraGlobals": Array [], - "filter": undefined, - "forceCoverageMatch": Array [], - "globalSetup": undefined, - "globalTeardown": undefined, - "globals": Object {}, - "haste": Object {}, - "injectGlobals": true, - "moduleDirectories": Array [], - "moduleFileExtensions": Array [ - "js", - ], - "moduleLoader": "/test_module_loader_path", - "moduleNameMapper": Array [], - "modulePathIgnorePatterns": Array [], - "modulePaths": Array [], - "name": "test", - "prettierPath": "prettier", - "resetMocks": false, - "resetModules": false, - "resolver": undefined, - "restoreMocks": false, - "rootDir": "/", - "roots": Array [], - "runner": "jest-runner", - "setupFiles": Array [], - "setupFilesAfterEnv": Array [], - "skipFilter": false, - "skipNodeResolution": false, - "slowTestThreshold": 5, - "snapshotResolver": undefined, - "snapshotSerializers": Array [], - "testEnvironment": "node", - "testEnvironmentOptions": Object {}, - "testLocationInResults": false, - "testMatch": Array [], - "testPathIgnorePatterns": Array [], - "testRegex": Array [ - "\\\\.test\\\\.js$", - ], - "testRunner": "jest-jasmine2", - "testURL": "http://localhost", - "timers": "real", - "transform": Array [ - Array [ - "\\\\.js$", - "test_preprocessor", - Object {}, - ], - ], - "transformIgnorePatterns": Array [ - "/node_modules/", +[MockFunction] { + "calls": Array [ + Array [ + "module.exports = \\"banana\\";", + "/fruits/banana.js", + Object { + "collectCoverage": true, + "collectCoverageFrom": Array [], + "collectCoverageOnlyFrom": undefined, + "config": Object { + "automock": false, + "cache": true, + "cacheDirectory": "/cache/", + "clearMocks": false, + "coveragePathIgnorePatterns": Array [], + "cwd": "/test_root_dir/", + "detectLeaks": false, + "detectOpenHandles": false, + "displayName": undefined, + "errorOnDeprecated": false, + "extraGlobals": Array [], + "filter": undefined, + "forceCoverageMatch": Array [], + "globalSetup": undefined, + "globalTeardown": undefined, + "globals": Object {}, + "haste": Object {}, + "injectGlobals": true, + "moduleDirectories": Array [], + "moduleFileExtensions": Array [ + "js", + ], + "moduleLoader": "/test_module_loader_path", + "moduleNameMapper": Array [], + "modulePathIgnorePatterns": Array [], + "modulePaths": Array [], + "name": "test", + "prettierPath": "prettier", + "resetMocks": false, + "resetModules": false, + "resolver": undefined, + "restoreMocks": false, + "rootDir": "/", + "roots": Array [], + "runner": "jest-runner", + "setupFiles": Array [], + "setupFilesAfterEnv": Array [], + "skipFilter": false, + "skipNodeResolution": false, + "slowTestThreshold": 5, + "snapshotResolver": undefined, + "snapshotSerializers": Array [], + "testEnvironment": "node", + "testEnvironmentOptions": Object {}, + "testLocationInResults": false, + "testMatch": Array [], + "testPathIgnorePatterns": Array [], + "testRegex": Array [ + "\\\\.test\\\\.js$", + ], + "testRunner": "jest-jasmine2", + "testURL": "http://localhost", + "timers": "real", + "transform": Array [ + Array [ + "\\\\.js$", + "test_preprocessor", + Object {}, + ], + ], + "transformIgnorePatterns": Array [ + "/node_modules/", + ], + "unmockedModulePathPatterns": undefined, + "watchPathIgnorePatterns": Array [], + }, + "configString": "{\\"automock\\":false,\\"cache\\":true,\\"cacheDirectory\\":\\"/cache/\\",\\"clearMocks\\":false,\\"coveragePathIgnorePatterns\\":[],\\"cwd\\":\\"/test_root_dir/\\",\\"detectLeaks\\":false,\\"detectOpenHandles\\":false,\\"errorOnDeprecated\\":false,\\"extraGlobals\\":[],\\"forceCoverageMatch\\":[],\\"globals\\":{},\\"haste\\":{},\\"injectGlobals\\":true,\\"moduleDirectories\\":[],\\"moduleFileExtensions\\":[\\"js\\"],\\"moduleLoader\\":\\"/test_module_loader_path\\",\\"moduleNameMapper\\":[],\\"modulePathIgnorePatterns\\":[],\\"modulePaths\\":[],\\"name\\":\\"test\\",\\"prettierPath\\":\\"prettier\\",\\"resetMocks\\":false,\\"resetModules\\":false,\\"restoreMocks\\":false,\\"rootDir\\":\\"/\\",\\"roots\\":[],\\"runner\\":\\"jest-runner\\",\\"setupFiles\\":[],\\"setupFilesAfterEnv\\":[],\\"skipFilter\\":false,\\"skipNodeResolution\\":false,\\"slowTestThreshold\\":5,\\"snapshotSerializers\\":[],\\"testEnvironment\\":\\"node\\",\\"testEnvironmentOptions\\":{},\\"testLocationInResults\\":false,\\"testMatch\\":[],\\"testPathIgnorePatterns\\":[],\\"testRegex\\":[\\"\\\\\\\\.test\\\\\\\\.js$\\"],\\"testRunner\\":\\"jest-jasmine2\\",\\"testURL\\":\\"http://localhost\\",\\"timers\\":\\"real\\",\\"transform\\":[[\\"\\\\\\\\.js$\\",\\"test_preprocessor\\",{}]],\\"transformIgnorePatterns\\":[\\"/node_modules/\\"],\\"watchPathIgnorePatterns\\":[]}", + "coverageProvider": "babel", + "instrument": true, + "supportsDynamicImport": false, + "supportsExportNamespaceFrom": false, + "supportsStaticESM": false, + "supportsTopLevelAwait": false, + }, ], - "unmockedModulePathPatterns": undefined, - "watchPathIgnorePatterns": Array [], - }, - "instrument": true, - "rootDir": "/", - "supportsDynamicImport": undefined, - "supportsExportNamespaceFrom": undefined, - "supportsStaticESM": undefined, - "supportsTopLevelAwait": undefined, + ], + "results": Array [ + Object { + "type": "return", + "value": "ab", + }, + ], } `; @@ -234,7 +252,7 @@ exports[`ScriptTransformer uses multiple preprocessors 1`] = ` const TRANSFORMED = { filename: '/fruits/banana.js', script: 'module.exports = "banana";', - config: '{"automock":false,"cache":true,"cacheDirectory":"/cache/","clearMocks":false,"coveragePathIgnorePatterns":[],"cwd":"/test_root_dir/","detectLeaks":false,"detectOpenHandles":false,"errorOnDeprecated":false,"extraGlobals":[],"forceCoverageMatch":[],"globals":{},"haste":{},"injectGlobals":true,"moduleDirectories":[],"moduleFileExtensions":["js"],"moduleLoader":"/test_module_loader_path","moduleNameMapper":[],"modulePathIgnorePatterns":[],"modulePaths":[],"name":"test","prettierPath":"prettier","resetMocks":false,"resetModules":false,"restoreMocks":false,"rootDir":"/","roots":[],"runner":"jest-runner","setupFiles":[],"setupFilesAfterEnv":[],"skipFilter":false,"skipNodeResolution":false,"slowTestThreshold":5,"snapshotSerializers":[],"testEnvironment":"node","testEnvironmentOptions":{},"testLocationInResults":false,"testMatch":[],"testPathIgnorePatterns":[],"testRegex":["\\\\.test\\\\.js$"],"testRunner":"jest-jasmine2","testURL":"http://localhost","timers":"real","transform":[["\\\\.js$","test_preprocessor",{}],["\\\\.css$","css-preprocessor",{}]],"transformIgnorePatterns":["/node_modules/"],"watchPathIgnorePatterns":[]}', + config: '{"collectCoverage":false,"collectCoverageFrom":[],"coverageProvider":"babel","supportsDynamicImport":false,"supportsExportNamespaceFrom":false,"supportsStaticESM":false,"supportsTopLevelAwait":false,"instrument":false,"config":{"automock":false,"cache":true,"cacheDirectory":"/cache/","clearMocks":false,"coveragePathIgnorePatterns":[],"cwd":"/test_root_dir/","detectLeaks":false,"detectOpenHandles":false,"errorOnDeprecated":false,"extraGlobals":[],"forceCoverageMatch":[],"globals":{},"haste":{},"injectGlobals":true,"moduleDirectories":[],"moduleFileExtensions":["js"],"moduleLoader":"/test_module_loader_path","moduleNameMapper":[],"modulePathIgnorePatterns":[],"modulePaths":[],"name":"test","prettierPath":"prettier","resetMocks":false,"resetModules":false,"restoreMocks":false,"rootDir":"/","roots":[],"runner":"jest-runner","setupFiles":[],"setupFilesAfterEnv":[],"skipFilter":false,"skipNodeResolution":false,"slowTestThreshold":5,"snapshotSerializers":[],"testEnvironment":"node","testEnvironmentOptions":{},"testLocationInResults":false,"testMatch":[],"testPathIgnorePatterns":[],"testRegex":["\\\\.test\\\\.js$"],"testRunner":"jest-jasmine2","testURL":"http://localhost","timers":"real","transform":[["\\\\.js$","test_preprocessor",{}],["\\\\.css$","css-preprocessor",{}]],"transformIgnorePatterns":["/node_modules/"],"watchPathIgnorePatterns":[]},"configString":"{\\"automock\\":false,\\"cache\\":true,\\"cacheDirectory\\":\\"/cache/\\",\\"clearMocks\\":false,\\"coveragePathIgnorePatterns\\":[],\\"cwd\\":\\"/test_root_dir/\\",\\"detectLeaks\\":false,\\"detectOpenHandles\\":false,\\"errorOnDeprecated\\":false,\\"extraGlobals\\":[],\\"forceCoverageMatch\\":[],\\"globals\\":{},\\"haste\\":{},\\"injectGlobals\\":true,\\"moduleDirectories\\":[],\\"moduleFileExtensions\\":[\\"js\\"],\\"moduleLoader\\":\\"/test_module_loader_path\\",\\"moduleNameMapper\\":[],\\"modulePathIgnorePatterns\\":[],\\"modulePaths\\":[],\\"name\\":\\"test\\",\\"prettierPath\\":\\"prettier\\",\\"resetMocks\\":false,\\"resetModules\\":false,\\"restoreMocks\\":false,\\"rootDir\\":\\"/\\",\\"roots\\":[],\\"runner\\":\\"jest-runner\\",\\"setupFiles\\":[],\\"setupFilesAfterEnv\\":[],\\"skipFilter\\":false,\\"skipNodeResolution\\":false,\\"slowTestThreshold\\":5,\\"snapshotSerializers\\":[],\\"testEnvironment\\":\\"node\\",\\"testEnvironmentOptions\\":{},\\"testLocationInResults\\":false,\\"testMatch\\":[],\\"testPathIgnorePatterns\\":[],\\"testRegex\\":[\\"\\\\\\\\.test\\\\\\\\.js$\\"],\\"testRunner\\":\\"jest-jasmine2\\",\\"testURL\\":\\"http://localhost\\",\\"timers\\":\\"real\\",\\"transform\\":[[\\"\\\\\\\\.js$\\",\\"test_preprocessor\\",{}],[\\"\\\\\\\\.css$\\",\\"css-preprocessor\\",{}]],\\"transformIgnorePatterns\\":[\\"/node_modules/\\"],\\"watchPathIgnorePatterns\\":[]}"}', }; `; @@ -251,7 +269,7 @@ exports[`ScriptTransformer uses the supplied preprocessor 1`] = ` const TRANSFORMED = { filename: '/fruits/banana.js', script: 'module.exports = "banana";', - config: '{"automock":false,"cache":true,"cacheDirectory":"/cache/","clearMocks":false,"coveragePathIgnorePatterns":[],"cwd":"/test_root_dir/","detectLeaks":false,"detectOpenHandles":false,"errorOnDeprecated":false,"extraGlobals":[],"forceCoverageMatch":[],"globals":{},"haste":{},"injectGlobals":true,"moduleDirectories":[],"moduleFileExtensions":["js"],"moduleLoader":"/test_module_loader_path","moduleNameMapper":[],"modulePathIgnorePatterns":[],"modulePaths":[],"name":"test","prettierPath":"prettier","resetMocks":false,"resetModules":false,"restoreMocks":false,"rootDir":"/","roots":[],"runner":"jest-runner","setupFiles":[],"setupFilesAfterEnv":[],"skipFilter":false,"skipNodeResolution":false,"slowTestThreshold":5,"snapshotSerializers":[],"testEnvironment":"node","testEnvironmentOptions":{},"testLocationInResults":false,"testMatch":[],"testPathIgnorePatterns":[],"testRegex":["\\\\.test\\\\.js$"],"testRunner":"jest-jasmine2","testURL":"http://localhost","timers":"real","transform":[["\\\\.js$","test_preprocessor",{}]],"transformIgnorePatterns":["/node_modules/"],"watchPathIgnorePatterns":[]}', + config: '{"collectCoverage":false,"collectCoverageFrom":[],"coverageProvider":"babel","supportsDynamicImport":false,"supportsExportNamespaceFrom":false,"supportsStaticESM":false,"supportsTopLevelAwait":false,"instrument":false,"config":{"automock":false,"cache":true,"cacheDirectory":"/cache/","clearMocks":false,"coveragePathIgnorePatterns":[],"cwd":"/test_root_dir/","detectLeaks":false,"detectOpenHandles":false,"errorOnDeprecated":false,"extraGlobals":[],"forceCoverageMatch":[],"globals":{},"haste":{},"injectGlobals":true,"moduleDirectories":[],"moduleFileExtensions":["js"],"moduleLoader":"/test_module_loader_path","moduleNameMapper":[],"modulePathIgnorePatterns":[],"modulePaths":[],"name":"test","prettierPath":"prettier","resetMocks":false,"resetModules":false,"restoreMocks":false,"rootDir":"/","roots":[],"runner":"jest-runner","setupFiles":[],"setupFilesAfterEnv":[],"skipFilter":false,"skipNodeResolution":false,"slowTestThreshold":5,"snapshotSerializers":[],"testEnvironment":"node","testEnvironmentOptions":{},"testLocationInResults":false,"testMatch":[],"testPathIgnorePatterns":[],"testRegex":["\\\\.test\\\\.js$"],"testRunner":"jest-jasmine2","testURL":"http://localhost","timers":"real","transform":[["\\\\.js$","test_preprocessor",{}]],"transformIgnorePatterns":["/node_modules/"],"watchPathIgnorePatterns":[]},"configString":"{\\"automock\\":false,\\"cache\\":true,\\"cacheDirectory\\":\\"/cache/\\",\\"clearMocks\\":false,\\"coveragePathIgnorePatterns\\":[],\\"cwd\\":\\"/test_root_dir/\\",\\"detectLeaks\\":false,\\"detectOpenHandles\\":false,\\"errorOnDeprecated\\":false,\\"extraGlobals\\":[],\\"forceCoverageMatch\\":[],\\"globals\\":{},\\"haste\\":{},\\"injectGlobals\\":true,\\"moduleDirectories\\":[],\\"moduleFileExtensions\\":[\\"js\\"],\\"moduleLoader\\":\\"/test_module_loader_path\\",\\"moduleNameMapper\\":[],\\"modulePathIgnorePatterns\\":[],\\"modulePaths\\":[],\\"name\\":\\"test\\",\\"prettierPath\\":\\"prettier\\",\\"resetMocks\\":false,\\"resetModules\\":false,\\"restoreMocks\\":false,\\"rootDir\\":\\"/\\",\\"roots\\":[],\\"runner\\":\\"jest-runner\\",\\"setupFiles\\":[],\\"setupFilesAfterEnv\\":[],\\"skipFilter\\":false,\\"skipNodeResolution\\":false,\\"slowTestThreshold\\":5,\\"snapshotSerializers\\":[],\\"testEnvironment\\":\\"node\\",\\"testEnvironmentOptions\\":{},\\"testLocationInResults\\":false,\\"testMatch\\":[],\\"testPathIgnorePatterns\\":[],\\"testRegex\\":[\\"\\\\\\\\.test\\\\\\\\.js$\\"],\\"testRunner\\":\\"jest-jasmine2\\",\\"testURL\\":\\"http://localhost\\",\\"timers\\":\\"real\\",\\"transform\\":[[\\"\\\\\\\\.js$\\",\\"test_preprocessor\\",{}]],\\"transformIgnorePatterns\\":[\\"/node_modules/\\"],\\"watchPathIgnorePatterns\\":[]}"}', }; `; diff --git a/packages/jest-transform/src/__tests__/script_transformer.test.ts b/packages/jest-transform/src/__tests__/script_transformer.test.ts index 02ea4ed4351c..f65218a175d4 100644 --- a/packages/jest-transform/src/__tests__/script_transformer.test.ts +++ b/packages/jest-transform/src/__tests__/script_transformer.test.ts @@ -9,7 +9,7 @@ import {wrap} from 'jest-snapshot-serializer-raw'; import {makeGlobalConfig, makeProjectConfig} from '@jest/test-utils'; import type {Config} from '@jest/types'; -import type {ShouldInstrumentOptions, Transformer} from '../types'; +import type {Options, ShouldInstrumentOptions, Transformer} from '../types'; jest .mock('graceful-fs', () => @@ -38,7 +38,7 @@ jest }, })) .mock('jest-haste-map', () => ({ - getCacheFilePath: (cacheDir, baseDir) => cacheDir + baseDir, + getCacheFilePath: (cacheDir: string, baseDir: string) => cacheDir + baseDir, })) .mock('jest-util', () => ({ ...jest.requireActual('jest-util'), @@ -663,7 +663,7 @@ describe('ScriptTransformer', () => { ); const {getCacheKey} = require('test_preprocessor'); - expect(getCacheKey.mock.calls[0][3]).toMatchSnapshot(); + expect(getCacheKey).toMatchSnapshot(); }); it('creates transformer with config', () => { @@ -793,7 +793,7 @@ describe('ScriptTransformer', () => { function getCoverageOptions( overrides: Partial = {}, -): ShouldInstrumentOptions { +): Options { const globalConfig = makeGlobalConfig(overrides); return { @@ -801,6 +801,10 @@ function getCoverageOptions( collectCoverageFrom: globalConfig.collectCoverageFrom, collectCoverageOnlyFrom: globalConfig.collectCoverageOnlyFrom, coverageProvider: globalConfig.coverageProvider, + supportsDynamicImport: false, + supportsExportNamespaceFrom: false, + supportsStaticESM: false, + supportsTopLevelAwait: false, }; } diff --git a/packages/jest-transform/src/index.ts b/packages/jest-transform/src/index.ts index efcdbfc65ae4..3695baded523 100644 --- a/packages/jest-transform/src/index.ts +++ b/packages/jest-transform/src/index.ts @@ -11,7 +11,6 @@ export { } from './ScriptTransformer'; export {default as shouldInstrument} from './shouldInstrument'; export type { - CacheKeyOptions, CallerTransformOptions, Transformer, ShouldInstrumentOptions, diff --git a/packages/jest-transform/src/types.ts b/packages/jest-transform/src/types.ts index 979c16c8ad42..18231f09eeb0 100644 --- a/packages/jest-transform/src/types.ts +++ b/packages/jest-transform/src/types.ts @@ -40,37 +40,35 @@ export type TransformResult = TransformTypes.TransformResult; export interface CallerTransformOptions { // names are copied from babel: https://babeljs.io/docs/en/options#caller - supportsDynamicImport?: boolean; - supportsExportNamespaceFrom?: boolean; - supportsStaticESM?: boolean; - supportsTopLevelAwait?: boolean; + supportsDynamicImport: boolean; + supportsExportNamespaceFrom: boolean; + supportsStaticESM: boolean; + supportsTopLevelAwait: boolean; } -export interface TransformOptions extends CallerTransformOptions { +export interface ReducedTransformOptions extends CallerTransformOptions { instrument: boolean; } -// TODO: For Jest 26 we should combine these into one options shape -export interface CacheKeyOptions extends TransformOptions { +export interface TransformOptions extends ReducedTransformOptions { config: Config.ProjectConfig; - rootDir: string; + /** A stringified version of the configuration - useful in cache busting */ + configString: string; } export interface Transformer { canInstrument?: boolean; - createTransformer?: (options?: any) => Transformer; + createTransformer?: (options?: unknown) => Transformer; getCacheKey?: ( - fileData: string, - filePath: Config.Path, - configStr: string, - options: CacheKeyOptions, + sourceText: string, + sourcePath: Config.Path, + options: TransformOptions, ) => string; process: ( sourceText: string, sourcePath: Config.Path, - config: Config.ProjectConfig, - options?: TransformOptions, + options: TransformOptions, ) => TransformedSource; } diff --git a/website/sidebars.json b/website/sidebars.json index 1cb762c601d6..be6ad0a82e9a 100644 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -40,7 +40,8 @@ "jest-object", "configuration", "cli", - "environment-variables" + "environment-variables", + "code-transformation" ] } } From 0f3b59da1e404ce2bba2db0fab3bb50ca7afa05e Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Mon, 16 Nov 2020 11:56:12 +0100 Subject: [PATCH 03/16] mention esm for transformers --- docs/CodeTransformation.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/CodeTransformation.md b/docs/CodeTransformation.md index f32ee5051f14..2a0ae9f21d6a 100644 --- a/docs/CodeTransformation.md +++ b/docs/CodeTransformation.md @@ -61,6 +61,8 @@ type TransformedSource = As can be seen, only `process` is mandatory to implement, although we highly recommend implementing `getCacheKey` as well, so we don't waste resources transpiling the same source file when we can read its previous result from disk. +Note that [ECMAScript module](ECMAScriptModules.md) support is indicated by the passed in `supports*` options. Specifically `supportsDynamicImport: true` means the transformer can return `import()` expressions, which is supported by both ESM and CJS. If `supportsStaticESM: true` it means top level `import` statements are supported and the code will be interpreted as ESM and not CJS. See [Node's docs](https://nodejs.org/api/esm.html#esm_differences_between_es_modules_and_commonjs) for details on the differences. + ### Examples ### TypeScript with type checking From e2f08810c88ee32ed78fc126cd2d999ecc9b8467 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Mon, 16 Nov 2020 11:59:37 +0100 Subject: [PATCH 04/16] instrument docs --- docs/CodeTransformation.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/CodeTransformation.md b/docs/CodeTransformation.md index 2a0ae9f21d6a..6fc1dc678759 100644 --- a/docs/CodeTransformation.md +++ b/docs/CodeTransformation.md @@ -23,6 +23,12 @@ You can write you own transformer. The API of a transformer is as follows: ```ts interface Transformer { + /** + * Indicates if the transformer is capabale of instrumenting the code for code coverage. + * + * If V8 coverage is _not_ active, and this is `true`, Jest will assume the code is instrumented. + * If this is `false` Jets will instrument the code returned by this transformer using Babel. + */ canInstrument?: boolean; createTransformer?: (options?: unknown) => Transformer; From 26e213f20aaa21fa9df7666c1ea9789d3bd02591 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Mon, 16 Nov 2020 11:59:41 +0100 Subject: [PATCH 05/16] cache key docs --- docs/CodeTransformation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/CodeTransformation.md b/docs/CodeTransformation.md index 6fc1dc678759..d29633295587 100644 --- a/docs/CodeTransformation.md +++ b/docs/CodeTransformation.md @@ -27,7 +27,7 @@ interface Transformer { * Indicates if the transformer is capabale of instrumenting the code for code coverage. * * If V8 coverage is _not_ active, and this is `true`, Jest will assume the code is instrumented. - * If this is `false` Jets will instrument the code returned by this transformer using Babel. + * If this is `false` Jets will instrument the code returned by this transformer using Babel. */ canInstrument?: boolean; createTransformer?: (options?: unknown) => Transformer; @@ -65,7 +65,7 @@ type TransformedSource = // RawSourceMap comes from [`source-map`](https://github.com/mozilla/source-map/blob/0.6.1/source-map.d.ts#L6-L12) ``` -As can be seen, only `process` is mandatory to implement, although we highly recommend implementing `getCacheKey` as well, so we don't waste resources transpiling the same source file when we can read its previous result from disk. +As can be seen, only `process` is mandatory to implement, although we highly recommend implementing `getCacheKey` as well, so we don't waste resources transpiling the same source file when we can read its previous result from disk. You can use [`@jest/create-cache-key-function`](https://www.npmjs.com/package/@jest/create-cache-key-function) to help implement it. Note that [ECMAScript module](ECMAScriptModules.md) support is indicated by the passed in `supports*` options. Specifically `supportsDynamicImport: true` means the transformer can return `import()` expressions, which is supported by both ESM and CJS. If `supportsStaticESM: true` it means top level `import` statements are supported and the code will be interpreted as ESM and not CJS. See [Node's docs](https://nodejs.org/api/esm.html#esm_differences_between_es_modules_and_commonjs) for details on the differences. From 9da4881e3c6f044c4405dc352196c4b7c5746453 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Mon, 16 Nov 2020 12:03:54 +0100 Subject: [PATCH 06/16] typo --- docs/CodeTransformation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/CodeTransformation.md b/docs/CodeTransformation.md index d29633295587..014fbfe992e9 100644 --- a/docs/CodeTransformation.md +++ b/docs/CodeTransformation.md @@ -27,7 +27,7 @@ interface Transformer { * Indicates if the transformer is capabale of instrumenting the code for code coverage. * * If V8 coverage is _not_ active, and this is `true`, Jest will assume the code is instrumented. - * If this is `false` Jets will instrument the code returned by this transformer using Babel. + * If V8 coverage is _not_ active, and this is `false`. Jest will instrument the code returned by this transformer using Babel. */ canInstrument?: boolean; createTransformer?: (options?: unknown) => Transformer; From 57912eb64b454af75f9886294ef8b2b6cc9539e5 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Mon, 16 Nov 2020 12:35:17 +0100 Subject: [PATCH 07/16] prettier --- docs/CodeTransformation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/CodeTransformation.md b/docs/CodeTransformation.md index 014fbfe992e9..db08d17200df 100644 --- a/docs/CodeTransformation.md +++ b/docs/CodeTransformation.md @@ -27,7 +27,7 @@ interface Transformer { * Indicates if the transformer is capabale of instrumenting the code for code coverage. * * If V8 coverage is _not_ active, and this is `true`, Jest will assume the code is instrumented. - * If V8 coverage is _not_ active, and this is `false`. Jest will instrument the code returned by this transformer using Babel. + * If V8 coverage is _not_ active, and this is `false`. Jest will instrument the code returned by this transformer using Babel. */ canInstrument?: boolean; createTransformer?: (options?: unknown) => Transformer; From cc52a5b2b6a74efdc55159fcfae811393cbf463a Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Mon, 16 Nov 2020 12:44:54 +0100 Subject: [PATCH 08/16] tests --- .../preprocessor.js | 6 +++--- e2e/snapshot-serializers/transformer.js | 2 +- .../preprocessor.js | 2 +- .../multiple-transformers/cssPreprocessor.js | 2 +- .../multiple-transformers/filePreprocessor.js | 2 +- packages/babel-jest/src/__tests__/index.ts | 18 +++++++++++------- 6 files changed, 18 insertions(+), 14 deletions(-) diff --git a/e2e/coverage-transform-instrumented/preprocessor.js b/e2e/coverage-transform-instrumented/preprocessor.js index 4d4b550fb020..b2792096ce55 100644 --- a/e2e/coverage-transform-instrumented/preprocessor.js +++ b/e2e/coverage-transform-instrumented/preprocessor.js @@ -18,16 +18,16 @@ const options = { module.exports = { canInstrument: true, - process(src, filename, config, transformOptions) { + process(src, filename, transformOptions) { options.filename = filename; - if (transformOptions && transformOptions.instrument) { + if (transformOptions.instrument) { options.auxiliaryCommentBefore = ' istanbul ignore next '; options.plugins = [ [ babelIstanbulPlugin, { - cwd: config.rootDir, + cwd: transformOptions.config.rootDir, exclude: [], }, ], diff --git a/e2e/snapshot-serializers/transformer.js b/e2e/snapshot-serializers/transformer.js index 834dd7b59e18..5606ac36381f 100644 --- a/e2e/snapshot-serializers/transformer.js +++ b/e2e/snapshot-serializers/transformer.js @@ -8,7 +8,7 @@ 'use strict'; module.exports = { - process(src, filename, config, options) { + process(src, filename) { if (/bar.js$/.test(filename)) { return `${src};\nmodule.exports = createPlugin('bar');`; } diff --git a/e2e/transform/custom-instrumenting-preprocessor/preprocessor.js b/e2e/transform/custom-instrumenting-preprocessor/preprocessor.js index 8b82efc7783c..b440dd977ed6 100644 --- a/e2e/transform/custom-instrumenting-preprocessor/preprocessor.js +++ b/e2e/transform/custom-instrumenting-preprocessor/preprocessor.js @@ -7,7 +7,7 @@ module.exports = { canInstrument: true, - process(src, filename, config, options) { + process(src, filename, options) { src = `${src};\nglobal.__PREPROCESSED__ = true;`; if (options.instrument) { diff --git a/e2e/transform/multiple-transformers/cssPreprocessor.js b/e2e/transform/multiple-transformers/cssPreprocessor.js index 1e1ddf67f070..5ca52dd30e54 100644 --- a/e2e/transform/multiple-transformers/cssPreprocessor.js +++ b/e2e/transform/multiple-transformers/cssPreprocessor.js @@ -6,7 +6,7 @@ */ module.exports = { - process(src, filename, config, options) { + process() { return ` module.exports = { root: 'App-root', diff --git a/e2e/transform/multiple-transformers/filePreprocessor.js b/e2e/transform/multiple-transformers/filePreprocessor.js index 66c2a52e0f5a..c49b641e62d2 100644 --- a/e2e/transform/multiple-transformers/filePreprocessor.js +++ b/e2e/transform/multiple-transformers/filePreprocessor.js @@ -8,7 +8,7 @@ const path = require('path'); module.exports = { - process(src, filename, config, options) { + process(src, filename) { return ` module.exports = '${path.basename(filename)}'; `; diff --git a/packages/babel-jest/src/__tests__/index.ts b/packages/babel-jest/src/__tests__/index.ts index 4ee2960bd65e..445a50d8dec8 100644 --- a/packages/babel-jest/src/__tests__/index.ts +++ b/packages/babel-jest/src/__tests__/index.ts @@ -35,11 +35,11 @@ beforeEach(() => { }); test('Returns source string with inline maps when no transformOptions is passed', () => { - const result = babelJest.process( - sourceString, - 'dummy_path.js', - makeProjectConfig(), - ) as any; + const result = babelJest.process(sourceString, 'dummy_path.js', { + config: makeProjectConfig(), + configString: JSON.stringify(makeProjectConfig()), + instrument: false, + }) as any; expect(typeof result).toBe('object'); expect(result.code).toBeDefined(); expect(result.map).toBeDefined(); @@ -86,7 +86,9 @@ describe('caller option correctly merges from defaults and options', () => { }, ], ])('%j -> %j', (input, output) => { - babelJest.process(sourceString, 'dummy_path.js', makeProjectConfig(), { + babelJest.process(sourceString, 'dummy_path.js', { + config: makeProjectConfig(), + configString: JSON.stringify(makeProjectConfig()), instrument: false, ...input, }); @@ -107,7 +109,9 @@ describe('caller option correctly merges from defaults and options', () => { test('can pass null to createTransformer', () => { const transformer = babelJest.createTransformer(null); - transformer.process(sourceString, 'dummy_path.js', makeProjectConfig(), { + transformer.process(sourceString, 'dummy_path.js', { + config: makeProjectConfig(), + configString: JSON.stringify(makeProjectConfig()), instrument: false, }); From babb77bc1c01ade4cc3903a5e344eee79ea490b6 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Mon, 16 Nov 2020 13:10:08 +0100 Subject: [PATCH 09/16] chore: update jest-create-cache-key-function --- .../jest-create-cache-key-function/src/index.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/jest-create-cache-key-function/src/index.ts b/packages/jest-create-cache-key-function/src/index.ts index e606e64f14e8..7d9afdaa97bb 100644 --- a/packages/jest-create-cache-key-function/src/index.ts +++ b/packages/jest-create-cache-key-function/src/index.ts @@ -12,15 +12,16 @@ import {readFileSync} from 'fs'; import {relative} from 'path'; import type {Config} from '@jest/types'; +// Should mirror `import('@jest/transform').TransformOptions` type CacheKeyOptions = { config: Config.ProjectConfig; + configString: string; instrument: boolean; }; type GetCacheKeyFunction = ( - fileData: string, - filePath: Config.Path, - configStr: string, + sourceText: string, + sourcePath: Config.Path, options: CacheKeyOptions, ) => string; @@ -39,14 +40,14 @@ function getGlobalCacheKey(files: Array, values: Array) { } function getCacheKeyFunction(globalCacheKey: string): GetCacheKeyFunction { - return (src, file, _configString, options) => { + return (sourceText, sourcePath, options) => { const {config, instrument} = options; return createHash('md5') .update(globalCacheKey) .update('\0', 'utf8') - .update(src) + .update(sourceText) .update('\0', 'utf8') - .update(config.rootDir ? relative(config.rootDir, file) : '') + .update(config.rootDir ? relative(config.rootDir, sourcePath) : '') .update('\0', 'utf8') .update(instrument ? 'instrument' : '') .digest('hex'); From afca168d290d3ffb4b817517a1e04c0f3436eed9 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Mon, 16 Nov 2020 13:21:33 +0100 Subject: [PATCH 10/16] patch fbjs-scripts --- package.json | 3 ++- patches/fbjs-scripts.patch | 13 +++++++++++++ yarn.lock | 20 +++++++++++++++++++- 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 patches/fbjs-scripts.patch diff --git a/package.json b/package.json index f0f6218ea3ab..3bea730d2ab7 100644 --- a/package.json +++ b/package.json @@ -144,6 +144,7 @@ }, "resolutions": { "@types/jest/jest-diff": "^25.1.0", - "@types/jest/pretty-format": "^25.1.0" + "@types/jest/pretty-format": "^25.1.0", + "fbjs-scripts": "patch:fbjs-scripts@^1.1.0#./patches/fbjs-scripts.patch" } } diff --git a/patches/fbjs-scripts.patch b/patches/fbjs-scripts.patch new file mode 100644 index 000000000000..d3da4a8c1950 --- /dev/null +++ b/patches/fbjs-scripts.patch @@ -0,0 +1,13 @@ +diff --git a/jest/createCacheKeyFunction.js b/jest/createCacheKeyFunction.js +index 890918962daebefedee59a48cd02906447f9de5b..2e73413d30a9964891608eb9ec02568b981b5ba0 100644 +--- a/jest/createCacheKeyFunction.js ++++ b/jest/createCacheKeyFunction.js +@@ -32,6 +32,8 @@ function getGlobalCacheKey(files, values) { + + function getCacheKeyFunction(globalCacheKey) { + return (src, file, configString, options) => { ++ // Jest 27 passes a single options bag which contains `configString` rather than as a separate argument ++ options = options || configString; + const {instrument, config} = options; + const rootDir = config && config.rootDir; + diff --git a/yarn.lock b/yarn.lock index 2c6bfbee05de..f06703de94a8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8998,7 +8998,7 @@ __metadata: languageName: node linkType: hard -"fbjs-scripts@npm:^1.1.0": +fbjs-scripts@^1.1.0: version: 1.2.0 resolution: "fbjs-scripts@npm:1.2.0" dependencies: @@ -9016,6 +9016,24 @@ __metadata: languageName: node linkType: hard +"fbjs-scripts@patch:fbjs-scripts@^1.1.0#./patches/fbjs-scripts.patch::locator=%40jest%2Fmonorepo%40workspace%3A.": + version: 1.2.0 + resolution: "fbjs-scripts@patch:fbjs-scripts@npm%3A1.2.0#./patches/fbjs-scripts.patch::version=1.2.0&hash=f23fdb&locator=%40jest%2Fmonorepo%40workspace%3A." + dependencies: + "@babel/core": ^7.0.0 + ansi-colors: ^1.0.1 + babel-preset-fbjs: ^3.2.0 + core-js: ^2.4.1 + cross-spawn: ^5.1.0 + fancy-log: ^1.3.2 + object-assign: ^4.0.1 + plugin-error: ^0.1.2 + semver: ^5.1.0 + through2: ^2.0.0 + checksum: 908b412866a6d1dd625b466153834f2f1e417f903e2c4a110840a14c2d7973e9ae9f03101be947ca01faf99dd198370cc405181a9223066ee803c84e41110190 + languageName: node + linkType: hard + "fbjs@npm:^1.0.0": version: 1.0.0 resolution: "fbjs@npm:1.0.0" From a9d9cbf67f0789b579f7069d130e7f58f9052b62 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Mon, 16 Nov 2020 13:33:31 +0100 Subject: [PATCH 11/16] tweak cache creater function --- packages/jest-create-cache-key-function/src/index.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/jest-create-cache-key-function/src/index.ts b/packages/jest-create-cache-key-function/src/index.ts index 7d9afdaa97bb..d1f5ff514eaa 100644 --- a/packages/jest-create-cache-key-function/src/index.ts +++ b/packages/jest-create-cache-key-function/src/index.ts @@ -40,8 +40,13 @@ function getGlobalCacheKey(files: Array, values: Array) { } function getCacheKeyFunction(globalCacheKey: string): GetCacheKeyFunction { - return (sourceText, sourcePath, options) => { - const {config, instrument} = options; + // @ts-expect-error + return (sourceText, sourcePath, configString, options) => { + // Jest 27 passes a single options bag which contains `configString` rather than as a separate argument. + // We can hide that API difference, though, so this module is usable for both jest@<27 and jest@>=27 + const inferredOptions: CacheKeyOptions = options || configString; + const {config, instrument} = inferredOptions; + return createHash('md5') .update(globalCacheKey) .update('\0', 'utf8') From e747197a328b6ef986e25cf7c3edad04fa6eca3d Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Mon, 16 Nov 2020 14:14:15 +0100 Subject: [PATCH 12/16] generic --- docs/CodeTransformation.md | 4 ++-- packages/babel-jest/src/index.ts | 30 +++++++++------------------- packages/jest-transform/src/types.ts | 4 ++-- 3 files changed, 13 insertions(+), 25 deletions(-) diff --git a/docs/CodeTransformation.md b/docs/CodeTransformation.md index db08d17200df..4748948459e2 100644 --- a/docs/CodeTransformation.md +++ b/docs/CodeTransformation.md @@ -22,7 +22,7 @@ If you override the `transform` configuration option `babel-jest` will no longer You can write you own transformer. The API of a transformer is as follows: ```ts -interface Transformer { +interface Transformer { /** * Indicates if the transformer is capabale of instrumenting the code for code coverage. * @@ -30,7 +30,7 @@ interface Transformer { * If V8 coverage is _not_ active, and this is `false`. Jest will instrument the code returned by this transformer using Babel. */ canInstrument?: boolean; - createTransformer?: (options?: unknown) => Transformer; + createTransformer?: (options?: OptionType) => Transformer; getCacheKey?: ( sourceText: string, diff --git a/packages/babel-jest/src/index.ts b/packages/babel-jest/src/index.ts index fe19c1d9ebf1..9951ad5e7628 100644 --- a/packages/babel-jest/src/index.ts +++ b/packages/babel-jest/src/index.ts @@ -9,8 +9,6 @@ import {createHash} from 'crypto'; import * as path from 'path'; import { PartialConfig, - PluginItem, - TransformCaller, TransformOptions, transformSync as babelTransform, } from '@babel/core'; @@ -28,22 +26,12 @@ const THIS_FILE = fs.readFileSync(__filename); const jestPresetPath = require.resolve('babel-preset-jest'); const babelIstanbulPlugin = require.resolve('babel-plugin-istanbul'); -// Narrow down the types -interface BabelJestTransformer extends Transformer { - canInstrument: true; -} -interface BabelJestTransformOptions extends TransformOptions { - caller: TransformCaller; - compact: false; - plugins: Array; - presets: Array; - sourceMaps: 'both'; -} - -const createTransformer = (userOptions?: unknown): BabelJestTransformer => { - const inputOptions: TransformOptions = - (userOptions as TransformOptions) ?? {}; - const options: BabelJestTransformOptions = { +type CreateTransformer = Transformer['createTransformer']; + +const createTransformer: CreateTransformer = userOptions => { + const inputOptions = userOptions ?? {}; + + const options = { ...inputOptions, caller: { name: 'babel-jest', @@ -57,7 +45,7 @@ const createTransformer = (userOptions?: unknown): BabelJestTransformer => { plugins: inputOptions.plugins ?? [], presets: (inputOptions.presets ?? []).concat(jestPresetPath), sourceMaps: 'both', - }; + } as const; function loadBabelConfig( cwd: Config.Path, @@ -172,8 +160,8 @@ const createTransformer = (userOptions?: unknown): BabelJestTransformer => { }; }; -const transformer: BabelJestTransformer & { - createTransformer: (options?: TransformOptions) => BabelJestTransformer; +const transformer: Transformer & { + createTransformer: CreateTransformer; } = { ...createTransformer(), // Assigned here so only the exported transformer has `createTransformer`, diff --git a/packages/jest-transform/src/types.ts b/packages/jest-transform/src/types.ts index 18231f09eeb0..bae59571bdaa 100644 --- a/packages/jest-transform/src/types.ts +++ b/packages/jest-transform/src/types.ts @@ -56,9 +56,9 @@ export interface TransformOptions extends ReducedTransformOptions { configString: string; } -export interface Transformer { +export interface Transformer { canInstrument?: boolean; - createTransformer?: (options?: unknown) => Transformer; + createTransformer?: (options?: OptionType) => Transformer; getCacheKey?: ( sourceText: string, From 13ce59dc07252a86ed80989b019804a359c6ed48 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Mon, 16 Nov 2020 14:16:43 +0100 Subject: [PATCH 13/16] less syntax --- packages/babel-jest/src/index.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/babel-jest/src/index.ts b/packages/babel-jest/src/index.ts index 9951ad5e7628..75e095729cc5 100644 --- a/packages/babel-jest/src/index.ts +++ b/packages/babel-jest/src/index.ts @@ -160,9 +160,7 @@ const createTransformer: CreateTransformer = userOptions => { }; }; -const transformer: Transformer & { - createTransformer: CreateTransformer; -} = { +const transformer: Transformer = { ...createTransformer(), // Assigned here so only the exported transformer has `createTransformer`, // instead of all created transformers by the function From c5c31e53d1ec90ad95ec731ecb59ac328005c05e Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Mon, 16 Nov 2020 14:20:45 +0100 Subject: [PATCH 14/16] link to new docs from syntax error --- packages/jest-transform/src/enhanceUnexpectedTokenMessage.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/jest-transform/src/enhanceUnexpectedTokenMessage.ts b/packages/jest-transform/src/enhanceUnexpectedTokenMessage.ts index e666361d4099..71e1ea1871d3 100644 --- a/packages/jest-transform/src/enhanceUnexpectedTokenMessage.ts +++ b/packages/jest-transform/src/enhanceUnexpectedTokenMessage.ts @@ -56,7 +56,9 @@ ${DOT}If you simply want to mock your non-JS modules (e.g. binary assets) you ca )} config option. You'll find more details and examples of these config options in the docs: -${chalk.cyan('https://jestjs.io/docs/en/configuration.html')} +${chalk.cyan('https://jestjs.io/docs/en/configuration')} +For information about custom transformations, see: +${chalk.cyan('http://localhost:3000/docs/en/code-transformation')} ${chalk.bold.red('Details:')} From df257c5585c1e399f329eeda125c17b87c2c5fc9 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Mon, 16 Nov 2020 14:24:34 +0100 Subject: [PATCH 15/16] make @jest/create-cache-key-function more type safe --- .../src/index.ts | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/packages/jest-create-cache-key-function/src/index.ts b/packages/jest-create-cache-key-function/src/index.ts index d1f5ff514eaa..63f9fbd3add8 100644 --- a/packages/jest-create-cache-key-function/src/index.ts +++ b/packages/jest-create-cache-key-function/src/index.ts @@ -12,19 +12,34 @@ import {readFileSync} from 'fs'; import {relative} from 'path'; import type {Config} from '@jest/types'; +type OldCacheKeyOptions = { + config: Config.ProjectConfig; + instrument: boolean; +}; + // Should mirror `import('@jest/transform').TransformOptions` -type CacheKeyOptions = { +type NewCacheKeyOptions = { config: Config.ProjectConfig; configString: string; instrument: boolean; }; -type GetCacheKeyFunction = ( +type OldGetCacheKeyFunction = ( + fileData: string, + filePath: Config.Path, + configStr: string, + options: OldCacheKeyOptions, +) => string; + +// Should mirror `import('@jest/transform').Transformer['getCacheKey']` +type NewGetCacheKeyFunction = ( sourceText: string, sourcePath: Config.Path, - options: CacheKeyOptions, + options: NewCacheKeyOptions, ) => string; +type GetCacheKeyFunction = OldGetCacheKeyFunction | NewGetCacheKeyFunction; + function getGlobalCacheKey(files: Array, values: Array) { return [ process.env.NODE_ENV, @@ -40,11 +55,10 @@ function getGlobalCacheKey(files: Array, values: Array) { } function getCacheKeyFunction(globalCacheKey: string): GetCacheKeyFunction { - // @ts-expect-error return (sourceText, sourcePath, configString, options) => { // Jest 27 passes a single options bag which contains `configString` rather than as a separate argument. // We can hide that API difference, though, so this module is usable for both jest@<27 and jest@>=27 - const inferredOptions: CacheKeyOptions = options || configString; + const inferredOptions = options || configString; const {config, instrument} = inferredOptions; return createHash('md5') From 1c0294c5d9573d4ea41cb174742ade06ec7b8703 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Mon, 16 Nov 2020 14:44:38 +0100 Subject: [PATCH 16/16] Update packages/jest-transform/src/enhanceUnexpectedTokenMessage.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michał Pierzchała --- packages/jest-transform/src/enhanceUnexpectedTokenMessage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/jest-transform/src/enhanceUnexpectedTokenMessage.ts b/packages/jest-transform/src/enhanceUnexpectedTokenMessage.ts index 71e1ea1871d3..99c66484f83a 100644 --- a/packages/jest-transform/src/enhanceUnexpectedTokenMessage.ts +++ b/packages/jest-transform/src/enhanceUnexpectedTokenMessage.ts @@ -58,7 +58,7 @@ ${DOT}If you simply want to mock your non-JS modules (e.g. binary assets) you ca You'll find more details and examples of these config options in the docs: ${chalk.cyan('https://jestjs.io/docs/en/configuration')} For information about custom transformations, see: -${chalk.cyan('http://localhost:3000/docs/en/code-transformation')} +${chalk.cyan('https://jestjs.io/docs/en/code-transformation')} ${chalk.bold.red('Details:')}