diff --git a/lib/6to5/traverse/scope.js b/lib/6to5/traverse/scope.js index cdaee0f2df..d872d8c3a1 100644 --- a/lib/6to5/traverse/scope.js +++ b/lib/6to5/traverse/scope.js @@ -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; }; /**