| 1 | "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|---|
| 2 |
|
|---|
| 3 | var _types = require('../parser/tokenizer/types');
|
|---|
| 4 |
|
|---|
| 5 |
|
|---|
| 6 | var _Transformer = require('./Transformer'); var _Transformer2 = _interopRequireDefault(_Transformer);
|
|---|
| 7 |
|
|---|
| 8 | const JEST_GLOBAL_NAME = "jest";
|
|---|
| 9 | const HOISTED_METHODS = ["mock", "unmock", "enableAutomock", "disableAutomock"];
|
|---|
| 10 |
|
|---|
| 11 | /**
|
|---|
| 12 | * Implementation of babel-plugin-jest-hoist, which hoists up some jest method
|
|---|
| 13 | * calls above the imports to allow them to override other imports.
|
|---|
| 14 | *
|
|---|
| 15 | * To preserve line numbers, rather than directly moving the jest.mock code, we
|
|---|
| 16 | * wrap each invocation in a function statement and then call the function from
|
|---|
| 17 | * the top of the file.
|
|---|
| 18 | */
|
|---|
| 19 | class JestHoistTransformer extends _Transformer2.default {
|
|---|
| 20 | __init() {this.hoistedFunctionNames = []}
|
|---|
| 21 |
|
|---|
| 22 | constructor(
|
|---|
| 23 | rootTransformer,
|
|---|
| 24 | tokens,
|
|---|
| 25 | nameManager,
|
|---|
| 26 | importProcessor,
|
|---|
| 27 | ) {
|
|---|
| 28 | super();this.rootTransformer = rootTransformer;this.tokens = tokens;this.nameManager = nameManager;this.importProcessor = importProcessor;JestHoistTransformer.prototype.__init.call(this);;
|
|---|
| 29 | }
|
|---|
| 30 |
|
|---|
| 31 | process() {
|
|---|
| 32 | if (
|
|---|
| 33 | this.tokens.currentToken().scopeDepth === 0 &&
|
|---|
| 34 | this.tokens.matches4(_types.TokenType.name, _types.TokenType.dot, _types.TokenType.name, _types.TokenType.parenL) &&
|
|---|
| 35 | this.tokens.identifierName() === JEST_GLOBAL_NAME
|
|---|
| 36 | ) {
|
|---|
| 37 | // TODO: This only works if imports transform is active, which it will be for jest.
|
|---|
| 38 | // But if jest adds module support and we no longer need the import transform, this needs fixing.
|
|---|
| 39 | if (_optionalChain([this, 'access', _ => _.importProcessor, 'optionalAccess', _2 => _2.getGlobalNames, 'call', _3 => _3(), 'optionalAccess', _4 => _4.has, 'call', _5 => _5(JEST_GLOBAL_NAME)])) {
|
|---|
| 40 | return false;
|
|---|
| 41 | }
|
|---|
| 42 | return this.extractHoistedCalls();
|
|---|
| 43 | }
|
|---|
| 44 |
|
|---|
| 45 | return false;
|
|---|
| 46 | }
|
|---|
| 47 |
|
|---|
| 48 | getHoistedCode() {
|
|---|
| 49 | if (this.hoistedFunctionNames.length > 0) {
|
|---|
| 50 | // This will be placed before module interop code, but that's fine since
|
|---|
| 51 | // imports aren't allowed in module mock factories.
|
|---|
| 52 | return this.hoistedFunctionNames.map((name) => `${name}();`).join("");
|
|---|
| 53 | }
|
|---|
| 54 | return "";
|
|---|
| 55 | }
|
|---|
| 56 |
|
|---|
| 57 | /**
|
|---|
| 58 | * Extracts any methods calls on the jest-object that should be hoisted.
|
|---|
| 59 | *
|
|---|
| 60 | * According to the jest docs, https://jestjs.io/docs/en/jest-object#jestmockmodulename-factory-options,
|
|---|
| 61 | * mock, unmock, enableAutomock, disableAutomock, are the methods that should be hoisted.
|
|---|
| 62 | *
|
|---|
| 63 | * We do not apply the same checks of the arguments as babel-plugin-jest-hoist does.
|
|---|
| 64 | */
|
|---|
| 65 | extractHoistedCalls() {
|
|---|
| 66 | // We're handling a chain of calls where `jest` may or may not need to be inserted for each call
|
|---|
| 67 | // in the chain, so remove the initial `jest` to make the loop implementation cleaner.
|
|---|
| 68 | this.tokens.removeToken();
|
|---|
| 69 | // Track some state so that multiple non-hoisted chained calls in a row keep their chaining
|
|---|
| 70 | // syntax.
|
|---|
| 71 | let followsNonHoistedJestCall = false;
|
|---|
| 72 |
|
|---|
| 73 | // Iterate through all chained calls on the jest object.
|
|---|
| 74 | while (this.tokens.matches3(_types.TokenType.dot, _types.TokenType.name, _types.TokenType.parenL)) {
|
|---|
| 75 | const methodName = this.tokens.identifierNameAtIndex(this.tokens.currentIndex() + 1);
|
|---|
| 76 | const shouldHoist = HOISTED_METHODS.includes(methodName);
|
|---|
| 77 | if (shouldHoist) {
|
|---|
| 78 | // We've matched e.g. `.mock(...)` or similar call.
|
|---|
| 79 | // Replace the initial `.` with `function __jestHoist(){jest.`
|
|---|
| 80 | const hoistedFunctionName = this.nameManager.claimFreeName("__jestHoist");
|
|---|
| 81 | this.hoistedFunctionNames.push(hoistedFunctionName);
|
|---|
| 82 | this.tokens.replaceToken(`function ${hoistedFunctionName}(){${JEST_GLOBAL_NAME}.`);
|
|---|
| 83 | this.tokens.copyToken();
|
|---|
| 84 | this.tokens.copyToken();
|
|---|
| 85 | this.rootTransformer.processBalancedCode();
|
|---|
| 86 | this.tokens.copyExpectedToken(_types.TokenType.parenR);
|
|---|
| 87 | this.tokens.appendCode(";}");
|
|---|
| 88 | followsNonHoistedJestCall = false;
|
|---|
| 89 | } else {
|
|---|
| 90 | // This is a non-hoisted method, so just transform the code as usual.
|
|---|
| 91 | if (followsNonHoistedJestCall) {
|
|---|
| 92 | // If we didn't hoist the previous call, we can leave the code as-is to chain off of the
|
|---|
| 93 | // previous method call. It's important to preserve the code here because we don't know
|
|---|
| 94 | // for sure that the method actually returned the jest object for chaining.
|
|---|
| 95 | this.tokens.copyToken();
|
|---|
| 96 | } else {
|
|---|
| 97 | // If we hoisted the previous call, we know it returns the jest object back, so we insert
|
|---|
| 98 | // the identifier `jest` to continue the chain.
|
|---|
| 99 | this.tokens.replaceToken(`${JEST_GLOBAL_NAME}.`);
|
|---|
| 100 | }
|
|---|
| 101 | this.tokens.copyToken();
|
|---|
| 102 | this.tokens.copyToken();
|
|---|
| 103 | this.rootTransformer.processBalancedCode();
|
|---|
| 104 | this.tokens.copyExpectedToken(_types.TokenType.parenR);
|
|---|
| 105 | followsNonHoistedJestCall = true;
|
|---|
| 106 | }
|
|---|
| 107 | }
|
|---|
| 108 |
|
|---|
| 109 | return true;
|
|---|
| 110 | }
|
|---|
| 111 | } exports.default = JestHoistTransformer;
|
|---|