visit.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. /**
  2. * Copyright (c) 2014-present, Facebook, Inc.
  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. "use strict";
  8. import assert from "assert";
  9. import { hoist } from "./hoist";
  10. import { Emitter } from "./emit";
  11. import replaceShorthandObjectMethod from "./replaceShorthandObjectMethod";
  12. import * as util from "./util";
  13. exports.getVisitor = ({ types: t }) => ({
  14. Method(path, state) {
  15. let node = path.node;
  16. if (!shouldRegenerate(node, state)) return;
  17. const container = t.functionExpression(
  18. null,
  19. [],
  20. t.cloneNode(node.body, false),
  21. node.generator,
  22. node.async,
  23. );
  24. path.get("body").set("body", [
  25. t.returnStatement(
  26. t.callExpression(container, []),
  27. ),
  28. ]);
  29. // Regardless of whether or not the wrapped function is a an async method
  30. // or generator the outer function should not be
  31. node.async = false;
  32. node.generator = false;
  33. // Unwrap the wrapper IIFE's environment so super and this and such still work.
  34. path
  35. .get("body.body.0.argument.callee")
  36. .unwrapFunctionEnvironment();
  37. },
  38. Function: {
  39. exit: util.wrapWithTypes(t, function(path, state) {
  40. let node = path.node;
  41. if (!shouldRegenerate(node, state)) return;
  42. // if this is an ObjectMethod, we need to convert it to an ObjectProperty
  43. path = replaceShorthandObjectMethod(path);
  44. node = path.node;
  45. let contextId = path.scope.generateUidIdentifier("context");
  46. let argsId = path.scope.generateUidIdentifier("args");
  47. path.ensureBlock();
  48. let bodyBlockPath = path.get("body");
  49. if (node.async) {
  50. bodyBlockPath.traverse(awaitVisitor);
  51. }
  52. bodyBlockPath.traverse(functionSentVisitor, {
  53. context: contextId
  54. });
  55. let outerBody = [];
  56. let innerBody = [];
  57. bodyBlockPath.get("body").forEach(function(childPath) {
  58. let node = childPath.node;
  59. if (t.isExpressionStatement(node) &&
  60. t.isStringLiteral(node.expression)) {
  61. // Babylon represents directives like "use strict" as elements
  62. // of a bodyBlockPath.node.directives array, but they could just
  63. // as easily be represented (by other parsers) as traditional
  64. // string-literal-valued expression statements, so we need to
  65. // handle that here. (#248)
  66. outerBody.push(node);
  67. } else if (node && node._blockHoist != null) {
  68. outerBody.push(node);
  69. } else {
  70. innerBody.push(node);
  71. }
  72. });
  73. if (outerBody.length > 0) {
  74. // Only replace the inner body if we actually hoisted any statements
  75. // to the outer body.
  76. bodyBlockPath.node.body = innerBody;
  77. }
  78. let outerFnExpr = getOuterFnExpr(path);
  79. // Note that getOuterFnExpr has the side-effect of ensuring that the
  80. // function has a name (so node.id will always be an Identifier), even
  81. // if a temporary name has to be synthesized.
  82. t.assertIdentifier(node.id);
  83. let innerFnId = t.identifier(node.id.name + "$");
  84. // Turn all declarations into vars, and replace the original
  85. // declarations with equivalent assignment expressions.
  86. let vars = hoist(path);
  87. let context = {
  88. usesThis: false,
  89. usesArguments: false,
  90. getArgsId: () => t.clone(argsId),
  91. };
  92. path.traverse(argumentsThisVisitor, context);
  93. if (context.usesArguments) {
  94. vars = vars || t.variableDeclaration("var", []);
  95. vars.declarations.push(t.variableDeclarator(
  96. t.clone(argsId),
  97. t.identifier("arguments"),
  98. ));
  99. }
  100. let emitter = new Emitter(contextId);
  101. emitter.explode(path.get("body"));
  102. if (vars && vars.declarations.length > 0) {
  103. outerBody.push(vars);
  104. }
  105. let wrapArgs = [emitter.getContextFunction(innerFnId)];
  106. let tryLocsList = emitter.getTryLocsList();
  107. if (node.generator) {
  108. wrapArgs.push(outerFnExpr);
  109. } else if (context.usesThis || tryLocsList || node.async) {
  110. // Async functions that are not generators don't care about the
  111. // outer function because they don't need it to be marked and don't
  112. // inherit from its .prototype.
  113. wrapArgs.push(t.nullLiteral());
  114. }
  115. if (context.usesThis) {
  116. wrapArgs.push(t.thisExpression());
  117. } else if (tryLocsList || node.async) {
  118. wrapArgs.push(t.nullLiteral());
  119. }
  120. if (tryLocsList) {
  121. wrapArgs.push(tryLocsList);
  122. } else if (node.async) {
  123. wrapArgs.push(t.nullLiteral());
  124. }
  125. if (node.async) {
  126. // Rename any locally declared "Promise" variable,
  127. // to use the global one.
  128. let currentScope = path.scope;
  129. do {
  130. if (currentScope.hasOwnBinding("Promise")) currentScope.rename("Promise");
  131. } while (currentScope = currentScope.parent);
  132. wrapArgs.push(t.identifier("Promise"));
  133. }
  134. let wrapCall = t.callExpression(
  135. util.runtimeProperty(node.async ? "async" : "wrap"),
  136. wrapArgs
  137. );
  138. outerBody.push(t.returnStatement(wrapCall));
  139. node.body = t.blockStatement(outerBody);
  140. // We injected a few new variable declarations (for every hoisted var),
  141. // so we need to add them to the scope.
  142. path.get("body.body").forEach(p => p.scope.registerDeclaration(p));
  143. const oldDirectives = bodyBlockPath.node.directives;
  144. if (oldDirectives) {
  145. // Babylon represents directives like "use strict" as elements of
  146. // a bodyBlockPath.node.directives array. (#248)
  147. node.body.directives = oldDirectives;
  148. }
  149. let wasGeneratorFunction = node.generator;
  150. if (wasGeneratorFunction) {
  151. node.generator = false;
  152. }
  153. if (node.async) {
  154. node.async = false;
  155. }
  156. if (wasGeneratorFunction && t.isExpression(node)) {
  157. util.replaceWithOrRemove(path, t.callExpression(util.runtimeProperty("mark"), [node]))
  158. path.addComment("leading", "#__PURE__");
  159. }
  160. const insertedLocs = emitter.getInsertedLocs();
  161. path.traverse({
  162. NumericLiteral(path) {
  163. if (!insertedLocs.has(path.node)) {
  164. return;
  165. }
  166. path.replaceWith(t.numericLiteral(path.node.value));
  167. },
  168. })
  169. // Generators are processed in 'exit' handlers so that regenerator only has to run on
  170. // an ES5 AST, but that means traversal will not pick up newly inserted references
  171. // to things like 'regeneratorRuntime'. To avoid this, we explicitly requeue.
  172. path.requeue();
  173. })
  174. }
  175. });
  176. // Check if a node should be transformed by regenerator
  177. function shouldRegenerate(node, state) {
  178. if (node.generator) {
  179. if (node.async) {
  180. // Async generator
  181. return state.opts.asyncGenerators !== false;
  182. } else {
  183. // Plain generator
  184. return state.opts.generators !== false;
  185. }
  186. } else if (node.async) {
  187. // Async function
  188. return state.opts.async !== false;
  189. } else {
  190. // Not a generator or async function.
  191. return false;
  192. }
  193. }
  194. // Given a NodePath for a Function, return an Expression node that can be
  195. // used to refer reliably to the function object from inside the function.
  196. // This expression is essentially a replacement for arguments.callee, with
  197. // the key advantage that it works in strict mode.
  198. function getOuterFnExpr(funPath) {
  199. const t = util.getTypes();
  200. let node = funPath.node;
  201. t.assertFunction(node);
  202. if (!node.id) {
  203. // Default-exported function declarations, and function expressions may not
  204. // have a name to reference, so we explicitly add one.
  205. node.id = funPath.scope.parent.generateUidIdentifier("callee");
  206. }
  207. if (node.generator && // Non-generator functions don't need to be marked.
  208. t.isFunctionDeclaration(node)) {
  209. // Return the identifier returned by runtime.mark(<node.id>).
  210. return getMarkedFunctionId(funPath);
  211. }
  212. return t.clone(node.id);
  213. }
  214. const markInfo = new WeakMap();
  215. function getMarkInfo(node) {
  216. if (!markInfo.has(node)) {
  217. markInfo.set(node, {});
  218. }
  219. return markInfo.get(node);
  220. }
  221. function getMarkedFunctionId(funPath) {
  222. const t = util.getTypes();
  223. const node = funPath.node;
  224. t.assertIdentifier(node.id);
  225. const blockPath = funPath.findParent(function (path) {
  226. return path.isProgram() || path.isBlockStatement();
  227. });
  228. if (!blockPath) {
  229. return node.id;
  230. }
  231. const block = blockPath.node;
  232. assert.ok(Array.isArray(block.body));
  233. const info = getMarkInfo(block);
  234. if (!info.decl) {
  235. info.decl = t.variableDeclaration("var", []);
  236. blockPath.unshiftContainer("body", info.decl);
  237. info.declPath = blockPath.get("body.0");
  238. }
  239. assert.strictEqual(info.declPath.node, info.decl);
  240. // Get a new unique identifier for our marked variable.
  241. const markedId = blockPath.scope.generateUidIdentifier("marked");
  242. const markCallExp = t.callExpression(
  243. util.runtimeProperty("mark"),
  244. [t.clone(node.id)]
  245. );
  246. const index = info.decl.declarations.push(
  247. t.variableDeclarator(markedId, markCallExp)
  248. ) - 1;
  249. const markCallExpPath =
  250. info.declPath.get("declarations." + index + ".init");
  251. assert.strictEqual(markCallExpPath.node, markCallExp);
  252. markCallExpPath.addComment("leading", "#__PURE__");
  253. return t.clone(markedId);
  254. }
  255. let argumentsThisVisitor = {
  256. "FunctionExpression|FunctionDeclaration|Method": function(path) {
  257. path.skip();
  258. },
  259. Identifier: function(path, state) {
  260. if (path.node.name === "arguments" && util.isReference(path)) {
  261. util.replaceWithOrRemove(path, state.getArgsId());
  262. state.usesArguments = true;
  263. }
  264. },
  265. ThisExpression: function(path, state) {
  266. state.usesThis = true;
  267. }
  268. };
  269. let functionSentVisitor = {
  270. MetaProperty(path) {
  271. let { node } = path;
  272. if (node.meta.name === "function" &&
  273. node.property.name === "sent") {
  274. const t = util.getTypes();
  275. util.replaceWithOrRemove(
  276. path,
  277. t.memberExpression(
  278. t.clone(this.context),
  279. t.identifier("_sent")
  280. )
  281. );
  282. }
  283. }
  284. };
  285. let awaitVisitor = {
  286. Function: function(path) {
  287. path.skip(); // Don't descend into nested function scopes.
  288. },
  289. AwaitExpression: function(path) {
  290. const t = util.getTypes();
  291. // Convert await expressions to yield expressions.
  292. let argument = path.node.argument;
  293. // Transforming `await x` to `yield regeneratorRuntime.awrap(x)`
  294. // causes the argument to be wrapped in such a way that the runtime
  295. // can distinguish between awaited and merely yielded values.
  296. util.replaceWithOrRemove(path, t.yieldExpression(
  297. t.callExpression(
  298. util.runtimeProperty("awrap"),
  299. [argument]
  300. ),
  301. false
  302. ));
  303. }
  304. };