UNPKG

ng-annotate-patched

Version:

add, remove and rebuild angularjs dependency injection annotations

155 lines (129 loc) 4.99 kB
// scope.js // MIT licensed, see LICENSE file // Copyright (c) 2013-2016 Olov Lassus <olov.lassus@gmail.com> "use strict"; const assert = require("assert"); module.exports = class Scope { constructor(args) { assert(["hoist", "block", "catch-block"].includes(args.kind)); assert(args.node !== null && typeof args.node === "object"); assert(args.parent === null || typeof args.parent === "object"); // kind === "hoist": function scopes, program scope, injected globals // kind === "block": ES6 block scopes // kind === "catch-block": catch block scopes this.kind = args.kind; // the AST node the block corresponds to this.node = args.node; // parent scope this.parent = args.parent; // children scopes for easier traversal (populated internally) this.children = []; // scope declarations. decls[variable_name] = { // kind: "fun" for functions, // "param" for function parameters, // "caught" for catch parameter // "var", // "const", // "let" // node: the AST node the declaration corresponds to // from: source code index from which it is visible at earliest // (only stored for "const", "let" [and "var"] nodes) // } this.decls = new Map(); // names of all variables declared outside this hoist scope but // referenced in this scope (immediately or in child). // only stored on hoist scopes for efficiency // (because we currently generate lots of empty block scopes) this.propagates = (this.kind === "hoist" ? new Set() : null); // scopes register themselves with their parents for easier traversal if (this.parent) { this.parent.children.push(this); } } print(indent) { indent = indent || 0; const scope = this; const names = this.decls.keys().map(name => { return `${name} [${scope.decls.get(name).kind}]`; }).join(", "); const propagates = this.propagates ? this.propagates.items().join(", ") : ""; console.log(`${fmt.repeat(" ", indent)}${this.node.type}: ${names}. propagates: ${propagates}`); for (const c of this.children) { c.print(indent + 2); } } add(name, kind, node, referableFromPos) { assert(["fun", "param", "var", "caught", "const", "let"].includes(kind)); // function isConstLet(kind) { // return ["const", "let"].includes(kind); // } let scope = this; // search nearest hoist-scope for fun, param and var's // const, let and caught variables go directly in the scope (which may be hoist, block or catch-block) if (["fun", "param", "var"].includes(kind)) { while (scope.kind !== "hoist") { // if (scope.decls.has(name) && isConstLet(scope.decls.get(name).kind)) { // could be caught // return error(getline(node), "{0} is already declared", name); // } scope = scope.parent; } } // name exists in scope and either new or existing kind is const|let => error // if (scope.decls.has(name) && (isConstLet(scope.decls.get(name).kind) || isConstLet(kind))) { // return error(getline(node), "{0} is already declared", name); // } const declaration = { kind, node, }; if (referableFromPos) { assert(["var", "const", "let"].includes(kind)); declaration.from = referableFromPos; } scope.decls.set(name, declaration); } getKind(name) { assert(typeof name === "string"); const decl = this.decls.get(name); return decl ? decl.kind : null; } getNode(name) { assert(typeof name === "string"); const decl = this.decls.get(name); return decl ? decl.node : null; } getFromPos(name) { assert(typeof name === "string"); const decl = this.decls.get(name); return decl ? decl.from : null; } hasOwn(name) { return this.decls.has(name); } remove(name) { return this.decls.remove(name); } doesPropagate(name) { return this.propagates.has(name); } markPropagates(name) { this.propagates.add(name); } closestHoistScope() { let scope = this; while (scope.kind !== "hoist") { scope = scope.parent; } return scope; } lookup(name) { for (let scope = this; scope; scope = scope.parent) { if (scope.decls.has(name)) { return scope; } else if (scope.kind === "hoist") { scope.propagates.add(name); } } return null; } };