clean up scope tracking and add some simple flow type tracking and inferrence #653
This commit is contained in:
@@ -30,11 +30,7 @@ function Scope(block, parentBlock, parent, file) {
|
||||
this.parentBlock = parentBlock;
|
||||
this.block = block;
|
||||
|
||||
var info = this.getInfo();
|
||||
|
||||
this.references = info.references;
|
||||
this.bindings = info.bindings;
|
||||
this.declarationKinds = info.declarationKinds;
|
||||
this.init();
|
||||
}
|
||||
|
||||
Scope.defaultDeclarations = flatten([globals.builtin, globals.browser, globals.node].map(Object.keys));
|
||||
@@ -126,12 +122,91 @@ Scope.prototype.generateTempBasedOnNode = function (node) {
|
||||
return id;
|
||||
};
|
||||
|
||||
Scope.prototype.checkBlockScopedCollisions = function (key, id) {
|
||||
if (this.declarationKinds["let"][key] || this.declarationKinds["const"][key]) {
|
||||
throw this.file.errorWithNode(id, "Duplicate declaration " + key, TypeError);
|
||||
}
|
||||
};
|
||||
|
||||
Scope.prototype.inferType = function (node) {
|
||||
var target;
|
||||
|
||||
if (t.isVariableDeclarator(node)) {
|
||||
target = node.init;
|
||||
}
|
||||
|
||||
if (t.isArrayExpression(target) || t.isObjectExpression(target)) {
|
||||
// todo: possibly call some helper that will resolve these to a type annotation
|
||||
}
|
||||
|
||||
if (t.isCallExpression(target)) {
|
||||
target = target.callee;
|
||||
}
|
||||
|
||||
if (t.isMemberExpression(target)) {
|
||||
// todo: crawl this and find the correct type, bail on anything that we cannot
|
||||
// possibly be 100% confident on
|
||||
}
|
||||
|
||||
if (t.isIdentifier(target)) {
|
||||
return this.getType(target.name);
|
||||
}
|
||||
};
|
||||
|
||||
Scope.prototype.registerType = function (key, id, node) {
|
||||
var type;
|
||||
|
||||
if (id.typeAnnotation) {
|
||||
type = id.typeAnnotation;
|
||||
}
|
||||
|
||||
if (!type) {
|
||||
type = this.inferType(node);
|
||||
}
|
||||
|
||||
if (type) {
|
||||
if (t.isTypeAnnotation(type)) type = type.typeAnnotation;
|
||||
this.types[key] = type;
|
||||
}
|
||||
};
|
||||
|
||||
Scope.prototype.register = function (node, reference, kind) {
|
||||
if (t.isVariableDeclaration(node)) {
|
||||
return this.registerVariableDeclaration(node);
|
||||
}
|
||||
|
||||
var ids = t.getBindingIdentifiers(node);
|
||||
|
||||
extend(this.references, ids);
|
||||
|
||||
if (reference) return;
|
||||
|
||||
for (var key in ids) {
|
||||
var id = ids[key];
|
||||
|
||||
this.checkBlockScopedCollisions(key, id);
|
||||
|
||||
this.registerType(key, id, node);
|
||||
this.bindings[key] = id;
|
||||
}
|
||||
|
||||
var kinds = this.declarationKinds[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);
|
||||
}
|
||||
};
|
||||
|
||||
var functionVariableVisitor = {
|
||||
enter: function (node, parent, scope, context, state) {
|
||||
if (t.isFor(node)) {
|
||||
each(t.FOR_INIT_KEYS, function (key) {
|
||||
var declar = node[key];
|
||||
if (t.isVar(declar)) state.add(declar);
|
||||
if (t.isVar(declar)) state.scope.register(declar);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -149,82 +224,69 @@ var functionVariableVisitor = {
|
||||
if (t.isExportDeclaration(node) && t.isDeclaration(node.declaration)) return;
|
||||
|
||||
// we've ran into a declaration!
|
||||
if (t.isDeclaration(node)) state.add(node);
|
||||
if (t.isDeclaration(node)) state.scope.register(node);
|
||||
}
|
||||
};
|
||||
|
||||
var programReferenceVisitor = {
|
||||
enter: function (node, parent, scope, context, add) {
|
||||
enter: function (node, parent, scope, context, state) {
|
||||
if (t.isReferencedIdentifier(node, parent) && !scope.hasReference(node.name)) {
|
||||
add(node, true);
|
||||
state.register(node, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var blockVariableVisitor = {
|
||||
enter: function (node, parent, scope, context, add) {
|
||||
enter: function (node, parent, scope, context, state) {
|
||||
if (t.isBlockScoped(node)) {
|
||||
add(node, false);
|
||||
state.register(node);
|
||||
} else if (t.isScope(node)) {
|
||||
context.skip();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Scope.prototype.getInfo = function () {
|
||||
Scope.prototype.init = function () {
|
||||
var parent = this.parent;
|
||||
var block = this.block;
|
||||
var self = this;
|
||||
if (block._scopeInfo) return block._scopeInfo;
|
||||
var i;
|
||||
|
||||
var info = block._scopeInfo = {};
|
||||
//
|
||||
|
||||
var bindings = info.bindings = object();
|
||||
var references = info.references = object();
|
||||
var types = info.types = object();
|
||||
var declarationKinds = info.declarationKinds = {
|
||||
"const": object(),
|
||||
"var": object(),
|
||||
"let": object()
|
||||
var info = block._scopeInfo;
|
||||
if (info) {
|
||||
extend(this, info);
|
||||
return;
|
||||
}
|
||||
|
||||
info = block._scopeInfo = {
|
||||
declarationKinds: {
|
||||
"const": object(),
|
||||
"var": object(),
|
||||
"let": object()
|
||||
},
|
||||
|
||||
references: object(),
|
||||
bindings: object(),
|
||||
types: object(),
|
||||
};
|
||||
|
||||
var add = function (node, reference) {
|
||||
var ids = t.getBindingIdentifiers(node);
|
||||
extend(this, info);
|
||||
|
||||
extend(references, ids);
|
||||
|
||||
if (!reference) {
|
||||
for (var key in ids) {
|
||||
var id = ids[key];
|
||||
|
||||
if (id.typeAnnotation) {
|
||||
types[id] = id.typeAnnotation;
|
||||
}
|
||||
|
||||
if (declarationKinds["let"][key] || declarationKinds["const"][key]) {
|
||||
throw self.file.errorWithNode(id, "Duplicate declaration " + key, TypeError);
|
||||
}
|
||||
}
|
||||
|
||||
extend(bindings, ids);
|
||||
|
||||
var kinds = declarationKinds[node.kind];
|
||||
if (kinds) extend(kinds, ids);
|
||||
}
|
||||
};
|
||||
//
|
||||
|
||||
if (parent && t.isBlockStatement(block) && t.isLoop(parent.block, { body: block })) {
|
||||
// delegate let bindings to the parent loop
|
||||
return info;
|
||||
return;
|
||||
}
|
||||
|
||||
// ForStatement - left, init
|
||||
|
||||
if (t.isLoop(block)) {
|
||||
each(t.FOR_INIT_KEYS, function (key) {
|
||||
var node = block[key];
|
||||
if (t.isBlockScoped(node)) add(node, false, true);
|
||||
});
|
||||
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, true);
|
||||
}
|
||||
|
||||
if (t.isBlockStatement(block.body)) {
|
||||
block = block.body;
|
||||
@@ -234,19 +296,27 @@ Scope.prototype.getInfo = function () {
|
||||
// Program, BlockStatement - let variables
|
||||
|
||||
if (t.isBlockStatement(block) || t.isProgram(block)) {
|
||||
traverse(block, blockVariableVisitor, this, add);
|
||||
traverse(block, blockVariableVisitor, this, this);
|
||||
}
|
||||
|
||||
// CatchClause - param
|
||||
|
||||
if (t.isCatchClause(block)) {
|
||||
add(block.param);
|
||||
this.register(block.param);
|
||||
}
|
||||
|
||||
// ComprehensionExpression - blocks
|
||||
|
||||
if (t.isComprehensionExpression(block)) {
|
||||
add(block);
|
||||
this.register(block);
|
||||
}
|
||||
|
||||
// Function - params, rest
|
||||
|
||||
if (t.isFunction(block)) {
|
||||
for (i = 0; i < block.params.length; i++) {
|
||||
this.register(block.params[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Program, Function - var variables
|
||||
@@ -254,7 +324,7 @@ Scope.prototype.getInfo = function () {
|
||||
if (t.isProgram(block) || t.isFunction(block)) {
|
||||
traverse(block, functionVariableVisitor, this, {
|
||||
blockId: block.id,
|
||||
add: add
|
||||
scope: this
|
||||
});
|
||||
}
|
||||
|
||||
@@ -262,25 +332,15 @@ Scope.prototype.getInfo = function () {
|
||||
if (!t.isProperty(this.parentBlock, { method: true })) {
|
||||
// SpiderMonkey AST doesn't use MethodDefinition here when it probably
|
||||
// should since they should be semantically the same?
|
||||
add(block.id);
|
||||
this.register(block.id);
|
||||
}
|
||||
}
|
||||
|
||||
// Program
|
||||
|
||||
if (t.isProgram(block)) {
|
||||
traverse(block, programReferenceVisitor, this, add);
|
||||
traverse(block, programReferenceVisitor, this, this);
|
||||
}
|
||||
|
||||
// Function - params, rest
|
||||
|
||||
if (t.isFunction(block)) {
|
||||
each(block.params, function (param) {
|
||||
add(param);
|
||||
});
|
||||
}
|
||||
|
||||
return info;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user