Spaces:
Paused
Paused
| /* | |
| 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. | |
| */ | |
| import estraverse from "estraverse"; | |
| import esrecurse from "esrecurse"; | |
| import Reference from "./reference.js"; | |
| import Variable from "./variable.js"; | |
| import PatternVisitor from "./pattern-visitor.js"; | |
| import { Definition, ParameterDefinition } from "./definition.js"; | |
| import { assert } from "./assert.js"; | |
| const { Syntax } = estraverse; | |
| /** | |
| * 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 !== void 0) { | |
| 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. | |
| /** | |
| * Visitor for import specifiers. | |
| */ | |
| 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) { | |
| let visitPatternOptions = options; | |
| let visitPatternCallback = callback; | |
| if (typeof options === "function") { | |
| visitPatternCallback = options; | |
| visitPatternOptions = { processRightHandNodes: false }; | |
| } | |
| traverseIdentifierInPattern( | |
| this.options, | |
| node, | |
| visitPatternOptions.processRightHandNodes ? this : null, | |
| visitPatternCallback | |
| ); | |
| } | |
| 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.scopeManager.__nestClassScope(node); | |
| if (node.id) { | |
| this.currentScope().__define(node.id, | |
| new Definition( | |
| Variable.ClassName, | |
| node.id, | |
| node | |
| )); | |
| } | |
| this.visit(node.superClass); | |
| 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, | |
| pattern, | |
| 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.isGlobalReturn()) { | |
| // 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); | |
| } | |
| // eslint-disable-next-line class-methods-use-this -- Desired as instance method | |
| PrivateIdentifier() { | |
| // Do nothing. | |
| } | |
| 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); | |
| } | |
| PropertyDefinition(node) { | |
| const { computed, key, value } = node; | |
| if (computed) { | |
| this.visit(key); | |
| } | |
| if (value) { | |
| this.scopeManager.__nestClassFieldInitializerScope(value); | |
| this.visit(value); | |
| this.close(value); | |
| } | |
| } | |
| StaticBlock(node) { | |
| this.scopeManager.__nestClassStaticBlockScope(node); | |
| this.visitChildren(node); | |
| this.close(node); | |
| } | |
| MethodDefinition(node) { | |
| this.visitProperty(node); | |
| } | |
| BreakStatement() {} // eslint-disable-line class-methods-use-this -- Desired as instance method | |
| ContinueStatement() {} // eslint-disable-line class-methods-use-this -- Desired as instance method | |
| 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); | |
| } | |
| // TODO: ExportDeclaration doesn't exist. for bc? | |
| ExportDeclaration(node) { | |
| this.visitExportDeclaration(node); | |
| } | |
| ExportAllDeclaration(node) { | |
| this.visitExportDeclaration(node); | |
| } | |
| ExportDefaultDeclaration(node) { | |
| this.visitExportDeclaration(node); | |
| } | |
| ExportNamedDeclaration(node) { | |
| this.visitExportDeclaration(node); | |
| } | |
| ExportSpecifier(node) { | |
| // TODO: `node.id` doesn't exist. for bc? | |
| const local = (node.id || node.local); | |
| this.visit(local); | |
| } | |
| MetaProperty() { // eslint-disable-line class-methods-use-this -- Desired as instance method | |
| // do nothing. | |
| } | |
| } | |
| export default Referencer; | |
| /* vim: set sw=4 ts=4 et tw=80 : */ | |