Skip to content

Commit

Permalink
fix(jest-transform): improve runtime errors and warnings (#11998)
Browse files Browse the repository at this point in the history
  • Loading branch information
mrazauskas authored Nov 1, 2021
1 parent 619f843 commit 10d9580
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 28 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
27 changes: 12 additions & 15 deletions packages/jest-transform/src/ScriptTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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());
}
}

Expand All @@ -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));
}
}

Expand Down Expand Up @@ -997,7 +994,7 @@ function assertSyncTransformer(
invariant(name);
invariant(
typeof transformer.process === 'function',
`Jest: synchronous transformer ${name} must export a "process" function.`,
makeInvalidSyncTransformerError(name),
);
}

Expand Down
18 changes: 8 additions & 10 deletions packages/jest-transform/src/__tests__/ScriptTransformer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand All @@ -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 () => {
Expand Down Expand Up @@ -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 () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -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`] = `
"<red><bold>● Invalid synchronous transformer module:</></>
<red> \\"skipped-required-props-preprocessor-only-async\\" specified in the \\"transform\\" object of Jest configuration</>
<red> must export a \`process\` function.</>
<red> <bold>Code Transformation Documentation:</></>
<red> https://jestjs.io/docs/code-transformation</>
<red></>"
`;
exports[`ScriptTransformer in async mode, passes expected transform options to getCacheKey 1`] = `
[MockFunction] {
"calls": Array [
Expand Down Expand Up @@ -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] {
Expand Down Expand Up @@ -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`] = `
"<red><bold>● Invalid transformer module:</></>
<red> \\"skipped-required-create-transformer-props-preprocessor\\" specified in the \\"transform\\" object of Jest configuration</>
<red> must export a \`process\` or \`processAsync\` or \`createTransformer\` function.</>
<red> <bold>Code Transformation Documentation:</></>
<red> https://jestjs.io/docs/code-transformation</>
<red></>"
`;
exports[`ScriptTransformer throws an error if neither \`process\` nor \`processAsync\` is defined 1`] = `
"<red><bold>● Invalid transformer module:</></>
<red> \\"skipped-required-props-preprocessor\\" specified in the \\"transform\\" object of Jest configuration</>
<red> must export a \`process\` or \`processAsync\` or \`createTransformer\` function.</>
<red> <bold>Code Transformation Documentation:</></>
<red> https://jestjs.io/docs/code-transformation</>
<red></>"
`;
exports[`ScriptTransformer transforms a file async properly 1`] = `
/* istanbul ignore next */
function cov_25u22311x4() {
Expand Down Expand Up @@ -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.
`;
67 changes: 67 additions & 0 deletions packages/jest-transform/src/runtimeErrorsAndWarnings.ts
Original file line number Diff line number Diff line change
@@ -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,
);

0 comments on commit 10d9580

Please sign in to comment.