dynamic scope tracking, toot toot - fixes #957
This commit is contained in:
parent
c4da0253c5
commit
17b34a2959
@ -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)];
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"],
|
||||
|
||||
@ -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"],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user