Projekt

Obecné

Profil

Stáhnout (18.2 KB) Statistiky
| Větev: | Revize:
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

    
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 : */
(5-5/8)