Projekt

Obecné

Profil

Stáhnout (18.2 KB) Statistiky
| Větev: | Revize:
1 3a515b92 cagy
/*
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
29
const Syntax = require("estraverse").Syntax;
30
const esrecurse = require("esrecurse");
31
const Reference = require("./reference");
32
const Variable = require("./variable");
33
const PatternVisitor = require("./pattern-visitor");
34
const definition = require("./definition");
35
const assert = require("assert");
36
37
const ParameterDefinition = definition.ParameterDefinition;
38
const 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
 */
48
function 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
67
class 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.
114
class 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
    visitPattern(node, options, callback) {
159
        if (typeof options === "function") {
160
            callback = options;
161
            options = { processRightHandNodes: false };
162
        }
163
        traverseIdentifierInPattern(
164
            this.options,
165
            node,
166
            options.processRightHandNodes ? this : null,
167
            callback);
168
    }
169
170
    visitFunction(node) {
171
        let i, iz;
172
173
        // FunctionDeclaration name is defined in upper scope
174
        // NOTE: Not referring variableScope. It is intended.
175
        // Since
176
        //  in ES5, FunctionDeclaration should be in FunctionBody.
177
        //  in ES6, FunctionDeclaration should be block scoped.
178
179
        if (node.type === Syntax.FunctionDeclaration) {
180
181
            // id is defined in upper scope
182
            this.currentScope().__define(node.id,
183
                    new Definition(
184
                        Variable.FunctionName,
185
                        node.id,
186
                        node,
187
                        null,
188
                        null,
189
                        null
190
                    ));
191
        }
192
193
        // FunctionExpression with name creates its special scope;
194
        // FunctionExpressionNameScope.
195
        if (node.type === Syntax.FunctionExpression && node.id) {
196
            this.scopeManager.__nestFunctionExpressionNameScope(node);
197
        }
198
199
        // Consider this function is in the MethodDefinition.
200
        this.scopeManager.__nestFunctionScope(node, this.isInnerMethodDefinition);
201
202
        const that = this;
203
204
        /**
205
         * Visit pattern callback
206
         * @param {pattern} pattern - pattern
207
         * @param {Object} info - info
208
         * @returns {void}
209
         */
210
        function visitPatternCallback(pattern, info) {
211
            that.currentScope().__define(pattern,
212
                new ParameterDefinition(
213
                    pattern,
214
                    node,
215
                    i,
216
                    info.rest
217
                ));
218
219
            that.referencingDefaultValue(pattern, info.assignments, null, true);
220
        }
221
222
        // Process parameter declarations.
223
        for (i = 0, iz = node.params.length; i < iz; ++i) {
224
            this.visitPattern(node.params[i], { processRightHandNodes: true }, visitPatternCallback);
225
        }
226
227
        // if there's a rest argument, add that
228
        if (node.rest) {
229
            this.visitPattern({
230
                type: "RestElement",
231
                argument: node.rest
232
            }, pattern => {
233
                this.currentScope().__define(pattern,
234
                    new ParameterDefinition(
235
                        pattern,
236
                        node,
237
                        node.params.length,
238
                        true
239
                    ));
240
            });
241
        }
242
243
        // In TypeScript there are a number of function-like constructs which have no body,
244
        // so check it exists before traversing
245
        if (node.body) {
246
247
            // Skip BlockStatement to prevent creating BlockStatement scope.
248
            if (node.body.type === Syntax.BlockStatement) {
249
                this.visitChildren(node.body);
250
            } else {
251
                this.visit(node.body);
252
            }
253
        }
254
255
        this.close(node);
256
    }
257
258
    visitClass(node) {
259
        if (node.type === Syntax.ClassDeclaration) {
260
            this.currentScope().__define(node.id,
261
                    new Definition(
262
                        Variable.ClassName,
263
                        node.id,
264
                        node,
265
                        null,
266
                        null,
267
                        null
268
                    ));
269
        }
270
271
        this.visit(node.superClass);
272
273
        this.scopeManager.__nestClassScope(node);
274
275
        if (node.id) {
276
            this.currentScope().__define(node.id,
277
                    new Definition(
278
                        Variable.ClassName,
279
                        node.id,
280
                        node
281
                    ));
282
        }
283
        this.visit(node.body);
284
285
        this.close(node);
286
    }
287
288
    visitProperty(node) {
289
        let previous;
290
291
        if (node.computed) {
292
            this.visit(node.key);
293
        }
294
295
        const isMethodDefinition = node.type === Syntax.MethodDefinition;
296
297
        if (isMethodDefinition) {
298
            previous = this.pushInnerMethodDefinition(true);
299
        }
300
        this.visit(node.value);
301
        if (isMethodDefinition) {
302
            this.popInnerMethodDefinition(previous);
303
        }
304
    }
