optimise scope tracking into a single pass - #1486

This commit is contained in:
Sebastian McKenzie 2015-05-30 22:07:03 -04:00
parent e119681b1f
commit ba19bd36a4

View File

@ -9,40 +9,31 @@ import extend from "lodash/object/extend";
import object from "../../helpers/object";
import * as t from "../../types";
var functionVariableVisitor = {
enter(node, parent, scope, state) {
if (t.isFor(node)) {
for (var key of (t.FOR_INIT_KEYS: Array)) {
var declar = this.get(key);
if (declar.isVar()) state.scope.registerBinding("var", declar);
}
var collectorVisitor = {
For(node, parent, scope) {
for (var key of (t.FOR_INIT_KEYS: Array)) {
var declar = this.get(key);
if (declar.isVar()) scope.getFunctionParent().registerBinding("var", declar);
}
},
// this block is a function so we'll stop since none of the variables
// declared within are accessible
if (this.isFunction()) return this.skip();
// function identifier doesn't belong to this scope
if (state.blockId && node === state.blockId) return;
Declaration(node, parent, scope) {
// delegate block scope handling to the `blockVariableVisitor`
if (this.isBlockScoped()) return;
// this will be hit again once we traverse into it after this iteration
if (this.isExportDeclaration() && t.isDeclaration(node.declaration)) return;
if (this.isExportDeclaration() && this.get("declaration").isDeclaration()) return;
// we've ran into a declaration!
if (this.isDeclaration()) state.scope.registerDeclaration(this);
}
};
scope.getFunctionParent().registerDeclaration(this);
},
var programReferenceVisitor = {
ReferencedIdentifier(node, parent, scope, state) {
ReferencedIdentifier(node, parent, scope) {
var bindingInfo = scope.getBinding(node.name);
if (bindingInfo) {
bindingInfo.reference();
} else {
state.addGlobal(node);
scope.getProgramParent().addGlobal(node);
}
},
@ -53,9 +44,9 @@ var programReferenceVisitor = {
}
},
Scopable(node, parent, scope, state) {
Scopable(node, parent, scope) {
for (var name in scope.bindings) {
state.references[name] = true;
scope.getProgramParent().references[name] = true;
}
},
@ -75,8 +66,8 @@ var programReferenceVisitor = {
}
},
LabeledStatement(node, parent, scope, state) {
state.addGlobal(node);
LabeledStatement(node, parent, scope) {
scope.getProgramParent().addGlobal(node);
},
AssignmentExpression(node, parent, scope) {
@ -89,18 +80,11 @@ var programReferenceVisitor = {
UnaryExpression(node, parent, scope) {
if (node.operator === "delete") scope.registerConstantViolation(this.get("left"), null);
}
};
var blockVariableVisitor = {
Scope() {
this.skip();
},
enter(node, parent, scope, state) {
if (this.isFunctionDeclaration() || this.isBlockScoped()) {
state.registerDeclaration(this);
}
BlockScoped(node, parent, scope, state) {
if (scope.path === this) scope = scope.parent;
scope.getBlockParent().registerDeclaration(this);
}
};
@ -317,19 +301,23 @@ export default class Scope {
* Description
*/
checkBlockScopedCollisions(kind: string, name: string, id: Object) {
var local = this.getOwnBindingInfo(name);
if (!local) return;
checkBlockScopedCollisions(local, kind: string, name: string, id: Object) {
// ignore parameters
if (kind === "param") return;
// ignore hoisted functions if there's also a local let
if (kind === "hoisted" && local.kind === "let") return;
var duplicate = false;
if (!duplicate) duplicate = kind === "let" || kind === "const" || local.kind === "let" || local.kind === "const" || local.kind === "module";
// don't allow duplicate bindings to exist alongside
if (!duplicate) duplicate = kind === "let" || local.kind === "let" || local.kind === "const" || local.kind === "module";
// don't allow a local of param with a kind of let
if (!duplicate) duplicate = local.kind === "param" && (kind === "let" || kind === "const");
if (duplicate) {
this.dump();
throw this.file.errorWithNode(id, messages.get("scopeDuplicateDeclaration", name), TypeError);
}
}
@ -479,7 +467,11 @@ export default class Scope {
for (var name in ids) {
var id = ids[name];
this.checkBlockScopedCollisions(kind, name, id);
var local = this.getOwnBindingInfo(name);
if (local) {
if (local.identifier === id) continue;
this.checkBlockScopedCollisions(local, kind, name, id);
}
this.bindings[name] = new Binding({
identifier: id,
@ -630,22 +622,6 @@ export default class Scope {
for (let param of (params: Array)) {
this.registerBinding("param", param);
}
path.get("body").traverse(blockVariableVisitor, this);
}
// Program, Function - var variables
if (path.isProgram() || path.isFunction()) {
this.traverse(path.node, functionVariableVisitor, {
blockId: path.get("id").node,
scope: this
});
}
// Program, BlockStatement, Function - let variables
if (path.isBlockStatement() || path.isProgram()) {
this.traverse(path.node, blockVariableVisitor, this);
}
// CatchClause - param
@ -662,9 +638,12 @@ export default class Scope {
// Program
if (path.isProgram()) {
this.traverse(path.node, programReferenceVisitor, this);
}
var parent = this.getProgramParent();
if (parent.crawling) return;
this.crawling = true;
path.traverse(collectorVisitor);
this.crawling = false;
}
/**
@ -700,7 +679,7 @@ export default class Scope {
this.file.attachAuxiliaryComment(declar);
var [declarPath] = path.get("body")[0]._containerInsertBefore([declar]);
var [declarPath] = path.unshiftContainer("body", [declar]);
this.registerBinding(kind, declarPath);
if (!unique) path.setData(dataKey, declar);
}
@ -714,10 +693,12 @@ export default class Scope {
getProgramParent() {
var scope = this;
while (scope.parent) {
scope = scope.parent;
}
return scope;
do {
if (scope.path.isProgram()) {
return scope;
}
} while (scope = scope.parent);
throw new Error("We couldn't find a Function or Program...");
}
/**
@ -727,24 +708,31 @@ export default class Scope {
getFunctionParent() {
var scope = this;
while (scope.parent && !t.isFunction(scope.block)) {
scope = scope.parent;
}
return scope;
do {
if (scope.path.isProgram() || scope.path.isFunction()) {
return scope;
}
} while (scope = scope.parent);
throw new Error("We couldn't find a Function or Program...");
}
/**
* Walk up the scope tree until we hit either a BlockStatement/Loop or reach the
* Walk up the scope tree until we hit either a BlockStatement/Loop/Program/Function/Switch or reach the
* very top and hit Program.
*/
getBlockParent() {
var scope = this;
do {
if (scope.path.isProgram() || scope.path.isBlockStatement()) {
if (scope.path.isProgram() ||
scope.path.isBlockStatement() ||
scope.path.isFunction() ||
scope.path.isLoop() ||
scope.path.isSwitchStatement()) {
return scope;
}
} while (scope = scope.parent);
throw new Error("We couldn't find a BlockStatement, For or Program...");
}
/**