209 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			209 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 
								 | 
							
								'use strict';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * This source code is licensed under the MIT license found in the
							 | 
						||
| 
								 | 
							
								 * LICENSE file in the root directory of this source tree.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								// Only used for types
							 | 
						||
| 
								 | 
							
								// eslint-disable-next-line
							 | 
						||
| 
								 | 
							
								// eslint-disable-next-line
							 | 
						||
| 
								 | 
							
								const invariant = (condition, message) => {
							 | 
						||
| 
								 | 
							
								  if (!condition) {
							 | 
						||
| 
								 | 
							
								    throw new Error('babel-plugin-jest-hoist: ' + message);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}; // 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 WHITELISTED_IDENTIFIERS = new Set([
							 | 
						||
| 
								 | 
							
								  'Array',
							 | 
						||
| 
								 | 
							
								  'ArrayBuffer',
							 | 
						||
| 
								 | 
							
								  'Boolean',
							 | 
						||
| 
								 | 
							
								  'DataView',
							 | 
						||
| 
								 | 
							
								  'Date',
							 | 
						||
| 
								 | 
							
								  'Error',
							 | 
						||
| 
								 | 
							
								  'EvalError',
							 | 
						||
| 
								 | 
							
								  'Float32Array',
							 | 
						||
| 
								 | 
							
								  'Float64Array',
							 | 
						||
| 
								 | 
							
								  'Function',
							 | 
						||
| 
								 | 
							
								  'Generator',
							 | 
						||
| 
								 | 
							
								  'GeneratorFunction',
							 | 
						||
| 
								 | 
							
								  'Infinity',
							 | 
						||
| 
								 | 
							
								  'Int16Array',
							 | 
						||
| 
								 | 
							
								  'Int32Array',
							 | 
						||
| 
								 | 
							
								  'Int8Array',
							 | 
						||
| 
								 | 
							
								  'InternalError',
							 | 
						||
| 
								 | 
							
								  'Intl',
							 | 
						||
| 
								 | 
							
								  'JSON',
							 | 
						||
| 
								 | 
							
								  'Map',
							 | 
						||
| 
								 | 
							
								  'Math',
							 | 
						||
| 
								 | 
							
								  'NaN',
							 | 
						||
| 
								 | 
							
								  'Number',
							 | 
						||
| 
								 | 
							
								  'Object',
							 | 
						||
| 
								 | 
							
								  'Promise',
							 | 
						||
| 
								 | 
							
								  'Proxy',
							 | 
						||
| 
								 | 
							
								  'RangeError',
							 | 
						||
| 
								 | 
							
								  'ReferenceError',
							 | 
						||
| 
								 | 
							
								  'Reflect',
							 | 
						||
| 
								 | 
							
								  'RegExp',
							 | 
						||
| 
								 | 
							
								  'Set',
							 | 
						||
| 
								 | 
							
								  'String',
							 | 
						||
| 
								 | 
							
								  'Symbol',
							 | 
						||
| 
								 | 
							
								  'SyntaxError',
							 | 
						||
| 
								 | 
							
								  'TypeError',
							 | 
						||
| 
								 | 
							
								  'URIError',
							 | 
						||
| 
								 | 
							
								  'Uint16Array',
							 | 
						||
| 
								 | 
							
								  'Uint32Array',
							 | 
						||
| 
								 | 
							
								  'Uint8Array',
							 | 
						||
| 
								 | 
							
								  'Uint8ClampedArray',
							 | 
						||
| 
								 | 
							
								  'WeakMap',
							 | 
						||
| 
								 | 
							
								  'WeakSet',
							 | 
						||
| 
								 | 
							
								  'arguments',
							 | 
						||
| 
								 | 
							
								  'console',
							 | 
						||
| 
								 | 
							
								  'expect',
							 | 
						||
| 
								 | 
							
								  'isNaN',
							 | 
						||
| 
								 | 
							
								  'jest',
							 | 
						||
| 
								 | 
							
								  'parseFloat',
							 | 
						||
| 
								 | 
							
								  'parseInt',
							 | 
						||
| 
								 | 
							
								  'require',
							 | 
						||
| 
								 | 
							
								  'undefined'
							 | 
						||
| 
								 | 
							
								]);
							 | 
						||
| 
								 | 
							
								Object.keys(global).forEach(name => {
							 | 
						||
| 
								 | 
							
								  WHITELISTED_IDENTIFIERS.add(name);
							 | 
						||
| 
								 | 
							
								});
							 | 
						||
