add File::generateUidIdentifier method

This commit is contained in:
Sebastian McKenzie
2014-11-20 21:08:33 +11:00
parent ff6677a4b7
commit 71ad511322
6 changed files with 163 additions and 155 deletions

View File

@@ -147,7 +147,7 @@ File.prototype.parse = function (code) {
File.prototype.transform = function (ast) {
this.ast = ast;
this.scope = new Scope(null, ast.program);
this.scope = new Scope(ast.program);
var self = this;
@@ -196,6 +196,10 @@ File.prototype.generateUid = function (name, scope) {
return uid;
};
File.prototype.generateUidIdentifier = function (name, scope) {
return t.identifier(this.generateUid(name, scope));
};
File.prototype._generateUid = function (name) {
var uids = this.uids;
var i = uids[name] || 1;

View File

@@ -6,11 +6,11 @@ var go = function (getBody, node, file, scope) {
var thisId;
var getArgumentsId = function () {
return argumentsId = argumentsId || t.identifier(file.generateUid("arguments", scope));
return argumentsId = argumentsId || file.generateUidIdentifier("arguments", scope);
};
var getThisId = function () {
return thisId = thisId || t.identifier(file.generateUid("this", scope));
return thisId = thisId || file.generateUidIdentifier("this", scope);
};
// traverse the function and find all alias functions so we can alias

View File

@@ -36,7 +36,7 @@ function Class(node, file, scope) {
this.instanceMutatorMap = {};
this.staticMutatorMap = {};
this.hasConstructor = false;
this.className = node.id || t.identifier(file.generateUid("class", scope));
this.className = node.id || file.generateUidIdentifier("class", scope);
this.superName = node.superClass;
}
@@ -58,7 +58,7 @@ Class.prototype.run = function () {
superClassArgument = superClassCallee = getMemberExpressionObject(superName);
} else if (!t.isIdentifier(superName)) {
superClassArgument = superName;
superClassCallee = superName = t.identifier(file.generateUid("ref", this.scope));
superClassCallee = superName = file.generateUidIdentifier("ref", this.scope);
}
}

View File

@@ -77,7 +77,7 @@ var pushPattern = function (opts) {
var scope = opts.scope;
if (!t.isMemberExpression(parentId) && !t.isIdentifier(parentId)) {
var key = t.identifier(file.generateUid("ref", scope));
var key = file.generateUidIdentifier("ref", scope);
nodes.push(t.variableDeclaration("var", [
t.variableDeclarator(key, parentId)
@@ -97,7 +97,7 @@ exports.ForOfStatement = function (node, parent, file, scope) {
var pattern = declar.declarations[0].id;
if (!t.isPattern(pattern)) return;
var key = t.identifier(file.generateUid("ref", scope));
var key = file.generateUidIdentifier("ref", scope);
node.left = t.variableDeclaration(declar.kind, [
t.variableDeclarator(key, null)
]);
@@ -125,7 +125,7 @@ exports.Function = function (node, parent, file, scope) {
if (!t.isPattern(pattern)) return pattern;
hasDestructuring = true;
var parentId = t.identifier(file.generateUid("ref", scope));
var parentId = file.generateUidIdentifier("ref", scope);
pushPattern({
kind: "var",
@@ -155,7 +155,7 @@ exports.ExpressionStatement = function (node, parent, file, scope) {
var nodes = [];
var ref = t.identifier(file.generateUid("ref", scope));
var ref = file.generateUidIdentifier("ref", scope);
nodes.push(t.variableDeclaration("var", [
t.variableDeclarator(ref, expr.right)
]));

View File

@@ -5,7 +5,7 @@ exports.ForOfStatement = function (node, parent, file, scope) {
var left = node.left;
var declar;
var stepKey = t.identifier(file.generateUid("step", scope));
var stepKey = file.generateUidIdentifier("step", scope);
var stepValue = t.memberExpression(stepKey, t.identifier("value"));
if (t.isIdentifier(left)) {

View File

@@ -11,6 +11,7 @@
var runtimeProperty = require("./util").runtimeProperty;
var Emitter = require("./emit").Emitter;
var hoist = require("./hoist").hoist;
var Scope = require("../../../traverse/scope");
var types = require("ast-types");
var t = require("../../../types");
@@ -18,169 +19,172 @@ var runtimeAsyncMethod = runtimeProperty("async");
var runtimeWrapMethod = runtimeProperty("wrap");
var runtimeMarkMethod = runtimeProperty("mark");
exports.transform = function transform(node) {
return types.visit(node, visitor);
exports.transform = function transform(node, file) {
return types.visit(node, {
visitFunction: function (path) {
return visitor.call(this, path, file);
}
});
};
var visitor = types.PathVisitor.fromMethodsObject({
visitFunction: function (path) {
// Calling this.traverse(path) first makes for a post-order traversal.
this.traverse(path);
var visitor = function (path, file) {
// Calling this.traverse(path) first makes for a post-order traversal.
this.traverse(path);
var node = path.value;
var node = path.value;
var scope = new Scope(node);
if (!node.generator && !node.async) {
if (!node.generator && !node.async) {
return;
}
node.generator = false;
if (node.expression) {
// Transform expression lambdas into normal functions.
node.expression = false;
node.body = t.blockStatement([
t.returnStatement(node.body)
]);
}
if (node.async) {
awaitVisitor.visit(path.get("body"));
}
var outerFnId = node.id || (
node.id = file.generateUidIdentifier("callee", scope)
);
var innerFnId = t.identifier(node.id.name + "$");
var contextId = file.generateUidIdentifier("context$", scope);
var vars = hoist(path);
var emitter = new Emitter(contextId);
emitter.explode(path.get("body"));
var outerBody = [];
if (vars && vars.declarations.length > 0) {
outerBody.push(vars);
}
var wrapArgs = [
emitter.getContextFunction(innerFnId),
// Async functions don't care about the outer function because they
// don't need it to be marked and don't inherit from its .prototype.
node.async ? t.literal(null) : outerFnId,
t.thisExpression()
];
var tryEntryList = emitter.getTryEntryList();
if (tryEntryList) {
wrapArgs.push(tryEntryList);
}
var wrapCall = t.callExpression(
node.async ? runtimeAsyncMethod : runtimeWrapMethod,
wrapArgs
);
outerBody.push(t.returnStatement(wrapCall));
node.body = t.blockStatement(outerBody);
if (node.async) {
node.async = false;
return;
}
if (t.isFunctionDeclaration(node)) {
var pp = path.parent;
while (pp && !(t.isBlockStatement(pp.value) || t.isProgram(pp.value))) {
pp = pp.parent;
}
if (!pp) {
return;
}
node.generator = false;
// Here we turn the FunctionDeclaration into a named
// FunctionExpression that will be assigned to a variable of the
// same name at the top of the enclosing block. This is important
// for a very subtle reason: named function expressions can refer to
// themselves by name without fear that the binding may change due
// to code executing outside the function, whereas function
// declarations are vulnerable to the following rebinding:
//
// function f() { return f }
// var g = f;
// f = "asdf";
// g(); // "asdf"
//
// One way to prevent the problem illustrated above is to transform
// the function declaration thus:
//
// var f = function f() { return f };
// var g = f;
// f = "asdf";
// g(); // f
// g()()()()(); // f
//
// In the code below, we transform generator function declarations
// in the following way:
//
// gen().next(); // { value: gen, done: true }
// function *gen() {
// return gen;
// }
//
// becomes something like
//
// var gen = runtime.mark(function *gen() {
// return gen;
// });
// gen().next(); // { value: gen, done: true }
//
// which ensures that the generator body can always reliably refer
// to gen by name.
if (node.expression) {
// Transform expression lambdas into normal functions.
node.expression = false;
node.body = t.blockStatement([
t.returnStatement(node.body)
]);
}
// Remove the FunctionDeclaration so that we can add it back as a
// FunctionExpression passed to runtime.mark.
path.replace();
if (node.async) {
awaitVisitor.visit(path.get("body"));
}
// Change the type of the function to be an expression instead of a
// declaration. Note that all the other fields are the same.
node.type = "FunctionExpression";
var outerFnId = node.id || (
node.id = path.scope.parent.declareTemporary("callee$")
);
var varDecl = t.variableDeclaration("var", [
t.variableDeclarator(
node.id,
t.callExpression(runtimeMarkMethod, [node])
)
]);
var innerFnId = t.identifier(node.id.name + "$");
var contextId = path.scope.declareTemporary("context$");
var vars = hoist(path);
// Copy any comments preceding the function declaration to the
// variable declaration, to avoid weird formatting consequences.
t.inheritsComments(varDecl, node);
t.removeComments(node);
var emitter = new Emitter(contextId);
emitter.explode(path.get("body"));
varDecl._blockHoist = true;
var outerBody = [];
var bodyPath = pp.get("body");
var bodyLen = bodyPath.value.length;
if (vars && vars.declarations.length > 0) {
outerBody.push(vars);
}
var wrapArgs = [
emitter.getContextFunction(innerFnId),
// Async functions don't care about the outer function because they
// don't need it to be marked and don't inherit from its .prototype.
node.async ? t.literal(null) : outerFnId,
t.thisExpression()
];
var tryEntryList = emitter.getTryEntryList();
if (tryEntryList) {
wrapArgs.push(tryEntryList);
}
var wrapCall = t.callExpression(
node.async ? runtimeAsyncMethod : runtimeWrapMethod,
wrapArgs
);
outerBody.push(t.returnStatement(wrapCall));
node.body = t.blockStatement(outerBody);
if (node.async) {
node.async = false;
return;
}
if (t.isFunctionDeclaration(node)) {
var pp = path.parent;
while (pp && !(t.isBlockStatement(pp.value) || t.isProgram(pp.value))) {
pp = pp.parent;
}
if (!pp) {
for (var i = 0; i < bodyLen; ++i) {
var firstStmtPath = bodyPath.get(i);
if (!shouldNotHoistAbove(firstStmtPath)) {
firstStmtPath.insertBefore(varDecl);
return;
}
// Here we turn the FunctionDeclaration into a named
// FunctionExpression that will be assigned to a variable of the
// same name at the top of the enclosing block. This is important
// for a very subtle reason: named function expressions can refer to
// themselves by name without fear that the binding may change due
// to code executing outside the function, whereas function
// declarations are vulnerable to the following rebinding:
//
// function f() { return f }
// var g = f;
// f = "asdf";
// g(); // "asdf"
//
// One way to prevent the problem illustrated above is to transform
// the function declaration thus:
//
// var f = function f() { return f };
// var g = f;
// f = "asdf";
// g(); // f
// g()()()()(); // f
//
// In the code below, we transform generator function declarations
// in the following way:
//
// gen().next(); // { value: gen, done: true }
// function *gen() {
// return gen;
// }
//
// becomes something like
//
// var gen = runtime.mark(function *gen() {
// return gen;
// });
// gen().next(); // { value: gen, done: true }
//
// which ensures that the generator body can always reliably refer
// to gen by name.
// Remove the FunctionDeclaration so that we can add it back as a
// FunctionExpression passed to runtime.mark.
path.replace();
// Change the type of the function to be an expression instead of a
// declaration. Note that all the other fields are the same.
node.type = "FunctionExpression";
var varDecl = t.variableDeclaration("var", [
t.variableDeclarator(
node.id,
t.callExpression(runtimeMarkMethod, [node])
)
]);
// Copy any comments preceding the function declaration to the
// variable declaration, to avoid weird formatting consequences.
t.inheritsComments(varDecl, node);
t.removeComments(node);
varDecl._blockHoist = true;
var bodyPath = pp.get("body");
var bodyLen = bodyPath.value.length;
for (var i = 0; i < bodyLen; ++i) {
var firstStmtPath = bodyPath.get(i);
if (!shouldNotHoistAbove(firstStmtPath)) {
firstStmtPath.insertBefore(varDecl);
return;
}
}
bodyPath.push(varDecl);
} else {
t.assertFunctionExpression(node);
return t.callExpression(runtimeMarkMethod, [node]);
}
bodyPath.push(varDecl);
} else {
t.assertFunctionExpression(node);
return t.callExpression(runtimeMarkMethod, [node]);
}
});
};
function shouldNotHoistAbove(stmtPath) {
var value = stmtPath.value;