123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- // scopetools.js
- // MIT licensed, see LICENSE file
- // Copyright (c) 2013-2015 Olov Lassus <olov.lassus@gmail.com>
- "use strict";
- const assert = require("assert");
- const traverse = require("ordered-ast-traverse");
- const Scope = require("./scope");
- const is = require("simple-is");
- module.exports = {
- setupScopeAndReferences: setupScopeAndReferences,
- isReference: isReference,
- };
- function setupScopeAndReferences(root) {
- traverse(root, {pre: createScopes});
- createTopScope(root.$scope);
- }
- function createScopes(node, parent) {
- node.$parent = parent;
- node.$scope = parent ? parent.$scope : null; // may be overridden
- if (isNonFunctionBlock(node, parent)) {
- // A block node is a scope unless parent is a function
- node.$scope = new Scope({
- kind: "block",
- node: node,
- parent: parent.$scope,
- });
- } else if (node.type === "VariableDeclaration") {
- // Variable declarations names goes in current scope
- node.declarations.forEach(function(declarator) {
- const name = declarator.id.name;
- node.$scope.add(name, node.kind, declarator.id, declarator.range[1]);
- });
- } else if (isFunction(node)) {
- // Function is a scope, with params in it
- // There's no block-scope under it
- node.$scope = new Scope({
- kind: "hoist",
- node: node,
- parent: parent.$scope,
- });
- // function has a name
- if (node.id) {
- if (node.type === "FunctionDeclaration") {
- // Function name goes in parent scope for declared functions
- parent.$scope.add(node.id.name, "fun", node.id, null);
- } else if (node.type === "FunctionExpression") {
- // Function name goes in function's scope for named function expressions
- node.$scope.add(node.id.name, "fun", node.id, null);
- } else {
- assert(false);
- }
- }
- node.params.forEach(function(param) {
- node.$scope.add(param.name, "param", param, null);
- });
- } else if (isForWithConstLet(node) || isForInOfWithConstLet(node)) {
- // For(In/Of) loop with const|let declaration is a scope, with declaration in it
- // There may be a block-scope under it
- node.$scope = new Scope({
- kind: "block",
- node: node,
- parent: parent.$scope,
- });
- } else if (node.type === "CatchClause") {
- const identifier = node.param;
- node.$scope = new Scope({
- kind: "catch-block",
- node: node,
- parent: parent.$scope,
- });
- node.$scope.add(identifier.name, "caught", identifier, null);
- // All hoist-scope keeps track of which variables that are propagated through,
- // i.e. an reference inside the scope points to a declaration outside the scope.
- // This is used to mark "taint" the name since adding a new variable in the scope,
- // with a propagated name, would change the meaning of the existing references.
- //
- // catch(e) is special because even though e is a variable in its own scope,
- // we want to make sure that catch(e){let e} is never transformed to
- // catch(e){var e} (but rather var e$0). For that reason we taint the use of e
- // in the closest hoist-scope, i.e. where var e$0 belongs.
- node.$scope.closestHoistScope().markPropagates(identifier.name);
- } else if (node.type === "Program") {
- // Top-level program is a scope
- // There's no block-scope under it
- node.$scope = new Scope({
- kind: "hoist",
- node: node,
- parent: null,
- });
- }
- }
- function createTopScope(programScope) {
- function inject(obj) {
- for (let name in obj) {
- const writeable = obj[name];
- const kind = (writeable ? "var" : "const");
- if (topScope.hasOwn(name)) {
- topScope.remove(name);
- }
- topScope.add(name, kind, {loc: {start: {line: -1}}}, -1);
- }
- }
- const topScope = new Scope({
- kind: "hoist",
- node: {},
- parent: null,
- });
- const complementary = {
- undefined: false,
- Infinity: false,
- console: false,
- };
- inject(complementary);
- // inject(jshint_vars.reservedVars);
- // inject(jshint_vars.ecmaIdentifiers);
- // link it in
- programScope.parent = topScope;
- topScope.children.push(programScope);
- return topScope;
- }
- function isConstLet(kind) {
- return kind === "const" || kind === "let";
- }
- function isNonFunctionBlock(node, parent) {
- return node.type === "BlockStatement" && parent.type !== "FunctionDeclaration" && parent.type !== "FunctionExpression";
- }
- function isForWithConstLet(node) {
- return node.type === "ForStatement" && node.init && node.init.type === "VariableDeclaration" && isConstLet(node.init.kind);
- }
- function isForInOfWithConstLet(node) {
- return isForInOf(node) && node.left.type === "VariableDeclaration" && isConstLet(node.left.kind);
- }
- function isForInOf(node) {
- return node.type === "ForInStatement" || node.type === "ForOfStatement";
- }
- function isFunction(node) {
- return node.type === "FunctionDeclaration" || node.type === "FunctionExpression";
- }
- function isReference(node) {
- const parent = node.$parent;
- return node.$refToScope ||
- node.type === "Identifier" &&
- !(parent.type === "VariableDeclarator" && parent.id === node) && // var|let|const $
- !(parent.type === "MemberExpression" && parent.computed === false && parent.property === node) && // obj.$
- !(parent.type === "Property" && parent.key === node) && // {$: ...}
- !(parent.type === "LabeledStatement" && parent.label === node) && // $: ...
- !(parent.type === "CatchClause" && parent.param === node) && // catch($)
- !(isFunction(parent) && parent.id === node) && // function $(..
- !(isFunction(parent) && is.someof(node, parent.params)) && // function f($)..
- true;
- }
|