add babel-plugin-transform-regenerator/lib

This commit is contained in:
Sebastian McKenzie 2015-10-30 01:02:36 +00:00
parent b9160b3a6c
commit f058b07375
8 changed files with 1915 additions and 0 deletions

1
.gitignore vendored
View File

@ -15,5 +15,6 @@ dist
/packages/babel-runtime/helpers/*.js
/packages/babel-runtime/regenerator/*.js
/packages/*/lib
!/packages/babel-plugin-transform-regenerator/lib
_babel.github.io
/tests/.browser-build.js

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,152 @@
/**
* 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 traverse = require("babel-traverse");
var assert = require("assert");
var t = require("babel-types");
var hasOwn = Object.prototype.hasOwnProperty;
// The hoist function takes a FunctionExpression or FunctionDeclaration
// and replaces any Declaration nodes in its body with assignments, then
// returns a VariableDeclaration containing just the names of the removed
// declarations.
exports.hoist = function(funPath) {
assert.ok(funPath instanceof traverse.NodePath);
t.assertFunction(funPath.node);
var vars = {};
function varDeclToExpr(vdec, includeIdentifiers) {
t.assertVariableDeclaration(vdec);
// TODO assert.equal(vdec.kind, "var");
var exprs = [];
vdec.declarations.forEach(function(dec) {
vars[dec.id.name] = dec.id;
if (dec.init) {
exprs.push(t.assignmentExpression(
"=", dec.id, dec.init
));
} else if (includeIdentifiers) {
exprs.push(dec.id);
}
});
if (exprs.length === 0)
return null;
if (exprs.length === 1)
return exprs[0];
return t.sequenceExpression(exprs);
}
funPath.get("body").traverse({
VariableDeclaration: {
exit: function(path) {
var expr = varDeclToExpr(path.node, false);
if (expr === null) {
path.remove();
} else {
// We don't need to traverse this expression any further because
// there can't be any new declarations inside an expression.
path.replaceWith(t.expressionStatement(expr));
}
// Since the original node has been either removed or replaced,
// avoid traversing it any further.
path.skip();
}
},
ForStatement: function(path) {
var init = path.node.init;
if (t.isVariableDeclaration(init)) {
path.get("init").replaceWith(varDeclToExpr(init, false));
}
},
ForXStatement: function(path) {
var left = path.get("left");
if (left.isVariableDeclaration()) {
left.replaceWith(varDeclToExpr(left.node, true));
}
},
FunctionDeclaration: function(path) {
var node = path.node;
vars[node.id.name] = node.id;
var parentNode = path.parent.node;
var assignment = t.expressionStatement(
t.assignmentExpression(
"=",
node.id,
t.functionExpression(
node.id,
node.params,
node.body,
node.generator,
node.expression
)
)
);
if (path.parentPath.isBlockStatement()) {
// Insert the assignment form before the first statement in the
// enclosing block.
path.parentPath.unshiftContainer("body", assignment);
// Remove the function declaration now that we've inserted the
// equivalent assignment form at the beginning of the block.
path.remove();
} else {
// If the parent node is not a block statement, then we can just
// replace the declaration with the equivalent assignment form
// without worrying about hoisting it.
path.replaceWith(assignment);
}
// Don't hoist variables out of inner functions.
path.skip();
},
FunctionExpression: function(path) {
// Don't descend into nested function expressions.
path.skip();
}
});
var paramNames = {};
funPath.get("params").forEach(function(paramPath) {
var param = paramPath.node;
if (t.isIdentifier(param)) {
paramNames[param.name] = param;
} else {
// Variables declared by destructuring parameter patterns will be
// harmlessly re-declared.
}
});
var declarations = [];
Object.keys(vars).forEach(function(name) {
if (!hasOwn.call(paramNames, name)) {
declarations.push(t.variableDeclarator(vars[name], null));
}
});
if (declarations.length === 0) {
return null; // Be sure to handle this case!
}
return t.variableDeclaration("var", declarations);
};

View File

@ -0,0 +1,175 @@
/**
* 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 t = require("babel-types");
var inherits = require("util").inherits;
var hasOwn = Object.prototype.hasOwnProperty;
function Entry() {
assert.ok(this instanceof Entry);
}
function FunctionEntry(returnLoc) {
Entry.call(this);
t.assertLiteral(returnLoc);
this.returnLoc = returnLoc;
}
inherits(FunctionEntry, Entry);
exports.FunctionEntry = FunctionEntry;
function LoopEntry(breakLoc, continueLoc, label) {
Entry.call(this);
t.assertLiteral(breakLoc);
t.assertLiteral(continueLoc);
if (label) {
t.assertIdentifier(label);
} else {
label = null;
}
this.breakLoc = breakLoc;
this.continueLoc = continueLoc;
this.label = label;
}
inherits(LoopEntry, Entry);
exports.LoopEntry = LoopEntry;
function SwitchEntry(breakLoc) {
Entry.call(this);
t.assertLiteral(breakLoc);
this.breakLoc = breakLoc;
}
inherits(SwitchEntry, Entry);
exports.SwitchEntry = SwitchEntry;
function TryEntry(firstLoc, catchEntry, finallyEntry) {
Entry.call(this);
t.assertLiteral(firstLoc);
if (catchEntry) {
assert.ok(catchEntry instanceof CatchEntry);
} else {
catchEntry = null;
}
if (finallyEntry) {
assert.ok(finallyEntry instanceof FinallyEntry);
} else {
finallyEntry = null;
}
// Have to have one or the other (or both).
assert.ok(catchEntry || finallyEntry);
this.firstLoc = firstLoc;
this.catchEntry = catchEntry;
this.finallyEntry = finallyEntry;
}
inherits(TryEntry, Entry);
exports.TryEntry = TryEntry;
function CatchEntry(firstLoc, paramId) {
Entry.call(this);
t.assertLiteral(firstLoc);
t.assertIdentifier(paramId);
this.firstLoc = firstLoc;
this.paramId = paramId;
}
inherits(CatchEntry, Entry);
exports.CatchEntry = CatchEntry;
function FinallyEntry(firstLoc, afterLoc) {
Entry.call(this);
t.assertLiteral(firstLoc);
t.assertLiteral(afterLoc);
this.firstLoc = firstLoc;
this.afterLoc = afterLoc;
}
inherits(FinallyEntry, Entry);
exports.FinallyEntry = FinallyEntry;
function LabeledEntry(breakLoc, label) {
Entry.call(this);
t.assertLiteral(breakLoc);
t.assertIdentifier(label);
this.breakLoc = breakLoc;
this.label = label;
}
inherits(LabeledEntry, Entry);
exports.LabeledEntry = LabeledEntry;
function LeapManager(emitter) {
assert.ok(this instanceof LeapManager);
var Emitter = require("./emit").Emitter;
assert.ok(emitter instanceof Emitter);
this.emitter = emitter;
this.entryStack = [new FunctionEntry(emitter.finalLoc)];
}
var LMp = LeapManager.prototype;
exports.LeapManager = LeapManager;
LMp.withEntry = function(entry, callback) {
assert.ok(entry instanceof Entry);
this.entryStack.push(entry);
try {
callback.call(this.emitter);
} finally {
var popped = this.entryStack.pop();
assert.strictEqual(popped, entry);
}
};
LMp._findLeapLocation = function(property, label) {
for (var i = this.entryStack.length - 1; i >= 0; --i) {
var entry = this.entryStack[i];
var loc = entry[property];
if (loc) {
if (label) {
if (entry.label &&
entry.label.name === label.name) {
return loc;
}
} else if (entry instanceof LabeledEntry) {
// Ignore LabeledEntry entries unless we are actually breaking to
// a label.
} else {
return loc;
}
}
}
return null;
};
LMp.getBreakLoc = function(label) {
return this._findLeapLocation("breakLoc", label);
};
LMp.getContinueLoc = function(label) {
return this._findLeapLocation("continueLoc", label);
};

View File

@ -0,0 +1,103 @@
/**
* 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 m = require("private").makeAccessor();
var t = require("babel-types");
var hasOwn = Object.prototype.hasOwnProperty;
function makePredicate(propertyName, knownTypes) {
function onlyChildren(node) {
t.assertNode(node);
// Assume no side effects until we find out otherwise.
var result = false;
function check(child) {
if (result) {
// Do nothing.
} else if (Array.isArray(child)) {
child.some(check);
} else if (t.isNode(child)) {
assert.strictEqual(result, false);
result = predicate(child);
}
return result;
}
var keys = t.VISITOR_KEYS[node.type];
if (keys) {
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var child = node[key];
check(child);
}
}
return result;
}
function predicate(node) {
t.assertNode(node);
var meta = m(node);
if (hasOwn.call(meta, propertyName))
return meta[propertyName];
// Certain types are "opaque," which means they have no side
// effects or leaps and we don't care about their subexpressions.
if (hasOwn.call(opaqueTypes, node.type))
return meta[propertyName] = false;
if (hasOwn.call(knownTypes, node.type))
return meta[propertyName] = true;
return meta[propertyName] = onlyChildren(node);
}
predicate.onlyChildren = onlyChildren;
return predicate;
}
var opaqueTypes = {
FunctionExpression: true
};
// These types potentially have side effects regardless of what side
// effects their subexpressions have.
var sideEffectTypes = {
CallExpression: true, // Anything could happen!
ForInStatement: true, // Modifies the key variable.
UnaryExpression: true, // Think delete.
BinaryExpression: true, // Might invoke .toString() or .valueOf().
AssignmentExpression: true, // Side-effecting by definition.
UpdateExpression: true, // Updates are essentially assignments.
NewExpression: true // Similar to CallExpression.
};
// These types are the direct cause of all leaps in control flow.
var leapTypes = {
YieldExpression: true,
BreakStatement: true,
ContinueStatement: true,
ReturnStatement: true,
ThrowStatement: true
};
// All leap types are also side effect types.
for (var type in leapTypes) {
if (hasOwn.call(leapTypes, type)) {
sideEffectTypes[type] = leapTypes[type];
}
}
exports.hasSideEffects = makePredicate("hasSideEffects", sideEffectTypes);
exports.containsLeap = makePredicate("containsLeap", leapTypes);

View File

@ -0,0 +1,23 @@
/**
* 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 t = require("babel-types");
exports.runtimeProperty = function(name) {
return t.memberExpression(
t.identifier("regeneratorRuntime"),
t.identifier(name),
false
);
};
exports.isReference = function(path) {
return path.isReferenced() || path.parentPath.isAssignmentExpression({ left: path.node });
};

View File

@ -0,0 +1,258 @@
/**
* 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 traverse = require("babel-traverse");
var babylon = require("babylon");
var assert = require("assert");
var fs = require("fs");
var t = require("babel-types");
var hoist = require("./hoist").hoist;
var Emitter = require("./emit").Emitter;
var util = require("./util");
var runtimeProperty = util.runtimeProperty;
var getMarkInfo = require("private").makeAccessor();
exports.visitor = {
Function: {
exit: function(path, state) {
var node = path.node;
if (node.generator) {
if (node.async) {
// Async generator
if (state.opts.asyncGenerators === false) return;
} else {
// Plain generator
if (state.opts.generators === false) return;
}
} else if (node.async) {
// Async function
if (state.opts.async === false) return;
} else {
// Not a generator or async function.
return;
}
if (node.expression) {
// Transform expression lambdas into normal functions.
node.expression = false;
node.body = t.blockStatement([
t.returnStatement(node.body)
]);
}
if (node.async) {
path.get("body").traverse(awaitVisitor);
}
var bodyBlockPath = path.get("body");
var outerBody = [];
var innerBody = [];
bodyBlockPath.get("body").forEach(function(childPath) {
var node = childPath.node;
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.
bodyBlockPath.node.body = 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.
t.assertIdentifier(node.id);
var innerFnId = t.identifier(node.id.name + "$");
var contextId = path.scope.generateUidIdentifier("context");
var argsId = path.scope.generateUidIdentifier("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 || t.variableDeclaration("var", []);
vars.declarations.push(t.variableDeclarator(
argsId, t.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 : t.nullLiteral(null),
t.thisExpression()
];
var tryLocsList = emitter.getTryLocsList();
if (tryLocsList) {
wrapArgs.push(tryLocsList);
}
var wrapCall = t.callExpression(
runtimeProperty(node.async ? "async" : "wrap"),
wrapArgs
);
outerBody.push(t.returnStatement(wrapCall));
node.body = t.blockStatement(outerBody);
var wasGeneratorFunction = node.generator;
if (wasGeneratorFunction) {
node.generator = false;
}
if (node.async) {
node.async = false;
}
if (wasGeneratorFunction &&
t.isExpression(node)) {
path.replaceWith(t.callExpression(runtimeProperty("mark"), [node]));
}
}
}
};
// 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.node;
t.assertFunction(node);
if (node.generator && // Non-generator functions don't need to be marked.
t.isFunctionDeclaration(node)) {
var pp = funPath.findParent(function (path) {
return path.isProgram() || path.isBlockStatement();
});
if (!pp) {
return node.id;
}
var markDecl = getRuntimeMarkDecl(pp);
var markedArray = markDecl.declarations[0].id;
var funDeclIdArray = markDecl.declarations[0].init.callee.object;
t.assertArrayExpression(funDeclIdArray);
var index = funDeclIdArray.elements.length;
funDeclIdArray.elements.push(node.id);
return t.memberExpression(
markedArray,
t.numberLiteral(index),
true
);
}
return node.id || (
node.id = funPath.scope.parent.generateUidIdentifier("callee")
);
}
function getRuntimeMarkDecl(blockPath) {
assert.ok(blockPath instanceof traverse.NodePath);
var block = blockPath.node;
assert.ok(Array.isArray(block.body));
var info = getMarkInfo(block);
if (info.decl) {
return info.decl;
}
info.decl = t.variableDeclaration("var", [
t.variableDeclarator(
blockPath.scope.generateUidIdentifier("marked"),
t.callExpression(
t.memberExpression(
t.arrayExpression([]),
t.identifier("map"),
false
),
[runtimeProperty("mark")]
)
)
]);
blockPath.unshiftContainer("body", info.decl);
return info.decl;
}
function renameArguments(funcPath, argsId) {
assert.ok(funcPath instanceof traverse.NodePath);
var state = {
didRenameArguments: false,
argsId: argsId
};
funcPath.traverse(argumentsVisitor, state);
// 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 state.didRenameArguments;
}
var argumentsVisitor = {
"FunctionExpression|FunctionDeclaration": function(path) {
path.skip();
},
Identifier: function(path, state) {
if (path.node.name === "arguments" && util.isReference(path)) {
path.replaceWith(state.argsId);
state.didRenameArguments = true;
}
}
};
var awaitVisitor = {
Function: function(path) {
path.skip(); // Don't descend into nested function scopes.
},
AwaitExpression: function(path) {
// Convert await and await* expressions to yield expressions.
var argument = path.node.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.
path.replaceWith(t.yieldExpression(
t.callExpression(
runtimeProperty("awrap"),
[argument]
),
false
));
}
};