From 582120310bf7d0aeee1e3c56f16b83bd3db46e82 Mon Sep 17 00:00:00 2001 From: mrazauskas <72159681+mrazauskas@users.noreply.github.com> Date: Tue, 26 Oct 2021 20:16:00 +0300 Subject: [PATCH 1/4] fix: better invalid transformer errors --- .../jest-transform/src/ScriptTransformer.ts | 22 +++++++++++++++---- .../src/__tests__/ScriptTransformer.test.ts | 14 ++++++------ .../ScriptTransformer.test.ts.snap | 20 +++++++++++++++++ 3 files changed, 45 insertions(+), 11 deletions(-) diff --git a/packages/jest-transform/src/ScriptTransformer.ts b/packages/jest-transform/src/ScriptTransformer.ts index 3591fa294fdf..e8c93516ca0c 100644 --- a/packages/jest-transform/src/ScriptTransformer.ts +++ b/packages/jest-transform/src/ScriptTransformer.ts @@ -10,6 +10,7 @@ import * as path from 'path'; import {transformSync as babelTransform} from '@babel/core'; // @ts-expect-error: should just be `require.resolve`, but the tests mess that up import babelPluginIstanbul from 'babel-plugin-istanbul'; +import chalk = require('chalk'); import {fromSource as sourcemapFromSource} from 'convert-source-map'; import stableStringify = require('fast-json-stable-stringify'); import * as fs from 'graceful-fs'; @@ -250,6 +251,21 @@ class ScriptTransformer { } async loadTransformers(): Promise { + const makeInvalidTransformerError = (transformPath: string) => + chalk.red( + [ + chalk.bold('\u25cf Invalid transformer module:'), + ` "${slash( + transformPath, + )}" specified in the "transform" object of Jest configuration`, + ' must export a `process` or `processAsync` or `createTransformer` function.', + '', + ' Code Transformation Documentation:', + ' https://jestjs.io/docs/code-transformation', + '', + ].join('\n'), + ); + await Promise.all( this._config.transform.map( async ([, transformPath, transformerConfig]) => { @@ -258,7 +274,7 @@ class ScriptTransformer { ); if (!transformer) { - throw new TypeError('Jest: a transform must export something.'); + throw new Error(makeInvalidTransformerError(transformPath)); } if (typeof transformer.createTransformer === 'function') { transformer = transformer.createTransformer(transformerConfig); @@ -267,9 +283,7 @@ class ScriptTransformer { typeof transformer.process !== 'function' && typeof transformer.processAsync !== 'function' ) { - throw new TypeError( - 'Jest: a transform must export a `process` or `processAsync` function.', - ); + throw new Error(makeInvalidTransformerError(transformPath)); } const res = {transformer, transformerConfig}; this._transformCache.set(transformPath, res); diff --git a/packages/jest-transform/src/__tests__/ScriptTransformer.test.ts b/packages/jest-transform/src/__tests__/ScriptTransformer.test.ts index 94ade78a4a2c..494329e0e41e 100644 --- a/packages/jest-transform/src/__tests__/ScriptTransformer.test.ts +++ b/packages/jest-transform/src/__tests__/ScriptTransformer.test.ts @@ -486,14 +486,14 @@ describe('ScriptTransformer', () => { await Promise.all([...promisesToReject, ...promisesToResolve]); }); - it('throws an error if neither `process` nor `processAsync is defined', async () => { + it('throws an error if neither `process` nor `processAsync` is defined', async () => { config = { ...config, transform: [['\\.js$', 'skipped-required-props-preprocessor', {}]], }; - await expect(() => createScriptTransformer(config)).rejects.toThrow( - 'Jest: a transform must export a `process` or `processAsync` function.', - ); + await expect(() => + createScriptTransformer(config), + ).rejects.toThrowErrorMatchingSnapshot(); }); it("(in sync mode) throws an error if `process` isn't defined", async () => { @@ -537,9 +537,9 @@ describe('ScriptTransformer', () => { ], ], }; - await expect(() => createScriptTransformer(config)).rejects.toThrow( - 'Jest: a transform must export a `process` or `processAsync` function.', - ); + await expect(() => + createScriptTransformer(config), + ).rejects.toThrowErrorMatchingSnapshot(); }); it("shouldn't throw error without process method. But with correct createTransformer method", async () => { diff --git a/packages/jest-transform/src/__tests__/__snapshots__/ScriptTransformer.test.ts.snap b/packages/jest-transform/src/__tests__/__snapshots__/ScriptTransformer.test.ts.snap index ffa97cef1c0b..cf7368d73225 100644 --- a/packages/jest-transform/src/__tests__/__snapshots__/ScriptTransformer.test.ts.snap +++ b/packages/jest-transform/src/__tests__/__snapshots__/ScriptTransformer.test.ts.snap @@ -340,6 +340,26 @@ exports[`ScriptTransformer passes expected transform options to getCacheKeyAsync } `; +exports[`ScriptTransformer throws an error if createTransformer returns object without \`process\` method 1`] = ` +"● Invalid transformer module: + \\"skipped-required-create-transformer-props-preprocessor\\" specified in the \\"transform\\" object of Jest configuration + must export a \`process\` or \`processAsync\` or \`createTransformer\` function. + + Code Transformation Documentation: + https://jestjs.io/docs/code-transformation +" +`; + +exports[`ScriptTransformer throws an error if neither \`process\` nor \`processAsync\` is defined 1`] = ` +"● Invalid transformer module: + \\"skipped-required-props-preprocessor\\" specified in the \\"transform\\" object of Jest configuration + must export a \`process\` or \`processAsync\` or \`createTransformer\` function. + + Code Transformation Documentation: + https://jestjs.io/docs/code-transformation +" +`; + exports[`ScriptTransformer transforms a file async properly 1`] = ` /* istanbul ignore next */ function cov_25u22311x4() { From eed21806e2c5670749ed5d668ea5d213d1b9de3e Mon Sep 17 00:00:00 2001 From: mrazauskas <72159681+mrazauskas@users.noreply.github.com> Date: Tue, 26 Oct 2021 20:49:33 +0300 Subject: [PATCH 2/4] add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6daba1c00e5f..3ef2215d3d9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixes - `[expect]` Allow again `expect.Matchers` generic with single value ([#11986](https://github.com/facebook/jest/pull/11986)) +- `[jest-transform]` Improve the invalid transformer module error message ([#11998](https://github.com/facebook/jest/pull/11998)) ### Chore & Maintenance From b37fe9931ec35c2010cfd7004ee9f4d61402a559 Mon Sep 17 00:00:00 2001 From: mrazauskas <72159681+mrazauskas@users.noreply.github.com> Date: Fri, 29 Oct 2021 10:01:18 +0300 Subject: [PATCH 3/4] more useful errors --- .../jest-transform/src/ScriptTransformer.ts | 37 ++++-------- .../src/__tests__/ScriptTransformer.test.ts | 4 +- .../ScriptTransformer.test.ts.snap | 33 +++++++--- .../src/runtimeErrorsAndWarnings.ts | 60 +++++++++++++++++++ 4 files changed, 97 insertions(+), 37 deletions(-) create mode 100644 packages/jest-transform/src/runtimeErrorsAndWarnings.ts diff --git a/packages/jest-transform/src/ScriptTransformer.ts b/packages/jest-transform/src/ScriptTransformer.ts index e8c93516ca0c..b643eaf91857 100644 --- a/packages/jest-transform/src/ScriptTransformer.ts +++ b/packages/jest-transform/src/ScriptTransformer.ts @@ -10,7 +10,6 @@ import * as path from 'path'; import {transformSync as babelTransform} from '@babel/core'; // @ts-expect-error: should just be `require.resolve`, but the tests mess that up import babelPluginIstanbul from 'babel-plugin-istanbul'; -import chalk = require('chalk'); import {fromSource as sourcemapFromSource} from 'convert-source-map'; import stableStringify = require('fast-json-stable-stringify'); import * as fs from 'graceful-fs'; @@ -26,6 +25,12 @@ import { tryRealpath, } from 'jest-util'; import handlePotentialSyntaxError from './enhanceUnexpectedTokenMessage'; +import { + makeInvalidReturnValueError, + makeInvalidSourceMapWarning, + makeInvalidSyncTransformerError, + makeInvalidTransformerError, +} from './runtimeErrorsAndWarnings'; import shouldInstrument from './shouldInstrument'; import type { Options, @@ -251,21 +256,6 @@ class ScriptTransformer { } async loadTransformers(): Promise { - const makeInvalidTransformerError = (transformPath: string) => - chalk.red( - [ - chalk.bold('\u25cf Invalid transformer module:'), - ` "${slash( - transformPath, - )}" specified in the "transform" object of Jest configuration`, - ' must export a `process` or `processAsync` or `createTransformer` function.', - '', - ' Code Transformation Documentation:', - ' https://jestjs.io/docs/code-transformation', - '', - ].join('\n'), - ); - await Promise.all( this._config.transform.map( async ([, transformPath, transformerConfig]) => { @@ -387,11 +377,7 @@ class ScriptTransformer { } else if (processed != null && typeof processed.code === 'string') { transformed = processed; } else { - throw new TypeError( - "Jest: a transform's `process` function must return a string, " + - 'or an object with `code` key containing this string. ' + - "It's `processAsync` function must return a Promise resolving to it.", - ); + throw new Error(makeInvalidReturnValueError()); } } @@ -405,11 +391,8 @@ class ScriptTransformer { } } catch { const transformPath = this._getTransformPath(filename); - console.warn( - `jest-transform: The source map produced for the file ${filename} ` + - `by ${transformPath} was invalid. Proceeding without source ` + - 'mapping for that file.', - ); + invariant(transformPath); + console.warn(makeInvalidSourceMapWarning(filename, transformPath)); } } @@ -1011,7 +994,7 @@ function assertSyncTransformer( invariant(name); invariant( typeof transformer.process === 'function', - `Jest: synchronous transformer ${name} must export a "process" function.`, + makeInvalidSyncTransformerError(name), ); } diff --git a/packages/jest-transform/src/__tests__/ScriptTransformer.test.ts b/packages/jest-transform/src/__tests__/ScriptTransformer.test.ts index 494329e0e41e..90691081727c 100644 --- a/packages/jest-transform/src/__tests__/ScriptTransformer.test.ts +++ b/packages/jest-transform/src/__tests__/ScriptTransformer.test.ts @@ -506,9 +506,7 @@ describe('ScriptTransformer', () => { const scriptTransformer = await createScriptTransformer(config); expect(() => scriptTransformer.transformSource('sample.js', '', {instrument: false}), - ).toThrow( - 'Jest: synchronous transformer skipped-required-props-preprocessor-only-async must export a "process" function.', - ); + ).toThrowErrorMatchingSnapshot(); }); it('(in async mode) handles only sync `process`', async () => { diff --git a/packages/jest-transform/src/__tests__/__snapshots__/ScriptTransformer.test.ts.snap b/packages/jest-transform/src/__tests__/__snapshots__/ScriptTransformer.test.ts.snap index cf7368d73225..b3bee10792be 100644 --- a/packages/jest-transform/src/__tests__/__snapshots__/ScriptTransformer.test.ts.snap +++ b/packages/jest-transform/src/__tests__/__snapshots__/ScriptTransformer.test.ts.snap @@ -1,5 +1,14 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`ScriptTransformer (in sync mode) throws an error if \`process\` isn't defined 1`] = ` +"● Invalid synchronous transformer module: + \\"skipped-required-props-preprocessor-only-async\\" specified in the \\"transform\\" object of Jest configuration + must export a \`process\` function. + Code Transformation Documentation: + https://jestjs.io/docs/code-transformation +" +`; + exports[`ScriptTransformer in async mode, passes expected transform options to getCacheKey 1`] = ` [MockFunction] { "calls": Array [ @@ -126,7 +135,11 @@ const TRANSFORMED = { exports[`ScriptTransformer in async mode, uses the supplied preprocessor 2`] = `module.exports = "react";`; -exports[`ScriptTransformer in async mode, warns of unparseable inlined source maps from the preprocessor 1`] = `jest-transform: The source map produced for the file /fruits/banana.js by preprocessor-with-sourcemaps was invalid. Proceeding without source mapping for that file.`; +exports[`ScriptTransformer in async mode, warns of unparseable inlined source maps from the preprocessor 1`] = ` +● Invalid source map: + The source map for "/fruits/banana.js" returned by "preprocessor-with-sourcemaps" is invalid. + Proceeding without source mapping for that file. +`; exports[`ScriptTransformer passes expected transform options to getCacheKey 1`] = ` [MockFunction] { @@ -344,8 +357,7 @@ exports[`ScriptTransformer throws an error if createTransformer returns object w "● Invalid transformer module: \\"skipped-required-create-transformer-props-preprocessor\\" specified in the \\"transform\\" object of Jest configuration must export a \`process\` or \`processAsync\` or \`createTransformer\` function. - - Code Transformation Documentation: + Code Transformation Documentation: https://jestjs.io/docs/code-transformation " `; @@ -354,8 +366,7 @@ exports[`ScriptTransformer throws an error if neither \`process\` nor \`processA "● Invalid transformer module: \\"skipped-required-props-preprocessor\\" specified in the \\"transform\\" object of Jest configuration must export a \`process\` or \`processAsync\` or \`createTransformer\` function. - - Code Transformation Documentation: + Code Transformation Documentation: https://jestjs.io/docs/code-transformation " `; @@ -708,6 +719,14 @@ const TRANSFORMED = { exports[`ScriptTransformer uses the supplied preprocessor 2`] = `module.exports = "react";`; -exports[`ScriptTransformer warns of unparseable inlined source maps from the async preprocessor 1`] = `jest-transform: The source map produced for the file /fruits/banana.js by async-preprocessor-with-sourcemaps was invalid. Proceeding without source mapping for that file.`; +exports[`ScriptTransformer warns of unparseable inlined source maps from the async preprocessor 1`] = ` +● Invalid source map: + The source map for "/fruits/banana.js" returned by "async-preprocessor-with-sourcemaps" is invalid. + Proceeding without source mapping for that file. +`; -exports[`ScriptTransformer warns of unparseable inlined source maps from the preprocessor 1`] = `jest-transform: The source map produced for the file /fruits/banana.js by preprocessor-with-sourcemaps was invalid. Proceeding without source mapping for that file.`; +exports[`ScriptTransformer warns of unparseable inlined source maps from the preprocessor 1`] = ` +● Invalid source map: + The source map for "/fruits/banana.js" returned by "preprocessor-with-sourcemaps" is invalid. + Proceeding without source mapping for that file. +`; diff --git a/packages/jest-transform/src/runtimeErrorsAndWarnings.ts b/packages/jest-transform/src/runtimeErrorsAndWarnings.ts new file mode 100644 index 000000000000..cbf814a8beb9 --- /dev/null +++ b/packages/jest-transform/src/runtimeErrorsAndWarnings.ts @@ -0,0 +1,60 @@ +import chalk = require('chalk'); +import slash = require('slash'); + +const BULLET = '\u25cf '; +const DOCUMENTATION_NOTE = ` ${chalk.bold( + 'Code Transformation Documentation:', +)} + https://jestjs.io/docs/code-transformation +`; + +export const makeInvalidReturnValueError = (): string => + chalk.red( + [ + chalk.bold(BULLET + 'Invalid return value:'), + ` Code transformer's \`process\` function must return a string or an object`, + ' with `code` key containing a string. If `processAsync` function is implemented,', + ' it must return a Promise resolving to one of these values.', + '', + ].join('\n') + DOCUMENTATION_NOTE, + ); + +export const makeInvalidSourceMapWarning = ( + filename: string, + transformPath: string, +): string => + chalk.yellow( + [ + chalk.bold(BULLET + 'Invalid source map:'), + ` The source map for "${slash(filename)}" returned by "${slash( + transformPath, + )}" is invalid.`, + ' Proceeding without source mapping for that file.', + ].join('\n'), + ); + +export const makeInvalidSyncTransformerError = ( + transformPath: string, +): string => + chalk.red( + [ + chalk.bold(BULLET + 'Invalid synchronous transformer module:'), + ` "${slash( + transformPath, + )}" specified in the "transform" object of Jest configuration`, + ' must export a `process` function.', + '', + ].join('\n') + DOCUMENTATION_NOTE, + ); + +export const makeInvalidTransformerError = (transformPath: string): string => + chalk.red( + [ + chalk.bold(BULLET + 'Invalid transformer module:'), + ` "${slash( + transformPath, + )}" specified in the "transform" object of Jest configuration`, + ' must export a `process` or `processAsync` or `createTransformer` function.', + '', + ].join('\n') + DOCUMENTATION_NOTE, + ); From f78ee4606fb9557e0b9355f3b022ee0504068730 Mon Sep 17 00:00:00 2001 From: mrazauskas <72159681+mrazauskas@users.noreply.github.com> Date: Fri, 29 Oct 2021 10:34:25 +0300 Subject: [PATCH 4/4] add copyright header --- packages/jest-transform/src/runtimeErrorsAndWarnings.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/jest-transform/src/runtimeErrorsAndWarnings.ts b/packages/jest-transform/src/runtimeErrorsAndWarnings.ts index cbf814a8beb9..0da3e097c5c5 100644 --- a/packages/jest-transform/src/runtimeErrorsAndWarnings.ts +++ b/packages/jest-transform/src/runtimeErrorsAndWarnings.ts @@ -1,3 +1,10 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + import chalk = require('chalk'); import slash = require('slash');