index.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. 'use strict';
  2. /**
  3. * Copyright (c) Facebook, Inc. and its affiliates. 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. // Only used for types
  10. // eslint-disable-next-line
  11. // eslint-disable-next-line
  12. const invariant = (condition, message) => {
  13. if (!condition) {
  14. throw new Error('babel-plugin-jest-hoist: ' + message);
  15. }
  16. }; // We allow `jest`, `expect`, `require`, all default Node.js globals and all
  17. // ES2015 built-ins to be used inside of a `jest.mock` factory.
  18. // We also allow variables prefixed with `mock` as an escape-hatch.
  19. const WHITELISTED_IDENTIFIERS = new Set([
  20. 'Array',
  21. 'ArrayBuffer',
  22. 'Boolean',
  23. 'DataView',
  24. 'Date',
  25. 'Error',
  26. 'EvalError',
  27. 'Float32Array',
  28. 'Float64Array',
  29. 'Function',
  30. 'Generator',
  31. 'GeneratorFunction',
  32. 'Infinity',
  33. 'Int16Array',
  34. 'Int32Array',
  35. 'Int8Array',
  36. 'InternalError',
  37. 'Intl',
  38. 'JSON',
  39. 'Map',
  40. 'Math',
  41. 'NaN',
  42. 'Number',
  43. 'Object',
  44. 'Promise',
  45. 'Proxy',
  46. 'RangeError',
  47. 'ReferenceError',
  48. 'Reflect',
  49. 'RegExp',
  50. 'Set',
  51. 'String',
  52. 'Symbol',
  53. 'SyntaxError',
  54. 'TypeError',
  55. 'URIError',
  56. 'Uint16Array',
  57. 'Uint32Array',
  58. 'Uint8Array',
  59. 'Uint8ClampedArray',
  60. 'WeakMap',
  61. 'WeakSet',
  62. 'arguments',
  63. 'console',
  64. 'expect',
  65. 'isNaN',
  66. 'jest',
  67. 'parseFloat',
  68. 'parseInt',
  69. 'require',
  70. 'undefined'
  71. ]);
  72. Object.getOwnPropertyNames(global).forEach(name => {
  73. WHITELISTED_IDENTIFIERS.add(name);
  74. });
  75. const JEST_GLOBAL = {
  76. name: 'jest'
  77. }; // TODO: Should be Visitor<{ids: Set<NodePath<Identifier>>}>, but `ReferencedIdentifier` doesn't exist
  78. const IDVisitor = {
  79. ReferencedIdentifier(path) {
  80. // @ts-ignore: passed as Visitor State
  81. this.ids.add(path);
  82. },
  83. blacklist: ['TypeAnnotation', 'TSTypeAnnotation', 'TSTypeReference']
  84. };
  85. const FUNCTIONS = Object.create(null);
  86. FUNCTIONS.mock = args => {
  87. if (args.length === 1) {
  88. return args[0].isStringLiteral() || args[0].isLiteral();
  89. } else if (args.length === 2 || args.length === 3) {
  90. const moduleFactory = args[1];
  91. invariant(
  92. moduleFactory.isFunction(),
  93. 'The second argument of `jest.mock` must be an inline function.'
  94. );
  95. const ids = new Set();
  96. const parentScope = moduleFactory.parentPath.scope; // @ts-ignore: Same as above: ReferencedIdentifier doesn't exist
  97. moduleFactory.traverse(IDVisitor, {
  98. ids
  99. });
  100. var _iteratorNormalCompletion = true;
  101. var _didIteratorError = false;
  102. var _iteratorError = undefined;
  103. try {
  104. for (
  105. var _iterator = ids[Symbol.iterator](), _step;
  106. !(_iteratorNormalCompletion = (_step = _iterator.next()).done);
  107. _iteratorNormalCompletion = true
  108. ) {
  109. const id = _step.value;
  110. const name = id.node.name;
  111. let found = false;
  112. let scope = id.scope;
  113. while (scope !== parentScope) {
  114. if (scope.bindings[name]) {
  115. found = true;
  116. break;
  117. }
  118. scope = scope.parent;
  119. }
  120. if (!found) {
  121. invariant(
  122. (scope.hasGlobal(name) && WHITELISTED_IDENTIFIERS.has(name)) ||
  123. /^mock/i.test(name) || // Allow istanbul's coverage variable to pass.
  124. /^(?:__)?cov/.test(name),
  125. 'The module factory of `jest.mock()` is not allowed to ' +
  126. 'reference any out-of-scope variables.\n' +
  127. 'Invalid variable access: ' +
  128. name +
  129. '\n' +
  130. 'Whitelisted objects: ' +
  131. Array.from(WHITELISTED_IDENTIFIERS).join(', ') +
  132. '.\n' +
  133. 'Note: This is a precaution to guard against uninitialized mock ' +
  134. 'variables. If it is ensured that the mock is required lazily, ' +
  135. 'variable names prefixed with `mock` (case insensitive) are permitted.'
  136. );
  137. }
  138. }
  139. } catch (err) {
  140. _didIteratorError = true;
  141. _iteratorError = err;
  142. } finally {
  143. try {
  144. if (!_iteratorNormalCompletion && _iterator.return != null) {
  145. _iterator.return();
  146. }
  147. } finally {
  148. if (_didIteratorError) {
  149. throw _iteratorError;
  150. }
  151. }
  152. }
  153. return true;
  154. }
  155. return false;
  156. };
  157. FUNCTIONS.unmock = args => args.length === 1 && args[0].isStringLiteral();
  158. FUNCTIONS.deepUnmock = args => args.length === 1 && args[0].isStringLiteral();
  159. FUNCTIONS.disableAutomock = FUNCTIONS.enableAutomock = args =>
  160. args.length === 0;
  161. module.exports = () => {
  162. const shouldHoistExpression = expr => {
  163. if (!expr.isCallExpression()) {
  164. return false;
  165. }
  166. const callee = expr.get('callee');
  167. const expressionArguments = expr.get('arguments'); // TODO: avoid type casts - the types can be arrays (is it possible to ignore that without casting?)
  168. const object = callee.get('object');
  169. const property = callee.get('property');
  170. return (
  171. property.isIdentifier() &&
  172. FUNCTIONS[property.node.name] &&
  173. (object.isIdentifier(JEST_GLOBAL) ||
  174. (callee.isMemberExpression() && shouldHoistExpression(object))) &&
  175. FUNCTIONS[property.node.name](expressionArguments)
  176. );
  177. };
  178. const visitor = {
  179. ExpressionStatement(path) {
  180. if (shouldHoistExpression(path.get('expression'))) {
  181. // @ts-ignore: private, magical property
  182. path.node._blockHoist = Infinity;
  183. }
  184. }
  185. };
  186. return {
  187. visitor
  188. };
  189. };