in packages/transform/src/emit.js [951:1271]
Ep.explodeExpression = function(path, ignoreResult) {
const t = util.getTypes();
let expr = path.node;
if (expr) {
t.assertExpression(expr);
} else {
return expr;
}
let self = this;
let result; // Used optionally by several cases below.
let after;
function finish(expr) {
t.assertExpression(expr);
if (ignoreResult) {
self.emit(expr);
}
return expr;
}
// If the expression does not contain a leap, then we either emit the
// expression as a standalone statement or return it whole.
if (!meta.containsLeap(expr)) {
return finish(expr);
}
// If any child contains a leap (such as a yield or labeled continue or
// break statement), then any sibling subexpressions will almost
// certainly have to be exploded in order to maintain the order of their
// side effects relative to the leaping child(ren).
let hasLeapingChildren = meta.containsLeap.onlyChildren(expr);
// If ignoreResult is true, then we must take full responsibility for
// emitting the expression with all its side effects, and we should not
// return a result.
switch (expr.type) {
case "MemberExpression":
return finish(t.memberExpression(
self.explodeExpression(path.get("object")),
expr.computed
? self.explodeViaTempVar(null, path.get("property"), hasLeapingChildren)
: expr.property,
expr.computed
));
case "CallExpression":
let calleePath = path.get("callee");
let argsPath = path.get("arguments");
let newCallee;
let newArgs;
let hasLeapingArgs = argsPath.some(
argPath => meta.containsLeap(argPath.node)
);
let injectFirstArg = null;
if (t.isMemberExpression(calleePath.node)) {
if (hasLeapingArgs) {
// If the arguments of the CallExpression contained any yield
// expressions, then we need to be sure to evaluate the callee
// before evaluating the arguments, but if the callee was a member
// expression, then we must be careful that the object of the
// member expression still gets bound to `this` for the call.
let newObject = self.explodeViaTempVar(
// Assign the exploded callee.object expression to a temporary
// variable so that we can use it twice without reevaluating it.
self.makeTempVar(),
calleePath.get("object"),
hasLeapingChildren
);
let newProperty = calleePath.node.computed
? self.explodeViaTempVar(null, calleePath.get("property"), hasLeapingChildren)
: calleePath.node.property;
injectFirstArg = newObject;
newCallee = t.memberExpression(
t.memberExpression(
t.cloneDeep(newObject),
newProperty,
calleePath.node.computed
),
t.identifier("call"),
false
);
} else {
newCallee = self.explodeExpression(calleePath);
}
} else {
newCallee = self.explodeViaTempVar(null, calleePath, hasLeapingChildren);
if (t.isMemberExpression(newCallee)) {
// If the callee was not previously a MemberExpression, then the
// CallExpression was "unqualified," meaning its `this` object
// should be the global object. If the exploded expression has
// become a MemberExpression (e.g. a context property, probably a
// temporary variable), then we need to force it to be unqualified
// by using the (0, object.property)(...) trick; otherwise, it
// will receive the object of the MemberExpression as its `this`
// object.
newCallee = t.sequenceExpression([
t.numericLiteral(0),
t.cloneDeep(newCallee)
]);
}
}
if (hasLeapingArgs) {
newArgs = argsPath.map(argPath => self.explodeViaTempVar(null, argPath, hasLeapingChildren));
if (injectFirstArg) newArgs.unshift(injectFirstArg);
newArgs = newArgs.map(arg => t.cloneDeep(arg));
} else {
newArgs = path.node.arguments;
}
return finish(t.callExpression(newCallee, newArgs));
case "NewExpression":
return finish(t.newExpression(
self.explodeViaTempVar(null, path.get("callee"), hasLeapingChildren),
path.get("arguments").map(function(argPath) {
return self.explodeViaTempVar(null, argPath, hasLeapingChildren);
})
));
case "ObjectExpression":
return finish(t.objectExpression(
path.get("properties").map(function(propPath) {
if (propPath.isObjectProperty()) {
return t.objectProperty(
propPath.node.key,
self.explodeViaTempVar(null, propPath.get("value"), hasLeapingChildren),
propPath.node.computed
);
} else {
return propPath.node;
}
})
));
case "ArrayExpression":
return finish(t.arrayExpression(
path.get("elements").map(function(elemPath) {
if (elemPath.isSpreadElement()) {
return t.spreadElement(
self.explodeViaTempVar(null, elemPath.get("argument"), hasLeapingChildren)
);
} else {
return self.explodeViaTempVar(null, elemPath, hasLeapingChildren);
}
})
));
case "SequenceExpression":
let lastIndex = expr.expressions.length - 1;
path.get("expressions").forEach(function(exprPath) {
if (exprPath.key === lastIndex) {
result = self.explodeExpression(exprPath, ignoreResult);
} else {
self.explodeExpression(exprPath, true);
}
});
return result;
case "LogicalExpression":
after = this.loc();
if (!ignoreResult) {
result = self.makeTempVar();
}
let left = self.explodeViaTempVar(result, path.get("left"), hasLeapingChildren);
if (expr.operator === "&&") {
self.jumpIfNot(left, after);
} else {
assert.strictEqual(expr.operator, "||");
self.jumpIf(left, after);
}
self.explodeViaTempVar(result, path.get("right"), hasLeapingChildren, ignoreResult);
self.mark(after);
return result;
case "ConditionalExpression":
let elseLoc = this.loc();
after = this.loc();
let test = self.explodeExpression(path.get("test"));
self.jumpIfNot(test, elseLoc);
if (!ignoreResult) {
result = self.makeTempVar();
}
self.explodeViaTempVar(result, path.get("consequent"), hasLeapingChildren, ignoreResult);
self.jump(after);
self.mark(elseLoc);
self.explodeViaTempVar(result, path.get("alternate"), hasLeapingChildren, ignoreResult);
self.mark(after);
return result;
case "UnaryExpression":
return finish(t.unaryExpression(
expr.operator,
// Can't (and don't need to) break up the syntax of the argument.
// Think about delete a[b].
self.explodeExpression(path.get("argument")),
!!expr.prefix
));
case "BinaryExpression":
return finish(t.binaryExpression(
expr.operator,
self.explodeViaTempVar(null, path.get("left"), hasLeapingChildren),
self.explodeViaTempVar(null, path.get("right"), hasLeapingChildren)
));
case "AssignmentExpression":
if (expr.operator === "=") {
// If this is a simple assignment, the left hand side does not need
// to be read before the right hand side is evaluated, so we can
// avoid the more complicated logic below.
return finish(t.assignmentExpression(
expr.operator,
self.explodeExpression(path.get("left")),
self.explodeExpression(path.get("right"))
));
}
const lhs = self.explodeExpression(path.get("left"));
const temp = self.emitAssign(self.makeTempVar(), lhs);
// For example,
//
// x += yield y
//
// becomes
//
// context.t0 = x
// x = context.t0 += yield y
//
// so that the left-hand side expression is read before the yield.
// Fixes https://github.com/facebook/regenerator/issues/345.
return finish(t.assignmentExpression(
"=",
t.cloneDeep(lhs),
t.assignmentExpression(
expr.operator,
t.cloneDeep(temp),
self.explodeExpression(path.get("right"))
)
));
case "UpdateExpression":
return finish(t.updateExpression(
expr.operator,
self.explodeExpression(path.get("argument")),
expr.prefix
));
case "YieldExpression":
after = this.loc();
let arg = expr.argument && self.explodeExpression(path.get("argument"));
if (arg && expr.delegate) {
let result = self.makeTempVar();
let ret = t.returnStatement(t.callExpression(
self.contextProperty("delegateYield"),
[
arg,
t.stringLiteral(result.property.name),
after
]
));
ret.loc = expr.loc;
self.emit(ret);
self.mark(after);
return result;
}
self.emitAssign(self.contextProperty("next"), after);
let ret = t.returnStatement(t.cloneDeep(arg) || null);
// Preserve the `yield` location so that source mappings for the statements
// link back to the yield properly.
ret.loc = expr.loc;
self.emit(ret);
self.mark(after);
return self.contextProperty("sent");
case "ClassExpression":
return finish(self.explodeClass(path));
default:
throw new Error(
"unknown Expression of type " +
JSON.stringify(expr.type));
}
};