Spaces:
Runtime error
Runtime error
/* | |
Copyright (C) 2015 Yusuke Suzuki <[email protected]> | |
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. | |
*/ | |
; | |
/* 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) { | |
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.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); | |
} | |
// 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 | |
// do nothing. | |
} | |
} | |
module.exports = Referencer; | |
/* vim: set sw=4 ts=4 et tw=80 : */ | |