index.js 4.4 KB

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