123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613 |
- /*
- Copyright (C) 2015 Yusuke Suzuki <utatane.tea@gmail.com>
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are met:
- * Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
- "use strict";
- /* eslint-disable no-underscore-dangle */
- /* eslint-disable no-undefined */
- const Syntax = require("estraverse").Syntax;
- const esrecurse = require("esrecurse");
- const Reference = require("./reference");
- const Variable = require("./variable");
- const PatternVisitor = require("./pattern-visitor");
- const definition = require("./definition");
- const assert = require("assert");
- const ParameterDefinition = definition.ParameterDefinition;
- const Definition = definition.Definition;
- /**
- * Traverse identifier in pattern
- * @param {Object} options - options
- * @param {pattern} rootPattern - root pattern
- * @param {Refencer} referencer - referencer
- * @param {callback} callback - callback
- * @returns {void}
- */
- function traverseIdentifierInPattern(options, rootPattern, referencer, callback) {
- // Call the callback at left hand identifier nodes, and Collect right hand nodes.
- const visitor = new PatternVisitor(options, rootPattern, callback);
- visitor.visit(rootPattern);
- // Process the right hand nodes recursively.
- if (referencer !== null && referencer !== undefined) {
- visitor.rightHandNodes.forEach(referencer.visit, referencer);
- }
- }
- // Importing ImportDeclaration.
- // http://people.mozilla.org/~jorendorff/es6-draft.html#sec-moduledeclarationinstantiation
- // https://github.com/estree/estree/blob/master/es6.md#importdeclaration
- // FIXME: Now, we don't create module environment, because the context is
- // implementation dependent.
- class Importer extends esrecurse.Visitor {
- constructor(declaration, referencer) {
- super(null, referencer.options);
- this.declaration = declaration;
- this.referencer = referencer;
- }
- visitImport(id, specifier) {
- this.referencer.visitPattern(id, pattern => {
- this.referencer.currentScope().__define(pattern,
- new Definition(
- Variable.ImportBinding,
- pattern,
- specifier,
- this.declaration,
- null,
- null
- ));
- });
- }
- ImportNamespaceSpecifier(node) {
- const local = (node.local || node.id);
- if (local) {
- this.visitImport(local, node);
- }
- }
- ImportDefaultSpecifier(node) {
- const local = (node.local || node.id);
- this.visitImport(local, node);
- }
- ImportSpecifier(node) {
- const local = (node.local || node.id);
- if (node.name) {
- this.visitImport(node.name, node);
- } else {
- this.visitImport(local, node);
- }
- }
- }
- // Referencing variables and creating bindings.
- class Referencer extends esrecurse.Visitor {
- constructor(options, scopeManager) {
- super(null, options);
- this.options = options;
- this.scopeManager = scopeManager;
- this.parent = null;
- this.isInnerMethodDefinition = false;
- }
- currentScope() {
- return this.scopeManager.__currentScope;
- }
- close(node) {
- while (this.currentScope() && node === this.currentScope().block) {
- this.scopeManager.__currentScope = this.currentScope().__close(this.scopeManager);
- }
- }
- pushInnerMethodDefinition(isInnerMethodDefinition) {
- const previous = this.isInnerMethodDefinition;
- this.isInnerMethodDefinition = isInnerMethodDefinition;
- return previous;
- }
- popInnerMethodDefinition(isInnerMethodDefinition) {
- this.isInnerMethodDefinition = isInnerMethodDefinition;
- }
- referencingDefaultValue(pattern, assignments, maybeImplicitGlobal, init) {
- const scope = this.currentScope();
- assignments.forEach(assignment => {
- scope.__referencing(
- pattern,
- Reference.WRITE,
- assignment.right,
- maybeImplicitGlobal,
- pattern !== assignment.left,
- init);
- });
- }
- visitPattern(node, options, callback) {
- if (typeof options === "function") {
- callback = options;
- options = { processRightHandNodes: false };
- }
- traverseIdentifierInPattern(
- this.options,
- node,
- options.processRightHandNodes ? this : null,
- callback);
- }
- visitFunction(node) {
- let i, iz;
- // FunctionDeclaration name is defined in upper scope
- // NOTE: Not referring variableScope. It is intended.
- // Since
- // in ES5, FunctionDeclaration should be in FunctionBody.
- // in ES6, FunctionDeclaration should be block scoped.
- if (node.type === Syntax.FunctionDeclaration) {
- // id is defined in upper scope
- this.currentScope().__define(node.id,
- new Definition(
- Variable.FunctionName,
- node.id,
- node,
- null,
- null,
- null
- ));
- }
- // FunctionExpression with name creates its special scope;
- // FunctionExpressionNameScope.
- if (node.type === Syntax.FunctionExpression && node.id) {
- this.scopeManager.__nestFunctionExpressionNameScope(node);
- }
- // Consider this function is in the MethodDefinition.
- this.scopeManager.__nestFunctionScope(node, this.isInnerMethodDefinition);
- const that = this;
- /**
- * Visit pattern callback
- * @param {pattern} pattern - pattern
- * @param {Object} info - info
- * @returns {void}
- */
- function visitPatternCallback(pattern, info) {
- that.currentScope().__define(pattern,
- new ParameterDefinition(
- pattern,
- node,
- i,
- info.rest
- ));
- that.referencingDefaultValue(pattern, info.assignments, null, true);
- }
- // Process parameter declarations.
- for (i = 0, iz = node.params.length; i < iz; ++i) {
- this.visitPattern(node.params[i], { processRightHandNodes: true }, visitPatternCallback);
- }
- // if there's a rest argument, add that
- if (node.rest) {
- this.visitPattern({
- type: "RestElement",
- argument: node.rest
- }, pattern => {
- this.currentScope().__define(pattern,
- new ParameterDefinition(
- pattern,
- node,
- node.params.length,
- true
- ));
- });
- }
- // In TypeScript there are a number of function-like constructs which have no body,
- // so check it exists before traversing
- if (node.body) {
- // Skip BlockStatement to prevent creating BlockStatement scope.
- if (node.body.type === Syntax.BlockStatement) {
- this.visitChildren(node.body);
- } else {
- this.visit(node.body);
- }
- }
- this.close(node);
- }
- visitClass(node) {
- if (node.type === Syntax.ClassDeclaration) {
- this.currentScope().__define(node.id,
- new Definition(
- Variable.ClassName,
- node.id,
- node,
- null,
- null,
- null
- ));
- }
- this.visit(node.superClass);
- this.scopeManager.__nestClassScope(node);
- if (node.id) {
- this.currentScope().__define(node.id,
- new Definition(
- Variable.ClassName,
- node.id,
- node
- ));
- }
- this.visit(node.body);
- this.close(node);
- }
- visitProperty(node) {
- let previous;
- if (node.computed) {
- this.visit(node.key);
- }
- const isMethodDefinition = node.type === Syntax.MethodDefinition;
- if (isMethodDefinition) {
- previous = this.pushInnerMethodDefinition(true);
- }
- this.visit(node.value);
- if (isMethodDefinition) {
- this.popInnerMethodDefinition(previous);
- }
- }
- visitForIn(node) {
- if (node.left.type === Syntax.VariableDeclaration && node.left.kind !== "var") {
- this.scopeManager.__nestForScope(node);
- }
- if (node.left.type === Syntax.VariableDeclaration) {
- this.visit(node.left);
- this.visitPattern(node.left.declarations[0].id, pattern => {
- this.currentScope().__referencing(pattern, Reference.WRITE, node.right, null, true, true);
- });
- } else {
- this.visitPattern(node.left, { processRightHandNodes: true }, (pattern, info) => {
- let maybeImplicitGlobal = null;
- if (!this.currentScope().isStrict) {
- maybeImplicitGlobal = {
- pattern,
- node
- };
- }
- this.referencingDefaultValue(pattern, info.assignments, maybeImplicitGlobal, false);
- this.currentScope().__referencing(pattern, Reference.WRITE, node.right, maybeImplicitGlobal, true, false);
- });
- }
- this.visit(node.right);
- this.visit(node.body);
- this.close(node);
- }
- visitVariableDeclaration(variableTargetScope, type, node, index) {
- const decl = node.declarations[index];
- const init = decl.init;
- this.visitPattern(decl.id, { processRightHandNodes: true }, (pattern, info) => {
- variableTargetScope.__define(
- pattern,
- new Definition(
- type,
- pattern,
- decl,
- node,
- index,
- node.kind
- )
- );
- this.referencingDefaultValue(pattern, info.assignments, null, true);
- if (init) {
- this.currentScope().__referencing(pattern, Reference.WRITE, init, null, !info.topLevel, true);
- }
- });
- }
- AssignmentExpression(node) {
- if (PatternVisitor.isPattern(node.left)) {
- if (node.operator === "=") {
- this.visitPattern(node.left, { processRightHandNodes: true }, (pattern, info) => {
- let maybeImplicitGlobal = null;
- if (!this.currentScope().isStrict) {
- maybeImplicitGlobal = {
- pattern,
- node
- };
- }
- this.referencingDefaultValue(pattern, info.assignments, maybeImplicitGlobal, false);
- this.currentScope().__referencing(pattern, Reference.WRITE, node.right, maybeImplicitGlobal, !info.topLevel, false);
- });
- } else {
- this.currentScope().__referencing(node.left, Reference.RW, node.right);
- }
- } else {
- this.visit(node.left);
- }
- this.visit(node.right);
- }
- CatchClause(node) {
- this.scopeManager.__nestCatchScope(node);
- this.visitPattern(node.param, { processRightHandNodes: true }, (pattern, info) => {
- this.currentScope().__define(pattern,
- new Definition(
- Variable.CatchClause,
- node.param,
- node,
- null,
- null,
- null
- ));
- this.referencingDefaultValue(pattern, info.assignments, null, true);
- });
- this.visit(node.body);
- this.close(node);
- }
- Program(node) {
- this.scopeManager.__nestGlobalScope(node);
- if (this.scopeManager.__isNodejsScope()) {
- // Force strictness of GlobalScope to false when using node.js scope.
- this.currentScope().isStrict = false;
- this.scopeManager.__nestFunctionScope(node, false);
- }
- if (this.scopeManager.__isES6() && this.scopeManager.isModule()) {
- this.scopeManager.__nestModuleScope(node);
- }
- if (this.scopeManager.isStrictModeSupported() && this.scopeManager.isImpliedStrict()) {
- this.currentScope().isStrict = true;
- }
- this.visitChildren(node);
- this.close(node);
- }
- Identifier(node) {
- this.currentScope().__referencing(node);
- }
- UpdateExpression(node) {
- if (PatternVisitor.isPattern(node.argument)) {
- this.currentScope().__referencing(node.argument, Reference.RW, null);
- } else {
- this.visitChildren(node);
- }
- }
- MemberExpression(node) {
- this.visit(node.object);
- if (node.computed) {
- this.visit(node.property);
- }
- }
- Property(node) {
- this.visitProperty(node);
- }
- MethodDefinition(node) {
- this.visitProperty(node);
- }
- BreakStatement() {} // eslint-disable-line class-methods-use-this
- ContinueStatement() {} // eslint-disable-line class-methods-use-this
- LabeledStatement(node) {
- this.visit(node.body);
- }
- ForStatement(node) {
- // Create ForStatement declaration.
- // NOTE: In ES6, ForStatement dynamically generates
- // per iteration environment. However, escope is
- // a static analyzer, we only generate one scope for ForStatement.
- if (node.init && node.init.type === Syntax.VariableDeclaration && node.init.kind !== "var") {
- this.scopeManager.__nestForScope(node);
- }
- this.visitChildren(node);
- this.close(node);
- }
- ClassExpression(node) {
- this.visitClass(node);
- }
- ClassDeclaration(node) {
- this.visitClass(node);
- }
- CallExpression(node) {
- // Check this is direct call to eval
- if (!this.scopeManager.__ignoreEval() && node.callee.type === Syntax.Identifier && node.callee.name === "eval") {
- // NOTE: This should be `variableScope`. Since direct eval call always creates Lexical environment and
- // let / const should be enclosed into it. Only VariableDeclaration affects on the caller's environment.
- this.currentScope().variableScope.__detectEval();
- }
- this.visitChildren(node);
- }
- BlockStatement(node) {
- if (this.scopeManager.__isES6()) {
- this.scopeManager.__nestBlockScope(node);
- }
- this.visitChildren(node);
- this.close(node);
- }
- ThisExpression() {
- this.currentScope().variableScope.__detectThis();
- }
- WithStatement(node) {
- this.visit(node.object);
- // Then nest scope for WithStatement.
- this.scopeManager.__nestWithScope(node);
- this.visit(node.body);
- this.close(node);
- }
- VariableDeclaration(node) {
- const variableTargetScope = (node.kind === "var") ? this.currentScope().variableScope : this.currentScope();
- for (let i = 0, iz = node.declarations.length; i < iz; ++i) {
- const decl = node.declarations[i];
- this.visitVariableDeclaration(variableTargetScope, Variable.Variable, node, i);
- if (decl.init) {
- this.visit(decl.init);
- }
- }
- }
- // sec 13.11.8
- SwitchStatement(node) {
- this.visit(node.discriminant);
- if (this.scopeManager.__isES6()) {
- this.scopeManager.__nestSwitchScope(node);
- }
- for (let i = 0, iz = node.cases.length; i < iz; ++i) {
- this.visit(node.cases[i]);
- }
- this.close(node);
- }
- FunctionDeclaration(node) {
- this.visitFunction(node);
- }
- FunctionExpression(node) {
- this.visitFunction(node);
- }
- ForOfStatement(node) {
- this.visitForIn(node);
- }
- ForInStatement(node) {
- this.visitForIn(node);
- }
- ArrowFunctionExpression(node) {
- this.visitFunction(node);
- }
- ImportDeclaration(node) {
- assert(this.scopeManager.__isES6() && this.scopeManager.isModule(), "ImportDeclaration should appear when the mode is ES6 and in the module context.");
- const importer = new Importer(node, this);
- importer.visit(node);
- }
- visitExportDeclaration(node) {
- if (node.source) {
- return;
- }
- if (node.declaration) {
- this.visit(node.declaration);
- return;
- }
- this.visitChildren(node);
- }
- ExportDeclaration(node) {
- this.visitExportDeclaration(node);
- }
- ExportNamedDeclaration(node) {
- this.visitExportDeclaration(node);
- }
- ExportSpecifier(node) {
- const local = (node.id || node.local);
- this.visit(local);
- }
- MetaProperty() { // eslint-disable-line class-methods-use-this
- // do nothing.
- }
- }
- module.exports = Referencer;
- /* vim: set sw=4 ts=4 et tw=80 : */
|