babel-plugin-jest-hoist
Advanced tools
Comparing version 29.0.2 to 29.2.0
@@ -7,23 +7,16 @@ 'use strict'; | ||
exports.default = jestHoist; | ||
function _template() { | ||
const data = require('@babel/template'); | ||
_template = function () { | ||
return data; | ||
}; | ||
return data; | ||
} | ||
function _types() { | ||
const data = require('@babel/types'); | ||
_types = function () { | ||
return data; | ||
}; | ||
return data; | ||
} | ||
/** | ||
@@ -36,2 +29,3 @@ * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. | ||
*/ | ||
const JEST_GLOBAL_NAME = 'jest'; | ||
@@ -41,6 +35,7 @@ const JEST_GLOBALS_MODULE_NAME = '@jest/globals'; | ||
const hoistedVariables = new WeakSet(); | ||
const hoistedJestExpressions = new WeakSet(); // We allow `jest`, `expect`, `require`, all default Node.js globals and all | ||
const hoistedJestExpressions = 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. | ||
const ALLOWED_IDENTIFIERS = new Set( | ||
@@ -111,7 +106,10 @@ [ | ||
}, | ||
blacklist: ['TypeAnnotation', 'TSTypeAnnotation', 'TSTypeReference'] | ||
blacklist: [ | ||
'TypeAnnotation', | ||
'TSTypeAnnotation', | ||
'TSTypeQuery', | ||
'TSTypeReference' | ||
] | ||
}; | ||
const FUNCTIONS = Object.create(null); | ||
FUNCTIONS.mock = args => { | ||
@@ -122,3 +120,2 @@ if (args.length === 1) { | ||
const moduleFactory = args[1]; | ||
if (!moduleFactory.isFunction()) { | ||
@@ -130,10 +127,8 @@ throw moduleFactory.buildCodeFrameError( | ||
} | ||
const ids = new Set(); | ||
const parentScope = moduleFactory.parentPath.scope; // @ts-expect-error: ReferencedIdentifier and blacklist are not known on visitors | ||
const parentScope = moduleFactory.parentPath.scope; | ||
// @ts-expect-error: ReferencedIdentifier and blacklist are not known on visitors | ||
moduleFactory.traverse(IDVisitor, { | ||
ids | ||
}); | ||
for (const id of ids) { | ||
@@ -143,25 +138,20 @@ const {name} = id.node; | ||
let scope = id.scope; | ||
while (scope !== parentScope) { | ||
if (scope.bindings[name]) { | ||
if (scope.bindings[name] != null) { | ||
found = true; | ||
break; | ||
} | ||
scope = scope.parent; | ||
} | ||
if (!found) { | ||
let isAllowedIdentifier = | ||
(scope.hasGlobal(name) && ALLOWED_IDENTIFIERS.has(name)) || | ||
/^mock/i.test(name) || // Allow istanbul's coverage variable to pass. | ||
/^mock/i.test(name) || | ||
// Allow istanbul's coverage variable to pass. | ||
/^(?:__)?cov/.test(name); | ||
if (!isAllowedIdentifier) { | ||
const binding = scope.bindings[name]; | ||
if (binding?.path.isVariableDeclarator()) { | ||
const {node} = binding.path; | ||
const initNode = node.init; | ||
if (initNode && binding.constant && scope.isPure(initNode, true)) { | ||
@@ -174,3 +164,2 @@ hoistedVariables.add(node); | ||
const imported = binding.path.node.imported; | ||
if ( | ||
@@ -182,3 +171,4 @@ importDecl.node.source.value === JEST_GLOBALS_MODULE_NAME && | ||
) { | ||
isAllowedIdentifier = true; // Imports are already hoisted, so we don't need to add it | ||
isAllowedIdentifier = true; | ||
// Imports are already hoisted, so we don't need to add it | ||
// to hoistedVariables. | ||
@@ -205,16 +195,10 @@ } | ||
} | ||
return true; | ||
} | ||
return false; | ||
}; | ||
FUNCTIONS.unmock = args => args.length === 1 && args[0].isStringLiteral(); | ||
FUNCTIONS.deepUnmock = args => args.length === 1 && args[0].isStringLiteral(); | ||
FUNCTIONS.disableAutomock = FUNCTIONS.enableAutomock = args => | ||
args.length === 0; | ||
const createJestObjectGetter = (0, _template().statement)` | ||
@@ -227,3 +211,2 @@ function GETTER_NAME() { | ||
`; | ||
const isJestObject = expression => { | ||
@@ -237,4 +220,4 @@ // global | ||
return true; | ||
} // import { jest } from '@jest/globals' | ||
} | ||
// import { jest } from '@jest/globals' | ||
if ( | ||
@@ -247,4 +230,4 @@ expression.referencesImport( | ||
return true; | ||
} // import * as JestGlobals from '@jest/globals' | ||
} | ||
// import * as JestGlobals from '@jest/globals' | ||
if ( | ||
@@ -259,6 +242,4 @@ expression.isMemberExpression() && | ||
} | ||
return false; | ||
}; | ||
const extractJestObjExprIfHoistable = expr => { | ||
@@ -268,10 +249,7 @@ if (!expr.isCallExpression()) { | ||
} | ||
const callee = expr.get('callee'); | ||
const args = expr.get('arguments'); | ||
if (!callee.isMemberExpression() || callee.node.computed) { | ||
return null; | ||
} | ||
const object = callee.get('object'); | ||
@@ -281,13 +259,13 @@ const property = callee.get('property'); | ||
const jestObjExpr = isJestObject(object) | ||
? object // The Jest object could be returned from another call since the functions are all chainable. | ||
: extractJestObjExprIfHoistable(object); | ||
? object | ||
: // The Jest object could be returned from another call since the functions are all chainable. | ||
extractJestObjExprIfHoistable(object); | ||
if (!jestObjExpr) { | ||
return null; | ||
} // Important: Call the function check last | ||
} | ||
// Important: Call the function check last | ||
// It might throw an error to display to the user, | ||
// which should only happen if we're already sure it's a call on the Jest object. | ||
let functionLooksHoistableOrInHoistable = FUNCTIONS[propertyName]?.(args); | ||
for ( | ||
@@ -304,3 +282,2 @@ let path = expr; | ||
} | ||
if (functionLooksHoistableOrInHoistable) { | ||
@@ -310,7 +287,6 @@ hoistedJestExpressions.add(expr.node); | ||
} | ||
return null; | ||
}; | ||
/* eslint-disable sort-keys */ | ||
function jestHoist() { | ||
@@ -323,3 +299,2 @@ return { | ||
} | ||
this.jestObjGetterIdentifier = | ||
@@ -337,3 +312,2 @@ program.scope.generateUidIdentifier('getJestObj'); | ||
}, | ||
visitor: { | ||
@@ -344,3 +318,2 @@ ExpressionStatement(exprStmt) { | ||
); | ||
if (jestObjExpr) { | ||
@@ -356,3 +329,2 @@ jestObjExpr.replaceWith( | ||
}, | ||
// in `post` to make sure we come after an import transform and can unshift above the `require`s | ||
@@ -366,3 +338,2 @@ post({path: program}) { | ||
}); | ||
function visitBlock(block) { | ||
@@ -382,3 +353,2 @@ // use a temporary empty statement instead of the real first statement, which may itself be hoisted | ||
varsHoistPoint.remove(); | ||
function visitCallExpr(callExpr) { | ||
@@ -388,3 +358,2 @@ const { | ||
} = callExpr; | ||
if ( | ||
@@ -395,6 +364,4 @@ (0, _types().isIdentifier)(callee) && | ||
const mockStmt = callExpr.getStatementParent(); | ||
if (mockStmt) { | ||
const mockStmtParent = mockStmt.parentPath; | ||
if (mockStmtParent.isBlock()) { | ||
@@ -408,3 +375,2 @@ const mockStmtNode = mockStmt.node; | ||
} | ||
function visitVariableDeclarator(varDecl) { | ||
@@ -415,3 +381,2 @@ if (hoistedVariables.has(varDecl.node)) { | ||
const {kind, declarations} = varDecl.parent; | ||
if (declarations.length === 1) { | ||
@@ -422,3 +387,2 @@ varDecl.parentPath.remove(); | ||
} | ||
varsHoistPoint.insertBefore( | ||
@@ -425,0 +389,0 @@ (0, _types().variableDeclaration)(kind, [varDecl.node]) |
{ | ||
"name": "babel-plugin-jest-hoist", | ||
"version": "29.0.2", | ||
"version": "29.2.0", | ||
"repository": { | ||
@@ -31,2 +31,3 @@ "type": "git", | ||
"@babel/preset-react": "^7.12.1", | ||
"@babel/preset-typescript": "^7.0.0", | ||
"@types/babel__template": "^7.0.2", | ||
@@ -41,3 +42,3 @@ "@types/node": "*", | ||
}, | ||
"gitHead": "616fcf56bb8481d29ba29cc34be32a92b1cf85e5" | ||
"gitHead": "ee5b37a4f4433afcfffb0356cea47739d8092287" | ||
} |
14380
376
8