305
306
    visitForIn(node) {
307
        if (node.left.type === Syntax.VariableDeclaration && node.left.kind !== "var") {
308
            this.scopeManager.__nestForScope(node);
309
        }
310
311
        if (node.left.type === Syntax.VariableDeclaration) {
312
            this.visit(node.left);
313
            this.visitPattern(node.left.declarations[0].id, pattern => {
314
                this.currentScope().__referencing(pattern, Reference.WRITE, node.right, null, true, true);
315
            });
316
        } else {
317
            this.visitPattern(node.left, { processRightHandNodes: true }, (pattern, info) => {
318
                let maybeImplicitGlobal = null;
319
320
                if (!this.currentScope().isStrict) {
321
                    maybeImplicitGlobal = {
322
                        pattern,
323
                        node
324
                    };
325
                }
326
                this.referencingDefaultValue(pattern, info.assignments, maybeImplicitGlobal, false);
327
                this.currentScope().__referencing(pattern, Reference.WRITE, node.right, maybeImplicitGlobal, true, false);
328
            });
329
        }
330
        this.visit(node.right);
331
        this.visit(node.body);
332
333
        this.close(node);
334
    }
335
336
    visitVariableDeclaration(variableTargetScope, type, node, index) {
337
338
        const decl = node.declarations[index];
339
        const init = decl.init;
340
341
        this.visitPattern(decl.id, { processRightHandNodes: true }, (pattern, info) => {
342
            variableTargetScope.__define(
343
                pattern,
344
                new Definition(
345
                    type,
346
                    pattern,
347
                    decl,
348
                    node,
349
                    index,
350
                    node.kind
351
                )
352
            );
353
354
            this.referencingDefaultValue(pattern, info.assignments, null, true);
355
            if (init) {
356
                this.currentScope().__referencing(pattern, Reference.WRITE, init, null, !info.topLevel, true);
357
            }
358
        });
359
    }
360
361
    AssignmentExpression(node) {
362
        if (PatternVisitor.isPattern(node.left)) {
363
            if (node.operator === "=") {
364
                this.visitPattern(node.left, { processRightHandNodes: true }, (pattern, info) => {
365
                    let maybeImplicitGlobal = null;
366
367
                    if (!this.currentScope().isStrict) {
368
                        maybeImplicitGlobal = {
369
                            pattern,
370
                            node
371
                        };
372
                    }
373
                    this.referencingDefaultValue(pattern, info.assignments, maybeImplicitGlobal, false);
374
                    this.currentScope().__referencing(pattern, Reference.WRITE, node.right, maybeImplicitGlobal, !info.topLevel, false);
375
                });
376
            } else {
377
                this.currentScope().__referencing(node.left, Reference.RW, node.right);
378
            }
379
        } else {
380
            this.visit(node.left);
381
        }
382
        this.visit(node.right);
383
    }
384
385
    CatchClause(node) {
386
        this.scopeManager.__nestCatchScope(node);
387
388
        this.visitPattern(node.param, { processRightHandNodes: true }, (pattern, info) => {
389
            this.currentScope().__define(pattern,
390
                new Definition(
391
                    Variable.CatchClause,
392
                    node.param,
393
                    node,
394
                    null,
395
                    null,
396
                    null
397
                ));
398
            this.referencingDefaultValue(pattern, info.assignments, null, true);
399
        });
400
        this.visit(node.body);
401
402
        this.close(node);
403
    }
404
405
    Program(node) {
406
        this.scopeManager.__nestGlobalScope(node);
407
408
        if (this.scopeManager.__isNodejsScope()) {
409
410
            // Force strictness of GlobalScope to false when using node.js scope.
411
            this.currentScope().isStrict = false;
412
            this.scopeManager.__nestFunctionScope(node, false);
413
        }
414
415
        if (this.scopeManager.__isES6() && this.scopeManager.isModule()) {
416
            this.scopeManager.__nestModuleScope(node);
417
        }
418
419
        if (this.scopeManager.isStrictModeSupported() && this.scopeManager.isImpliedStrict()) {
420
            this.currentScope().isStrict = true;
421
        }
422
423
        this.visitChildren(node);
424
        this.close(node);
425
    }
426
427
    Identifier(node) {
428
        this.currentScope().__referencing(node);
429
    }
430
431
    UpdateExpression(node) {
432
        if (PatternVisitor.isPattern(node.argument)) {
433
            this.currentScope().__referencing(node.argument, Reference.RW, null);
434
        } else {
435
            this.visitChildren(node);
436
        }
437
    }
438
439
    MemberExpression(node) {
440
        this.visit(node.object);
441
        if (node.computed) {
442
            this.visit(node.property);
443
        }
444
    }
