index.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. 'use strict'; /**
  2. * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. *
  7. *
  8. */
  9. function invariant(condition, message) {
  10. if (!condition) {
  11. throw new Error('babel-plugin-jest-hoist: ' + message);
  12. }
  13. }
  14. // We allow `jest`, `expect`, `require`, all default Node.js globals and all
  15. // ES2015 built-ins to be used inside of a `jest.mock` factory.
  16. // We also allow variables prefixed with `mock` as an escape-hatch.
  17. const WHITELISTED_IDENTIFIERS = {
  18. Array: true,
  19. ArrayBuffer: true,
  20. Boolean: true,
  21. DataView: true,
  22. Date: true,
  23. Error: true,
  24. EvalError: true,
  25. Float32Array: true,
  26. Float64Array: true,
  27. Function: true,
  28. Generator: true,
  29. GeneratorFunction: true,
  30. Infinity: true,
  31. Int16Array: true,
  32. Int32Array: true,
  33. Int8Array: true,
  34. InternalError: true,
  35. Intl: true,
  36. JSON: true,
  37. Map: true,
  38. Math: true,
  39. NaN: true,
  40. Number: true,
  41. Object: true,
  42. Promise: true,
  43. Proxy: true,
  44. RangeError: true,
  45. ReferenceError: true,
  46. Reflect: true,
  47. RegExp: true,
  48. Set: true,
  49. String: true,
  50. Symbol: true,
  51. SyntaxError: true,
  52. TypeError: true,
  53. URIError: true,
  54. Uint16Array: true,
  55. Uint32Array: true,
  56. Uint8Array: true,
  57. Uint8ClampedArray: true,
  58. WeakMap: true,
  59. WeakSet: true,
  60. arguments: true,
  61. expect: true,
  62. jest: true,
  63. require: true,
  64. undefined: true };
  65. Object.keys(global).forEach(name => WHITELISTED_IDENTIFIERS[name] = true);
  66. const JEST_GLOBAL = { name: 'jest' };
  67. const IDVisitor = {
  68. ReferencedIdentifier(path) {
  69. this.ids.add(path);
  70. },
  71. blacklist: ['TypeAnnotation'] };
  72. const FUNCTIONS = Object.create(null);
  73. FUNCTIONS.mock = args => {
  74. if (args.length === 1) {
  75. return args[0].isStringLiteral();
  76. } else if (args.length === 2 || args.length === 3) {
  77. const moduleFactory = args[1];
  78. invariant(
  79. moduleFactory.isFunction(),
  80. 'The second argument of `jest.mock` must be a function.');
  81. const ids = new Set();
  82. const parentScope = moduleFactory.parentPath.scope;
  83. moduleFactory.traverse(IDVisitor, { ids });
  84. for (const id of ids) {
  85. const name = id.node.name;
  86. let found = false;
  87. let scope = id.scope;
  88. while (scope !== parentScope) {
  89. if (scope.bindings[name]) {
  90. found = true;
  91. break;
  92. }
  93. scope = scope.parent;
  94. }
  95. if (!found) {
  96. invariant(
  97. scope.hasGlobal(name) && WHITELISTED_IDENTIFIERS[name] ||
  98. /^mock/.test(name) ||
  99. // Allow istanbul's coverage variable to pass.
  100. /^(?:__)?cov/.test(name),
  101. 'The module factory of `jest.mock()` is not allowed to ' +
  102. 'reference any out-of-scope variables.\n' +
  103. 'Invalid variable access: ' +
  104. name +
  105. '\n' +
  106. 'Whitelisted objects: ' +
  107. Object.keys(WHITELISTED_IDENTIFIERS).join(', ') +
  108. '.\n' +
  109. 'Note: This is a precaution to guard against uninitialized mock ' +
  110. 'variables. If it is ensured that the mock is required lazily, ' +
  111. 'variable names prefixed with `mock` are permitted.');
  112. }
  113. }
  114. return true;
  115. }
  116. return false;
  117. };
  118. FUNCTIONS.unmock = args => args.length === 1 && args[0].isStringLiteral();
  119. FUNCTIONS.deepUnmock = args => args.length === 1 && args[0].isStringLiteral();
  120. FUNCTIONS.disableAutomock = FUNCTIONS.enableAutomock = args =>
  121. args.length === 0;
  122. module.exports = () => {
  123. const isJest = callee =>
  124. callee.get('object').isIdentifier(JEST_GLOBAL) ||
  125. callee.isMemberExpression() && isJest(callee.get('object'));
  126. const shouldHoistExpression = expr => {
  127. if (!expr.isCallExpression()) {
  128. return false;
  129. }
  130. const callee = expr.get('callee');
  131. const object = callee.get('object');
  132. const property = callee.get('property');
  133. return (
  134. property.isIdentifier() &&
  135. FUNCTIONS[property.node.name] && (
  136. object.isIdentifier(JEST_GLOBAL) ||
  137. callee.isMemberExpression() && shouldHoistExpression(object)) &&
  138. FUNCTIONS[property.node.name](expr.get('arguments')));
  139. };
  140. return {
  141. visitor: {
  142. ExpressionStatement(path) {
  143. if (shouldHoistExpression(path.get('expression'))) {
  144. path.node._blockHoist = Infinity;
  145. }
  146. } } };
  147. };