UNPKG

7.71 kBJavaScriptView Raw
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
11import assert from "assert";
12import * as t from "babel-types";
13import { hoist } from "./hoist";
14import { Emitter } from "./emit";
15import * as util from "./util";
16
17let getMarkInfo = require("private").makeAccessor();
18
19exports.visitor = {
20 Function: {
21 exit: function(path, state) {
22 let node = path.node;
23
24 if (node.generator) {
25 if (node.async) {
26 // Async generator
27 if (state.opts.asyncGenerators === false) return;
28 } else {
29 // Plain generator
30 if (state.opts.generators === false) return;
31 }
32 } else if (node.async) {
33 // Async function
34 if (state.opts.async === false) return;
35 } else {
36 // Not a generator or async function.
37 return;
38 }
39
40 let contextId = path.scope.generateUidIdentifier("context");
41 let argsId = path.scope.generateUidIdentifier("args");
42
43 path.ensureBlock();
44 let bodyBlockPath = path.get("body");
45
46 if (node.async) {
47 bodyBlockPath.traverse(awaitVisitor);
48 }
49
50 bodyBlockPath.traverse(functionSentVisitor, {
51 context: contextId
52 });
53
54 let outerBody = [];
55 let innerBody = [];
56
57 bodyBlockPath.get("body").forEach(function(childPath) {
58 let node = childPath.node;
59 if (node && node._blockHoist != null) {
60 outerBody.push(node);
61 } else {
62 innerBody.push(node);
63 }
64 });
65
66 if (outerBody.length > 0) {
67 // Only replace the inner body if we actually hoisted any statements
68 // to the outer body.
69 bodyBlockPath.node.body = innerBody;
70 }
71
72 let outerFnExpr = getOuterFnExpr(path);
73 // Note that getOuterFnExpr has the side-effect of ensuring that the
74 // function has a name (so node.id will always be an Identifier), even
75 // if a temporary name has to be synthesized.
76 t.assertIdentifier(node.id);
77 let innerFnId = t.identifier(node.id.name + "$");
78
79 // Turn all declarations into vars, and replace the original
80 // declarations with equivalent assignment expressions.
81 let vars = hoist(path);
82
83 let didRenameArguments = renameArguments(path, argsId);
84 if (didRenameArguments) {
85 vars = vars || t.variableDeclaration("var", []);
86 vars.declarations.push(t.variableDeclarator(
87 argsId, t.identifier("arguments")
88 ));
89 }
90
91 let emitter = new Emitter(contextId);
92 emitter.explode(path.get("body"));
93
94 if (vars && vars.declarations.length > 0) {
95 outerBody.push(vars);
96 }
97
98 let wrapArgs = [
99 emitter.getContextFunction(innerFnId),
100 // Async functions that are not generators don't care about the
101 // outer function because they don't need it to be marked and don't
102 // inherit from its .prototype.
103 node.generator ? outerFnExpr : t.nullLiteral(),
104 t.thisExpression()
105 ];
106
107 let tryLocsList = emitter.getTryLocsList();
108 if (tryLocsList) {
109 wrapArgs.push(tryLocsList);
110 }
111
112 let wrapCall = t.callExpression(
113 util.runtimeProperty(node.async ? "async" : "wrap"),
114 wrapArgs
115 );
116
117 outerBody.push(t.returnStatement(wrapCall));
118 node.body = t.blockStatement(outerBody);
119
120 let wasGeneratorFunction = node.generator;
121 if (wasGeneratorFunction) {
122 node.generator = false;
123 }
124
125 if (node.async) {
126 node.async = false;
127 }
128
129 if (wasGeneratorFunction && t.isExpression(node)) {
130 path.replaceWith(t.callExpression(util.runtimeProperty("mark"), [node]));
131 }
132
133 // Generators are processed in 'exit' handlers so that regenerator only has to run on
134 // an ES5 AST, but that means traversal will not pick up newly inserted references
135 // to things like 'regeneratorRuntime'. To avoid this, we explicitly requeue.
136 path.requeue();
137 }
138 }
139};
140
141// Given a NodePath for a Function, return an Expression node that can be
142// used to refer reliably to the function object from inside the function.
143// This expression is essentially a replacement for arguments.callee, with
144// the key advantage that it works in strict mode.
145function getOuterFnExpr(funPath) {
146 let node = funPath.node;
147 t.assertFunction(node);
148
149 if (!node.id){
150 // Default-exported function declarations, and function expressions may not
151 // have a name to reference, so we explicitly add one.
152 node.id = funPath.scope.parent.generateUidIdentifier("callee");
153 }
154
155 if (node.generator && // Non-generator functions don't need to be marked.
156 t.isFunctionDeclaration(node)) {
157 let pp = funPath.findParent(function (path) {
158 return path.isProgram() || path.isBlockStatement();
159 });
160
161 if (!pp) {
162 return node.id;
163 }
164
165 let markDecl = getRuntimeMarkDecl(pp);
166 let markedArray = markDecl.declarations[0].id;
167 let funDeclIdArray = markDecl.declarations[0].init.callee.object;
168 t.assertArrayExpression(funDeclIdArray);
169
170 let index = funDeclIdArray.elements.length;
171 funDeclIdArray.elements.push(node.id);
172
173 return t.memberExpression(
174 markedArray,
175 t.numericLiteral(index),
176 true
177 );
178 }
179
180 return node.id;
181}
182
183function getRuntimeMarkDecl(blockPath) {
184 let block = blockPath.node;
185 assert.ok(Array.isArray(block.body));
186
187 let info = getMarkInfo(block);
188 if (info.decl) {
189 return info.decl;
190 }
191
192 info.decl = t.variableDeclaration("var", [
193 t.variableDeclarator(
194 blockPath.scope.generateUidIdentifier("marked"),
195 t.callExpression(
196 t.memberExpression(
197 t.arrayExpression([]),
198 t.identifier("map"),
199 false
200 ),
201 [util.runtimeProperty("mark")]
202 )
203 )
204 ]);
205
206 blockPath.unshiftContainer("body", info.decl);
207
208 return info.decl;
209}
210
211function renameArguments(funcPath, argsId) {
212 let state = {
213 didRenameArguments: false,
214 argsId: argsId
215 };
216
217 funcPath.traverse(argumentsVisitor, state);
218
219 // If the traversal replaced any arguments references, then we need to
220 // alias the outer function's arguments binding (be it the implicit
221 // arguments object or some other parameter or variable) to the variable
222 // named by argsId.
223 return state.didRenameArguments;
224}
225
226let argumentsVisitor = {
227 "FunctionExpression|FunctionDeclaration": function(path) {
228 path.skip();
229 },
230
231 Identifier: function(path, state) {
232 if (path.node.name === "arguments" && util.isReference(path)) {
233 path.replaceWith(state.argsId);
234 state.didRenameArguments = true;
235 }
236 }
237};
238
239let functionSentVisitor = {
240 MetaProperty(path) {
241 let { node } = path;
242
243 if (node.meta.name === "function" && node.property.name === "sent") {
244 path.replaceWith(t.memberExpression(this.context, t.identifier("_sent")));
245 }
246 }
247};
248
249let awaitVisitor = {
250 Function: function(path) {
251 path.skip(); // Don't descend into nested function scopes.
252 },
253
254 AwaitExpression: function(path) {
255 // Convert await expressions to yield expressions.
256 let argument = path.node.argument;
257
258 // Transforming `await x` to `yield regeneratorRuntime.awrap(x)`
259 // causes the argument to be wrapped in such a way that the runtime
260 // can distinguish between awaited and merely yielded values.
261 path.replaceWith(t.yieldExpression(
262 t.callExpression(
263 util.runtimeProperty("awrap"),
264 [argument]
265 ),
266 false
267 ));
268 }
269};