Skip to content

Commit 611d1a4

Browse files
authored
Improve babel-plugin-jest-hoist logic (#15415)
1 parent 3c50aab commit 611d1a4

File tree

3 files changed

+48
-53
lines changed

3 files changed

+48
-53
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
### Fixes
5151

5252
- `[babel-plugin-jest-hoist]` Use `denylist` instead of the deprecated `blacklist` for Babel 8 support ([#14109](https://github.com/jestjs/jest/pull/14109))
53+
- `[babel-plugin-jest-hoist]` Do not rely on buggy Babel behaviour ([#15415](https://github.com/jestjs/jest/pull/15415))
5354
- `[expect]` Check error instance type for `toThrow/toThrowError` ([#14576](https://github.com/jestjs/jest/pull/14576))
5455
- `[expect]` Improve diff for failing `expect.objectContaining` ([#15038](https://github.com/jestjs/jest/pull/15038))
5556
- `[expect]` Use `Array.isArray` to check if an array is an `Array` ([#15101](https://github.com/jestjs/jest/pull/15101))

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

Lines changed: 41 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,17 @@ import type {PluginObj} from '@babel/core';
1010
import {statement} from '@babel/template';
1111
import type {NodePath} from '@babel/traverse';
1212
import {
13-
type BlockStatement,
1413
type CallExpression,
15-
type EmptyStatement,
1614
type Expression,
1715
type Identifier,
1816
type ImportDeclaration,
1917
type MemberExpression,
2018
type Node,
21-
type Program,
19+
type Statement,
2220
type Super,
2321
type VariableDeclaration,
2422
type VariableDeclarator,
2523
callExpression,
26-
emptyStatement,
2724
isIdentifier,
2825
variableDeclaration,
2926
} from '@babel/types';
@@ -312,53 +309,6 @@ const extractJestObjExprIfHoistable = (expr: NodePath): JestObjInfo | null => {
312309
return null;
313310
};
314311

315-
function visitBlock(block: NodePath<BlockStatement> | NodePath<Program>) {
316-
// use a temporary empty statement instead of the real first statement, which may itself be hoisted
317-
const [varsHoistPoint, callsHoistPoint] = block.unshiftContainer<
318-
BlockStatement | Program,
319-
'body',
320-
[EmptyStatement, EmptyStatement]
321-
>('body', [emptyStatement(), emptyStatement()]);
322-
block.traverse({
323-
CallExpression: visitCallExpr,
324-
VariableDeclarator: visitVariableDeclarator,
325-
// do not traverse into nested blocks, or we'll hoist calls in there out to this block
326-
denylist: ['BlockStatement'],
327-
});
328-
callsHoistPoint.remove();
329-
varsHoistPoint.remove();
330-
331-
function visitCallExpr(callExpr: NodePath<CallExpression>) {
332-
if (hoistedJestGetters.has(callExpr.node)) {
333-
const mockStmt = callExpr.getStatementParent();
334-
335-
if (mockStmt) {
336-
const mockStmtParent = mockStmt.parentPath;
337-
if (mockStmtParent.isBlock()) {
338-
const mockStmtNode = mockStmt.node;
339-
mockStmt.remove();
340-
callsHoistPoint.insertBefore(mockStmtNode);
341-
}
342-
}
343-
}
344-
}
345-
346-
function visitVariableDeclarator(varDecl: NodePath<VariableDeclarator>) {
347-
if (hoistedVariables.has(varDecl.node)) {
348-
// should be assert function, but it's not. So let's cast below
349-
varDecl.parentPath.assertVariableDeclaration();
350-
351-
const {kind, declarations} = varDecl.parent as VariableDeclaration;
352-
if (declarations.length === 1) {
353-
varDecl.parentPath.remove();
354-
} else {
355-
varDecl.remove();
356-
}
357-
varsHoistPoint.insertBefore(variableDeclaration(kind, [varDecl.node]));
358-
}
359-
}
360-
}
361-
362312
/* eslint-disable sort-keys */
363313
export default function jestHoist(): PluginObj<{
364314
declareJestObjGetterIdentifier: () => Identifier;
@@ -404,8 +354,46 @@ export default function jestHoist(): PluginObj<{
404354
},
405355
// in `post` to make sure we come after an import transform and can unshift above the `require`s
406356
post({path: program}) {
407-
visitBlock(program);
408-
program.traverse({BlockStatement: visitBlock});
357+
type Item = {calls: Array<Statement>; vars: Array<Statement>};
358+
359+
const stack: Array<Item> = [{calls: [], vars: []}];
360+
program.traverse({
361+
BlockStatement: {
362+
enter() {
363+
stack.push({calls: [], vars: []});
364+
},
365+
exit(path) {
366+
const item = stack.pop()!;
367+
path.node.body.unshift(...item.vars, ...item.calls);
368+
},
369+
},
370+
CallExpression(callExpr: NodePath<CallExpression>) {
371+
if (hoistedJestGetters.has(callExpr.node)) {
372+
const mockStmt = callExpr.getStatementParent();
373+
374+
if (mockStmt && mockStmt.parentPath.isBlock()) {
375+
stack.at(-1)!.calls.push(mockStmt.node);
376+
mockStmt.remove();
377+
}
378+
}
379+
},
380+
VariableDeclarator(varDecl: NodePath<VariableDeclarator>) {
381+
if (hoistedVariables.has(varDecl.node)) {
382+
// should be assert function, but it's not. So let's cast below
383+
varDecl.parentPath.assertVariableDeclaration();
384+
385+
const {kind, declarations} = varDecl.parent as VariableDeclaration;
386+
if (declarations.length === 1) {
387+
varDecl.parentPath.remove();
388+
} else {
389+
varDecl.remove();
390+
}
391+
stack.at(-1)!.vars.push(variableDeclaration(kind, [varDecl.node]));
392+
}
393+
},
394+
});
395+
const item = stack.pop()!;
396+
program.node.body.unshift(...item.vars, ...item.calls);
409397
},
410398
};
411399
}

scripts/lintTs.mjs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,12 @@ try {
111111
'@typescript-eslint/no-redundant-type-constituents': 'off',
112112
},
113113
},
114+
// {
115+
// files: ['packages/babel-plugin-jest-hoist/src/index.ts'],
116+
// rules: {
117+
// '@typescript-eslint/strict-boolean-expressions': 'off',
118+
// },
119+
// },
114120
],
115121
parser: '@typescript-eslint/parser',
116122
parserOptions: {

0 commit comments

Comments
 (0)