From 10d9580edec4058ffd8a7c52786331379e32e711 Mon Sep 17 00:00:00 2001 From: mrazauskas <72159681+mrazauskas@users.noreply.github.com> Date: Mon, 1 Nov 2021 16:11:18 +0200 Subject: [PATCH] fix(jest-transform): improve runtime errors and warnings (#11998) --- CHANGELOG.md | 1 + .../jest-transform/src/ScriptTransformer.ts | 27 ++++---- .../src/__tests__/ScriptTransformer.test.ts | 18 +++-- .../ScriptTransformer.test.ts.snap | 45 ++++++++++++- .../src/runtimeErrorsAndWarnings.ts | 67 +++++++++++++++++++ 5 files changed, 130 insertions(+), 28 deletions(-) create mode 100644 packages/jest-transform/src/runtimeErrorsAndWarnings.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index bd9009ea09b5..4fd5bcd972f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - `[expect]` Allow again `expect.Matchers` generic with single value ([#11986](https://github.com/facebook/jest/pull/11986)) - `[jest-environment-jsdom]` Add `@types/jsdom` dependency ([#11999](https://github.com/facebook/jest/pull/11999)) +- `[jest-transform]` Improve error and warning messages ([#11998](https://github.com/facebook/jest/pull/11998)) ### Chore & Maintenance diff --git a/packages/jest-transform/src/ScriptTransformer.ts b/packages/jest-transform/src/ScriptTransformer.ts index 3591fa294fdf..b643eaf91857 100644 --- a/packages/jest-transform/src/ScriptTransformer.ts +++ b/packages/jest-transform/src/ScriptTransformer.ts @@ -25,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, @@ -258,7 +264,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 +273,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); @@ -373,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()); } } @@ -391,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)); } } @@ -997,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 94ade78a4a2c..90691081727c 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 () => { @@ -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 () => { @@ -537,9 +535,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..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] { @@ -340,6 +353,24 @@ 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() { @@ -688,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..0da3e097c5c5 --- /dev/null +++ b/packages/jest-transform/src/runtimeErrorsAndWarnings.ts @@ -0,0 +1,67 @@ +/** + * 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'); + +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, + );