|
|
- /*
- 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 : */
|