restructure Scope API and internal data structure

This commit is contained in:
Sebastian McKenzie
2015-02-10 23:52:23 +11:00
parent edc8bee38e
commit 8598000a69
11 changed files with 137 additions and 165 deletions

View File

@@ -456,14 +456,14 @@ File.prototype.generateUid = function (name, scope) {
do {
uid = this._generateUid(name, i);
i++;
} while (scope.hasReference(uid));
} while (scope.hasBinding(uid) || scope.hasGlobal(uid));
return uid;
};
File.prototype.generateUidIdentifier = function (name, scope) {
scope = scope || this.scope;
var id = t.identifier(this.generateUid(name, scope));
scope.addBindingToFunctionScope(id, "uid");
scope.getFunctionParent().registerBinding("uid", id);
return id;
};

View File

@@ -18,7 +18,7 @@ var getObjRef = function (node, nodes, file, scope) {
} else if (t.isMemberExpression(node)) {
ref = node.object;
if (t.isIdentifier(ref) && scope.hasReference(ref.name)) {
if (t.isIdentifier(ref) && scope.hasGlobal(ref.name)) {
// the object reference that we need to save is locally declared
// so as per the previous comment we can be 100% sure evaluating
// it multiple times will be safe

View File

@@ -13,7 +13,7 @@ var visitor = {
// check that we don't have a local variable declared as that removes the need
// for the wrapper
var localDeclar = scope.getBinding(state.id);
var localDeclar = scope.getBindingIdentifier(state.id);
if (localDeclar !== state.outerDeclar) return;
state.selfReference = true;
@@ -31,7 +31,7 @@ exports.property = function (node, file, scope) {
var state = {
id: id,
selfReference: false,
outerDeclar: scope.getBinding(id),
outerDeclar: scope.getBindingIdentifier(id),
};
scope.traverse(node, visitor, state);

View File

@@ -146,7 +146,7 @@ DefaultFormatter.prototype.remapExportAssignment = function (node) {
DefaultFormatter.prototype.isLocalReference = function (node, scope) {
var localExports = this.localExports;
var name = node.name;
return t.isIdentifier(node) && localExports[name] && localExports[name] === scope.getBinding(name);
return t.isIdentifier(node) && localExports[name] && localExports[name] === scope.getBindingIdentifier(name);
};
DefaultFormatter.prototype.getModuleName = function () {

View File

@@ -10,7 +10,7 @@ var visitor = {
if (!declared) return;
// declared node is different in this scope
if (scope.getBinding(node.name) !== declared) return;
if (scope.getBindingIdentifier(node.name) !== declared) return;
var assert = t.callExpression(
state.file.addHelper("temporal-assert-defined"),

View File

@@ -136,7 +136,7 @@ function replace(node, parent, scope, remaps) {
var remap = remaps[node.name];
if (!remap) return;
var ownBinding = scope.getBinding(node.name);
var ownBinding = scope.getBindingIdentifier(node.name);
if (ownBinding === remap.binding) {
node.name = remap.uid;
} else {
@@ -175,7 +175,7 @@ BlockScoping.prototype.remap = function () {
// this is the defining identifier of a declaration
var ref = letRefs[key];
if (scope.parentHasReference(key)) {
if (scope.parentHasBinding(key) || scope.hasGlobal(key)) {
var uid = scope.generateUidIdentifier(ref.name).name;
ref.name = uid;

View File

@@ -11,23 +11,25 @@ var visitor = {
if (t.isAssignmentExpression(node) || t.isUpdateExpression(node)) {
var ids = t.getBindingIdentifiers(node);
for (var key in ids) {
var id = ids[key];
for (var name in ids) {
var id = ids[name];
var constant = state.constants[key];
var constant = state.constants[name];
// no constant exists
if (!constant) continue;
var constantIdentifier = constant.identifier;
// check if the assignment id matches the constant declaration id
// if it does then it was the id used to initially declare the
// constant so we can just ignore it
if (id === constant) continue;
if (id === constantIdentifier) continue;
// check if there's been a local binding that shadows this constant
if (!scope.bindingEquals(key, constant)) continue;
if (!scope.bindingIdentifierEquals(name, constantIdentifier)) continue;
throw state.file.errorWithNode(id, key + " is read-only");
throw state.file.errorWithNode(id, name + " is read-only");
}
} else if (t.isScope(node)) {
this.skip();

View File

@@ -18,7 +18,7 @@ var iifeVisitor = {
enter: function (node, parent, scope, state) {
if (!t.isReferencedIdentifier(node, parent)) return;
if (!state.scope.hasOwnBinding(node.name)) return;
if (state.scope.bindingEquals(node.name, node)) return;
if (state.scope.bindingIdentifierEquals(node.name, node)) return;
state.iife = true;
this.stop();
@@ -78,7 +78,7 @@ exports.Function = function (node, parent, scope, file) {
node.params[i] = placeholder;
if (!state.iife) {
if (t.isIdentifier(right) && scope.hasOwnReference(right.name)) {
if (t.isIdentifier(right) && scope.hasOwnBinding(right.name)) {
state.iife = true;
} else {
scope.traverse(right, iifeVisitor, state);

View File

@@ -219,7 +219,7 @@ TailCallTransformer.prototype.subTransformCallExpression = function (node) {
}
// only tail recursion can be optimized as for now
if (!t.isIdentifier(callee) || !this.scope.bindingEquals(callee.name, this.ownerId)) {
if (!t.isIdentifier(callee) || !this.scope.bindingIdentifierEquals(callee.name, this.ownerId)) {
return;
}

View File

@@ -30,11 +30,11 @@ var astVisitor = {
if (!t.isReferenced(obj, node)) return;
if (!node.computed && coreHas(obj) && has(core[obj.name], prop.name) && !scope.getBinding(obj.name)) {
if (!node.computed && coreHas(obj) && has(core[obj.name], prop.name) && !scope.getBindingIdentifier(obj.name)) {
this.skip();
return t.prependToMemberExpression(node, file.get("coreIdentifier"));
}
} else if (t.isReferencedIdentifier(node, parent) && !t.isMemberExpression(parent) && contains(ALIASABLE_CONSTRUCTORS, node.name) && !scope.getBinding(node.name)) {
} else if (t.isReferencedIdentifier(node, parent) && !t.isMemberExpression(parent) && contains(ALIASABLE_CONSTRUCTORS, node.name) && !scope.getBindingIdentifier(node.name)) {
// Symbol() -> _core.Symbol(); new Promise -> new _core.Promise
return t.memberExpression(file.get("coreIdentifier"), node);
} else if (t.isCallExpression(node)) {

View File

@@ -134,29 +134,28 @@ Scope.prototype.generateTempBasedOnNode = function (node) {
return id;
};
Scope.prototype.checkBlockScopedCollisions = function (kind, key, id) {
if (kind === "param") return;
if (kind === "hoisted" && this.bindingKinds["let"][key]) return;
Scope.prototype.checkBlockScopedCollisions = function (kind, name, id) {
var local = this.getOwnBindingInfo(name);
if (!local) return;
if (this.bindingKinds["let"][key] || this.bindingKinds["const"][key]) {
throw this.file.errorWithNode(id, "Duplicate declaration " + key, TypeError);
if (kind === "param") return;
if (kind === "hoisted" && local.kind === "let") return;
if (local.kind === "let" || local.kind === "const") {
throw this.file.errorWithNode(id, "Duplicate declaration " + name, TypeError);
}
};
Scope.prototype.rename = function (oldName, newName) {
var info = this.getBindingWithScope(oldName);
console.log(info);
var info = this.getBindingInfo(oldName);
if (!info) return;
var binding = info.binding;
var binding = info.identifier;
var scope = info.scope;
scope.references[newName] = binding;
this.clearOwnBinding(oldName);
scope.bindings[newName] = binding;
delete scope.references[oldName];
delete scope.bindings[oldName];
binding.name = newName;
scope.traverse(scope.block, {
@@ -164,7 +163,7 @@ Scope.prototype.rename = function (oldName, newName) {
if (t.isReferencedIdentifier(node, parent) && node.name === oldName) {
node.name = newName;
} else if (t.isScope(node)) {
if (t.getBinding(oldName) !== binding) {
if (t.getBindingIdentifier(oldName) !== binding) {
this.skip();
}
}
@@ -188,11 +187,11 @@ Scope.prototype.inferType = function (node) {
}
if (t.isIdentifier(target)) {
return this.getType(target.name);
// todo
}
};
Scope.prototype.registerType = function (key, id, node) {
Scope.prototype.getTypeAnnotation = function (key, id, node) {
var type;
if (id.typeAnnotation) {
@@ -205,38 +204,53 @@ Scope.prototype.registerType = function (key, id, node) {
if (type) {
if (t.isTypeAnnotation(type)) type = type.typeAnnotation;
this.types[key] = type;
return type;
}
};
Scope.prototype.register = function (node, reference, kind) {
if (t.isVariableDeclaration(node)) {
return this.registerVariableDeclaration(node);
Scope.prototype.clearOwnBinding = function (name) {
delete this.bindings[name];
};
Scope.prototype.registerDeclaration = function (node) {
if (t.isFunctionDeclaration(node)) {
this.registerBinding("hoisted", node);
} else if (t.isVariableDeclaration(node)) {
for (var i = 0; i < node.declarations.length; i++) {
this.registerBinding(node.kind, node.declarations[i]);
}
} else if (t.isClassDeclaration(node)) {
this.registerBinding("let", node);
} else if (t.isImportDeclaration(node) || t.isExportDeclaration(node)) {
this.registerBinding("module", node);
} else {
this.registerBinding("unknown", node);
}
};
Scope.prototype.registerBinding = function (kind, node) {
if (!kind) throw new ReferenceError("no `kind`");
var ids = t.getBindingIdentifiers(node);
extend(this.references, ids);
for (var name in ids) {
var id = ids[name];
if (reference) return;
this.checkBlockScopedCollisions(kind, name, id);
for (var key in ids) {
var id = ids[key];
this.checkBlockScopedCollisions(kind, key, id);
this.registerType(key, id, node);
this.bindings[key] = id;
this.bindings[name] = {
typeAnnotation: this.getTypeAnnotation(name, id, node),
identifier: id,
scope: this,
kind: kind
};
}
var kinds = this.bindingKinds[kind];
if (kinds) extend(kinds, ids);
};
Scope.prototype.registerVariableDeclaration = function (declar) {
var declars = declar.declarations;
for (var i = 0; i < declars.length; i++) {
this.register(declars[i], false, declar.kind);
this.registerBinding(declars[i], declar.kind);
}
};
@@ -245,7 +259,7 @@ var functionVariableVisitor = {
if (t.isFor(node)) {
each(t.FOR_INIT_KEYS, function (key) {
var declar = node[key];
if (t.isVar(declar)) state.scope.register(declar);
if (t.isVar(declar)) state.scope.registerBinding("var", declar);
});
}
@@ -263,26 +277,38 @@ var functionVariableVisitor = {
if (t.isExportDeclaration(node) && t.isDeclaration(node.declaration)) return;
// we've ran into a declaration!
if (t.isDeclaration(node)) state.scope.register(node);
if (t.isDeclaration(node)) state.scope.registerDeclaration(node);
}
};
Scope.prototype.addGlobal = function (node) {
this.globals[node.name] = node;
};
Scope.prototype.hasGlobal = function (name) {
var scope = this;
do {
if (scope.globals[name]) return true;
} while (scope = scope.parent);
return false;
};
var programReferenceVisitor = {
enter: function (node, parent, scope, state) {
if (t.isReferencedIdentifier(node, parent) && !scope.hasReference(node.name)) {
state.register(node, true);
if (t.isReferencedIdentifier(node, parent) && !scope.hasBinding(node.name)) {
state.addGlobal(node);
} else if (t.isLabeledStatement(node)) {
state.register(node.label, true);
state.addGlobal(node);
}
}
};
var blockVariableVisitor = {
enter: function (node, parent, scope, state) {
if (t.isFunctionDeclaration(node)) {
state.register(node, false, "hoisted");
} else if (t.isBlockScoped(node)) {
state.register(node, false, "let");
if (t.isFunctionDeclaration(node) || t.isBlockScoped(node)) {
state.registerDeclaration(node);
} else if (t.isScope(node)) {
this.skip();
}
@@ -303,18 +329,8 @@ Scope.prototype.crawl = function () {
}
info = block._scopeInfo = {
bindingKinds: {
hoisted: object(),
"const": object(),
param: object(),
"var": object(),
"let": object(),
uid: object()
},
references: object(),
bindings: object(),
types: object(),
bindings: object(),
globals: object()
};
extend(this, info);
@@ -333,7 +349,7 @@ Scope.prototype.crawl = function () {
if (t.isLoop(block)) {
for (i = 0; i < t.FOR_INIT_KEYS.length; i++) {
var node = block[t.FOR_INIT_KEYS[i]];
if (t.isBlockScoped(node)) this.register(node, false, "let");
if (t.isBlockScoped(node)) this.registerBinding("let", node);
}
if (t.isBlockStatement(block.body)) {
@@ -345,7 +361,7 @@ Scope.prototype.crawl = function () {
if (t.isFunctionExpression(block) && block.id) {
if (!t.isProperty(this.parentBlock, { method: true })) {
this.register(block.id, null, "var");
this.registerBinding("var", block.id);
}
}
@@ -353,7 +369,7 @@ Scope.prototype.crawl = function () {
if (t.isFunction(block)) {
for (i = 0; i < block.params.length; i++) {
this.register(block.params[i], null, "param");
this.registerBinding("param", block.params[i]);
}
this.traverse(block.body, blockVariableVisitor, this);
}
@@ -367,13 +383,13 @@ Scope.prototype.crawl = function () {
// CatchClause - param
if (t.isCatchClause(block)) {
this.register(block.param, null, "let");
this.registerBinding("let", block.param);
}
// ComprehensionExpression - blocks
if (t.isComprehensionExpression(block)) {
this.register(block, null, "let");
this.registerBinding("let", block);
}
// Program, Function - var variables
@@ -418,46 +434,6 @@ Scope.prototype.push = function (opts) {
}
};
/**
* Walk up the scope tree until we hit a `Function` and then
* push our `node` to it's references.
*
* @param {Object} node
* @param {String} [kind]
*/
Scope.prototype.addBindingToFunctionScope = function (node, kind) {
var scope = this.getFunctionParent();
var ids = t.getBindingIdentifiers(node);
extend(scope.bindings, ids);
extend(scope.references, ids);
if (kind) extend(scope.bindingKinds[kind], ids);
};
/**
* Walk up the scope tree and check to see if it has a binding with the provided
* name, if it does return the binding identifier and scope.
*
* @param {String} name
* @returns {Object} { binding, scope }
*/
Scope.prototype.getBindingWithScope = function (name) {
var scope = this;
do {
binding = scope.getOwnBinding(name);
var if (binding) {
return {
binding: binding,
scope: scope
};
}
} while (scope = scope.parent);
};
/**
* Walk up the scope tree until we hit either a Function or reach the
* very top and hit Program.
@@ -501,67 +477,61 @@ Scope.prototype.getAllBindingsOfKind = function (kind) {
var scope = this;
do {
defaults(ids, scope.bindingKinds[kind]);
for (var name in scope.bindings) {
var binding = scope.bindings[name];
if (binding.kind === kind) ids[name] = binding;
}
scope = scope.parent;
} while (scope);
return ids;
};
//
// misc
Scope.prototype.get = function (id, type) {
return id && (this.getOwn(id, type) || this.parentGet(id, type));
Scope.prototype.bindingIdentifierEquals = function (name, node) {
return this.getBindingIdentifier(name) === node;
};
Scope.prototype.getOwn = function (id, type) {
var refs = {
reference: this.references,
binding: this.bindings,
type: this.types
}[type];
return refs && has(refs, id) && refs[id];
// get
Scope.prototype.getBindingInfo = function (name) {
var scope = this;
do {
var binding = scope.getOwnBindingInfo(name);
if (binding) return binding;
} while (scope = scope.parent);
};
Scope.prototype.parentGet = function (id, type) {
return this.parent && this.parent.get(id, type);
Scope.prototype.getOwnBindingInfo = function (name) {
return this.bindings[name];
};
Scope.prototype.has = function (id, type) {
if (!id) return false;
if (this.hasOwn(id, type)) return true;
if (this.parentHas(id, type)) return true;
if (contains(Scope.defaultDeclarations, id)) return true;
Scope.prototype.getBindingIdentifier = function (name) {
var info = this.getBindingInfo(name);
return info && info.identifier;
};
Scope.prototype.getOwnBindingIdentifier = function (name) {
var binding = this.bindings[name];
return binding && binding.identifier;
};
// has
Scope.prototype.hasOwnBinding = function (name) {
return !!this.getOwnBindingInfo(name);
};
Scope.prototype.hasBinding = function (name) {
if (!name) return false;
if (this.hasOwnBinding(name)) return true;
if (this.parentHasBinding(name)) return true;
if (contains(Scope.defaultDeclarations, name)) return true;
return false;
};
Scope.prototype.hasOwn = function (id, type) {
return !!this.getOwn(id, type);
Scope.prototype.parentHasBinding = function (name) {
return this.parent && this.parent.hasBinding(name);
};
Scope.prototype.parentHas = function (id, type) {
return this.parent && this.parent.has(id, type);
};
each({
reference: "Reference",
binding: "Binding",
type: "Type"
}, function (title, type) {
Scope.prototype[type + "Equals"] = function (id, node) {
return this["get" + title](id) === node;
};
each([
"get",
"has",
"getOwn",
"hasOwn",
"parentGet",
"parentHas",
], function (methodName) {
Scope.prototype[methodName + title] = function (id) {
return this[methodName](id, type);
};
});
});