]>
code.delx.au - gnu-emacs-elpa/blob - lib/escope.js
2 Copyright (C) 2012-2013 Yusuke Suzuki <utatane.tea@gmail.com>
3 Copyright (C) 2013 Alex Seville <hi@alexanderseville.com>
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are met:
8 * Redistributions of source code must retain the above copyright
9 notice, this list of conditions and the following disclaimer.
10 * Redistributions in binary form must reproduce the above copyright
11 notice, this list of conditions and the following disclaimer in the
12 documentation and/or other materials provided with the distribution.
14 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
18 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 * Escope (<a href="http://github.com/Constellation/escope">escope</a>) is an <a
28 * href="http://www.ecma-international.org/publications/standards/Ecma-262.htm">ECMAScript</a>
29 * scope analyzer extracted from the <a
30 * href="http://github.com/Constellation/esmangle">esmangle project</a/>.
32 * <em>escope</em> finds lexical scopes in a source program, i.e. areas of that
33 * program where different occurrences of the same identifier refer to the same
34 * variable. With each scope the contained variables are collected, and each
35 * identifier reference in code is linked to its corresponding variable (if
38 * <em>escope</em> works on a syntax tree of the parsed source code which has
40 * href="https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API">
41 * Mozilla Parser API</a>. E.g. <a href="http://esprima.org">esprima</a> is a parser
42 * that produces such syntax trees.
44 * The main interface is the {@link analyze} function.
48 /*jslint bitwise:true */
49 /*global exports:true, define:true, require:true*/
50 (function (factory
, global
) {
53 function namespace(str
, obj
) {
54 var i
, iz
, names
, name
;
55 names
= str
.split('.');
56 for (i
= 0, iz
= names
.length
; i
< iz
; ++i
) {
58 if (obj
.hasOwnProperty(name
)) {
61 obj
= (obj
[name
] = {});
67 // Universal Module Definition (UMD) to support AMD, CommonJS/Node.js,
68 // and plain browser loading,
69 if (typeof define
=== 'function' && define
.amd
) {
70 define('escope', ['exports', 'estraverse'], function (exports
, estraverse
) {
71 factory(exports
, global
, estraverse
);
73 } else if (typeof exports
!== 'undefined') {
74 factory(exports
, global
, require('./estraverse'));
76 factory(namespace('escope', global
), global
, global
.estraverse
);
78 }(function (exports
, global
, estraverse
) {
88 Syntax
= estraverse
.Syntax
;
90 if (typeof global
.Map
!== 'undefined') {
94 Map
= function Map() {
98 Map
.prototype.get = function MapGet(key
) {
100 if (this.__data
.hasOwnProperty(key
)) {
101 return this.__data
[key
];
106 Map
.prototype.has
= function MapHas(key
) {
108 return this.__data
.hasOwnProperty(key
);
111 Map
.prototype.set = function MapSet(key
, val
) {
113 this.__data
[key
] = val
;
116 Map
.prototype['delete'] = function MapDelete(key
) {
118 return delete this.__data
[key
];
122 function assert(cond
, text
) {
124 throw new Error(text
);
128 function defaultOptions() {
135 function updateDeeply(target
, override
) {
138 function isHashObject(target
) {
139 return typeof target
=== 'object' && target
instanceof Object
&& !(target
instanceof RegExp
);
142 for (key
in override
) {
143 if (override
.hasOwnProperty(key
)) {
145 if (isHashObject(val
)) {
146 if (isHashObject(target
[key
])) {
147 updateDeeply(target
[key
], val
);
149 target
[key
] = updateDeeply({}, val
);
160 * A Reference represents a single occurrence of an identifier in code.
163 function Reference(ident
, scope
, flag
, writeExpr
, maybeImplicitGlobal
) {
165 * Identifier syntax node.
166 * @member {esprima#Identifier} Reference#identifier
168 this.identifier
= ident
;
170 * Reference to the enclosing Scope.
171 * @member {Scope} Reference#from
175 * Whether the reference comes from a dynamic scope (such as 'eval',
176 * 'with', etc.), and may be trapped by dynamic scopes.
177 * @member {boolean} Reference#tainted
179 this.tainted
= false;
181 * The variable this reference is resolved with.
182 * @member {Variable} Reference#resolved
184 this.resolved
= null;
186 * The read-write mode of the reference. (Value is one of {@link
187 * Reference.READ}, {@link Reference.RW}, {@link Reference.WRITE}).
188 * @member {number} Reference#flag
192 if (this.isWrite()) {
194 * If reference is writeable, this is the tree being written to it.
195 * @member {esprima#Node} Reference#writeExpr
197 this.writeExpr
= writeExpr
;
200 * Whether the Reference might refer to a global variable.
201 * @member {boolean} Reference#__maybeImplicitGlobal
204 this.__maybeImplicitGlobal
= maybeImplicitGlobal
;
208 * @constant Reference.READ
211 Reference
.READ
= 0x1;
213 * @constant Reference.WRITE
216 Reference
.WRITE
= 0x2;
218 * @constant Reference.RW
224 * Whether the reference is static.
225 * @method Reference#isStatic
228 Reference
.prototype.isStatic
= function isStatic() {
229 return !this.tainted
&& this.resolved
&& this.resolved
.scope
.isStatic();
233 * Whether the reference is writeable.
234 * @method Reference#isWrite
237 Reference
.prototype.isWrite
= function isWrite() {
238 return this.flag
& Reference
.WRITE
;
242 * Whether the reference is readable.
243 * @method Reference#isRead
246 Reference
.prototype.isRead
= function isRead() {
247 return this.flag
& Reference
.READ
;
251 * Whether the reference is read-only.
252 * @method Reference#isReadOnly
255 Reference
.prototype.isReadOnly
= function isReadOnly() {
256 return this.flag
=== Reference
.READ
;
260 * Whether the reference is write-only.
261 * @method Reference#isWriteOnly
264 Reference
.prototype.isWriteOnly
= function isWriteOnly() {
265 return this.flag
=== Reference
.WRITE
;
269 * Whether the reference is read-write.
270 * @method Reference#isReadWrite
273 Reference
.prototype.isReadWrite
= function isReadWrite() {
274 return this.flag
=== Reference
.RW
;
278 * A Variable represents a locally scoped identifier. These include arguments to
282 function Variable(name
, scope
) {
284 * The variable name, as given in the source code.
285 * @member {String} Variable#name
289 * List of defining occurrences of this variable (like in 'var ...'
290 * statements or as parameter), as AST nodes.
291 * @member {esprima.Identifier[]} Variable#identifiers
293 this.identifiers
= [];
295 * List of {@link Reference|references} of this variable (excluding parameter entries)
296 * in its defining scope and all nested scopes. For defining
297 * occurrences only see {@link Variable#defs}.
298 * @member {Reference[]} Variable#references
300 this.references
= [];
303 * List of defining occurrences of this variable (like in 'var ...'
304 * statements or as parameter), as custom objects.
305 * @typedef {Object} DefEntry
306 * @property {String} DefEntry.type - the type of the occurrence (e.g.
307 * "Parameter", "Variable", ...)
308 * @property {esprima.Identifier} DefEntry.name - the identifier AST node of the occurrence
309 * @property {esprima.Node} DefEntry.node - the enclosing node of the
311 * @property {esprima.Node} [DefEntry.parent] - the enclosing statement
312 * node of the identifier
313 * @member {DefEntry[]} Variable#defs
317 this.tainted
= false;
319 * Whether this is a stack variable.
320 * @member {boolean} Variable#stack
324 * Reference to the enclosing Scope.
325 * @member {Scope} Variable#scope
330 Variable
.CatchClause
= 'CatchClause';
331 Variable
.Parameter
= 'Parameter';
332 Variable
.FunctionName
= 'FunctionName';
333 Variable
.Variable
= 'Variable';
334 Variable
.ImplicitGlobalVariable
= 'ImplicitGlobalVariable';
336 function isStrictScope(scope
, block
) {
337 var body
, i
, iz
, stmt
, expr
;
339 // When upper scope is exists and strict, inner scope is also strict.
340 if (scope
.upper
&& scope
.upper
.isStrict
) {
344 if (scope
.type
=== 'function') {
346 } else if (scope
.type
=== 'global') {
352 if (options
.directive
) {
353 for (i
= 0, iz
= body
.body
.length
; i
< iz
; ++i
) {
355 if (stmt
.type
!== 'DirectiveStatement') {
358 if (stmt
.raw
=== '"use strict"' || stmt
.raw
=== '\'use strict\'') {
363 for (i
= 0, iz
= body
.body
.length
; i
< iz
; ++i
) {
365 if (stmt
.type
!== Syntax
.ExpressionStatement
) {
368 expr
= stmt
.expression
;
369 if (expr
.type
!== Syntax
.Literal
|| typeof expr
.value
!== 'string') {
372 if (expr
.raw
!= null) {
373 if (expr
.raw
=== '"use strict"' || expr
.raw
=== '\'use strict\'') {
377 if (expr
.value
=== 'use strict') {
389 function Scope(block
, opt
) {
393 * One of 'catch', 'with', 'function' or 'global'.
394 * @member {String} Scope#type
397 (block
.type
=== Syntax
.CatchClause
) ? 'catch' :
398 (block
.type
=== Syntax
.WithStatement
) ? 'with' :
399 (block
.type
=== Syntax
.Program
) ? 'global' : 'function';
401 * The scoped {@link Variable}s of this scope, as <code>{ Variable.name
402 * : Variable }</code>.
403 * @member {Map} Scope#set
405 this.set = new Map();
407 * The tainted variables of this scope, as <code>{ Variable.name :
409 * @member {Map} Scope#taints */
410 this.taints
= new Map();
412 * Generally, through the lexical scoping of JS you can always know
413 * which variable an identifier in the source code refers to. There are
414 * a few exceptions to this rule. With 'global' and 'with' scopes you
415 * can only decide at runtime which variable a reference refers to.
416 * Moreover, if 'eval()' is used in a scope, it might introduce new
417 * bindings in this or its prarent scopes.
418 * All those scopes are considered 'dynamic'.
419 * @member {boolean} Scope#dynamic
421 this.dynamic
= this.type
=== 'global' || this.type
=== 'with';
423 * A reference to the scope-defining syntax node.
424 * @member {esprima.Node} Scope#block
428 * The {@link Reference|references} that are not resolved with this scope.
429 * @member {Reference[]} Scope#through
433 * The scoped {@link Variable}s of this scope. In the case of a
434 * 'function' scope this includes the automatic argument <em>arguments</em> as
435 * its first element, as well as all further formal arguments.
436 * @member {Variable[]} Scope#variables
440 * Any variable {@link Reference|reference} found in this scope. This
441 * includes occurrences of local variables as well as variables from
442 * parent scopes (including the global scope). For local variables
443 * this also includes defining occurrences (like in a 'var' statement).
444 * In a 'function' scope this does not include the occurrences of the
445 * formal parameter in the parameter list.
446 * @member {Reference[]} Scope#references
448 this.references
= [];
450 * List of {@link Reference}s that are left to be resolved (i.e. which
451 * need to be linked to the variable they refer to). Used internally to
452 * resolve bindings during scope analysis. On a finalized scope
453 * analysis, all sopes have <em>left</em> value <strong>null</strong>.
454 * @member {Reference[]} Scope#left
458 * For 'global' and 'function' scopes, this is a self-reference. For
459 * other scope types this is the <em>variableScope</em> value of the
461 * @member {Scope} Scope#variableScope
464 (this.type
=== 'global' || this.type
=== 'function') ? this : currentScope
.variableScope
;
466 * Whether this scope is created by a FunctionExpression.
467 * @member {boolean} Scope#functionExpressionScope
469 this.functionExpressionScope
= false;
471 * Whether this is a scope that contains an 'eval()' invocation.
472 * @member {boolean} Scope#directCallToEvalScope
474 this.directCallToEvalScope
= false;
476 * @member {boolean} Scope#thisFound
478 this.thisFound
= false;
479 body
= this.type
=== 'function' ? block
.body
: block
;
481 this.__define(block
.id
, {
482 type
: Variable
.FunctionName
,
486 this.functionExpressionScope
= true;
488 if (this.type
=== 'function') {
489 variable
= new Variable('arguments', this);
490 this.taints
.set('arguments', true);
491 this.set.set('arguments', variable
);
492 this.variables
.push(variable
);
495 if (block
.type
=== Syntax
.FunctionExpression
&& block
.id
) {
496 new Scope(block
, { naming
: true });
501 * Reference to the parent {@link Scope|scope}.
502 * @member {Scope} Scope#upper
504 this.upper
= currentScope
;
506 * Whether 'use strict' is in effect in this scope.
507 * @member {boolean} Scope#isStrict
509 this.isStrict
= isStrictScope(this, block
);
512 * List of nested {@link Scope}s.
513 * @member {Scope[]} Scope#childScopes
515 this.childScopes
= [];
517 currentScope
.childScopes
.push(this);
523 if (this.type
=== 'global') {
525 globalScope
.implicit
= {
533 Scope
.prototype.__close
= function __close() {
534 var i
, iz
, ref
, current
, node
, implicit
;
536 // Because if this is global environment, upper is null
537 if (!this.dynamic
|| options
.optimistic
) {
539 for (i
= 0, iz
= this.left
.length
; i
< iz
; ++i
) {
541 if (!this.__resolve(ref
)) {
542 this.__delegateToUpperScope(ref
);
546 // this is "global" / "with" / "function with eval" environment
547 if (this.type
=== 'with') {
548 for (i
= 0, iz
= this.left
.length
; i
< iz
; ++i
) {
551 this.__delegateToUpperScope(ref
);
554 for (i
= 0, iz
= this.left
.length
; i
< iz
; ++i
) {
555 // notify all names are through to global
559 current
.through
.push(ref
);
560 current
= current
.upper
;
566 if (this.type
=== 'global') {
568 for (i
= 0, iz
= this.left
.length
; i
< iz
; ++i
) {
570 if (ref
.__maybeImplicitGlobal
&& !this.set.has(ref
.identifier
.name
)) {
571 implicit
.push(ref
.__maybeImplicitGlobal
);
575 // create an implicit global variable from assignment expression
576 for (i
= 0, iz
= implicit
.length
; i
< iz
; ++i
) {
578 this.__defineImplicit(node
.left
, {
579 type
: Variable
.ImplicitGlobalVariable
,
587 currentScope
= this.upper
;
590 Scope
.prototype.__resolve
= function __resolve(ref
) {
592 name
= ref
.identifier
.name
;
593 if (this.set.has(name
)) {
594 variable
= this.set.get(name
);
595 variable
.references
.push(ref
);
596 variable
.stack
= variable
.stack
&& ref
.from.variableScope
=== this.variableScope
;
598 variable
.tainted
= true;
599 this.taints
.set(variable
.name
, true);
601 ref
.resolved
= variable
;
607 Scope
.prototype.__delegateToUpperScope
= function __delegateToUpperScope(ref
) {
609 this.upper
.left
.push(ref
);
611 this.through
.push(ref
);
614 Scope
.prototype.__defineImplicit
= function __defineImplicit(node
, info
) {
616 if (node
&& node
.type
=== Syntax
.Identifier
) {
618 if (!this.implicit
.set.has(name
)) {
619 variable
= new Variable(name
, this);
620 variable
.identifiers
.push(node
);
621 variable
.defs
.push(info
);
622 this.implicit
.set.set(name
, variable
);
623 this.implicit
.variables
.push(variable
);
625 variable
= this.implicit
.set.get(name
);
626 variable
.identifiers
.push(node
);
627 variable
.defs
.push(info
);
632 Scope
.prototype.__define
= function __define(node
, info
) {
634 if (node
&& node
.type
=== Syntax
.Identifier
) {
636 if (!this.set.has(name
)) {
637 variable
= new Variable(name
, this);
638 variable
.identifiers
.push(node
);
639 variable
.defs
.push(info
);
640 this.set.set(name
, variable
);
641 this.variables
.push(variable
);
643 variable
= this.set.get(name
);
644 variable
.identifiers
.push(node
);
645 variable
.defs
.push(info
);
650 Scope
.prototype.__referencing
= function __referencing(node
, assign
, writeExpr
, maybeImplicitGlobal
) {
652 // because Array element may be null
653 if (node
&& node
.type
=== Syntax
.Identifier
) {
654 ref
= new Reference(node
, this, assign
|| Reference
.READ
, writeExpr
, maybeImplicitGlobal
);
655 this.references
.push(ref
);
660 Scope
.prototype.__detectEval
= function __detectEval() {
663 this.directCallToEvalScope
= true;
665 current
.dynamic
= true;
666 current
= current
.upper
;
670 Scope
.prototype.__detectThis
= function __detectThis() {
671 this.thisFound
= true;
674 Scope
.prototype.__isClosed
= function isClosed() {
675 return this.left
=== null;
678 // API Scope#resolve(name)
679 // returns resolved reference
680 Scope
.prototype.resolve
= function resolve(ident
) {
682 assert(this.__isClosed(), 'scope should be closed');
683 assert(ident
.type
=== Syntax
.Identifier
, 'target should be identifier');
684 for (i
= 0, iz
= this.references
.length
; i
< iz
; ++i
) {
685 ref
= this.references
[i
];
686 if (ref
.identifier
=== ident
) {
693 // API Scope#isStatic
694 // returns this scope is static
695 Scope
.prototype.isStatic
= function isStatic() {
696 return !this.dynamic
;
699 // API Scope#isArgumentsMaterialized
700 // return this scope has materialized arguments
701 Scope
.prototype.isArgumentsMaterialized
= function isArgumentsMaterialized() {
702 // TODO(Constellation)
703 // We can more aggressive on this condition like this.
706 // // arguments of t is always hidden.
707 // function arguments() {
712 // This is not function scope
713 if (this.type
!== 'function') {
717 if (!this.isStatic()) {
721 variable
= this.set.get('arguments');
722 assert(variable
, 'always have arguments variable');
723 return variable
.tainted
|| variable
.references
.length
!== 0;
726 // API Scope#isThisMaterialized
727 // return this scope has materialized `this` reference
728 Scope
.prototype.isThisMaterialized
= function isThisMaterialized() {
729 // This is not function scope
730 if (this.type
!== 'function') {
733 if (!this.isStatic()) {
736 return this.thisFound
;
739 Scope
.mangledName
= '__$escope$__';
741 Scope
.prototype.attach
= function attach() {
742 if (!this.functionExpressionScope
) {
743 this.block
[Scope
.mangledName
] = this;
747 Scope
.prototype.detach
= function detach() {
748 if (!this.functionExpressionScope
) {
749 delete this.block
[Scope
.mangledName
];
753 Scope
.prototype.isUsedName = function (name
) {
754 if (this.set.has(name
)) {
757 for (var i
= 0, iz
= this.through
.length
; i
< iz
; ++i
) {
758 if (this.through
[i
].identifier
.name
=== name
) {
766 * @class ScopeManager
768 function ScopeManager(scopes
) {
769 this.scopes
= scopes
;
770 this.attached
= false;
773 // Returns appropliate scope for this node
774 ScopeManager
.prototype.__get
= function __get(node
) {
777 return node
[Scope
.mangledName
] || null;
779 if (Scope
.isScopeRequired(node
)) {
780 for (i
= 0, iz
= this.scopes
.length
; i
< iz
; ++i
) {
781 scope
= this.scopes
[i
];
782 if (!scope
.functionExpressionScope
) {
783 if (scope
.block
=== node
) {
792 ScopeManager
.prototype.acquire
= function acquire(node
) {
793 return this.__get(node
);
796 ScopeManager
.prototype.release
= function release(node
) {
797 var scope
= this.__get(node
);
801 if (!scope
.functionExpressionScope
) {
810 ScopeManager
.prototype.attach
= function attach() {
812 for (i
= 0, iz
= this.scopes
.length
; i
< iz
; ++i
) {
813 this.scopes
[i
].attach();
815 this.attached
= true;
818 ScopeManager
.prototype.detach
= function detach() {
820 for (i
= 0, iz
= this.scopes
.length
; i
< iz
; ++i
) {
821 this.scopes
[i
].detach();
823 this.attached
= false;
826 Scope
.isScopeRequired
= function isScopeRequired(node
) {
827 return Scope
.isVariableScopeRequired(node
) || node
.type
=== Syntax
.WithStatement
|| node
.type
=== Syntax
.CatchClause
;
830 Scope
.isVariableScopeRequired
= function isVariableScopeRequired(node
) {
831 return node
.type
=== Syntax
.Program
|| node
.type
=== Syntax
.FunctionExpression
|| node
.type
=== Syntax
.FunctionDeclaration
;
835 * Main interface function. Takes an Esprima syntax tree and returns the
838 * @param {esprima.Tree} tree
839 * @param {Object} providedOptions - Options that tailor the scope analysis
840 * @param {boolean} [providedOptions.optimistic=false] - the optimistic flag
841 * @param {boolean} [providedOptions.directive=false]- the directive flag
842 * @param {boolean} [providedOptions.ignoreEval=false]- whether to check 'eval()' calls
843 * @return {ScopeManager}
845 function analyze(tree
, providedOptions
) {
848 options
= updateDeeply(defaultOptions(), providedOptions
);
849 resultScopes
= scopes
= [];
853 // attach scope and collect / resolve names
854 estraverse
.traverse(tree
, {
855 enter
: function enter(node
) {
857 if (Scope
.isScopeRequired(node
)) {
862 case Syntax
.AssignmentExpression
:
863 if (node
.operator
=== '=') {
864 currentScope
.__referencing(node
.left
, Reference
.WRITE
, node
.right
, (!currentScope
.isStrict
&& node
.left
.name
!= null) && node
);
866 currentScope
.__referencing(node
.left
, Reference
.RW
, node
.right
);
868 currentScope
.__referencing(node
.right
);
871 case Syntax
.ArrayExpression
:
872 for (i
= 0, iz
= node
.elements
.length
; i
< iz
; ++i
) {
873 currentScope
.__referencing(node
.elements
[i
]);
877 case Syntax
.BlockStatement
:
880 case Syntax
.BinaryExpression
:
881 currentScope
.__referencing(node
.left
);
882 currentScope
.__referencing(node
.right
);
885 case Syntax
.BreakStatement
:
888 case Syntax
.CallExpression
:
889 currentScope
.__referencing(node
.callee
);
890 for (i
= 0, iz
= node
['arguments'].length
; i
< iz
; ++i
) {
891 currentScope
.__referencing(node
['arguments'][i
]);
894 // check this is direct call to eval
895 if (!options
.ignoreEval
&& node
.callee
.type
=== Syntax
.Identifier
&& node
.callee
.name
=== 'eval') {
896 currentScope
.variableScope
.__detectEval();
900 case Syntax
.CatchClause
:
901 currentScope
.__define(node
.param
, {
902 type
: Variable
.CatchClause
,
908 case Syntax
.ConditionalExpression
:
909 currentScope
.__referencing(node
.test
);
910 currentScope
.__referencing(node
.consequent
);
911 currentScope
.__referencing(node
.alternate
);
914 case Syntax
.ContinueStatement
:
917 case Syntax
.DirectiveStatement
:
920 case Syntax
.DoWhileStatement
:
921 currentScope
.__referencing(node
.test
);
924 case Syntax
.DebuggerStatement
:
927 case Syntax
.EmptyStatement
:
930 case Syntax
.ExpressionStatement
:
931 currentScope
.__referencing(node
.expression
);
934 case Syntax
.ForStatement
:
935 currentScope
.__referencing(node
.init
);
936 currentScope
.__referencing(node
.test
);
937 currentScope
.__referencing(node
.update
);
940 case Syntax
.ForInStatement
:
941 if (node
.left
.type
=== Syntax
.VariableDeclaration
) {
942 currentScope
.__referencing(node
.left
.declarations
[0].id
, Reference
.WRITE
, null, false);
944 currentScope
.__referencing(node
.left
, Reference
.WRITE
, null, (!currentScope
.isStrict
&& node
.left
.name
!= null) && node
);
946 currentScope
.__referencing(node
.right
);
949 case Syntax
.FunctionDeclaration
:
950 // FunctionDeclaration name is defined in upper scope
951 currentScope
.upper
.__define(node
.id
, {
952 type
: Variable
.FunctionName
,
956 for (i
= 0, iz
= node
.params
.length
; i
< iz
; ++i
) {
957 currentScope
.__define(node
.params
[i
], {
958 type
: Variable
.Parameter
,
959 name
: node
.params
[i
],
966 case Syntax
.FunctionExpression
:
967 // id is defined in upper scope
968 for (i
= 0, iz
= node
.params
.length
; i
< iz
; ++i
) {
969 currentScope
.__define(node
.params
[i
], {
970 type
: Variable
.Parameter
,
971 name
: node
.params
[i
],
978 case Syntax
.Identifier
:
981 case Syntax
.IfStatement
:
982 currentScope
.__referencing(node
.test
);
988 case Syntax
.LabeledStatement
:
991 case Syntax
.LogicalExpression
:
992 currentScope
.__referencing(node
.left
);
993 currentScope
.__referencing(node
.right
);
996 case Syntax
.MemberExpression
:
997 currentScope
.__referencing(node
.object
);
999 currentScope
.__referencing(node
.property
);
1003 case Syntax
.NewExpression
:
1004 currentScope
.__referencing(node
.callee
);
1005 for (i
= 0, iz
= node
['arguments'].length
; i
< iz
; ++i
) {
1006 currentScope
.__referencing(node
['arguments'][i
]);
1010 case Syntax
.ObjectExpression
:
1013 case Syntax
.Program
:
1016 case Syntax
.Property
:
1017 currentScope
.__referencing(node
.value
);
1020 case Syntax
.ReturnStatement
:
1021 currentScope
.__referencing(node
.argument
);
1024 case Syntax
.SequenceExpression
:
1025 for (i
= 0, iz
= node
.expressions
.length
; i
< iz
; ++i
) {
1026 currentScope
.__referencing(node
.expressions
[i
]);
1030 case Syntax
.SwitchStatement
:
1031 currentScope
.__referencing(node
.discriminant
);
1034 case Syntax
.SwitchCase
:
1035 currentScope
.__referencing(node
.test
);
1038 case Syntax
.ThisExpression
:
1039 currentScope
.variableScope
.__detectThis();
1042 case Syntax
.ThrowStatement
:
1043 currentScope
.__referencing(node
.argument
);
1046 case Syntax
.TryStatement
:
1049 case Syntax
.UnaryExpression
:
1050 currentScope
.__referencing(node
.argument
);
1053 case Syntax
.UpdateExpression
:
1054 currentScope
.__referencing(node
.argument
, Reference
.RW
, null);
1057 case Syntax
.VariableDeclaration
:
1058 for (i
= 0, iz
= node
.declarations
.length
; i
< iz
; ++i
) {
1059 decl
= node
.declarations
[i
];
1060 currentScope
.variableScope
.__define(decl
.id
, {
1061 type
: Variable
.Variable
,
1068 // initializer is found
1069 currentScope
.__referencing(decl
.id
, Reference
.WRITE
, decl
.init
, false);
1070 currentScope
.__referencing(decl
.init
);
1075 case Syntax
.VariableDeclarator
:
1078 case Syntax
.WhileStatement
:
1079 currentScope
.__referencing(node
.test
);
1082 case Syntax
.WithStatement
:
1083 // WithStatement object is referenced at upper scope
1084 currentScope
.upper
.__referencing(node
.object
);
1089 leave
: function leave(node
) {
1090 while (currentScope
&& node
=== currentScope
.block
) {
1091 currentScope
.__close();
1096 assert(currentScope
=== null);
1101 return new ScopeManager(resultScopes
);
1104 /** @name module:escope.version */
1105 exports
.version
= '1.0.1';
1106 /** @name module:escope.Reference */
1107 exports
.Reference
= Reference
;
1108 /** @name module:escope.Variable */
1109 exports
.Variable
= Variable
;
1110 /** @name module:escope.Scope */
1111 exports
.Scope
= Scope
;
1112 /** @name module:escope.ScopeManager */
1113 exports
.ScopeManager
= ScopeManager
;
1114 /** @name module:escope.analyze */
1115 exports
.analyze
= analyze
;
1117 /* vim: set sw=4 ts=4 et tw=80 : */