445
446
    Property(node) {
447
        this.visitProperty(node);
448
    }
449
450
    MethodDefinition(node) {
451
        this.visitProperty(node);
452
    }
453
454
    BreakStatement() {} // eslint-disable-line class-methods-use-this
455
456
    ContinueStatement() {} // eslint-disable-line class-methods-use-this
457
458
    LabeledStatement(node) {
459
        this.visit(node.body);
460
    }
461
462
    ForStatement(node) {
463
464
        // Create ForStatement declaration.
465
        // NOTE: In ES6, ForStatement dynamically generates
466
        // per iteration environment. However, escope is
467
        // a static analyzer, we only generate one scope for ForStatement.
468
        if (node.init && node.init.type === Syntax.VariableDeclaration && node.init.kind !== "var") {
469
            this.scopeManager.__nestForScope(node);
470
        }
471
472
        this.visitChildren(node);
473
474
        this.close(node);
475
    }
476
477
    ClassExpression(node) {
478
        this.visitClass(node);
479
    }
480
481
    ClassDeclaration(node) {
482
        this.visitClass(node);
483
    }
484
485
    CallExpression(node) {
486
487
        // Check this is direct call to eval
488
        if (!this.scopeManager.__ignoreEval() && node.callee.type === Syntax.Identifier && node.callee.name === "eval") {
489
490
            // NOTE: This should be `variableScope`. Since direct eval call always creates Lexical environment and
491
            // let / const should be enclosed into it. Only VariableDeclaration affects on the caller's environment.
492
            this.currentScope().variableScope.__detectEval();
493
        }
494
        this.visitChildren(node);
495
    }
496
497
    BlockStatement(node) {
498
        if (this.scopeManager.__isES6()) {
499
            this.scopeManager.__nestBlockScope(node);
500
        }
501
502
        this.visitChildren(node);
503
504
        this.close(node);
505
    }
506
507
    ThisExpression() {
508
        this.currentScope().variableScope.__detectThis();
509
    }
510
511
    WithStatement(node) {
512
        this.visit(node.object);
513
514
        // Then nest scope for WithStatement.
515
        this.scopeManager.__nestWithScope(node);
516
517
        this.visit(node.body);
518
519
        this.close(node);
520
    }
521
522
    VariableDeclaration(node) {
523
        const variableTargetScope = (node.kind === "var") ? this.currentScope().variableScope : this.currentScope();
524
525
        for (let i = 0, iz = node.declarations.length; i < iz; ++i) {
526
            const decl = node.declarations[i];
527
528
            this.visitVariableDeclaration(variableTargetScope, Variable.Variable, node, i);
529
            if (decl.init) {
530
                this.visit(decl.init);
531
            }
532
        }
533
    }
534
535
    // sec 13.11.8
536
    SwitchStatement(node) {
537
        this.visit(node.discriminant);
538
539
        if (this.scopeManager.__isES6()) {
540
            this.scopeManager.__nestSwitchScope(node);
541
        }
542
543
        for (let i = 0, iz = node.cases.length; i < iz; ++i) {
544
            this.visit(node.cases[i]);
545
        }
546
547
        this.close(node);
548
    }
549
550
    FunctionDeclaration(node) {
551
        this.visitFunction(node);
552
    }
553
554
    FunctionExpression(node) {
555
        this.visitFunction(node);
556
    }
557
558
    ForOfStatement(node) {
559
        this.visitForIn(node);
560
    }
561
562
    ForInStatement(node) {
563
        this.visitForIn(node);
564
    }
565
566
    ArrowFunctionExpression(node) {
567
        this.visitFunction(node);
568
    }
569
570
    ImportDeclaration(node) {
571
        assert(this.scopeManager.__isES6() && this.scopeManager.isModule(), "ImportDeclaration should appear when the mode is ES6 and in the module context.");
572
573
        const importer = new Importer(node, this);
574
575
        importer.visit(node);
576
    }
577
578
    visitExportDeclaration(node) {
579
        if (node.source) {
580
            return;
581
        }
582
        if (node.declaration) {
583
            this.visit(node.declaration);
584
            return;
585
        }
586
587
        this.visitChildren(node);
588
    }
589
590
    ExportDeclaration(node) {
591
        this.visitExportDeclaration(node);
592
    }
593
594
    ExportNamedDeclaration(node) {
595
        this.visitExportDeclaration(node);
596
    }
597
598
    ExportSpecifier(node) {
599
        const local = (node.id || node.local);
600
601
        this.visit(local);
602
    }
603
604
    MetaProperty() { // eslint-disable-line class-methods-use-this
605
606
        // do nothing.
607
    }
608
}
609
610
module.exports = Referencer;
611
612
/* vim: set sw=4 ts=4 et tw=80 : */