diff --git a/packages/babel-plugin-jest-hoist/src/__tests__/__snapshots__/hoistPlugin.test.ts.snap b/packages/babel-plugin-jest-hoist/src/__tests__/__snapshots__/hoistPlugin.test.ts.snap index 3255331311b5..09486e91ae92 100644 --- a/packages/babel-plugin-jest-hoist/src/__tests__/__snapshots__/hoistPlugin.test.ts.snap +++ b/packages/babel-plugin-jest-hoist/src/__tests__/__snapshots__/hoistPlugin.test.ts.snap @@ -6,6 +6,8 @@ jest.mock('./App', () => () =>
Hello world
); ↓ ↓ ↓ ↓ ↓ ↓ +var _jsxFileName = "/root/project/src/file.js"; + _getJestObj().mock("./App", () => () => /*#__PURE__*/ _jsxDEV( "div", @@ -15,7 +17,7 @@ _getJestObj().mock("./App", () => () => void 0, false, { - fileName: _hoistedMockFactoryVariable(), + fileName: _jsxFileName, lineNumber: 1, columnNumber: 32 }, @@ -24,7 +26,6 @@ _getJestObj().mock("./App", () => () => ); import { jsxDEV as _jsxDEV } from "react/jsx-dev-runtime"; -var _jsxFileName = "/root/project/src/file.js"; function _getJestObj() { const { jest } = require("@jest/globals"); @@ -34,10 +35,6 @@ function _getJestObj() { return jest; } -function _hoistedMockFactoryVariable() { - return "/root/project/src/file.js"; -} - `; diff --git a/packages/babel-plugin-jest-hoist/src/index.ts b/packages/babel-plugin-jest-hoist/src/index.ts index 7ecafb8847da..e6d37508871f 100644 --- a/packages/babel-plugin-jest-hoist/src/index.ts +++ b/packages/babel-plugin-jest-hoist/src/index.ts @@ -16,15 +16,20 @@ import { Identifier, Node, Program, + VariableDeclaration, + VariableDeclarator, callExpression, emptyStatement, isIdentifier, + variableDeclaration, } from '@babel/types'; const JEST_GLOBAL_NAME = 'jest'; const JEST_GLOBALS_MODULE_NAME = '@jest/globals'; const JEST_GLOBALS_MODULE_JEST_EXPORT_NAME = 'jest'; +const hoistedVariables = new WeakSet(); + // We allow `jest`, `expect`, `require`, all default Node.js globals and all // ES2015 built-ins to be used inside of a `jest.mock` factory. // We also allow variables prefixed with `mock` as an escape-hatch. @@ -146,20 +151,7 @@ FUNCTIONS.mock = args => { const initNode = binding.path.node.init; if (initNode && binding.constant && scope.isPure(initNode, true)) { - const identifier = scope.generateUidIdentifier( - 'hoistedMockFactoryVariable', - ); - - binding.path.parentPath.insertAfter( - createHoistedVariable({ - HOISTED_NAME: identifier, - HOISTED_VALUE: binding.path.node.init, - }), - ); - - // replace reference with a call to the new function - id.replaceWith(callExpression(identifier, [])); - + hoistedVariables.add(binding.path.node); isAllowedIdentifier = true; } } @@ -202,12 +194,6 @@ function GETTER_NAME() { } `; -const createHoistedVariable = statement` -function HOISTED_NAME() { - return HOISTED_VALUE; -} -`; - const isJestObject = (expression: NodePath): boolean => { // global if ( @@ -317,6 +303,7 @@ export default (): PluginObj<{ // in `post` to make sure we come after an import transform and can unshift above the `require`s post({path: program}) { const self = this; + visitBlock(program); program.traverse({ BlockStatement: visitBlock, @@ -324,17 +311,19 @@ export default (): PluginObj<{ function visitBlock(block: NodePath | NodePath) { // use a temporary empty statement instead of the real first statement, which may itself be hoisted - const [firstNonHoistedStatementOfBlock] = block.unshiftContainer( - 'body', + const [varsHoistPoint, callsHoistPoint] = block.unshiftContainer('body', [ emptyStatement(), - ); + emptyStatement(), + ]); block.traverse({ CallExpression: visitCallExpr, + VariableDeclarator: visitVariableDeclarator, // do not traverse into nested blocks, or we'll hoist calls in there out to this block // @ts-expect-error blacklist is not known blacklist: ['BlockStatement'], }); - firstNonHoistedStatementOfBlock.remove(); + callsHoistPoint.remove(); + varsHoistPoint.remove(); function visitCallExpr(callExpr: NodePath) { const { @@ -351,11 +340,25 @@ export default (): PluginObj<{ const mockStmtParent = mockStmt.parentPath; if (mockStmtParent.isBlock()) { mockStmt.remove(); - firstNonHoistedStatementOfBlock.insertBefore(mockStmtNode); + callsHoistPoint.insertBefore(mockStmtNode); } } } } + + function visitVariableDeclarator(varDecl: NodePath) { + if (hoistedVariables.has(varDecl.node)) { + const {kind, declarations} = varDecl.parent as VariableDeclaration; + if (declarations.length === 1) { + varDecl.parentPath.remove(); + } else { + varDecl.remove(); + } + varsHoistPoint.insertBefore( + variableDeclaration(kind, [varDecl.node]), + ); + } + } } }, });