| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| 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; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| function traverseIdentifierInPattern( |
| options, |
| rootPattern, |
| referencer, |
| callback, |
| ) { |
| |
| const visitor = new PatternVisitor(options, rootPattern, callback); |
|
|
| visitor.visit(rootPattern); |
|
|
| |
| if (referencer !== null && referencer !== void 0) { |
| visitor.rightHandNodes.forEach(referencer.visit, referencer); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| 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); |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| 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; |
|
|
| |
| |
| |
| |
| |
|
|
| if (node.type === Syntax.FunctionDeclaration) { |
| |
| this.currentScope().__define( |
| node.id, |
| new Definition( |
| Variable.FunctionName, |
| node.id, |
| node, |
| null, |
| null, |
| null, |
| ), |
| ); |
| } |
|
|
| |
| |
| if (node.type === Syntax.FunctionExpression && node.id) { |
| this.scopeManager.__nestFunctionExpressionNameScope(node); |
| } |
|
|
| |
| this.scopeManager.__nestFunctionScope( |
| node, |
| this.isInnerMethodDefinition, |
| ); |
|
|
| const that = this; |
|
|
| |
| |
| |
| |
| |
| |
| function visitPatternCallback(pattern, info) { |
| that.currentScope().__define( |
| pattern, |
| new ParameterDefinition(pattern, node, i, info.rest), |
| ); |
|
|
| that.referencingDefaultValue(pattern, info.assignments, null, true); |
| } |
|
|
| |
| for (i = 0, iz = node.params.length; i < iz; ++i) { |
| this.visitPattern( |
| node.params[i], |
| { processRightHandNodes: true }, |
| visitPatternCallback, |
| ); |
| } |
|
|
| |
| if (node.rest) { |
| this.visitPattern( |
| { |
| type: "RestElement", |
| argument: node.rest, |
| }, |
| pattern => { |
| this.currentScope().__define( |
| pattern, |
| new ParameterDefinition( |
| pattern, |
| node, |
| node.params.length, |
| true, |
| ), |
| ); |
| }, |
| ); |
| } |
|
|
| |
| |
| if (node.body) { |
| |
| 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()) { |
| |
| 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); |
| } |
|
|
| |
| PrivateIdentifier() { |
| |
| } |
|
|
| 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() {} |
|
|
| ContinueStatement() {} |
|
|
| LabeledStatement(node) { |
| this.visit(node.body); |
| } |
|
|
| ForStatement(node) { |
| |
| |
| |
| |
| 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) { |
| |
| if ( |
| !this.scopeManager.__ignoreEval() && |
| node.callee.type === Syntax.Identifier && |
| node.callee.name === "eval" |
| ) { |
| |
| |
| 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); |
|
|
| |
| 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); |
| } |
| } |
| } |
|
|
| |
| 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); |
| } |
|
|
| ExportAllDeclaration(node) { |
| this.visitExportDeclaration(node); |
| } |
|
|
| ExportDefaultDeclaration(node) { |
| this.visitExportDeclaration(node); |
| } |
|
|
| ExportNamedDeclaration(node) { |
| this.visitExportDeclaration(node); |
| } |
|
|
| ExportSpecifier(node) { |
| |
| const local = node.id || node.local; |
|
|
| this.visit(local); |
| } |
|
|
| |
| MetaProperty() { |
| |
| } |
|
|
| JSXIdentifier(node) { |
| |
| if (this.scopeManager.__isJSXEnabled() && node.name !== "this") { |
| this.currentScope().__referencing(node); |
| } |
| } |
|
|
| JSXMemberExpression(node) { |
| this.visit(node.object); |
| } |
|
|
| JSXElement(node) { |
| if (this.scopeManager.__isJSXEnabled()) { |
| this.visit(node.openingElement); |
| node.children.forEach(this.visit, this); |
| } else { |
| this.visitChildren(node); |
| } |
| } |
|
|
| JSXOpeningElement(node) { |
| if (this.scopeManager.__isJSXEnabled()) { |
| const nameNode = node.name; |
| const isComponentName = |
| nameNode.type === "JSXIdentifier" && |
| nameNode.name[0].toUpperCase() === nameNode.name[0]; |
| const isComponent = |
| isComponentName || nameNode.type === "JSXMemberExpression"; |
|
|
| |
| if (isComponent) { |
| this.visit(nameNode); |
| } |
| } |
|
|
| node.attributes.forEach(this.visit, this); |
| } |
|
|
| JSXAttribute(node) { |
| if (node.value) { |
| this.visit(node.value); |
| } |
| } |
|
|
| JSXExpressionContainer(node) { |
| this.visit(node.expression); |
| } |
|
|
| JSXNamespacedName(node) { |
| this.visit(node.namespace); |
| this.visit(node.name); |
| } |
| } |
|
|
| export default Referencer; |
|
|
| |
|
|