• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2  Copyright (C) 2015 Yusuke Suzuki <utatane.tea@gmail.com>
3
4  Redistribution and use in source and binary forms, with or without
5  modification, are permitted provided that the following conditions are met:
6
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.
12
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.
23*/
24"use strict";
25
26/* eslint-disable no-underscore-dangle */
27/* eslint-disable no-undefined */
28
29const Syntax = require("estraverse").Syntax;
30const esrecurse = require("esrecurse");
31const Reference = require("./reference");
32const Variable = require("./variable");
33const PatternVisitor = require("./pattern-visitor");
34const definition = require("./definition");
35const assert = require("assert");
36
37const ParameterDefinition = definition.ParameterDefinition;
38const Definition = definition.Definition;
39
40/**
41 * Traverse identifier in pattern
42 * @param {Object} options - options
43 * @param {pattern} rootPattern - root pattern
44 * @param {Refencer} referencer - referencer
45 * @param {callback} callback - callback
46 * @returns {void}
47 */
48function traverseIdentifierInPattern(options, rootPattern, referencer, callback) {
49
50    // Call the callback at left hand identifier nodes, and Collect right hand nodes.
51    const visitor = new PatternVisitor(options, rootPattern, callback);
52
53    visitor.visit(rootPattern);
54
55    // Process the right hand nodes recursively.
56    if (referencer !== null && referencer !== undefined) {
57        visitor.rightHandNodes.forEach(referencer.visit, referencer);
58    }
59}
60
61// Importing ImportDeclaration.
62// http://people.mozilla.org/~jorendorff/es6-draft.html#sec-moduledeclarationinstantiation
63// https://github.com/estree/estree/blob/master/es6.md#importdeclaration
64// FIXME: Now, we don't create module environment, because the context is
65// implementation dependent.
66
67class Importer extends esrecurse.Visitor {
68    constructor(declaration, referencer) {
69        super(null, referencer.options);
70        this.declaration = declaration;
71        this.referencer = referencer;
72    }
73
74    visitImport(id, specifier) {
75        this.referencer.visitPattern(id, pattern => {
76            this.referencer.currentScope().__define(pattern,
77                new Definition(
78                    Variable.ImportBinding,
79                    pattern,
80                    specifier,
81                    this.declaration,
82                    null,
83                    null
84                ));
85        });
86    }
87
88    ImportNamespaceSpecifier(node) {
89        const local = (node.local || node.id);
90
91        if (local) {
92            this.visitImport(local, node);
93        }
94    }
95
96    ImportDefaultSpecifier(node) {
97        const local = (node.local || node.id);
98
99        this.visitImport(local, node);
100    }
101
102    ImportSpecifier(node) {
103        const local = (node.local || node.id);
104
105        if (node.name) {
106            this.visitImport(node.name, node);
107        } else {
108            this.visitImport(local, node);
109        }
110    }
111}
112
113// Referencing variables and creating bindings.
114class Referencer extends esrecurse.Visitor {
115    constructor(options, scopeManager) {
116        super(null, options);
117        this.options = options;
118        this.scopeManager = scopeManager;
119        this.parent = null;
120        this.isInnerMethodDefinition = false;
121    }
122
123    currentScope() {
124        return this.scopeManager.__currentScope;
125    }
126
127    close(node) {
128        while (this.currentScope() && node === this.currentScope().block) {
129            this.scopeManager.__currentScope = this.currentScope().__close(this.scopeManager);
130        }
131    }
132
133    pushInnerMethodDefinition(isInnerMethodDefinition) {
134        const previous = this.isInnerMethodDefinition;
135
136        this.isInnerMethodDefinition = isInnerMethodDefinition;
137        return previous;
138    }
139
140    popInnerMethodDefinition(isInnerMethodDefinition) {
141        this.isInnerMethodDefinition = isInnerMethodDefinition;
142    }
143
144    referencingDefaultValue(pattern, assignments, maybeImplicitGlobal, init) {
145        const scope = this.currentScope();
146
147        assignments.forEach(assignment => {
148            scope.__referencing(
149                pattern,
150                Reference.WRITE,
151                assignment.right,
152                maybeImplicitGlobal,
153                pattern !== assignment.left,
154                init
155            );
156        });
157    }
158
159    visitPattern(node, options, callback) {
160        let visitPatternOptions = options;
161        let visitPatternCallback = callback;
162
163        if (typeof options === "function") {
164            visitPatternCallback = options;
165            visitPatternOptions = { processRightHandNodes: false };
166        }
167
168        traverseIdentifierInPattern(
169            this.options,
170            node,
171            visitPatternOptions.processRightHandNodes ? this : null,
172            visitPatternCallback
173        );
174    }
175
176    visitFunction(node) {
177        let i, iz;
178
179        // FunctionDeclaration name is defined in upper scope
180        // NOTE: Not referring variableScope. It is intended.
181        // Since
182        //  in ES5, FunctionDeclaration should be in FunctionBody.
183        //  in ES6, FunctionDeclaration should be block scoped.
184
185        if (node.type === Syntax.FunctionDeclaration) {
186
187            // id is defined in upper scope
188            this.currentScope().__define(node.id,
189                new Definition(
190                    Variable.FunctionName,
191                    node.id,
192                    node,
193                    null,
194                    null,
195                    null
196                ));
197        }
198
199        // FunctionExpression with name creates its special scope;
200        // FunctionExpressionNameScope.
201        if (node.type === Syntax.FunctionExpression && node.id) {
202            this.scopeManager.__nestFunctionExpressionNameScope(node);
203        }
204
205        // Consider this function is in the MethodDefinition.
206        this.scopeManager.__nestFunctionScope(node, this.isInnerMethodDefinition);
207
208        const that = this;
209
210        /**
211         * Visit pattern callback
212         * @param {pattern} pattern - pattern
213         * @param {Object} info - info
214         * @returns {void}
215         */
216        function visitPatternCallback(pattern, info) {
217            that.currentScope().__define(pattern,
218                new ParameterDefinition(
219                    pattern,
220                    node,
221                    i,
222                    info.rest
223                ));
224
225            that.referencingDefaultValue(pattern, info.assignments, null, true);
226        }
227
228        // Process parameter declarations.
229        for (i = 0, iz = node.params.length; i < iz; ++i) {
230            this.visitPattern(node.params[i], { processRightHandNodes: true }, visitPatternCallback);
231        }
232
233        // if there's a rest argument, add that
234        if (node.rest) {
235            this.visitPattern({
236                type: "RestElement",
237                argument: node.rest
238            }, pattern => {
239                this.currentScope().__define(pattern,
240                    new ParameterDefinition(
241                        pattern,
242                        node,
243                        node.params.length,
244                        true
245                    ));
246            });
247        }
248
249        // In TypeScript there are a number of function-like constructs which have no body,
250        // so check it exists before traversing
251        if (node.body) {
252
253            // Skip BlockStatement to prevent creating BlockStatement scope.
254            if (node.body.type === Syntax.BlockStatement) {
255                this.visitChildren(node.body);
256            } else {
257                this.visit(node.body);
258            }
259        }
260
261        this.close(node);
262    }
263
264    visitClass(node) {
265        if (node.type === Syntax.ClassDeclaration) {
266            this.currentScope().__define(node.id,
267                new Definition(
268                    Variable.ClassName,
269                    node.id,
270                    node,
271                    null,
272                    null,
273                    null
274                ));
275        }
276
277        this.visit(node.superClass);
278
279        this.scopeManager.__nestClassScope(node);
280
281        if (node.id) {
282            this.currentScope().__define(node.id,
283                new Definition(
284                    Variable.ClassName,
285                    node.id,
286                    node
287                ));
288        }
289        this.visit(node.body);
290
291        this.close(node);
292    }
293
294    visitProperty(node) {
295        let previous;
296
297        if (node.computed) {
298            this.visit(node.key);
299        }
300
301        const isMethodDefinition = node.type === Syntax.MethodDefinition;
302
303        if (isMethodDefinition) {
304            previous = this.pushInnerMethodDefinition(true);
305        }
306        this.visit(node.value);
307        if (isMethodDefinition) {
308            this.popInnerMethodDefinition(previous);
309        }
310    }
311
312    visitForIn(node) {
313        if (node.left.type === Syntax.VariableDeclaration && node.left.kind !== "var") {
314            this.scopeManager.__nestForScope(node);
315        }
316
317        if (node.left.type === Syntax.VariableDeclaration) {
318            this.visit(node.left);
319            this.visitPattern(node.left.declarations[0].id, pattern => {
320                this.currentScope().__referencing(pattern, Reference.WRITE, node.right, null, true, true);
321            });
322        } else {
323            this.visitPattern(node.left, { processRightHandNodes: true }, (pattern, info) => {
324                let maybeImplicitGlobal = null;
325
326                if (!this.currentScope().isStrict) {
327                    maybeImplicitGlobal = {
328                        pattern,
329                        node
330                    };
331                }
332                this.referencingDefaultValue(pattern, info.assignments, maybeImplicitGlobal, false);
333                this.currentScope().__referencing(pattern, Reference.WRITE, node.right, maybeImplicitGlobal, true, false);
334            });
335        }
336        this.visit(node.right);
337        this.visit(node.body);
338
339        this.close(node);
340    }
341
342    visitVariableDeclaration(variableTargetScope, type, node, index) {
343
344        const decl = node.declarations[index];
345        const init = decl.init;
346
347        this.visitPattern(decl.id, { processRightHandNodes: true }, (pattern, info) => {
348            variableTargetScope.__define(
349                pattern,
350                new Definition(
351                    type,
352                    pattern,
353                    decl,
354                    node,
355                    index,
356                    node.kind
357                )
358            );
359
360            this.referencingDefaultValue(pattern, info.assignments, null, true);
361            if (init) {
362                this.currentScope().__referencing(pattern, Reference.WRITE, init, null, !info.topLevel, true);
363            }
364        });
365    }
366
367    AssignmentExpression(node) {
368        if (PatternVisitor.isPattern(node.left)) {
369            if (node.operator === "=") {
370                this.visitPattern(node.left, { processRightHandNodes: true }, (pattern, info) => {
371                    let maybeImplicitGlobal = null;
372
373                    if (!this.currentScope().isStrict) {
374                        maybeImplicitGlobal = {
375                            pattern,
376                            node
377                        };
378                    }
379                    this.referencingDefaultValue(pattern, info.assignments, maybeImplicitGlobal, false);
380                    this.currentScope().__referencing(pattern, Reference.WRITE, node.right, maybeImplicitGlobal, !info.topLevel, false);
381                });
382            } else {
383                this.currentScope().__referencing(node.left, Reference.RW, node.right);
384            }
385        } else {
386            this.visit(node.left);
387        }
388        this.visit(node.right);
389    }
390
391    CatchClause(node) {
392        this.scopeManager.__nestCatchScope(node);
393
394        this.visitPattern(node.param, { processRightHandNodes: true }, (pattern, info) => {
395            this.currentScope().__define(pattern,
396                new Definition(
397                    Variable.CatchClause,
398                    node.param,
399                    node,
400                    null,
401                    null,
402                    null
403                ));
404            this.referencingDefaultValue(pattern, info.assignments, null, true);
405        });
406        this.visit(node.body);
407
408        this.close(node);
409    }
410
411    Program(node) {
412        this.scopeManager.__nestGlobalScope(node);
413
414        if (this.scopeManager.__isNodejsScope()) {
415
416            // Force strictness of GlobalScope to false when using node.js scope.
417            this.currentScope().isStrict = false;
418            this.scopeManager.__nestFunctionScope(node, false);
419        }
420
421        if (this.scopeManager.__isES6() && this.scopeManager.isModule()) {
422            this.scopeManager.__nestModuleScope(node);
423        }
424
425        if (this.scopeManager.isStrictModeSupported() && this.scopeManager.isImpliedStrict()) {
426            this.currentScope().isStrict = true;
427        }
428
429        this.visitChildren(node);
430        this.close(node);
431    }
432
433    Identifier(node) {
434        this.currentScope().__referencing(node);
435    }
436
437    UpdateExpression(node) {
438        if (PatternVisitor.isPattern(node.argument)) {
439            this.currentScope().__referencing(node.argument, Reference.RW, null);
440        } else {
441            this.visitChildren(node);
442        }
443    }
444
445    MemberExpression(node) {
446        this.visit(node.object);
447        if (node.computed) {
448            this.visit(node.property);
449        }
450    }
451
452    Property(node) {
453        this.visitProperty(node);
454    }
455
456    MethodDefinition(node) {
457        this.visitProperty(node);
458    }
459
460    BreakStatement() {} // eslint-disable-line class-methods-use-this
461
462    ContinueStatement() {} // eslint-disable-line class-methods-use-this
463
464    LabeledStatement(node) {
465        this.visit(node.body);
466    }
467
468    ForStatement(node) {
469
470        // Create ForStatement declaration.
471        // NOTE: In ES6, ForStatement dynamically generates
472        // per iteration environment. However, escope is
473        // a static analyzer, we only generate one scope for ForStatement.
474        if (node.init && node.init.type === Syntax.VariableDeclaration && node.init.kind !== "var") {
475            this.scopeManager.__nestForScope(node);
476        }
477
478        this.visitChildren(node);
479
480        this.close(node);
481    }
482
483    ClassExpression(node) {
484        this.visitClass(node);
485    }
486
487    ClassDeclaration(node) {
488        this.visitClass(node);
489    }
490
491    CallExpression(node) {
492
493        // Check this is direct call to eval
494        if (!this.scopeManager.__ignoreEval() && node.callee.type === Syntax.Identifier && node.callee.name === "eval") {
495
496            // NOTE: This should be `variableScope`. Since direct eval call always creates Lexical environment and
497            // let / const should be enclosed into it. Only VariableDeclaration affects on the caller's environment.
498            this.currentScope().variableScope.__detectEval();
499        }
500        this.visitChildren(node);
501    }
502
503    BlockStatement(node) {
504        if (this.scopeManager.__isES6()) {
505            this.scopeManager.__nestBlockScope(node);
506        }
507
508        this.visitChildren(node);
509
510        this.close(node);
511    }
512
513    ThisExpression() {
514        this.currentScope().variableScope.__detectThis();
515    }
516
517    WithStatement(node) {
518        this.visit(node.object);
519
520        // Then nest scope for WithStatement.
521        this.scopeManager.__nestWithScope(node);
522
523        this.visit(node.body);
524
525        this.close(node);
526    }
527
528    VariableDeclaration(node) {
529        const variableTargetScope = (node.kind === "var") ? this.currentScope().variableScope : this.currentScope();
530
531        for (let i = 0, iz = node.declarations.length; i < iz; ++i) {
532            const decl = node.declarations[i];
533
534            this.visitVariableDeclaration(variableTargetScope, Variable.Variable, node, i);
535            if (decl.init) {
536                this.visit(decl.init);
537            }
538        }
539    }
540
541    // sec 13.11.8
542    SwitchStatement(node) {
543        this.visit(node.discriminant);
544
545        if (this.scopeManager.__isES6()) {
546            this.scopeManager.__nestSwitchScope(node);
547        }
548
549        for (let i = 0, iz = node.cases.length; i < iz; ++i) {
550            this.visit(node.cases[i]);
551        }
552
553        this.close(node);
554    }
555
556    FunctionDeclaration(node) {
557        this.visitFunction(node);
558    }
559
560    FunctionExpression(node) {
561        this.visitFunction(node);
562    }
563
564    ForOfStatement(node) {
565        this.visitForIn(node);
566    }
567
568    ForInStatement(node) {
569        this.visitForIn(node);
570    }
571
572    ArrowFunctionExpression(node) {
573        this.visitFunction(node);
574    }
575
576    ImportDeclaration(node) {
577        assert(this.scopeManager.__isES6() && this.scopeManager.isModule(), "ImportDeclaration should appear when the mode is ES6 and in the module context.");
578
579        const importer = new Importer(node, this);
580
581        importer.visit(node);
582    }
583
584    visitExportDeclaration(node) {
585        if (node.source) {
586            return;
587        }
588        if (node.declaration) {
589            this.visit(node.declaration);
590            return;
591        }
592
593        this.visitChildren(node);
594    }
595
596    // TODO: ExportDeclaration doesn't exist. for bc?
597    ExportDeclaration(node) {
598        this.visitExportDeclaration(node);
599    }
600
601    ExportAllDeclaration(node) {
602        this.visitExportDeclaration(node);
603    }
604
605    ExportDefaultDeclaration(node) {
606        this.visitExportDeclaration(node);
607    }
608
609    ExportNamedDeclaration(node) {
610        this.visitExportDeclaration(node);
611    }
612
613    ExportSpecifier(node) {
614
615        // TODO: `node.id` doesn't exist. for bc?
616        const local = (node.id || node.local);
617
618        this.visit(local);
619    }
620
621    MetaProperty() { // eslint-disable-line class-methods-use-this
622
623        // do nothing.
624    }
625}
626
627module.exports = Referencer;
628
629/* vim: set sw=4 ts=4 et tw=80 : */
630