2 Copyright (C) 2015 Yusuke Suzuki <utatane.tea@gmail.com>
4 Redistribution and use in source and binary forms, with or without
5 modification, are permitted provided that the following conditions are met:
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.
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.
26 /* eslint-disable no-underscore-dangle */
27 /* eslint-disable no-undefined */
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");
37 const ParameterDefinition = definition.ParameterDefinition;
38 const Definition = definition.Definition;
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
48 function traverseIdentifierInPattern(options, rootPattern, referencer, callback) {
50 // Call the callback at left hand identifier nodes, and Collect right hand nodes.
51 const visitor = new PatternVisitor(options, rootPattern, callback);
53 visitor.visit(rootPattern);
55 // Process the right hand nodes recursively.
56 if (referencer !== null && referencer !== undefined) {
57 visitor.rightHandNodes.forEach(referencer.visit, referencer);
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.
67 class Importer extends esrecurse.Visitor {
68 constructor(declaration, referencer) {
69 super(null, referencer.options);
70 this.declaration = declaration;
71 this.referencer = referencer;
74 visitImport(id, specifier) {
75 this.referencer.visitPattern(id, pattern => {
76 this.referencer.currentScope().__define(pattern,
78 Variable.ImportBinding,
88 ImportNamespaceSpecifier(node) {
89 const local = (node.local || node.id);
92 this.visitImport(local, node);
96 ImportDefaultSpecifier(node) {
97 const local = (node.local || node.id);
99 this.visitImport(local, node);
102 ImportSpecifier(node) {
103 const local = (node.local || node.id);
106 this.visitImport(node.name, node);
108 this.visitImport(local, node);
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;
120 this.isInnerMethodDefinition = false;
124 return this.scopeManager.__currentScope;
128 while (this.currentScope() && node === this.currentScope().block) {
129 this.scopeManager.__currentScope = this.currentScope().__close(this.scopeManager);
133 pushInnerMethodDefinition(isInnerMethodDefinition) {
134 const previous = this.isInnerMethodDefinition;
136 this.isInnerMethodDefinition = isInnerMethodDefinition;
140 popInnerMethodDefinition(isInnerMethodDefinition) {
141 this.isInnerMethodDefinition = isInnerMethodDefinition;
144 referencingDefaultValue(pattern, assignments, maybeImplicitGlobal, init) {
145 const scope = this.currentScope();
147 assignments.forEach(assignment => {
153 pattern !== assignment.left,
159 visitPattern(node, options, callback) {
160 let visitPatternOptions = options;
161 let visitPatternCallback = callback;
163 if (typeof options === "function") {
164 visitPatternCallback = options;
165 visitPatternOptions = { processRightHandNodes: false };
168 traverseIdentifierInPattern(
171 visitPatternOptions.processRightHandNodes ? this : null,
176 visitFunction(node) {
179 // FunctionDeclaration name is defined in upper scope
180 // NOTE: Not referring variableScope. It is intended.
182 // in ES5, FunctionDeclaration should be in FunctionBody.
183 // in ES6, FunctionDeclaration should be block scoped.
185 if (node.type === Syntax.FunctionDeclaration) {
187 // id is defined in upper scope
188 this.currentScope().__define(node.id,
190 Variable.FunctionName,
199 // FunctionExpression with name creates its special scope;
200 // FunctionExpressionNameScope.
201 if (node.type === Syntax.FunctionExpression && node.id) {
202 this.scopeManager.__nestFunctionExpressionNameScope(node);
205 // Consider this function is in the MethodDefinition.
206 this.scopeManager.__nestFunctionScope(node, this.isInnerMethodDefinition);
211 * Visit pattern callback
212 * @param {pattern} pattern - pattern
213 * @param {Object} info - info
216 function visitPatternCallback(pattern, info) {
217 that.currentScope().__define(pattern,
218 new ParameterDefinition(
225 that.referencingDefaultValue(pattern, info.assignments, null, true);
228 // Process parameter declarations.
229 for (i = 0, iz = node.params.length; i < iz; ++i) {
230 this.visitPattern(node.params[i], { processRightHandNodes: true }, visitPatternCallback);
233 // if there's a rest argument, add that
239 this.currentScope().__define(pattern,
240 new ParameterDefinition(
249 // In TypeScript there are a number of function-like constructs which have no body,
250 // so check it exists before traversing
253 // Skip BlockStatement to prevent creating BlockStatement scope.
254 if (node.body.type === Syntax.BlockStatement) {
255 this.visitChildren(node.body);
257 this.visit(node.body);
265 if (node.type === Syntax.ClassDeclaration) {
266 this.currentScope().__define(node.id,
277 this.visit(node.superClass);
279 this.scopeManager.__nestClassScope(node);
282 this.currentScope().__define(node.id,
289 this.visit(node.body);
294 visitProperty(node) {
298 this.visit(node.key);
301 const isMethodDefinition = node.type === Syntax.MethodDefinition;
303 if (isMethodDefinition) {
304 previous = this.pushInnerMethodDefinition(true);
306 this.visit(node.value);
307 if (isMethodDefinition) {
308 this.popInnerMethodDefinition(previous);
313 if (node.left.type === Syntax.VariableDeclaration && node.left.kind !== "var") {
314 this.scopeManager.__nestForScope(node);
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);
323 this.visitPattern(node.left, { processRightHandNodes: true }, (pattern, info) => {
324 let maybeImplicitGlobal = null;
326 if (!this.currentScope().isStrict) {
327 maybeImplicitGlobal = {
332 this.referencingDefaultValue(pattern, info.assignments, maybeImplicitGlobal, false);
333 this.currentScope().__referencing(pattern, Reference.WRITE, node.right, maybeImplicitGlobal, true, false);
336 this.visit(node.right);
337 this.visit(node.body);
342 visitVariableDeclaration(variableTargetScope, type, node, index) {
344 const decl = node.declarations[index];
345 const init = decl.init;
347 this.visitPattern(decl.id, { processRightHandNodes: true }, (pattern, info) => {
348 variableTargetScope.__define(
360 this.referencingDefaultValue(pattern, info.assignments, null, true);
362 this.currentScope().__referencing(pattern, Reference.WRITE, init, null, !info.topLevel, true);
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;
373 if (!this.currentScope().isStrict) {
374 maybeImplicitGlobal = {
379 this.referencingDefaultValue(pattern, info.assignments, maybeImplicitGlobal, false);
380 this.currentScope().__referencing(pattern, Reference.WRITE, node.right, maybeImplicitGlobal, !info.topLevel, false);
383 this.currentScope().__referencing(node.left, Reference.RW, node.right);
386 this.visit(node.left);
388 this.visit(node.right);
392 this.scopeManager.__nestCatchScope(node);
394 this.visitPattern(node.param, { processRightHandNodes: true }, (pattern, info) => {
395 this.currentScope().__define(pattern,
397 Variable.CatchClause,
404 this.referencingDefaultValue(pattern, info.assignments, null, true);
406 this.visit(node.body);
412 this.scopeManager.__nestGlobalScope(node);
414 if (this.scopeManager.__isNodejsScope()) {
416 // Force strictness of GlobalScope to false when using node.js scope.
417 this.currentScope().isStrict = false;
418 this.scopeManager.__nestFunctionScope(node, false);
421 if (this.scopeManager.__isES6() && this.scopeManager.isModule()) {
422 this.scopeManager.__nestModuleScope(node);
425 if (this.scopeManager.isStrictModeSupported() && this.scopeManager.isImpliedStrict()) {
426 this.currentScope().isStrict = true;
429 this.visitChildren(node);
434 this.currentScope().__referencing(node);
437 UpdateExpression(node) {
438 if (PatternVisitor.isPattern(node.argument)) {
439 this.currentScope().__referencing(node.argument, Reference.RW, null);
441 this.visitChildren(node);
445 MemberExpression(node) {
446 this.visit(node.object);
448 this.visit(node.property);
453 this.visitProperty(node);
456 MethodDefinition(node) {
457 this.visitProperty(node);
460 BreakStatement() {} // eslint-disable-line class-methods-use-this
462 ContinueStatement() {} // eslint-disable-line class-methods-use-this
464 LabeledStatement(node) {
465 this.visit(node.body);
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);
478 this.visitChildren(node);
483 ClassExpression(node) {
484 this.visitClass(node);
487 ClassDeclaration(node) {
488 this.visitClass(node);
491 CallExpression(node) {
493 // Check this is direct call to eval
494 if (!this.scopeManager.__ignoreEval() && node.callee.type === Syntax.Identifier && node.callee.name === "eval") {
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();
500 this.visitChildren(node);
503 BlockStatement(node) {
504 if (this.scopeManager.__isES6()) {
505 this.scopeManager.__nestBlockScope(node);
508 this.visitChildren(node);
514 this.currentScope().variableScope.__detectThis();
517 WithStatement(node) {
518 this.visit(node.object);
520 // Then nest scope for WithStatement.
521 this.scopeManager.__nestWithScope(node);
523 this.visit(node.body);
528 VariableDeclaration(node) {
529 const variableTargetScope = (node.kind === "var") ? this.currentScope().variableScope : this.currentScope();
531 for (let i = 0, iz = node.declarations.length; i < iz; ++i) {
532 const decl = node.declarations[i];
534 this.visitVariableDeclaration(variableTargetScope, Variable.Variable, node, i);
536 this.visit(decl.init);
542 SwitchStatement(node) {
543 this.visit(node.discriminant);
545 if (this.scopeManager.__isES6()) {
546 this.scopeManager.__nestSwitchScope(node);
549 for (let i = 0, iz = node.cases.length; i < iz; ++i) {
550 this.visit(node.cases[i]);
556 FunctionDeclaration(node) {
557 this.visitFunction(node);
560 FunctionExpression(node) {
561 this.visitFunction(node);
564 ForOfStatement(node) {
565 this.visitForIn(node);
568 ForInStatement(node) {
569 this.visitForIn(node);
572 ArrowFunctionExpression(node) {
573 this.visitFunction(node);
576 ImportDeclaration(node) {
577 assert(this.scopeManager.__isES6() && this.scopeManager.isModule(), "ImportDeclaration should appear when the mode is ES6 and in the module context.");
579 const importer = new Importer(node, this);
581 importer.visit(node);
584 visitExportDeclaration(node) {
588 if (node.declaration) {
589 this.visit(node.declaration);
593 this.visitChildren(node);
596 // TODO: ExportDeclaration doesn't exist. for bc?
597 ExportDeclaration(node) {
598 this.visitExportDeclaration(node);
601 ExportAllDeclaration(node) {
602 this.visitExportDeclaration(node);
605 ExportDefaultDeclaration(node) {
606 this.visitExportDeclaration(node);
609 ExportNamedDeclaration(node) {
610 this.visitExportDeclaration(node);
613 ExportSpecifier(node) {
615 // TODO: `node.id` doesn't exist. for bc?
616 const local = (node.id || node.local);
621 MetaProperty() { // eslint-disable-line class-methods-use-this
627 module.exports = Referencer;
629 /* vim: set sw=4 ts=4 et tw=80 : */