Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(jest-transform): improve runtime errors and warnings #11998

Merged
merged 5 commits into from Nov 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
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
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
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
@@ -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
@@ -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,
);