UNPKG

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