You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
287 lines
8.7 KiB
JavaScript
287 lines
8.7 KiB
JavaScript
7 years ago
|
var assert = require("assert");
|
||
|
var types = require("./types");
|
||
|
var Type = types.Type;
|
||
|
var namedTypes = types.namedTypes;
|
||
|
var Node = namedTypes.Node;
|
||
|
var Expression = namedTypes.Expression;
|
||
|
var isArray = types.builtInTypes.array;
|
||
|
var hasOwn = Object.prototype.hasOwnProperty;
|
||
|
var b = types.builders;
|
||
|
|
||
|
function Scope(path, parentScope) {
|
||
|
assert.ok(this instanceof Scope);
|
||
|
assert.ok(path instanceof require("./node-path"));
|
||
|
ScopeType.assert(path.value);
|
||
|
|
||
|
var depth;
|
||
|
|
||
|
if (parentScope) {
|
||
|
assert.ok(parentScope instanceof Scope);
|
||
|
depth = parentScope.depth + 1;
|
||
|
} else {
|
||
|
parentScope = null;
|
||
|
depth = 0;
|
||
|
}
|
||
|
|
||
|
Object.defineProperties(this, {
|
||
|
path: { value: path },
|
||
|
node: { value: path.value },
|
||
|
isGlobal: { value: !parentScope, enumerable: true },
|
||
|
depth: { value: depth },
|
||
|
parent: { value: parentScope },
|
||
|
bindings: { value: {} }
|
||
|
});
|
||
|
}
|
||
|
|
||
|
var scopeTypes = [
|
||
|
// Program nodes introduce global scopes.
|
||
|
namedTypes.Program,
|
||
|
|
||
|
// Function is the supertype of FunctionExpression,
|
||
|
// FunctionDeclaration, ArrowExpression, etc.
|
||
|
namedTypes.Function,
|
||
|
|
||
|
// In case you didn't know, the caught parameter shadows any variable
|
||
|
// of the same name in an outer scope.
|
||
|
namedTypes.CatchClause
|
||
|
];
|
||
|
|
||
|
var ScopeType = Type.or.apply(Type, scopeTypes);
|
||
|
|
||
|
Scope.isEstablishedBy = function(node) {
|
||
|
return ScopeType.check(node);
|
||
|
};
|
||
|
|
||
|
var Sp = Scope.prototype;
|
||
|
|
||
|
// Will be overridden after an instance lazily calls scanScope.
|
||
|
Sp.didScan = false;
|
||
|
|
||
|
Sp.declares = function(name) {
|
||
|
this.scan();
|
||
|
return hasOwn.call(this.bindings, name);
|
||
|
};
|
||
|
|
||
|
Sp.declareTemporary = function(prefix) {
|
||
|
if (prefix) {
|
||
|
assert.ok(/^[a-z$_]/i.test(prefix), prefix);
|
||
|
} else {
|
||
|
prefix = "t$";
|
||
|
}
|
||
|
|
||
|
// Include this.depth in the name to make sure the name does not
|
||
|
// collide with any variables in nested/enclosing scopes.
|
||
|
prefix += this.depth.toString(36) + "$";
|
||
|
|
||
|
this.scan();
|
||
|
|
||
|
var index = 0;
|
||
|
while (this.declares(prefix + index)) {
|
||
|
++index;
|
||
|
}
|
||
|
|
||
|
var name = prefix + index;
|
||
|
return this.bindings[name] = types.builders.identifier(name);
|
||
|
};
|
||
|
|
||
|
Sp.injectTemporary = function(identifier, init) {
|
||
|
identifier || (identifier = this.declareTemporary());
|
||
|
|
||
|
var bodyPath = this.path.get("body");
|
||
|
if (namedTypes.BlockStatement.check(bodyPath.value)) {
|
||
|
bodyPath = bodyPath.get("body");
|
||
|
}
|
||
|
|
||
|
bodyPath.unshift(
|
||
|
b.variableDeclaration(
|
||
|
"var",
|
||
|
[b.variableDeclarator(identifier, init || null)]
|
||
|
)
|
||
|
);
|
||
|
|
||
|
return identifier;
|
||
|
};
|
||
|
|
||
|
Sp.scan = function(force) {
|
||
|
if (force || !this.didScan) {
|
||
|
for (var name in this.bindings) {
|
||
|
// Empty out this.bindings, just in cases.
|
||
|
delete this.bindings[name];
|
||
|
}
|
||
|
scanScope(this.path, this.bindings);
|
||
|
this.didScan = true;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
Sp.getBindings = function () {
|
||
|
this.scan();
|
||
|
return this.bindings;
|
||
|
};
|
||
|
|
||
|
function scanScope(path, bindings) {
|
||
|
var node = path.value;
|
||
|
ScopeType.assert(node);
|
||
|
|
||
|
if (namedTypes.CatchClause.check(node)) {
|
||
|
// A catch clause establishes a new scope but the only variable
|
||
|
// bound in that scope is the catch parameter. Any other
|
||
|
// declarations create bindings in the outer scope.
|
||
|
addPattern(path.get("param"), bindings);
|
||
|
|
||
|
} else {
|
||
|
recursiveScanScope(path, bindings);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function recursiveScanScope(path, bindings) {
|
||
|
var node = path.value;
|
||
|
|
||
|
if (path.parent &&
|
||
|
namedTypes.FunctionExpression.check(path.parent.node) &&
|
||
|
path.parent.node.id) {
|
||
|
addPattern(path.parent.get("id"), bindings);
|
||
|
}
|
||
|
|
||
|
if (!node) {
|
||
|
// None of the remaining cases matter if node is falsy.
|
||
|
|
||
|
} else if (isArray.check(node)) {
|
||
|
path.each(function(childPath) {
|
||
|
recursiveScanChild(childPath, bindings);
|
||
|
});
|
||
|
|
||
|
} else if (namedTypes.Function.check(node)) {
|
||
|
path.get("params").each(function(paramPath) {
|
||
|
addPattern(paramPath, bindings);
|
||
|
});
|
||
|
|
||
|
recursiveScanChild(path.get("body"), bindings);
|
||
|
|
||
|
} else if (namedTypes.VariableDeclarator.check(node)) {
|
||
|
addPattern(path.get("id"), bindings);
|
||
|
recursiveScanChild(path.get("init"), bindings);
|
||
|
|
||
|
} else if (node.type === "ImportSpecifier" ||
|
||
|
node.type === "ImportNamespaceSpecifier" ||
|
||
|
node.type === "ImportDefaultSpecifier") {
|
||
|
addPattern(
|
||
|
// Esprima used to use the .name field to refer to the local
|
||
|
// binding identifier for ImportSpecifier nodes, but .id for
|
||
|
// ImportNamespaceSpecifier and ImportDefaultSpecifier nodes.
|
||
|
// ESTree/Acorn/ESpree use .local for all three node types.
|
||
|
path.get(node.local ? "local" :
|
||
|
node.name ? "name" : "id"),
|
||
|
bindings
|
||
|
);
|
||
|
|
||
|
} else if (Node.check(node) && !Expression.check(node)) {
|
||
|
types.eachField(node, function(name, child) {
|
||
|
var childPath = path.get(name);
|
||
|
assert.strictEqual(childPath.value, child);
|
||
|
recursiveScanChild(childPath, bindings);
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function recursiveScanChild(path, bindings) {
|
||
|
var node = path.value;
|
||
|
|
||
|
if (!node || Expression.check(node)) {
|
||
|
// Ignore falsy values and Expressions.
|
||
|
|
||
|
} else if (namedTypes.FunctionDeclaration.check(node)) {
|
||
|
addPattern(path.get("id"), bindings);
|
||
|
|
||
|
} else if (namedTypes.ClassDeclaration &&
|
||
|
namedTypes.ClassDeclaration.check(node)) {
|
||
|
addPattern(path.get("id"), bindings);
|
||
|
|
||
|
} else if (ScopeType.check(node)) {
|
||
|
if (namedTypes.CatchClause.check(node)) {
|
||
|
var catchParamName = node.param.name;
|
||
|
var hadBinding = hasOwn.call(bindings, catchParamName);
|
||
|
|
||
|
// Any declarations that occur inside the catch body that do
|
||
|
// not have the same name as the catch parameter should count
|
||
|
// as bindings in the outer scope.
|
||
|
recursiveScanScope(path.get("body"), bindings);
|
||
|
|
||
|
// If a new binding matching the catch parameter name was
|
||
|
// created while scanning the catch body, ignore it because it
|
||
|
// actually refers to the catch parameter and not the outer
|
||
|
// scope that we're currently scanning.
|
||
|
if (!hadBinding) {
|
||
|
delete bindings[catchParamName];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
recursiveScanScope(path, bindings);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function addPattern(patternPath, bindings) {
|
||
|
var pattern = patternPath.value;
|
||
|
namedTypes.Pattern.assert(pattern);
|
||
|
|
||
|
if (namedTypes.Identifier.check(pattern)) {
|
||
|
if (hasOwn.call(bindings, pattern.name)) {
|
||
|
bindings[pattern.name].push(patternPath);
|
||
|
} else {
|
||
|
bindings[pattern.name] = [patternPath];
|
||
|
}
|
||
|
|
||
|
} else if (namedTypes.ObjectPattern &&
|
||
|
namedTypes.ObjectPattern.check(pattern)) {
|
||
|
patternPath.get('properties').each(function(propertyPath) {
|
||
|
var property = propertyPath.value;
|
||
|
if (namedTypes.Pattern.check(property)) {
|
||
|
addPattern(propertyPath, bindings);
|
||
|
} else if (namedTypes.Property.check(property)) {
|
||
|
addPattern(propertyPath.get('value'), bindings);
|
||
|
} else if (namedTypes.SpreadProperty &&
|
||
|
namedTypes.SpreadProperty.check(property)) {
|
||
|
addPattern(propertyPath.get('argument'), bindings);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
} else if (namedTypes.ArrayPattern &&
|
||
|
namedTypes.ArrayPattern.check(pattern)) {
|
||
|
patternPath.get('elements').each(function(elementPath) {
|
||
|
var element = elementPath.value;
|
||
|
if (namedTypes.Pattern.check(element)) {
|
||
|
addPattern(elementPath, bindings);
|
||
|
} else if (namedTypes.SpreadElement &&
|
||
|
namedTypes.SpreadElement.check(element)) {
|
||
|
addPattern(elementPath.get("argument"), bindings);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
} else if (namedTypes.PropertyPattern &&
|
||
|
namedTypes.PropertyPattern.check(pattern)) {
|
||
|
addPattern(patternPath.get('pattern'), bindings);
|
||
|
|
||
|
} else if ((namedTypes.SpreadElementPattern &&
|
||
|
namedTypes.SpreadElementPattern.check(pattern)) ||
|
||
|
(namedTypes.SpreadPropertyPattern &&
|
||
|
namedTypes.SpreadPropertyPattern.check(pattern))) {
|
||
|
addPattern(patternPath.get('argument'), bindings);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Sp.lookup = function(name) {
|
||
|
for (var scope = this; scope; scope = scope.parent)
|
||
|
if (scope.declares(name))
|
||
|
break;
|
||
|
return scope;
|
||
|
};
|
||
|
|
||
|
Sp.getGlobalScope = function() {
|
||
|
var scope = this;
|
||
|
while (!scope.isGlobal)
|
||
|
scope = scope.parent;
|
||
|
return scope;
|
||
|
};
|
||
|
|
||
|
module.exports = Scope;
|