]> code.delx.au - gnu-emacs-elpa/blob - lib/escope.js
Include external libraries.
[gnu-emacs-elpa] / lib / escope.js
1 /*
2 Copyright (C) 2012-2013 Yusuke Suzuki <utatane.tea@gmail.com>
3 Copyright (C) 2013 Alex Seville <hi@alexanderseville.com>
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are met:
7
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.
13
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.
24 */
25
26 /**
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/>.
31 * <p>
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
36 * possible).
37 * <p>
38 * <em>escope</em> works on a syntax tree of the parsed source code which has
39 * to adhere to the <a
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.
43 * <p>
44 * The main interface is the {@link analyze} function.
45 * @module
46 */
47
48 /*jslint bitwise:true */
49 /*global exports:true, define:true, require:true*/
50 (function (factory, global) {
51 'use strict';
52
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) {
57 name = names[i];
58 if (obj.hasOwnProperty(name)) {
59 obj = obj[name];
60 } else {
61 obj = (obj[name] = {});
62 }
63 }
64 return obj;
65 }
66
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);
72 });
73 } else if (typeof exports !== 'undefined') {
74 factory(exports, global, require('./estraverse'));
75 } else {
76 factory(namespace('escope', global), global, global.estraverse);
77 }
78 }(function (exports, global, estraverse) {
79 'use strict';
80
81 var Syntax,
82 Map,
83 currentScope,
84 globalScope,
85 scopes,
86 options;
87
88 Syntax = estraverse.Syntax;
89
90 if (typeof global.Map !== 'undefined') {
91 // ES6 Map
92 Map = global.Map;
93 } else {
94 Map = function Map() {
95 this.__data = {};
96 };
97
98 Map.prototype.get = function MapGet(key) {
99 key = '$' + key;
100 if (this.__data.hasOwnProperty(key)) {
101 return this.__data[key];
102 }
103 return undefined;
104 };
105
106 Map.prototype.has = function MapHas(key) {
107 key = '$' + key;
108 return this.__data.hasOwnProperty(key);
109 };
110
111 Map.prototype.set = function MapSet(key, val) {
112 key = '$' + key;
113 this.__data[key] = val;
114 };
115
116 Map.prototype['delete'] = function MapDelete(key) {
117 key = '$' + key;
118 return delete this.__data[key];
119 };
120 }
121
122 function assert(cond, text) {
123 if (!cond) {
124 throw new Error(text);
125 }
126 }
127
128 function defaultOptions() {
129 return {
130 optimistic: false,
131 directive: false
132 };
133 }
134
135 function updateDeeply(target, override) {
136 var key, val;
137
138 function isHashObject(target) {
139 return typeof target === 'object' && target instanceof Object && !(target instanceof RegExp);
140 }
141
142 for (key in override) {
143 if (override.hasOwnProperty(key)) {
144 val = override[key];
145 if (isHashObject(val)) {
146 if (isHashObject(target[key])) {
147 updateDeeply(target[key], val);
148 } else {
149 target[key] = updateDeeply({}, val);
150 }
151 } else {
152 target[key] = val;
153 }
154 }
155 }
156 return target;
157 }
158
159 /**
160 * A Reference represents a single occurrence of an identifier in code.
161 * @class Reference
162 */
163 function Reference(ident, scope, flag, writeExpr, maybeImplicitGlobal) {
164 /**
165 * Identifier syntax node.
166 * @member {esprima#Identifier} Reference#identifier
167 */
168 this.identifier = ident;
169 /**
170 * Reference to the enclosing Scope.
171 * @member {Scope} Reference#from
172 */
173 this.from = scope;
174 /**
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
178 */
179 this.tainted = false;
180 /**
181 * The variable this reference is resolved with.
182 * @member {Variable} Reference#resolved
183 */
184 this.resolved = null;
185 /**
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
189 * @private
190 */
191 this.flag = flag;
192 if (this.isWrite()) {
193 /**
194 * If reference is writeable, this is the tree being written to it.
195 * @member {esprima#Node} Reference#writeExpr
196 */
197 this.writeExpr = writeExpr;
198 }
199 /**
200 * Whether the Reference might refer to a global variable.
201 * @member {boolean} Reference#__maybeImplicitGlobal
202 * @private
203 */
204 this.__maybeImplicitGlobal = maybeImplicitGlobal;
205 }
206
207 /**
208 * @constant Reference.READ
209 * @private
210 */
211 Reference.READ = 0x1;
212 /**
213 * @constant Reference.WRITE
214 * @private
215 */
216 Reference.WRITE = 0x2;
217 /**
218 * @constant Reference.RW
219 * @private
220 */
221 Reference.RW = 0x3;
222
223 /**
224 * Whether the reference is static.
225 * @method Reference#isStatic
226 * @return {boolean}
227 */
228 Reference.prototype.isStatic = function isStatic() {
229 return !this.tainted && this.resolved && this.resolved.scope.isStatic();
230 };
231
232 /**
233 * Whether the reference is writeable.
234 * @method Reference#isWrite
235 * @return {boolean}
236 */
237 Reference.prototype.isWrite = function isWrite() {
238 return this.flag & Reference.WRITE;
239 };
240
241 /**
242 * Whether the reference is readable.
243 * @method Reference#isRead
244 * @return {boolean}
245 */
246 Reference.prototype.isRead = function isRead() {
247 return this.flag & Reference.READ;
248 };
249
250 /**
251 * Whether the reference is read-only.
252 * @method Reference#isReadOnly
253 * @return {boolean}
254 */
255 Reference.prototype.isReadOnly = function isReadOnly() {
256 return this.flag === Reference.READ;
257 };
258
259 /**
260 * Whether the reference is write-only.
261 * @method Reference#isWriteOnly
262 * @return {boolean}
263 */
264 Reference.prototype.isWriteOnly = function isWriteOnly() {
265 return this.flag === Reference.WRITE;
266 };
267
268 /**
269 * Whether the reference is read-write.
270 * @method Reference#isReadWrite
271 * @return {boolean}
272 */
273 Reference.prototype.isReadWrite = function isReadWrite() {
274 return this.flag === Reference.RW;
275 };
276
277 /**
278 * A Variable represents a locally scoped identifier. These include arguments to
279 * functions.
280 * @class Variable
281 */
282 function Variable(name, scope) {
283 /**
284 * The variable name, as given in the source code.
285 * @member {String} Variable#name
286 */
287 this.name = name;
288 /**
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
292 */
293 this.identifiers = [];
294 /**
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
299 */
300 this.references = [];
301
302 /**
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
310 * identifier
311 * @property {esprima.Node} [DefEntry.parent] - the enclosing statement
312 * node of the identifier
313 * @member {DefEntry[]} Variable#defs
314 */
315 this.defs = [];
316
317 this.tainted = false;
318 /**
319 * Whether this is a stack variable.
320 * @member {boolean} Variable#stack
321 */
322 this.stack = true;
323 /**
324 * Reference to the enclosing Scope.
325 * @member {Scope} Variable#scope
326 */
327 this.scope = scope;
328 }
329
330 Variable.CatchClause = 'CatchClause';
331 Variable.Parameter = 'Parameter';
332 Variable.FunctionName = 'FunctionName';
333 Variable.Variable = 'Variable';
334 Variable.ImplicitGlobalVariable = 'ImplicitGlobalVariable';
335
336 function isStrictScope(scope, block) {
337 var body, i, iz, stmt, expr;
338
339 // When upper scope is exists and strict, inner scope is also strict.
340 if (scope.upper && scope.upper.isStrict) {
341 return true;
342 }
343
344 if (scope.type === 'function') {
345 body = block.body;
346 } else if (scope.type === 'global') {
347 body = block;
348 } else {
349 return false;
350 }
351
352 if (options.directive) {
353 for (i = 0, iz = body.body.length; i < iz; ++i) {
354 stmt = body.body[i];
355 if (stmt.type !== 'DirectiveStatement') {
356 break;
357 }
358 if (stmt.raw === '"use strict"' || stmt.raw === '\'use strict\'') {
359 return true;
360 }
361 }
362 } else {
363 for (i = 0, iz = body.body.length; i < iz; ++i) {
364 stmt = body.body[i];
365 if (stmt.type !== Syntax.ExpressionStatement) {
366 break;
367 }
368 expr = stmt.expression;
369 if (expr.type !== Syntax.Literal || typeof expr.value !== 'string') {
370 break;
371 }
372 if (expr.raw != null) {
373 if (expr.raw === '"use strict"' || expr.raw === '\'use strict\'') {
374 return true;
375 }
376 } else {
377 if (expr.value === 'use strict') {
378 return true;
379 }
380 }
381 }
382 }
383 return false;
384 }
385
386 /**
387 * @class Scope
388 */
389 function Scope(block, opt) {
390 var variable, body;
391
392 /**
393 * One of 'catch', 'with', 'function' or 'global'.
394 * @member {String} Scope#type
395 */
396 this.type =
397 (block.type === Syntax.CatchClause) ? 'catch' :
398 (block.type === Syntax.WithStatement) ? 'with' :
399 (block.type === Syntax.Program) ? 'global' : 'function';
400 /**
401 * The scoped {@link Variable}s of this scope, as <code>{ Variable.name
402 * : Variable }</code>.
403 * @member {Map} Scope#set
404 */
405 this.set = new Map();
406 /**
407 * The tainted variables of this scope, as <code>{ Variable.name :
408 * boolean }</code>.
409 * @member {Map} Scope#taints */
410 this.taints = new Map();
411 /**
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
420 */
421 this.dynamic = this.type === 'global' || this.type === 'with';
422 /**
423 * A reference to the scope-defining syntax node.
424 * @member {esprima.Node} Scope#block
425 */
426 this.block = block;
427 /**
428 * The {@link Reference|references} that are not resolved with this scope.
429 * @member {Reference[]} Scope#through
430 */
431 this.through = [];
432 /**
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
437 */
438 this.variables = [];
439 /**
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
447 */
448 this.references = [];
449 /**
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
455 */
456 this.left = [];
457 /**
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
460 * parent scope.
461 * @member {Scope} Scope#variableScope
462 */
463 this.variableScope =
464 (this.type === 'global' || this.type === 'function') ? this : currentScope.variableScope;
465 /**
466 * Whether this scope is created by a FunctionExpression.
467 * @member {boolean} Scope#functionExpressionScope
468 */
469 this.functionExpressionScope = false;
470 /**
471 * Whether this is a scope that contains an 'eval()' invocation.
472 * @member {boolean} Scope#directCallToEvalScope
473 */
474 this.directCallToEvalScope = false;
475 /**
476 * @member {boolean} Scope#thisFound
477 */
478 this.thisFound = false;
479 body = this.type === 'function' ? block.body : block;
480 if (opt.naming) {
481 this.__define(block.id, {
482 type: Variable.FunctionName,
483 name: block.id,
484 node: block
485 });
486 this.functionExpressionScope = true;
487 } else {
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);
493 }
494
495 if (block.type === Syntax.FunctionExpression && block.id) {
496 new Scope(block, { naming: true });
497 }
498 }
499
500 /**
501 * Reference to the parent {@link Scope|scope}.
502 * @member {Scope} Scope#upper
503 */
504 this.upper = currentScope;
505 /**
506 * Whether 'use strict' is in effect in this scope.
507 * @member {boolean} Scope#isStrict
508 */
509 this.isStrict = isStrictScope(this, block);
510
511 /**
512 * List of nested {@link Scope}s.
513 * @member {Scope[]} Scope#childScopes
514 */
515 this.childScopes = [];
516 if (currentScope) {
517 currentScope.childScopes.push(this);
518 }
519
520
521 // RAII
522 currentScope = this;
523 if (this.type === 'global') {
524 globalScope = this;
525 globalScope.implicit = {
526 set: new Map(),
527 variables: []
528 };
529 }
530 scopes.push(this);
531 }
532
533 Scope.prototype.__close = function __close() {
534 var i, iz, ref, current, node, implicit;
535
536 // Because if this is global environment, upper is null
537 if (!this.dynamic || options.optimistic) {
538 // static resolve
539 for (i = 0, iz = this.left.length; i < iz; ++i) {
540 ref = this.left[i];
541 if (!this.__resolve(ref)) {
542 this.__delegateToUpperScope(ref);
543 }
544 }
545 } else {
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) {
549 ref = this.left[i];
550 ref.tainted = true;
551 this.__delegateToUpperScope(ref);
552 }
553 } else {
554 for (i = 0, iz = this.left.length; i < iz; ++i) {
555 // notify all names are through to global
556 ref = this.left[i];
557 current = this;
558 do {
559 current.through.push(ref);
560 current = current.upper;
561 } while (current);
562 }
563 }
564 }
565
566 if (this.type === 'global') {
567 implicit = [];
568 for (i = 0, iz = this.left.length; i < iz; ++i) {
569 ref = this.left[i];
570 if (ref.__maybeImplicitGlobal && !this.set.has(ref.identifier.name)) {
571 implicit.push(ref.__maybeImplicitGlobal);
572 }
573 }
574
575 // create an implicit global variable from assignment expression
576 for (i = 0, iz = implicit.length; i < iz; ++i) {
577 node = implicit[i];
578 this.__defineImplicit(node.left, {
579 type: Variable.ImplicitGlobalVariable,
580 name: node.left,
581 node: node
582 });
583 }
584 }
585
586 this.left = null;
587 currentScope = this.upper;
588 };
589
590 Scope.prototype.__resolve = function __resolve(ref) {
591 var variable, name;
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;
597 if (ref.tainted) {
598 variable.tainted = true;
599 this.taints.set(variable.name, true);
600 }
601 ref.resolved = variable;
602 return true;
603 }
604 return false;
605 };
606
607 Scope.prototype.__delegateToUpperScope = function __delegateToUpperScope(ref) {
608 if (this.upper) {
609 this.upper.left.push(ref);
610 }
611 this.through.push(ref);
612 };
613
614 Scope.prototype.__defineImplicit = function __defineImplicit(node, info) {
615 var name, variable;
616 if (node && node.type === Syntax.Identifier) {
617 name = node.name;
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);
624 } else {
625 variable = this.implicit.set.get(name);
626 variable.identifiers.push(node);
627 variable.defs.push(info);
628 }
629 }
630 };
631
632 Scope.prototype.__define = function __define(node, info) {
633 var name, variable;
634 if (node && node.type === Syntax.Identifier) {
635 name = node.name;
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);
642 } else {
643 variable = this.set.get(name);
644 variable.identifiers.push(node);
645 variable.defs.push(info);
646 }
647 }
648 };
649
650 Scope.prototype.__referencing = function __referencing(node, assign, writeExpr, maybeImplicitGlobal) {
651 var ref;
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);
656 this.left.push(ref);
657 }
658 };
659
660 Scope.prototype.__detectEval = function __detectEval() {
661 var current;
662 current = this;
663 this.directCallToEvalScope = true;
664 do {
665 current.dynamic = true;
666 current = current.upper;
667 } while (current);
668 };
669
670 Scope.prototype.__detectThis = function __detectThis() {
671 this.thisFound = true;
672 };
673
674 Scope.prototype.__isClosed = function isClosed() {
675 return this.left === null;
676 };
677
678 // API Scope#resolve(name)
679 // returns resolved reference
680 Scope.prototype.resolve = function resolve(ident) {
681 var ref, i, iz;
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) {
687 return ref;
688 }
689 }
690 return null;
691 };
692
693 // API Scope#isStatic
694 // returns this scope is static
695 Scope.prototype.isStatic = function isStatic() {
696 return !this.dynamic;
697 };
698
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.
704 //
705 // function t() {
706 // // arguments of t is always hidden.
707 // function arguments() {
708 // }
709 // }
710 var variable;
711
712 // This is not function scope
713 if (this.type !== 'function') {
714 return true;
715 }
716
717 if (!this.isStatic()) {
718 return true;
719 }
720
721 variable = this.set.get('arguments');
722 assert(variable, 'always have arguments variable');
723 return variable.tainted || variable.references.length !== 0;
724 };
725
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') {
731 return true;
732 }
733 if (!this.isStatic()) {
734 return true;
735 }
736 return this.thisFound;
737 };
738
739 Scope.mangledName = '__$escope$__';
740
741 Scope.prototype.attach = function attach() {
742 if (!this.functionExpressionScope) {
743 this.block[Scope.mangledName] = this;
744 }
745 };
746
747 Scope.prototype.detach = function detach() {
748 if (!this.functionExpressionScope) {
749 delete this.block[Scope.mangledName];
750 }
751 };
752
753 Scope.prototype.isUsedName = function (name) {
754 if (this.set.has(name)) {
755 return true;
756 }
757 for (var i = 0, iz = this.through.length; i < iz; ++i) {
758 if (this.through[i].identifier.name === name) {
759 return true;
760 }
761 }
762 return false;
763 };
764
765 /**
766 * @class ScopeManager
767 */
768 function ScopeManager(scopes) {
769 this.scopes = scopes;
770 this.attached = false;
771 }
772
773 // Returns appropliate scope for this node
774 ScopeManager.prototype.__get = function __get(node) {
775 var i, iz, scope;
776 if (this.attached) {
777 return node[Scope.mangledName] || null;
778 }
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) {
784 return scope;
785 }
786 }
787 }
788 }
789 return null;
790 };
791
792 ScopeManager.prototype.acquire = function acquire(node) {
793 return this.__get(node);
794 };
795
796 ScopeManager.prototype.release = function release(node) {
797 var scope = this.__get(node);
798 if (scope) {
799 scope = scope.upper;
800 while (scope) {
801 if (!scope.functionExpressionScope) {
802 return scope;
803 }
804 scope = scope.upper;
805 }
806 }
807 return null;
808 };
809
810 ScopeManager.prototype.attach = function attach() {
811 var i, iz;
812 for (i = 0, iz = this.scopes.length; i < iz; ++i) {
813 this.scopes[i].attach();
814 }
815 this.attached = true;
816 };
817
818 ScopeManager.prototype.detach = function detach() {
819 var i, iz;
820 for (i = 0, iz = this.scopes.length; i < iz; ++i) {
821 this.scopes[i].detach();
822 }
823 this.attached = false;
824 };
825
826 Scope.isScopeRequired = function isScopeRequired(node) {
827 return Scope.isVariableScopeRequired(node) || node.type === Syntax.WithStatement || node.type === Syntax.CatchClause;
828 };
829
830 Scope.isVariableScopeRequired = function isVariableScopeRequired(node) {
831 return node.type === Syntax.Program || node.type === Syntax.FunctionExpression || node.type === Syntax.FunctionDeclaration;
832 };
833
834 /**
835 * Main interface function. Takes an Esprima syntax tree and returns the
836 * analyzed scopes.
837 * @function analyze
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}
844 */
845 function analyze(tree, providedOptions) {
846 var resultScopes;
847
848 options = updateDeeply(defaultOptions(), providedOptions);
849 resultScopes = scopes = [];
850 currentScope = null;
851 globalScope = null;
852
853 // attach scope and collect / resolve names
854 estraverse.traverse(tree, {
855 enter: function enter(node) {
856 var i, iz, decl;
857 if (Scope.isScopeRequired(node)) {
858 new Scope(node, {});
859 }
860
861 switch (node.type) {
862 case Syntax.AssignmentExpression:
863 if (node.operator === '=') {
864 currentScope.__referencing(node.left, Reference.WRITE, node.right, (!currentScope.isStrict && node.left.name != null) && node);
865 } else {
866 currentScope.__referencing(node.left, Reference.RW, node.right);
867 }
868 currentScope.__referencing(node.right);
869 break;
870
871 case Syntax.ArrayExpression:
872 for (i = 0, iz = node.elements.length; i < iz; ++i) {
873 currentScope.__referencing(node.elements[i]);
874 }
875 break;
876
877 case Syntax.BlockStatement:
878 break;
879
880 case Syntax.BinaryExpression:
881 currentScope.__referencing(node.left);
882 currentScope.__referencing(node.right);
883 break;
884
885 case Syntax.BreakStatement:
886 break;
887
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]);
892 }
893
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();
897 }
898 break;
899
900 case Syntax.CatchClause:
901 currentScope.__define(node.param, {
902 type: Variable.CatchClause,
903 name: node.param,
904 node: node
905 });
906 break;
907
908 case Syntax.ConditionalExpression:
909 currentScope.__referencing(node.test);
910 currentScope.__referencing(node.consequent);
911 currentScope.__referencing(node.alternate);
912 break;
913
914 case Syntax.ContinueStatement:
915 break;
916
917 case Syntax.DirectiveStatement:
918 break;
919
920 case Syntax.DoWhileStatement:
921 currentScope.__referencing(node.test);
922 break;
923
924 case Syntax.DebuggerStatement:
925 break;
926
927 case Syntax.EmptyStatement:
928 break;
929
930 case Syntax.ExpressionStatement:
931 currentScope.__referencing(node.expression);
932 break;
933
934 case Syntax.ForStatement:
935 currentScope.__referencing(node.init);
936 currentScope.__referencing(node.test);
937 currentScope.__referencing(node.update);
938 break;
939
940 case Syntax.ForInStatement:
941 if (node.left.type === Syntax.VariableDeclaration) {
942 currentScope.__referencing(node.left.declarations[0].id, Reference.WRITE, null, false);
943 } else {
944 currentScope.__referencing(node.left, Reference.WRITE, null, (!currentScope.isStrict && node.left.name != null) && node);
945 }
946 currentScope.__referencing(node.right);
947 break;
948
949 case Syntax.FunctionDeclaration:
950 // FunctionDeclaration name is defined in upper scope
951 currentScope.upper.__define(node.id, {
952 type: Variable.FunctionName,
953 name: node.id,
954 node: node
955 });
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],
960 node: node,
961 index: i
962 });
963 }
964 break;
965
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],
972 node: node,
973 index: i
974 });
975 }
976 break;
977
978 case Syntax.Identifier:
979 break;
980
981 case Syntax.IfStatement:
982 currentScope.__referencing(node.test);
983 break;
984
985 case Syntax.Literal:
986 break;
987
988 case Syntax.LabeledStatement:
989 break;
990
991 case Syntax.LogicalExpression:
992 currentScope.__referencing(node.left);
993 currentScope.__referencing(node.right);
994 break;
995
996 case Syntax.MemberExpression:
997 currentScope.__referencing(node.object);
998 if (node.computed) {
999 currentScope.__referencing(node.property);
1000 }
1001 break;
1002
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]);
1007 }
1008 break;
1009
1010 case Syntax.ObjectExpression:
1011 break;
1012
1013 case Syntax.Program:
1014 break;
1015
1016 case Syntax.Property:
1017 currentScope.__referencing(node.value);
1018 break;
1019
1020 case Syntax.ReturnStatement:
1021 currentScope.__referencing(node.argument);
1022 break;
1023
1024 case Syntax.SequenceExpression:
1025 for (i = 0, iz = node.expressions.length; i < iz; ++i) {
1026 currentScope.__referencing(node.expressions[i]);
1027 }
1028 break;
1029
1030 case Syntax.SwitchStatement:
1031 currentScope.__referencing(node.discriminant);
1032 break;
1033
1034 case Syntax.SwitchCase:
1035 currentScope.__referencing(node.test);
1036 break;
1037
1038 case Syntax.ThisExpression:
1039 currentScope.variableScope.__detectThis();
1040 break;
1041
1042 case Syntax.ThrowStatement:
1043 currentScope.__referencing(node.argument);
1044 break;
1045
1046 case Syntax.TryStatement:
1047 break;
1048
1049 case Syntax.UnaryExpression:
1050 currentScope.__referencing(node.argument);
1051 break;
1052
1053 case Syntax.UpdateExpression:
1054 currentScope.__referencing(node.argument, Reference.RW, null);
1055 break;
1056
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,
1062 name: decl.id,
1063 node: decl,
1064 index: i,
1065 parent: node
1066 });
1067 if (decl.init) {
1068 // initializer is found
1069 currentScope.__referencing(decl.id, Reference.WRITE, decl.init, false);
1070 currentScope.__referencing(decl.init);
1071 }
1072 }
1073 break;
1074
1075 case Syntax.VariableDeclarator:
1076 break;
1077
1078 case Syntax.WhileStatement:
1079 currentScope.__referencing(node.test);
1080 break;
1081
1082 case Syntax.WithStatement:
1083 // WithStatement object is referenced at upper scope
1084 currentScope.upper.__referencing(node.object);
1085 break;
1086 }
1087 },
1088
1089 leave: function leave(node) {
1090 while (currentScope && node === currentScope.block) {
1091 currentScope.__close();
1092 }
1093 }
1094 });
1095
1096 assert(currentScope === null);
1097 globalScope = null;
1098 scopes = null;
1099 options = null;
1100
1101 return new ScopeManager(resultScopes);
1102 }
1103
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;
1116 }, this));
1117 /* vim: set sw=4 ts=4 et tw=80 : */