visit.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. /**
  2. * Copyright (c) 2014, Facebook, Inc.
  3. * All rights reserved.
  4. *
  5. * This source code is licensed under the BSD-style license found in the
  6. * https://raw.github.com/facebook/regenerator/master/LICENSE file. An
  7. * additional grant of patent rights can be found in the PATENTS file in
  8. * the same directory.
  9. */
  10. var assert = require("assert");
  11. var fs = require("fs");
  12. var types = require("ast-types");
  13. var n = types.namedTypes;
  14. var b = types.builders;
  15. var isArray = types.builtInTypes.array;
  16. var isObject = types.builtInTypes.object;
  17. var NodePath = types.NodePath;
  18. var hoist = require("./hoist").hoist;
  19. var Emitter = require("./emit").Emitter;
  20. var runtimeProperty = require("./util").runtimeProperty;
  21. exports.transform = function transform(node, options) {
  22. options = options || {};
  23. var path = node instanceof NodePath ? node : new NodePath(node);
  24. visitor.visit(path, options);
  25. node = path.value;
  26. options.madeChanges = visitor.wasChangeReported();
  27. return node;
  28. };
  29. var visitor = types.PathVisitor.fromMethodsObject({
  30. reset: function(node, options) {
  31. this.options = options;
  32. },
  33. visitFunction: function(path) {
  34. // Calling this.traverse(path) first makes for a post-order traversal.
  35. this.traverse(path);
  36. var node = path.value;
  37. var shouldTransformAsync = node.async && !this.options.disableAsync;
  38. if (!node.generator && !shouldTransformAsync) {
  39. return;
  40. }
  41. this.reportChanged();
  42. node.generator = false;
  43. if (node.expression) {
  44. // Transform expression lambdas into normal functions.
  45. node.expression = false;
  46. node.body = b.blockStatement([
  47. b.returnStatement(node.body)
  48. ]);
  49. }
  50. if (shouldTransformAsync) {
  51. awaitVisitor.visit(path.get("body"));
  52. }
  53. var outerFnId = node.id || (
  54. node.id = path.scope.parent.declareTemporary("callee$")
  55. );
  56. var outerBody = [];
  57. var bodyBlock = path.value.body;
  58. bodyBlock.body = bodyBlock.body.filter(function (node) {
  59. if (node && node._blockHoist != null) {
  60. outerBody.push(node);
  61. return false;
  62. } else {
  63. return true;
  64. }
  65. });
  66. var innerFnId = b.identifier(node.id.name + "$");
  67. var contextId = path.scope.declareTemporary("context$");
  68. var vars = hoist(path);
  69. var emitter = new Emitter(contextId);
  70. emitter.explode(path.get("body"));
  71. if (vars && vars.declarations.length > 0) {
  72. outerBody.push(vars);
  73. }
  74. var wrapArgs = [
  75. emitter.getContextFunction(innerFnId),
  76. // Async functions don't care about the outer function because they
  77. // don't need it to be marked and don't inherit from its .prototype.
  78. shouldTransformAsync ? b.literal(null) : outerFnId,
  79. b.thisExpression()
  80. ];
  81. var tryLocsList = emitter.getTryLocsList();
  82. if (tryLocsList) {
  83. wrapArgs.push(tryLocsList);
  84. }
  85. var wrapCall = b.callExpression(
  86. shouldTransformAsync ? runtimeProperty("async") : runtimeProperty("wrap"),
  87. wrapArgs
  88. );
  89. outerBody.push(b.returnStatement(wrapCall));
  90. node.body = b.blockStatement(outerBody);
  91. if (shouldTransformAsync) {
  92. node.async = false;
  93. return;
  94. }
  95. if (n.FunctionDeclaration.check(node)) {
  96. var pp = path.parent;
  97. while (pp && !(n.BlockStatement.check(pp.value) ||
  98. n.Program.check(pp.value))) {
  99. pp = pp.parent;
  100. }
  101. if (!pp) {
  102. return;
  103. }
  104. // Here we turn the FunctionDeclaration into a named
  105. // FunctionExpression that will be assigned to a variable of the
  106. // same name at the top of the enclosing block. This is important
  107. // for a very subtle reason: named function expressions can refer to
  108. // themselves by name without fear that the binding may change due
  109. // to code executing outside the function, whereas function
  110. // declarations are vulnerable to the following rebinding:
  111. //
  112. // function f() { return f }
  113. // var g = f;
  114. // f = "asdf";
  115. // g(); // "asdf"
  116. //
  117. // One way to prevent the problem illustrated above is to transform
  118. // the function declaration thus:
  119. //
  120. // var f = function f() { return f };
  121. // var g = f;
  122. // f = "asdf";
  123. // g(); // f
  124. // g()()()()(); // f
  125. //
  126. // In the code below, we transform generator function declarations
  127. // in the following way:
  128. //
  129. // gen().next(); // { value: gen, done: true }
  130. // function *gen() {
  131. // return gen;
  132. // }
  133. //
  134. // becomes something like
  135. //
  136. // var gen = runtime.mark(function *gen() {
  137. // return gen;
  138. // });
  139. // gen().next(); // { value: gen, done: true }
  140. //
  141. // which ensures that the generator body can always reliably refer
  142. // to gen by name.
  143. // Remove the FunctionDeclaration so that we can add it back as a
  144. // FunctionExpression passed to runtime.mark.
  145. path.replace();
  146. // Change the type of the function to be an expression instead of a
  147. // declaration. Note that all the other fields are the same.
  148. node.type = "FunctionExpression";
  149. var varDecl = b.variableDeclaration("var", [
  150. b.variableDeclarator(
  151. node.id,
  152. b.callExpression(runtimeProperty("mark"), [node])
  153. )
  154. ]);
  155. if (node.comments) {
  156. // Copy any comments preceding the function declaration to the
  157. // variable declaration, to avoid weird formatting consequences.
  158. varDecl.leadingComments = node.leadingComments;
  159. varDecl.trailingComments = node.trailingComments;
  160. node.leadingComments = null;
  161. node.trailingComments = null;
  162. }
  163. varDecl._blockHoist = 3;
  164. var bodyPath = pp.get("body");
  165. var bodyLen = bodyPath.value.length;
  166. bodyPath.push(varDecl);
  167. } else {
  168. n.FunctionExpression.assert(node);
  169. return b.callExpression(runtimeProperty("mark"), [node]);
  170. }
  171. }
  172. });
  173. function shouldNotHoistAbove(stmtPath) {
  174. var value = stmtPath.value;
  175. n.Statement.assert(value);
  176. // If the first statement is a "use strict" declaration, make sure to
  177. // insert hoisted declarations afterwards.
  178. if (n.ExpressionStatement.check(value) &&
  179. n.Literal.check(value.expression) &&
  180. value.expression.value === "use strict") {
  181. return true;
  182. }
  183. if (n.VariableDeclaration.check(value)) {
  184. for (var i = 0; i < value.declarations.length; ++i) {
  185. var decl = value.declarations[i];
  186. if (n.CallExpression.check(decl.init) &&
  187. types.astNodesAreEquivalent(decl.init.callee,
  188. runtimeProperty("mark"))) {
  189. return true;
  190. }
  191. }
  192. }
  193. return false;
  194. }
  195. var awaitVisitor = types.PathVisitor.fromMethodsObject({
  196. visitFunction: function(path) {
  197. return false; // Don't descend into nested function scopes.
  198. },
  199. visitAwaitExpression: function(path) {
  200. // Convert await and await* expressions to yield expressions.
  201. var argument = path.value.argument;
  202. // If the parser supports await* syntax using a boolean .all property
  203. // (#171), desugar that syntax to yield Promise.all(argument).
  204. if (path.value.all) {
  205. argument = b.callExpression(
  206. b.memberExpression(
  207. b.identifier("Promise"),
  208. b.identifier("all"),
  209. false
  210. ),
  211. [argument]
  212. );
  213. }
  214. return b.yieldExpression(argument, false);
  215. }
  216. });