| 
								 | 
							
								const JEST_GLOBAL = {
							 | 
						||
| 
								 | 
							
								  name: 'jest'
							 | 
						||
| 
								 | 
							
								}; // TODO: Should be Visitor<{ids: Set<NodePath<Identifier>>}>, but `ReferencedIdentifier` doesn't exist
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const IDVisitor = {
							 | 
						||
| 
								 | 
							
								  ReferencedIdentifier(path) {
							 | 
						||
| 
								 | 
							
								    // @ts-ignore: passed as Visitor State
							 | 
						||
| 
								 | 
							
								    this.ids.add(path);
							 | 
						||
| 
								 | 
							
								  },
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  blacklist: ['TypeAnnotation', 'TSTypeAnnotation', 'TSTypeReference']
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								const FUNCTIONS = Object.create(null);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								FUNCTIONS.mock = args => {
							 | 
						||
| 
								 | 
							
								  if (args.length === 1) {
							 | 
						||
| 
								 | 
							
								    return args[0].isStringLiteral() || args[0].isLiteral();
							 | 
						||
| 
								 | 
							
								  } else if (args.length === 2 || args.length === 3) {
							 | 
						||
| 
								 | 
							
								    const moduleFactory = args[1];
							 | 
						||
| 
								 | 
							
								    invariant(
							 | 
						||
| 
								 | 
							
								      moduleFactory.isFunction(),
							 | 
						||
| 
								 | 
							
								      'The second argument of `jest.mock` must be an inline function.'
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								    const ids = new Set();
							 | 
						||
| 
								 | 
							
								    const parentScope = moduleFactory.parentPath.scope; // @ts-ignore: Same as above: ReferencedIdentifier doesn't exist
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    moduleFactory.traverse(IDVisitor, {
							 | 
						||
| 
								 | 
							
								      ids
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								    var _iteratorNormalCompletion = true;
							 | 
						||
| 
								 | 
							
								    var _didIteratorError = false;
							 | 
						||
| 
								 | 
							
								    var _iteratorError = undefined;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    try {
							 | 
						||
| 
								 | 
							
								      for (
							 | 
						||
| 
								 | 
							
								        var _iterator = ids[Symbol.iterator](), _step;
							 | 
						||
| 
								 | 
							
								        !(_iteratorNormalCompletion = (_step = _iterator.next()).done);
							 | 
						||
| 
								 | 
							
								        _iteratorNormalCompletion = true
							 | 
						||
| 
								 | 
							
								      ) {
							 | 
						||
| 
								 | 
							
								        const id = _step.value;
							 | 
						||
| 
								 | 
							
								        const name = id.node.name;
							 | 
						||
| 
								 | 
							
								        let found = false;
							 | 
						||
| 
								 | 
							
								        let scope = id.scope;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        while (scope !== parentScope) {
							 | 
						||
| 
								 | 
							
								          if (scope.bindings[name]) {
							 | 
						||
| 
								 | 
							
								            found = true;
							 | 
						||
| 
								 | 
							
								            break;
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								          scope = scope.parent;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if (!found) {
							 | 
						||
| 
								 | 
							
								          invariant(
							 | 
						||
| 
								 | 
							
								            (scope.hasGlobal(name) && WHITELISTED_IDENTIFIERS.has(name)) ||
							 | 
						||
| 
								 | 
							
								            /^mock/i.test(name) || // Allow istanbul's coverage variable to pass.
							 | 
						||
| 
								 | 
							
								              /^(?:__)?cov/.test(name),
							 | 
						||
| 
								 | 
							
								            'The module factory of `jest.mock()` is not allowed to ' +
							 | 
						||
| 
								 | 
							
								              'reference any out-of-scope variables.\n' +
							 | 
						||
| 
								 | 
							
								              'Invalid variable access: ' +
							 | 
						||
| 
								 | 
							
								              name +
							 | 
						||
| 
								 | 
							
								              '\n' +
							 | 
						||
| 
								 | 
							
								              'Whitelisted objects: ' +
							 | 
						||
| 
								 | 
							
								              Array.from(WHITELISTED_IDENTIFIERS).join(', ') +
							 | 
						||
| 
								 | 
							
								              '.\n' +
							 | 
						||
| 
								 | 
							
								              'Note: This is a precaution to guard against uninitialized mock ' +
							 | 
						||
| 
								 | 
							
								              'variables. If it is ensured that the mock is required lazily, ' +
							 | 
						||
| 
								 | 
							
								              'variable names prefixed with `mock` (case insensitive) are permitted.'
							 | 
						||
| 
								 | 
							
								          );
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    } catch (err) {
							 | 
						||
| 
								 | 
							
								      _didIteratorError = true;
							 | 
						||
| 
								 | 
							
								      _iteratorError = err;
							 | 
						||
| 
								 | 
							
								    } finally {
							 | 
						||
| 
								 | 
							
								      try {
							 | 
						||
| 
								 | 
							
								        if (!_iteratorNormalCompletion && _iterator.return != null) {
							 | 
						||
| 
								 | 
							
								          _iterator.return();
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      } finally {
							 | 
						||
| 
								 | 
							
								        if (_didIteratorError) {
							 | 
						||
| 
								 | 
							
								          throw _iteratorError;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    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;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								module.exports = () => {
							 | 
						||
| 
								 | 
							
								  const shouldHoistExpression = expr => {
							 | 
						||
| 
								 | 
							
								    if (!expr.isCallExpression()) {
							 | 
						||
| 
								 | 
							
								      return false;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const callee = expr.get('callee');
							 | 
						||
| 
								 | 
							
								    const expressionArguments = expr.get('arguments'); // TODO: avoid type casts - the types can be arrays (is it possible to ignore that without casting?)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const object = callee.get('object');
							 | 
						||
| 
								 | 
							
								    const property = callee.get('property');
							 | 
						||
| 
								 | 
							
								    return (
							 | 
						||
| 
								 | 
							
								      property.isIdentifier() &&
							 | 
						||
| 
								 | 
							
								      FUNCTIONS[property.node.name] &&
							 | 
						||
| 
								 | 
							
								      (object.isIdentifier(JEST_GLOBAL) ||
							 | 
						||
| 
								 | 
							
								        (callee.isMemberExpression() && shouldHoistExpression(object))) &&
							 | 
						||
| 
								 | 
							
								      FUNCTIONS[property.node.name](expressionArguments)
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								  };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const visitor = {
							 | 
						||
| 
								 | 
							
								    ExpressionStatement(path) {
							 | 
						||
| 
								 | 
							
								      if (shouldHoistExpression(path.get('expression'))) {
							 | 
						||
| 
								 | 
							
								        // @ts-ignore: private, magical property
							 | 
						||
| 
								 | 
							
								        path.node._blockHoist = Infinity;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  };
							 | 
						||
| 
								 | 
							
								  return {
							 | 
						||
| 
								 | 
							
								    visitor
							 | 
						||
| 
								 | 
							
								  };
							 | 
						||
| 
								 | 
							
								};
							 |