Skip to content

Commit 93555aa

Browse files
authored
feat: show better errors from babel-plugin-jest-hoist (#8865)
1 parent b8a4a85 commit 93555aa

File tree

2 files changed

+92
-92
lines changed

2 files changed

+92
-92
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
### Features
44

5+
- `[babel-plugin-jest-hoist]` Show codeframe on static hoisting issues ([#8865](https://github.com/facebook/jest/pull/8865))
56
- `[jest-config]` [**BREAKING**] Set default display name color based on runner ([#8689](https://github.com/facebook/jest/pull/8689))
67

78
### Fixes

packages/babel-plugin-jest-hoist/src/index.ts

Lines changed: 91 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -7,76 +7,70 @@
77
*/
88

99
// Only used for types
10-
// eslint-disable-next-line
10+
/* eslint-disable import/no-extraneous-dependencies */
1111
import {NodePath, Visitor} from '@babel/traverse';
12-
// eslint-disable-next-line
1312
import {Identifier} from '@babel/types';
14-
15-
const invariant = (condition: unknown, message: string) => {
16-
if (!condition) {
17-
throw new Error('babel-plugin-jest-hoist: ' + message);
18-
}
19-
};
13+
/* eslint-enable */
2014

2115
// We allow `jest`, `expect`, `require`, all default Node.js globals and all
2216
// ES2015 built-ins to be used inside of a `jest.mock` factory.
2317
// We also allow variables prefixed with `mock` as an escape-hatch.
24-
const WHITELISTED_IDENTIFIERS: Set<string> = new Set([
25-
'Array',
26-
'ArrayBuffer',
27-
'Boolean',
28-
'DataView',
29-
'Date',
30-
'Error',
31-
'EvalError',
32-
'Float32Array',
33-
'Float64Array',
34-
'Function',
35-
'Generator',
36-
'GeneratorFunction',
37-
'Infinity',
38-
'Int16Array',
39-
'Int32Array',
40-
'Int8Array',
41-
'InternalError',
42-
'Intl',
43-
'JSON',
44-
'Map',
45-
'Math',
46-
'NaN',
47-
'Number',
48-
'Object',
49-
'Promise',
50-
'Proxy',
51-
'RangeError',
52-
'ReferenceError',
53-
'Reflect',
54-
'RegExp',
55-
'Set',
56-
'String',
57-
'Symbol',
58-
'SyntaxError',
59-
'TypeError',
60-
'URIError',
61-
'Uint16Array',
62-
'Uint32Array',
63-
'Uint8Array',
64-
'Uint8ClampedArray',
65-
'WeakMap',
66-
'WeakSet',
67-
'arguments',
68-
'console',
69-
'expect',
70-
'isNaN',
71-
'jest',
72-
'parseFloat',
73-
'parseInt',
74-
'require',
75-
'undefined',
76-
]);
77-
Object.getOwnPropertyNames(global).forEach(name => {
78-
WHITELISTED_IDENTIFIERS.add(name);
79-
});
18+
const WHITELISTED_IDENTIFIERS = new Set<string>(
19+
[
20+
'Array',
21+
'ArrayBuffer',
22+
'Boolean',
23+
'DataView',
24+
'Date',
25+
'Error',
26+
'EvalError',
27+
'Float32Array',
28+
'Float64Array',
29+
'Function',
30+
'Generator',
31+
'GeneratorFunction',
32+
'Infinity',
33+
'Int16Array',
34+
'Int32Array',
35+
'Int8Array',
36+
'InternalError',
37+
'Intl',
38+
'JSON',
39+
'Map',
40+
'Math',
41+
'NaN',
42+
'Number',
43+
'Object',
44+
'Promise',
45+
'Proxy',
46+
'RangeError',
47+
'ReferenceError',
48+
'Reflect',
49+
'RegExp',
50+
'Set',
51+
'String',
52+
'Symbol',
53+
'SyntaxError',
54+
'TypeError',
55+
'URIError',
56+
'Uint16Array',
57+
'Uint32Array',
58+
'Uint8Array',
59+
'Uint8ClampedArray',
60+
'WeakMap',
61+
'WeakSet',
62+
'arguments',
63+
'console',
64+
'expect',
65+
'isNaN',
66+
'jest',
67+
'parseFloat',
68+
'parseInt',
69+
'require',
70+
'undefined',
71+
...Object.getOwnPropertyNames(global),
72+
].sort(),
73+
);
8074

8175
const JEST_GLOBAL = {name: 'jest'};
8276
// TODO: Should be Visitor<{ids: Set<NodePath<Identifier>>}>, but `ReferencedIdentifier` doesn't exist
@@ -93,15 +87,18 @@ const FUNCTIONS: Record<
9387
(args: Array<NodePath>) => boolean
9488
> = Object.create(null);
9589

96-
FUNCTIONS.mock = (args: Array<NodePath>) => {
90+
FUNCTIONS.mock = args => {
9791
if (args.length === 1) {
9892
return args[0].isStringLiteral() || args[0].isLiteral();
9993
} else if (args.length === 2 || args.length === 3) {
10094
const moduleFactory = args[1];
101-
invariant(
102-
moduleFactory.isFunction(),
103-
'The second argument of `jest.mock` must be an inline function.',
104-
);
95+
96+
if (!moduleFactory.isFunction()) {
97+
throw moduleFactory.buildCodeFrameError(
98+
'The second argument of `jest.mock` must be an inline function.\n',
99+
TypeError,
100+
);
101+
}
105102

106103
const ids: Set<NodePath<Identifier>> = new Set();
107104
const parentScope = moduleFactory.parentPath.scope;
@@ -122,23 +119,28 @@ FUNCTIONS.mock = (args: Array<NodePath>) => {
122119
}
123120

124121
if (!found) {
125-
invariant(
122+
const isAllowedIdentifier =
126123
(scope.hasGlobal(name) && WHITELISTED_IDENTIFIERS.has(name)) ||
127-
/^mock/i.test(name) ||
128-
// Allow istanbul's coverage variable to pass.
129-
/^(?:__)?cov/.test(name),
130-
'The module factory of `jest.mock()` is not allowed to ' +
131-
'reference any out-of-scope variables.\n' +
132-
'Invalid variable access: ' +
133-
name +
134-
'\n' +
135-
'Whitelisted objects: ' +
136-
Array.from(WHITELISTED_IDENTIFIERS).join(', ') +
137-
'.\n' +
138-
'Note: This is a precaution to guard against uninitialized mock ' +
139-
'variables. If it is ensured that the mock is required lazily, ' +
140-
'variable names prefixed with `mock` (case insensitive) are permitted.',
141-
);
124+
/^mock/i.test(name) ||
125+
// Allow istanbul's coverage variable to pass.
126+
/^(?:__)?cov/.test(name);
127+
128+
if (!isAllowedIdentifier) {
129+
throw id.buildCodeFrameError(
130+
'The module factory of `jest.mock()` is not allowed to ' +
131+
'reference any out-of-scope variables.\n' +
132+
'Invalid variable access: ' +
133+
name +
134+
'\n' +
135+
'Whitelisted objects: ' +
136+
Array.from(WHITELISTED_IDENTIFIERS).join(', ') +
137+
'.\n' +
138+
'Note: This is a precaution to guard against uninitialized mock ' +
139+
'variables. If it is ensured that the mock is required lazily, ' +
140+
'variable names prefixed with `mock` (case insensitive) are permitted.\n',
141+
ReferenceError,
142+
);
143+
}
142144
}
143145
}
144146

@@ -147,13 +149,10 @@ FUNCTIONS.mock = (args: Array<NodePath>) => {
147149
return false;
148150
};
149151

150-
FUNCTIONS.unmock = (args: Array<NodePath>) =>
151-
args.length === 1 && args[0].isStringLiteral();
152-
FUNCTIONS.deepUnmock = (args: Array<NodePath>) =>
153-
args.length === 1 && args[0].isStringLiteral();
154-
FUNCTIONS.disableAutomock = FUNCTIONS.enableAutomock = (
155-
args: Array<NodePath>,
156-
) => args.length === 0;
152+
FUNCTIONS.unmock = args => args.length === 1 && args[0].isStringLiteral();
153+
FUNCTIONS.deepUnmock = args => args.length === 1 && args[0].isStringLiteral();
154+
FUNCTIONS.disableAutomock = FUNCTIONS.enableAutomock = args =>
155+
args.length === 0;
157156

158157
export = () => {
159158
const shouldHoistExpression = (expr: NodePath): boolean => {

0 commit comments

Comments
 (0)