2 Copyright (C) 2015 Yusuke Suzuki <utatane.tea@gmail.com>
4 Redistribution and use in source and binary forms, with or without
5 modification, are permitted provided that the following conditions are met:
7 * Redistributions of source code must retain the above copyright
8 notice, this list of conditions and the following disclaimer.
9 * Redistributions in binary form must reproduce the above copyright
10 notice, this list of conditions and the following disclaimer in the
11 documentation and/or other materials provided with the distribution.
13 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
14 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16 ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
17 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 /* eslint-disable no-underscore-dangle */
27 /* eslint-disable no-undefined */
29 const Syntax = require("estraverse").Syntax;
31 const Reference = require("./reference");
32 const Variable = require("./variable");
33 const Definition = require("./definition").Definition;
34 const assert = require("assert");
37 * Test if scope is struct
38 * @param {Scope} scope - scope
39 * @param {Block} block - block
40 * @param {boolean} isMethodDefinition - is method definition
41 * @param {boolean} useDirective - use directive
42 * @returns {boolean} is strict scope
44 function isStrictScope(scope, block, isMethodDefinition, useDirective) {
47 // When upper scope is exists and strict, inner scope is also strict.
48 if (scope.upper && scope.upper.isStrict) {
52 if (isMethodDefinition) {
56 if (scope.type === "class" || scope.type === "module") {
60 if (scope.type === "block" || scope.type === "switch") {
64 if (scope.type === "function") {
65 if (block.type === Syntax.ArrowFunctionExpression && block.body.type !== Syntax.BlockStatement) {
69 if (block.type === Syntax.Program) {
78 } else if (scope.type === "global") {
84 // Search 'use strict' directive.
86 for (let i = 0, iz = body.body.length; i < iz; ++i) {
87 const stmt = body.body[i];
89 if (stmt.type !== Syntax.DirectiveStatement) {
92 if (stmt.raw === "\"use strict\"" || stmt.raw === "'use strict'") {
97 for (let i = 0, iz = body.body.length; i < iz; ++i) {
98 const stmt = body.body[i];
100 if (stmt.type !== Syntax.ExpressionStatement) {
103 const expr = stmt.expression;
105 if (expr.type !== Syntax.Literal || typeof expr.value !== "string") {
108 if (expr.raw !== null && expr.raw !== undefined) {
109 if (expr.raw === "\"use strict\"" || expr.raw === "'use strict'") {
113 if (expr.value === "use strict") {
124 * @param {ScopeManager} scopeManager - scope manager
125 * @param {Scope} scope - scope
128 function registerScope(scopeManager, scope) {
129 scopeManager.scopes.push(scope);
131 const scopes = scopeManager.__nodeToScope.get(scope.block);
136 scopeManager.__nodeToScope.set(scope.block, [scope]);
141 * Should be statically
142 * @param {Object} def - def
143 * @returns {boolean} should be statically
145 function shouldBeStatically(def) {
147 (def.type === Variable.ClassName) ||
148 (def.type === Variable.Variable && def.parent.kind !== "var")
156 constructor(scopeManager, type, upperScope, block, isMethodDefinition) {
159 * One of 'module', 'block', 'switch', 'function', 'catch', 'with', 'function', 'class', 'global'.
160 * @member {String} Scope#type
165 * The scoped {@link Variable}s of this scope, as <code>{ Variable.name
166 * : Variable }</code>.
167 * @member {Map} Scope#set
169 this.set = new Map();
172 * The tainted variables of this scope, as <code>{ Variable.name :
174 * @member {Map} Scope#taints */
175 this.taints = new Map();
178 * Generally, through the lexical scoping of JS you can always know
179 * which variable an identifier in the source code refers to. There are
180 * a few exceptions to this rule. With 'global' and 'with' scopes you
181 * can only decide at runtime which variable a reference refers to.
182 * Moreover, if 'eval()' is used in a scope, it might introduce new
183 * bindings in this or its parent scopes.
184 * All those scopes are considered 'dynamic'.
185 * @member {boolean} Scope#dynamic
187 this.dynamic = this.type === "global" || this.type === "with";
190 * A reference to the scope-defining syntax node.
191 * @member {espree.Node} Scope#block
196 * The {@link Reference|references} that are not resolved with this scope.
197 * @member {Reference[]} Scope#through
202 * The scoped {@link Variable}s of this scope. In the case of a
203 * 'function' scope this includes the automatic argument <em>arguments</em> as
204 * its first element, as well as all further formal arguments.
205 * @member {Variable[]} Scope#variables
210 * Any variable {@link Reference|reference} found in this scope. This
211 * includes occurrences of local variables as well as variables from
212 * parent scopes (including the global scope). For local variables
213 * this also includes defining occurrences (like in a 'var' statement).
214 * In a 'function' scope this does not include the occurrences of the
215 * formal parameter in the parameter list.
216 * @member {Reference[]} Scope#references
218 this.references = [];
221 * For 'global' and 'function' scopes, this is a self-reference. For
222 * other scope types this is the <em>variableScope</em> value of the
224 * @member {Scope} Scope#variableScope
227 (this.type === "global" || this.type === "function" || this.type === "module") ? this : upperScope.variableScope;
230 * Whether this scope is created by a FunctionExpression.
231 * @member {boolean} Scope#functionExpressionScope
233 this.functionExpressionScope = false;
236 * Whether this is a scope that contains an 'eval()' invocation.
237 * @member {boolean} Scope#directCallToEvalScope
239 this.directCallToEvalScope = false;
242 * @member {boolean} Scope#thisFound
244 this.thisFound = false;
249 * Reference to the parent {@link Scope|scope}.
250 * @member {Scope} Scope#upper
252 this.upper = upperScope;
255 * Whether 'use strict' is in effect in this scope.
256 * @member {boolean} Scope#isStrict
258 this.isStrict = isStrictScope(this, block, isMethodDefinition, scopeManager.__useDirective());
261 * List of nested {@link Scope}s.
262 * @member {Scope[]} Scope#childScopes
264 this.childScopes = [];
266 this.upper.childScopes.push(this);
269 this.__declaredVariables = scopeManager.__declaredVariables;
271 registerScope(scopeManager, this);
274 __shouldStaticallyClose(scopeManager) {
275 return (!this.dynamic || scopeManager.__isOptimistic());
278 __shouldStaticallyCloseForGlobal(ref) {
280 // On global scope, let/const/class declarations should be resolved statically.
281 const name = ref.identifier.name;
283 if (!this.set.has(name)) {
287 const variable = this.set.get(name);
288 const defs = variable.defs;
290 return defs.length > 0 && defs.every(shouldBeStatically);
293 __staticCloseRef(ref) {
294 if (!this.__resolve(ref)) {
295 this.__delegateToUpperScope(ref);
299 __dynamicCloseRef(ref) {
301 // notify all names are through to global
305 current.through.push(ref);
306 current = current.upper;
310 __globalCloseRef(ref) {
312 // let/const/class declarations should be resolved statically.
313 // others should be resolved dynamically.
314 if (this.__shouldStaticallyCloseForGlobal(ref)) {
315 this.__staticCloseRef(ref);
317 this.__dynamicCloseRef(ref);
321 __close(scopeManager) {
324 if (this.__shouldStaticallyClose(scopeManager)) {
325 closeRef = this.__staticCloseRef;
326 } else if (this.type !== "global") {
327 closeRef = this.__dynamicCloseRef;
329 closeRef = this.__globalCloseRef;
332 // Try Resolving all references in this scope.
333 for (let i = 0, iz = this.__left.length; i < iz; ++i) {
334 const ref = this.__left[i];
336 closeRef.call(this, ref);
343 // To override by function scopes.
344 // References in default parameters isn't resolved to variables which are in their function body.
345 __isValidResolution(ref, variable) { // eslint-disable-line class-methods-use-this, no-unused-vars
350 const name = ref.identifier.name;
352 if (!this.set.has(name)) {
355 const variable = this.set.get(name);
357 if (!this.__isValidResolution(ref, variable)) {
360 variable.references.push(ref);
361 variable.stack = variable.stack && ref.from.variableScope === this.variableScope;
363 variable.tainted = true;
364 this.taints.set(variable.name, true);
366 ref.resolved = variable;
371 __delegateToUpperScope(ref) {
373 this.upper.__left.push(ref);
375 this.through.push(ref);
378 __addDeclaredVariablesOfNode(variable, node) {
379 if (node === null || node === undefined) {
383 let variables = this.__declaredVariables.get(node);
385 if (variables === null || variables === undefined) {
387 this.__declaredVariables.set(node, variables);
389 if (variables.indexOf(variable) === -1) {
390 variables.push(variable);
394 __defineGeneric(name, set, variables, node, def) {
397 variable = set.get(name);
399 variable = new Variable(name, this);
400 set.set(name, variable);
401 variables.push(variable);
405 variable.defs.push(def);
406 this.__addDeclaredVariablesOfNode(variable, def.node);
407 this.__addDeclaredVariablesOfNode(variable, def.parent);
410 variable.identifiers.push(node);
414 __define(node, def) {
415 if (node && node.type === Syntax.Identifier) {
416 this.__defineGeneric(
426 __referencing(node, assign, writeExpr, maybeImplicitGlobal, partial, init) {
428 // because Array element may be null
429 if (!node || node.type !== Syntax.Identifier) {
433 // Specially handle like `this`.
434 if (node.name === "super") {
438 const ref = new Reference(node, this, assign || Reference.READ, writeExpr, maybeImplicitGlobal, !!partial, !!init);
440 this.references.push(ref);
441 this.__left.push(ref);
447 this.directCallToEvalScope = true;
449 current.dynamic = true;
450 current = current.upper;
455 this.thisFound = true;
459 return this.__left === null;
463 * returns resolved {Reference}
464 * @method Scope#resolve
465 * @param {Espree.Identifier} ident - identifier to be resolved.
466 * @returns {Reference} reference
471 assert(this.__isClosed(), "Scope should be closed.");
472 assert(ident.type === Syntax.Identifier, "Target should be identifier.");
473 for (i = 0, iz = this.references.length; i < iz; ++i) {
474 ref = this.references[i];
475 if (ref.identifier === ident) {
483 * returns this scope is static
484 * @method Scope#isStatic
485 * @returns {boolean} static
488 return !this.dynamic;
492 * returns this scope has materialized arguments
493 * @method Scope#isArgumentsMaterialized
494 * @returns {boolean} arguemnts materialized
496 isArgumentsMaterialized() { // eslint-disable-line class-methods-use-this
501 * returns this scope has materialized `this` reference
502 * @method Scope#isThisMaterialized
503 * @returns {boolean} this materialized
505 isThisMaterialized() { // eslint-disable-line class-methods-use-this
510 if (this.set.has(name)) {
513 for (let i = 0, iz = this.through.length; i < iz; ++i) {
514 if (this.through[i].identifier.name === name) {
522 class GlobalScope extends Scope {
523 constructor(scopeManager, block) {
524 super(scopeManager, "global", null, block, false);
530 * List of {@link Reference}s that are left to be resolved (i.e. which
531 * need to be linked to the variable they refer to).
532 * @member {Reference[]} Scope#implicit#left
538 __close(scopeManager) {
541 for (let i = 0, iz = this.__left.length; i < iz; ++i) {
542 const ref = this.__left[i];
544 if (ref.__maybeImplicitGlobal && !this.set.has(ref.identifier.name)) {
545 implicit.push(ref.__maybeImplicitGlobal);
549 // create an implicit global variable from assignment expression
550 for (let i = 0, iz = implicit.length; i < iz; ++i) {
551 const info = implicit[i];
553 this.__defineImplicit(info.pattern,
555 Variable.ImplicitGlobalVariable,
565 this.implicit.left = this.__left;
567 return super.__close(scopeManager);
570 __defineImplicit(node, def) {
571 if (node && node.type === Syntax.Identifier) {
572 this.__defineGeneric(
575 this.implicit.variables,
583 class ModuleScope extends Scope {
584 constructor(scopeManager, upperScope, block) {
585 super(scopeManager, "module", upperScope, block, false);
589 class FunctionExpressionNameScope extends Scope {
590 constructor(scopeManager, upperScope, block) {
591 super(scopeManager, "function-expression-name", upperScope, block, false);
592 this.__define(block.id,
594 Variable.FunctionName,
601 this.functionExpressionScope = true;
605 class CatchScope extends Scope {
606 constructor(scopeManager, upperScope, block) {
607 super(scopeManager, "catch", upperScope, block, false);
611 class WithScope extends Scope {
612 constructor(scopeManager, upperScope, block) {
613 super(scopeManager, "with", upperScope, block, false);
616 __close(scopeManager) {
617 if (this.__shouldStaticallyClose(scopeManager)) {
618 return super.__close(scopeManager);
621 for (let i = 0, iz = this.__left.length; i < iz; ++i) {
622 const ref = this.__left[i];
625 this.__delegateToUpperScope(ref);
633 class BlockScope extends Scope {
634 constructor(scopeManager, upperScope, block) {
635 super(scopeManager, "block", upperScope, block, false);
639 class SwitchScope extends Scope {
640 constructor(scopeManager, upperScope, block) {
641 super(scopeManager, "switch", upperScope, block, false);
645 class FunctionScope extends Scope {
646 constructor(scopeManager, upperScope, block, isMethodDefinition) {
647 super(scopeManager, "function", upperScope, block, isMethodDefinition);
649 // section 9.2.13, FunctionDeclarationInstantiation.
650 // NOTE Arrow functions never have an arguments objects.
651 if (this.block.type !== Syntax.ArrowFunctionExpression) {
652 this.__defineArguments();
656 isArgumentsMaterialized() {
658 // TODO(Constellation)
659 // We can more aggressive on this condition like this.
662 // // arguments of t is always hidden.
663 // function arguments() {
666 if (this.block.type === Syntax.ArrowFunctionExpression) {
670 if (!this.isStatic()) {
674 const variable = this.set.get("arguments");
676 assert(variable, "Always have arguments variable.");
677 return variable.tainted || variable.references.length !== 0;
680 isThisMaterialized() {
681 if (!this.isStatic()) {
684 return this.thisFound;
687 __defineArguments() {
688 this.__defineGeneric(
695 this.taints.set("arguments", true);
698 // References in default parameters isn't resolved to variables which are in their function body.
700 // function f(a = x) { // This `x` is resolved to the `x` in the outer scope.
704 __isValidResolution(ref, variable) {
706 // If `options.nodejsScope` is true, `this.block` becomes a Program node.
707 if (this.block.type === "Program") {
711 const bodyStart = this.block.body.range[0];
713 // It's invalid resolution in the following case:
715 variable.scope === this &&
716 ref.identifier.range[0] < bodyStart && // the reference is in the parameter part.
717 variable.defs.every(d => d.name.range[0] >= bodyStart) // the variable is in the body.
722 class ForScope extends Scope {
723 constructor(scopeManager, upperScope, block) {
724 super(scopeManager, "for", upperScope, block, false);
728 class ClassScope extends Scope {
729 constructor(scopeManager, upperScope, block) {
730 super(scopeManager, "class", upperScope, block, false);
738 FunctionExpressionNameScope,
748 /* vim: set sw=4 ts=4 et tw=80 : */