dynamic scope tracking, toot toot - fixes #957

This commit is contained in:
Sebastian McKenzie 2015-03-09 22:07:05 +11:00
parent c4da0253c5
commit 17b34a2959
5 changed files with 279 additions and 53 deletions

View File

@ -32,9 +32,11 @@ export function ExportDeclaration(node, parent, scope) {
if (node.default) {
if (t.isClassDeclaration(declar)) {
// export default class Foo {};
this.node = [getDeclar(), node];
node.declaration = declar.id;
return [getDeclar(), node];
} else if (t.isClassExpression(declar)) {
// export default class {};
var temp = scope.generateUidIdentifier("default");
declar = t.variableDeclaration("var", [
t.variableDeclarator(temp, declar)
@ -42,25 +44,26 @@ export function ExportDeclaration(node, parent, scope) {
node.declaration = temp;
return [getDeclar(), node];
} else if (t.isFunctionDeclaration(declar)) {
// export default function Foo() {}
node._blockHoist = 2;
node.declaration = declar.id;
return [getDeclar(), node];
}
} else {
if (t.isFunctionDeclaration(declar)) {
// export function Foo() {}
node.specifiers = [t.importSpecifier(declar.id, declar.id)];
node.declaration = null;
node._blockHoist = 2;
return [getDeclar(), node];
} else if (t.isVariableDeclaration(declar)) {
// export var foo = "bar";
var specifiers = [];
var bindings = t.getBindingIdentifiers(declar);
for (var key in bindings) {
var id = bindings[key];
specifiers.push(t.exportSpecifier(id, id));
}
return [declar, t.exportDeclaration(null, specifiers)];
}
}

View File

@ -56,15 +56,9 @@ export default class TraversalPath {
this.scope = TraversalPath.getScope(this.node, this.parent, this.context.scope);
}
refreshScope() {
// todo: remove all binding identifiers associated with this node
// todo: if it hasn't been deleted and just replaced with node/s then add their bindings
}
setContext(parentPath, context, key) {
this.shouldRemove = false;
this.shouldSkip = false;
this.shouldStop = false;
this.shouldSkip = false;
this.shouldStop = false;
this.parentPath = parentPath || this.parentPath;
this.context = context;
@ -76,8 +70,9 @@ export default class TraversalPath {
}
remove() {
this.shouldRemove = true;
this.shouldSkip = true;
this.refreshScope(this.node, []);
this.container[this.key] = null;
this.flatten();
}
skip() {
@ -93,30 +88,58 @@ export default class TraversalPath {
this.context.flatten();
}
refreshScope(oldNode, newNodes) {
var scope = this.scope;
if (!scope) return;
if (!t.isAssignmentExpression(oldNode)) {
var bindings = t.getBindingIdentifiers(oldNode);
for (var key in bindings) {
if (scope.bindingIdentifierEquals(key, bindings[key])) {
scope.removeBinding(key);
}
}
}
for (let i = 0; i < newNodes.length; i++) {
var newNode = newNodes[i];
scope.refreshDeclaration(newNode);
}
}
refresh() {
var node = this.node;
this.refreshScope(node, [node]);
}
get node() {
return this.container[this.key];
}
set node(replacement) {
var isArray = Array.isArray(replacement);
if (!replacement) return this.remove();
var oldNode = this.node;
var isArray = Array.isArray(replacement);
var replacements = isArray ? replacement : [replacement];
// inherit comments from original node to the first replacement node
var inheritTo = replacement;
if (isArray) inheritTo = replacement[0];
if (inheritTo) t.inheritsComments(inheritTo, this.node);
var inheritTo = replacements[0];
if (inheritTo) t.inheritsComments(inheritTo, oldNode);
// replace the node
this.container[this.key] = replacement;
// potentially create new scope
this.setScope();
// refresh scope with new/removed bindings
this.refreshScope(oldNode, replacements);
var file = this.scope && this.scope.file;
if (file) {
if (isArray) {
for (var i = 0; i < replacement.length; i++) {
file.checkNode(replacement[i], this.scope);
}
} else {
file.checkNode(replacement, this.scope);
for (var i = 0; i < replacements.length; i++) {
file.checkNode(replacements[i], this.scope);
}
}
@ -145,11 +168,6 @@ export default class TraversalPath {
if (replacement) {
this.node = replacement;
}
if (this.shouldRemove) {
this.container[this.key] = null;
this.flatten();
}
}
isBlacklisted() {
@ -169,16 +187,17 @@ export default class TraversalPath {
var node = this.node;
var opts = this.opts;
if (Array.isArray(node)) {
// traverse over these replacement nodes we purposely don't call exitNode
// as the original node has been destroyed
for (var i = 0; i < node.length; i++) {
traverse.node(node[i], opts, this.scope, this.state, this);
if (node) {
if (Array.isArray(node)) {
// traverse over these replacement nodes we purposely don't call exitNode
// as the original node has been destroyed
for (var i = 0; i < node.length; i++) {
traverse.node(node[i], opts, this.scope, this.state, this);
}
} else {
traverse.node(node, opts, this.scope, this.state, this);
this.call("exit");
}
} else {
traverse.node(node, opts, this.scope, this.state, this);
this.call("exit");
}
return this.shouldStop;

View File

@ -208,6 +208,14 @@ export default class Scope {
return id;
}
/**
* Description
*
* @param {String} kind
* @param {String} name
* @param {Node} id
*/
checkBlockScopedCollisions(kind, name, id) {
var local = this.getOwnBindingInfo(name);
if (!local) return;
@ -220,6 +228,13 @@ export default class Scope {
}
}
/**
* Description
*
* @param {String} oldName
* @param {String} newName
*/
rename(oldName, newName) {
newName ||= this.generateUidIdentifier(oldName).name;
@ -246,12 +261,18 @@ export default class Scope {
}
});
this.clearOwnBinding(oldName);
scope.removeOwnBinding(oldName);
scope.bindings[newName] = info;
binding.name = newName;
}
/**
* Description
*
* @param {Node} node
*/
inferType(node) {
var target;
@ -284,6 +305,13 @@ export default class Scope {
}
}
/**
* Description
*
* @param {String} name
* @param {String} genericName
*/
isTypeGeneric(name, genericName) {
var info = this.getBindingInfo(name);
if (!info) return false;
@ -292,10 +320,24 @@ export default class Scope {
return t.isGenericTypeAnnotation(type) && t.isIdentifier(type.id, { name: genericName });
}
/**
* Description
*
* @param {String} name
* @param {Node} type
*/
assignTypeGeneric(name, type) {
this.assignType(name, t.genericTypeAnnotation(t.identifier(type)));
}
/**
* Description
*
* @param {String} name
* @param {Node} type
*/
assignType(name, type) {
var info = this.getBindingInfo(name);
if (!info) return;
@ -303,6 +345,14 @@ export default class Scope {
info.typeAnnotation = type;
}
/**
* Description
*
* @param name
* @param id
* @param {Node} node
*/
getTypeAnnotation(name, id, node) {
var info = {
annotation: null,
@ -328,6 +378,13 @@ export default class Scope {
return info;
}
/**
* Description
*
* @param {Node} node
* @param {Number} [i]
*/
toArray(node, i) {
var file = this.file;
@ -354,10 +411,28 @@ export default class Scope {
return t.callExpression(file.addHelper(helperName), args);
}
clearOwnBinding(name) {
delete this.bindings[name];
/**
* Description
*
* @param {Node} node
*/
refreshDeclaration(node) {
if (t.isBlockScoped(node)) {
this.getBlockParent().registerDeclaration(node);
} else if (t.isVariableDeclaration(node, { kind: "var" })) {
this.getFunctionParent().registerDeclaration(node);
} else if (node === this.block) {
this.recrawl();
}
}
/**
* Description
*
* @param {Node} node
*/
registerDeclaration(node) {
if (t.isFunctionDeclaration(node)) {
this.registerBinding("hoisted", node);
@ -374,6 +449,12 @@ export default class Scope {
}
}
/**
* Description
*
* @param {Node} node
*/
registerBindingReassignment(node) {
var ids = t.getBindingIdentifiers(node);
for (var name in ids) {
@ -389,6 +470,13 @@ export default class Scope {
}
}
/**
* Description
*
* @param {String} kind
* @param {Node} node
*/
registerBinding(kind, node) {
if (!kind) throw new ReferenceError("no `kind`");
@ -413,16 +501,21 @@ export default class Scope {
}
}
registerVariableDeclaration(declar) {
var declars = declar.declarations;
for (var i = 0; i < declars.length; i++) {
this.registerBinding(declar.kind, declars[i]);
}
}
/**
* Description
*
* @param {Node} node
*/
addGlobal(node) {
this.globals[node.name] = node;
};
}
/**
* Description
*
* @param {String} name
*/
hasGlobal(name) {
var scope = this;
@ -434,6 +527,19 @@ export default class Scope {
return false;
}
/**
* Description
*/
recrawl() {
this.block._scopeInfo = null;
this.crawl();
}
/**
* Description
*/
crawl() {
var block = this.block;
var i;
@ -474,6 +580,12 @@ export default class Scope {
}
}
// Class
if (t.isClass(block) && block.id) {
this.registerBinding("var", block.id);
}
// Function - params, rest
if (t.isFunction(block)) {
@ -556,6 +668,19 @@ export default class Scope {
return scope;
}
/**
* Walk up the scope tree until we hit either a BlockStatement/Loop or reach the
* very top and hit Program.
*/
getBlockParent() {
var scope = this;
while (scope.parent && !t.isFunction(scope.block) && !t.isLoop(scope.block) && !t.isFunction(scope.block)) {
scope = scope.parent;
}
return scope;
}
/**
* Walks the scope tree and gathers **all** bindings.
*
@ -596,13 +721,23 @@ export default class Scope {
return ids;
}
// misc
/**
* Description
*
* @param {String} name
* @param {Object} node
* @returns {Boolean}
*/
bindingIdentifierEquals(name, node) {
return this.getBindingIdentifier(name) === node;
}
// get
/**
* Description
*
* @param {String} name
*/
getBindingInfo(name) {
var scope = this;
@ -613,25 +748,54 @@ export default class Scope {
} while (scope = scope.parent);
}
/**
* Description
*
* @param {String} name
*/
getOwnBindingInfo(name) {
return this.bindings[name];
}
/**
* Description
*
* @param {String} name
*/
getBindingIdentifier(name) {
var info = this.getBindingInfo(name);
return info && info.identifier;
}
/**
* Description
*
* @param {String} name
*/
getOwnBindingIdentifier(name) {
var binding = this.bindings[name];
return binding && binding.identifier;
}
/**
* Description
*
* @param {String} name
*/
getOwnImmutableBindingValue(name) {
return this._immutableBindingInfoToValue(this.getOwnBindingInfo(name));
}
/**
* Description
*
* @param {String} name
*/
getImmutableBindingValue(name) {
return this._immutableBindingInfoToValue(this.getBindingInfo(name));
}
@ -658,12 +822,22 @@ export default class Scope {
}
}
// has
/**
* Description
*
* @param {String} name
*/
hasOwnBinding(name) {
return !!this.getOwnBindingInfo(name);
}
/**
* Description
*
* @param {String} name
*/
hasBinding(name) {
if (!name) return false;
if (this.hasOwnBinding(name)) return true;
@ -673,7 +847,35 @@ export default class Scope {
return false;
}
/**
* Description
*
* @param {String} name
*/
parentHasBinding(name) {
return this.parent && this.parent.hasBinding(name);
}
/**
* Description
*
* @param {String} name
*/
removeOwnBinding(name) {
delete this.bindings[name];
}
/**
* Description
*
* @param {String} name
*/
removeBinding(name) {
var info = this.getBindingInfo(name);
if (!info) return;
info.scope.removeOwnBinding(name);
}
}

View File

@ -25,7 +25,7 @@
"ImportSpecifier": ["ModuleSpecifier"],
"ExportSpecifier": ["ModuleSpecifier"],
"BlockStatement": ["Statement", "Scopable"],
"BlockStatement": ["Scopable", "Statement"],
"Program": ["Scopable"],
"CatchClause": ["Scopable"],
@ -36,8 +36,8 @@
"SpreadProperty": ["UnaryLike"],
"SpreadElement": ["UnaryLike"],
"ClassDeclaration": ["Statement", "Declaration", "Class"],
"ClassExpression": ["Class", "Expression"],
"ClassDeclaration": ["Scope", "Class", "Statement", "Declaration"],
"ClassExpression": ["Scope", "Class", "Expression"],
"ForOfStatement": ["Scopable", "Statement", "For", "Loop"],
"ForInStatement": ["Scopable", "Statement", "For", "Loop"],

View File

@ -658,7 +658,9 @@ t.getBindingIdentifiers.keys = {
ImportBatchSpecifier: ["name"],
VariableDeclarator: ["id"],
FunctionDeclaration: ["id"],
FunctionExpression: ["id"],
ClassDeclaration: ["id"],
ClassExpression: ["id"],
SpreadElement: ["argument"],
RestElement: ["argument"],
UpdateExpression: ["argument"],