refactor _shadowFunctions transformer to not do an entire traverse per function

This commit is contained in:
Sebastian McKenzie 2015-05-11 11:38:22 +01:00
parent 8605e835eb
commit f373f8f003
3 changed files with 44 additions and 99 deletions

View File

@ -1,105 +1,33 @@
import * as t from "../../../types"; import * as t from "../../../types";
var functionChildrenVisitor = {
enter(node, parent, scope, state) {
if (this.isClass(node)) {
return this.skip();
}
if (this.isFunction() && !node.shadow) {
return this.skip();
}
if (node._shadowedFunctionLiteral) return this.skip();
var getId;
if (this.isIdentifier() && node.name === "arguments") {
getId = state.getArgumentsId;
} else if (this.isThisExpression()) {
getId = state.getThisId;
} else {
return;
}
if (this.isReferenced()) return getId();
}
};
var functionVisitor = {
enter(node, parent, scope, state) {
if (!node.shadow) {
if (this.isFunction()) {
// stop traversal of this node as it'll be hit again by this transformer
return this.skip();
} else {
return;
}
}
// traverse all child nodes of this function and find `arguments` and `this`
this.traverse(functionChildrenVisitor, state);
node.shadow = false;
return this.skip();
}
};
function aliasFunction(getBody, path, scope) {
var argumentsId;
var thisId;
var state = {
getArgumentsId() {
return argumentsId = argumentsId || scope.generateUidIdentifier("arguments");
},
getThisId() {
return thisId = thisId || scope.generateUidIdentifier("this");
}
};
// traverse the function and find all alias functions so we can alias
// `arguments` and `this` if necessary
path.traverse(functionVisitor, state);
var body;
var pushDeclaration = function (id, init) {
body = body || getBody();
body.unshift(t.variableDeclaration("var", [
t.variableDeclarator(id, init)
]));
};
if (argumentsId) {
pushDeclaration(argumentsId, t.identifier("arguments"));
}
if (thisId) {
pushDeclaration(thisId, t.thisExpression());
}
};
// todo: on all `this` and `arguments`, walk UP the tree instead of
// crawling the entire function tree
export var metadata = { export var metadata = {
group: "builtin-trailing" group: "builtin-trailing"
}; };
export function Program(node, parent, scope) { function remap(path, key, create) {
aliasFunction(function () { // ensure that we're shadowed
return node.body; if (!path.inShadow()) return;
}, this, scope);
var fnPath = path.findParent((node, path) => !node.shadow && (path.isFunction() || path.isProgram()));
var cached = fnPath.getData(key);
if (cached) return cached;
var init = create();
var id = path.scope.generateUidIdentifier(key);
fnPath.setData(key, id);
fnPath.scope.push({ id, init });
return id;
} }
export function FunctionDeclaration(node, parent, scope) { export function ThisExpression() {
aliasFunction(function () { return remap(this, "this", () => t.thisExpression());
t.ensureBlock(node);
return node.body.body;
}, this, scope);
} }
export { FunctionDeclaration as FunctionExpression }; export function ReferencedIdentifier(node) {
if (node.name === "arguments" && !node._shadowedFunctionLiteral) {
return remap(this, "arguments", () => t.identifier("arguments"));
}
}

View File

@ -23,9 +23,7 @@ export var Program = {
} }
export function ThisExpression() { export function ThisExpression() {
if (!this.findParent(function (node) { if (!this.findParent((node) => !node.shadow && THIS_BREAK_KEYS.indexOf(node.type) >= 0)) {
return !node.shadow && THIS_BREAK_KEYS.indexOf(node.type) >= 0;
})) {
return t.identifier("undefined"); return t.identifier("undefined");
} }
} }

View File

@ -125,6 +125,25 @@ export default class TraversalPath {
return false; return false;
} }
/**
* Description
*/
inShadow() {
var path = this;
while (path) {
if (path.isFunction()) {
if (path.node.shadow) {
return path;
} else {
return null;
}
}
path = path.parentPath;
}
return null;
}
/** /**
* Check whether this node was a part of the original AST. * Check whether this node was a part of the original AST.
*/ */
@ -148,7 +167,7 @@ export default class TraversalPath {
findParent(callback) { findParent(callback) {
var path = this; var path = this;
while (path) { while (path) {
if (callback(path.node)) return path.node; if (callback(path.node, path)) return path;
path = path.parentPath; path = path.parentPath;
} }
return null; return null;