File: //var/www/zaklada/html/node_modules/regenerator/lib/visit.js
/**
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* https://raw.github.com/facebook/regenerator/master/LICENSE file. An
* additional grant of patent rights can be found in the PATENTS file in
* the same directory.
*/
var assert = require("assert");
var fs = require("fs");
var recast = require("recast");
var types = recast.types;
var n = types.namedTypes;
var b = types.builders;
var isArray = types.builtInTypes.array;
var isObject = types.builtInTypes.object;
var NodePath = types.NodePath;
var hoist = require("./hoist").hoist;
var Emitter = require("./emit").Emitter;
var util = require("./util");
var runtimeProperty = util.runtimeProperty;
var getMarkInfo = require("private").makeAccessor();
exports.transform = function transform(node, options) {
options = options || {};
var path = node instanceof NodePath ? node : new NodePath(node);
visitor.visit(path, options);
node = path.value;
if (options.includeRuntime === true ||
(options.includeRuntime === 'if used' && visitor.wasChangeReported())) {
injectRuntime(n.File.check(node) ? node.program : node);
}
options.madeChanges = visitor.wasChangeReported();
return node;
};
function injectRuntime(program) {
n.Program.assert(program);
// Include the runtime by modifying the AST rather than by concatenating
// strings. This technique will allow for more accurate source mapping.
var runtimePath = require("..").runtime.path;
var runtime = fs.readFileSync(runtimePath, "utf8");
var runtimeBody = recast.parse(runtime, {
sourceFileName: runtimePath
}).program.body;
var body = program.body;
body.unshift.apply(body, runtimeBody);
}
var visitor = types.PathVisitor.fromMethodsObject({
reset: function(node, options) {
this.options = options;
},
visitFunction: function(path) {
// Calling this.traverse(path) first makes for a post-order traversal.
this.traverse(path);
var node = path.value;
var shouldTransformAsync = node.async && !this.options.disableAsync;
if (!node.generator && !shouldTransformAsync) {
return;
}
this.reportChanged();
if (node.expression) {
// Transform expression lambdas into normal functions.
node.expression = false;
node.body = b.blockStatement([
b.returnStatement(node.body)
]);
}
if (shouldTransformAsync) {
awaitVisitor.visit(path.get("body"));
}
var outerBody = [];
var innerBody = [];
var bodyPath = path.get("body", "body");
bodyPath.each(function(childPath) {
var node = childPath.value;
if (node && node._blockHoist != null) {
outerBody.push(node);
} else {
innerBody.push(node);
}
});
if (outerBody.length > 0) {
// Only replace the inner body if we actually hoisted any statements
// to the outer body.
bodyPath.replace(innerBody);
}
var outerFnExpr = getOuterFnExpr(path);
// Note that getOuterFnExpr has the side-effect of ensuring that the
// function has a name (so node.id will always be an Identifier), even
// if a temporary name has to be synthesized.
n.Identifier.assert(node.id);
var innerFnId = b.identifier(node.id.name + "$");
var contextId = path.scope.declareTemporary("context$");
var argsId = path.scope.declareTemporary("args$");
// Turn all declarations into vars, and replace the original
// declarations with equivalent assignment expressions.
var vars = hoist(path);
var didRenameArguments = renameArguments(path, argsId);
if (didRenameArguments) {
vars = vars || b.variableDeclaration("var", []);
vars.declarations.push(b.variableDeclarator(
argsId, b.identifier("arguments")
));
}
var emitter = new Emitter(contextId);
emitter.explode(path.get("body"));
if (vars && vars.declarations.length > 0) {
outerBody.push(vars);
}
var wrapArgs = [
emitter.getContextFunction(innerFnId),
// Async functions that are not generators don't care about the
// outer function because they don't need it to be marked and don't
// inherit from its .prototype.
node.generator ? outerFnExpr : b.literal(null),
b.thisExpression()
];
var tryLocsList = emitter.getTryLocsList();
if (tryLocsList) {
wrapArgs.push(tryLocsList);
}
var wrapCall = b.callExpression(
runtimeProperty(shouldTransformAsync ? "async" : "wrap"),
wrapArgs
);
outerBody.push(b.returnStatement(wrapCall));
node.body = b.blockStatement(outerBody);
var wasGeneratorFunction = node.generator;
if (wasGeneratorFunction) {
node.generator = false;
}
if (shouldTransformAsync) {
node.async = false;
}
if (wasGeneratorFunction &&
n.Expression.check(node)) {
return b.callExpression(runtimeProperty("mark"), [node]);
}
},
visitForOfStatement: function(path) {
this.traverse(path);
var node = path.value;
var tempIterId = path.scope.declareTemporary("t$");
var tempIterDecl = b.variableDeclarator(
tempIterId,
b.callExpression(
runtimeProperty("values"),
[node.right]
)
);
var tempInfoId = path.scope.declareTemporary("t$");
var tempInfoDecl = b.variableDeclarator(tempInfoId, null);
var init = node.left;
var loopId;
if (n.VariableDeclaration.check(init)) {
loopId = init.declarations[0].id;
init.declarations.push(tempIterDecl, tempInfoDecl);
} else {
loopId = init;
init = b.variableDeclaration("var", [
tempIterDecl,
tempInfoDecl
]);
}
n.Identifier.assert(loopId);
var loopIdAssignExprStmt = b.expressionStatement(
b.assignmentExpression(
"=",
loopId,
b.memberExpression(
tempInfoId,
b.identifier("value"),
false
)
)
);
if (n.BlockStatement.check(node.body)) {
node.body.body.unshift(loopIdAssignExprStmt);
} else {
node.body = b.blockStatement([
loopIdAssignExprStmt,
node.body
]);
}
return b.forStatement(
init,
b.unaryExpression(
"!",
b.memberExpression(
b.assignmentExpression(
"=",
tempInfoId,
b.callExpression(
b.memberExpression(
tempIterId,
b.identifier("next"),
false
),
[]
)
),
b.identifier("done"),
false
)
),
null,
node.body
);
}
});
// Given a NodePath for a Function, return an Expression node that can be
// used to refer reliably to the function object from inside the function.
// This expression is essentially a replacement for arguments.callee, with
// the key advantage that it works in strict mode.
function getOuterFnExpr(funPath) {
var node = funPath.value;
n.Function.assert(node);
if (node.generator && // Non-generator functions don't need to be marked.
n.FunctionDeclaration.check(node)) {
var pp = funPath.parent;
while (pp && !(n.BlockStatement.check(pp.value) ||
n.Program.check(pp.value))) {
pp = pp.parent;
}
if (!pp) {
return node.id;
}
var markDecl = getRuntimeMarkDecl(pp);
var markedArray = markDecl.declarations[0].id;
var funDeclIdArray = markDecl.declarations[0].init.callee.object;
n.ArrayExpression.assert(funDeclIdArray);
var index = funDeclIdArray.elements.length;
funDeclIdArray.elements.push(node.id);
return b.memberExpression(
markedArray,
b.literal(index),
true
);
}
return node.id || (
node.id = funPath.scope.parent.declareTemporary("callee$")
);
}
function getRuntimeMarkDecl(blockPath) {
assert.ok(blockPath instanceof NodePath);
var block = blockPath.node;
isArray.assert(block.body);
var info = getMarkInfo(block);
if (info.decl) {
return info.decl;
}
info.decl = b.variableDeclaration("var", [
b.variableDeclarator(
blockPath.scope.declareTemporary("marked"),
b.callExpression(
b.memberExpression(
b.arrayExpression([]),
b.identifier("map"),
false
),
[runtimeProperty("mark")]
)
)
]);
for (var i = 0; i < block.body.length; ++i) {
if (!shouldNotHoistAbove(blockPath.get("body", i))) {
break;
}
}
blockPath.get("body").insertAt(i, info.decl);
return info.decl;
}
function shouldNotHoistAbove(stmtPath) {
var value = stmtPath.value;
n.Statement.assert(value);
// If the first statement is a "use strict" declaration, make sure to
// insert hoisted declarations afterwards.
return n.ExpressionStatement.check(value) &&
n.Literal.check(value.expression) &&
value.expression.value === "use strict";
}
function renameArguments(funcPath, argsId) {
assert.ok(funcPath instanceof types.NodePath);
var func = funcPath.value;
var didRenameArguments = false;
recast.visit(funcPath, {
visitFunction: function(path) {
if (path.value === func) {
this.traverse(path);
} else {
return false;
}
},
visitIdentifier: function(path) {
if (path.value.name === "arguments" &&
util.isReference(path)) {
path.replace(argsId);
didRenameArguments = true;
return false;
}
this.traverse(path);
}
});
// If the traversal replaced any arguments references, then we need to
// alias the outer function's arguments binding (be it the implicit
// arguments object or some other parameter or variable) to the variable
// named by argsId.
return didRenameArguments;
}
var awaitVisitor = types.PathVisitor.fromMethodsObject({
visitFunction: function(path) {
return false; // Don't descend into nested function scopes.
},
visitAwaitExpression: function(path) {
// Convert await and await* expressions to yield expressions.
var argument = path.value.argument;
// If the parser supports await* syntax using a boolean .all property
// (#171), desugar that syntax to yield Promise.all(argument).
if (path.value.all) {
argument = b.callExpression(
b.memberExpression(
b.identifier("Promise"),
b.identifier("all"),
false
),
[argument]
);
}
// Transforming `await x` to `yield regeneratorRuntime.awrap(x)`
// causes the argument to be wrapped in such a way that the runtime
// can distinguish between awaited and merely yielded values.
return b.yieldExpression(
b.callExpression(
runtimeProperty("awrap"),
[argument]
),
false
);
}
});