lively.ast
Version:
Parsing JS code into ASTs and tools to query and transform these trees.
524 lines (436 loc) • 17.5 kB
JavaScript
/*global process, global, exports*/
import { obj } from "lively.lang";
import Visitor from "../generated/estree-visitor.js";
class PrinterVisitor extends Visitor {
accept(node, state, path) {
var pathString = path.map(ea =>
typeof ea === 'string' ? `.${ea}` : `[${ea}]`).join(''),
myChildren = [],
result = super.accept(node, {index: state.index, tree: myChildren}, path);
state.tree.push({
node: node,
path: pathString,
index: state.index++,
children: myChildren
});
return result;
}
}
class ComparisonVisitor extends Visitor {
recordNotEqual(node1, node2, state, msg) {
state.comparisons.errors.push({
node1: node1, node2: node2,
path: state.completePath, msg: msg
});
}
compareType(node1, node2, state) {
return this.compareField('type', node1, node2, state);
}
compareField(field, node1, node2, state) {
node2 = lively.PropertyPath(state.completePath.join('.')).get(node2);
if (node1 && node2 && node1[field] === node2[field]) return true;
if ((node1 && node1[field] === '*') || (node2 && node2[field] === '*')) return true;
var fullPath = state.completePath.join('.') + '.' + field, msg;
if (!node1) msg = "node1 on " + fullPath + " not defined";
else if (!node2) msg = 'node2 not defined but node1 (' + fullPath + ') is: '+ node1[field];
else msg = fullPath + ' is not equal: ' + node1[field] + ' vs. ' + node2[field];
this.recordNotEqual(node1, node2, state, msg);
return false;
}
accept(node1, node2, state, path) {
var patternNode = lively.PropertyPath(path.join('.')).get(node2);
if (node1 === '*' || patternNode === '*') return;
var nextState = {
completePath: path,
comparisons: state.comparisons
};
if (this.compareType(node1, node2, nextState))
this['visit' + node1.type](node1, node2, nextState, path);
}
visitFunction(node1, node2, state, path) {
// node1.generator has a specific type that is boolean
if (node1.generator) { this.compareField("generator", node1, node2, state); }
// node1.expression has a specific type that is boolean
if (node1.expression) { this.compareField("expression", node1, node2, state); }
return super.visitFunction(node1, node2, state, path);
}
visitSwitchStatement(node1, node2, state, path) {
// node1.lexical has a specific type that is boolean
if (node1.lexical) { this.compareField("lexical", node1, node2, state); }
return super.visitSwitchStatement(node1, node2, state, path);
}
visitForInStatement(node1, node2, state, path) {
// node1.each has a specific type that is boolean
if (node1.each) { this.compareField("each", node1, node2, state); }
return super.visitForInStatement(node1, node2, state, path);
}
visitFunctionDeclaration(node1, node2, state, path) {
// node1.generator has a specific type that is boolean
if (node1.generator) { this.compareField("generator", node1, node2, state); }
// node1.expression has a specific type that is boolean
if (node1.expression) { this.compareField("expression", node1, node2, state); }
return super.visitFunctionDeclaration(node1, node2, state, path);
}
visitVariableDeclaration(node1, node2, state, path) {
// node1.kind is "var" or "let" or "const"
this.compareField("kind", node1, node2, state);
return super.visitVariableDeclaration(node1, node2, state, path);
}
visitUnaryExpression(node1, node2, state, path) {
// node1.operator is an UnaryOperator enum:
// "-" | "+" | "!" | "~" | "typeof" | "void" | "delete"
this.compareField("operator", node1, node2, state);
// node1.prefix has a specific type that is boolean
if (node1.prefix) { this.compareField("prefix", node1, node2, state); }
return super.visitUnaryExpression(node1, node2, state, path);
}
visitBinaryExpression(node1, node2, state, path) {
// node1.operator is an BinaryOperator enum:
// "==" | "!=" | "===" | "!==" | | "<" | "<=" | ">" | ">=" | | "<<" | ">>" | ">>>" | | "+" | "-" | "*" | "/" | "%" | | "|" | "^" | "&" | "in" | | "instanceof" | ".."
this.compareField("operator", node1, node2, state);
return super.visitBinaryExpression(node1, node2, state, path);
}
visitAssignmentExpression(node1, node2, state, path) {
// node1.operator is an AssignmentOperator enum:
// "=" | "+=" | "-=" | "*=" | "/=" | "%=" | | "<<=" | ">>=" | ">>>=" | | "|=" | "^=" | "&="
this.compareField("operator", node1, node2, state);
return super.visitAssignmentExpression(node1, node2, state, path);
}
visitUpdateExpression(node1, node2, state, path) {
// node1.operator is an UpdateOperator enum:
// "++" | "--"
this.compareField("operator", node1, node2, state);
// node1.prefix has a specific type that is boolean
if (node1.prefix) { this.compareField("prefix", node1, node2, state); }
return super.visitUpdateExpression(node1, node2, state, path);
}
visitLogicalExpression(node1, node2, state, path) {
// node1.operator is an LogicalOperator enum:
// "||" | "&&"
this.compareField("operator", node1, node2, state);
return super.visitLogicalExpression(node1, node2, state, path);
}
visitMemberExpression(node1, node2, state, path) {
// node1.computed has a specific type that is boolean
if (node1.computed) { this.compareField("computed", node1, node2, state); }
return super.visitMemberExpression(node1, node2, state, path);
}
visitComprehensionBlock(node1, node2, state, path) {
// node1.each has a specific type that is boolean
if (node1.each) { this.compareField("each", node1, node2, state); }
return super.visitComprehensionBlock(node1, node2, state, path);
}
visitIdentifier(node1, node2, state, path) {
// node1.name has a specific type that is string
this.compareField("name", node1, node2, state);
return super.visitIdentifier(node1, node2, state, path);
}
visitLiteral(node1, node2, state, path) {
this.compareField("value", node1, node2, state);
return super.visitLiteral(node1, node2, state, path);
}
visitClassDeclaration(node1, node2, state, path) {
this.compareField("id", node1, node2, state);
if (node1.superClass) {
this.compareField("superClass", node1, node2, state);
}
this.compareField("body", node1, node2, state);
return super.visitClassDeclaration(node1, node2, state, path);
}
visitClassBody(node1, node2, state, path) {
this.compareField("body", node1, node2, state);
return super.visitClassBody(node1, node2, state, path);
}
visitMethodDefinition(node1, node2, state, path) {
this.compareField("static", node1, node2, state);
this.compareField("computed", node1, node2, state);
this.compareField("kind", node1, node2, state);
this.compareField("key", node1, node2, state);
this.compareField("value", node1, node2, state);
return super.visitMethodDefinition(node1, node2, state, path);
}
}
class ScopeVisitor extends Visitor {
newScope(scopeNode, parentScope) {
var scope = {
node: scopeNode,
varDecls: [],
varDeclPaths: [],
funcDecls: [],
funcDeclPaths: [],
classDecls: [],
classDeclPaths: [],
classExprs: [],
classExprPaths: [],
methodDecls: [],
methodDeclPaths: [],
importDecls: [],
importDeclPaths: [],
exportDecls: [],
exportDeclPaths: [],
refs: [],
thisRefs: [],
params: [],
catches: [],
subScopes: [],
resolvedRefMap: new Map()
}
if (parentScope) parentScope.subScopes.push(scope);
return scope;
}
visitVariableDeclaration(node, scope, path) {
scope.varDecls.push(node);
scope.varDeclPaths.push(path);
return super.visitVariableDeclaration(node, scope, path);
}
visitVariableDeclarator(node, scope, path) {
var visitor = this;
// ignore id
// // id is of types Pattern
// node["id"] = visitor.accept(node["id"], scope, path.concat(["id"]));
// init is of types Expression
if (node["init"]) {
node["init"] = visitor.accept(node["init"], scope, path.concat(["init"]));
}
return node;
}
visitFunction (node, scope, path) {
var newScope = this.newScope(node, scope);
newScope.params = Array.prototype.slice.call(node.params);
return newScope;
}
visitFunctionDeclaration (node, scope, path) {
var newScope = this.visitFunction(node, scope, path);
scope.funcDecls.push(node);
scope.funcDeclPaths.push(path);
// don't visit id and params
var visitor = this;
if (node.defaults) {
node["defaults"] = node["defaults"].reduce(function(results, ea, i) {
var result = visitor.accept(ea, newScope, path.concat(["defaults", i]));
if (Array.isArray(result)) results.push.apply(results, result);
else results.push(result);
return results;
}, []);
}
if (node.rest) {
node["rest"] = visitor.accept(node["rest"], newScope, path.concat(["rest"]));
}
node["body"] = visitor.accept(node["body"], newScope, path.concat(["body"]));
// loc is of types SourceLocation
if (node["loc"]) {
node["loc"] = visitor.accept(node["loc"], newScope, path.concat(["loc"]));
}
return node;
}
visitFunctionExpression (node, scope, path) {
var newScope = this.visitFunction(node, scope, path);
// don't visit id and params
var visitor = this;
if (node.defaults) {
node["defaults"] = node["defaults"].reduce(function(results, ea, i) {
var result = visitor.accept(ea, newScope, path.concat(["defaults", i]));
if (Array.isArray(result)) results.push.apply(results, result);
else results.push(result);
return results;
}, []);
}
if (node.rest) {
node["rest"] = visitor.accept(node["rest"], newScope, path.concat(["rest"]));
}
node["body"] = visitor.accept(node["body"], newScope, path.concat(["body"]));
// loc is of types SourceLocation
if (node["loc"]) {
node["loc"] = visitor.accept(node["loc"], newScope, path.concat(["loc"]));
}
return node;
}
visitArrowFunctionExpression(node, scope, path) {
var newScope = this.visitFunction(node, scope, path);
var visitor = this;
if (node.defaults) {
node["defaults"] = node["defaults"].reduce(function(results, ea, i) {
var result = visitor.accept(ea, newScope, path.concat(["defaults", i]));
if (Array.isArray(result)) results.push.apply(results, result);
else results.push(result);
return results;
}, []);
}
if (node.rest) {
node["rest"] = visitor.accept(node["rest"], newScope, path.concat(["rest"]));
}
// body is of types BlockStatement, Expression
node["body"] = visitor.accept(node["body"], newScope, path.concat(["body"]));
// loc is of types SourceLocation
if (node["loc"]) {
node["loc"] = visitor.accept(node["loc"], newScope, path.concat(["loc"]));
}
// node.generator has a specific type that is boolean
if (node.generator) {/*do stuff*/}
// node.expression has a specific type that is boolean
if (node.expression) {/*do stuff*/}
return node;
}
visitIdentifier(node, scope, path) {
scope.refs.push(node);
return super.visitIdentifier(node, scope, path);
}
visitMemberExpression(node, scope, path) {
// only visit property part when prop is computed so we don't gather
// prop ids
var visitor = this;
// object is of types Expression, Super
node["object"] = visitor.accept(node["object"], scope, path.concat(["object"]));
// property is of types Expression
if (node.computed) {
node["property"] = visitor.accept(node["property"], scope, path.concat(["property"]));
}
return node;
}
visitProperty(node, scope, path) {
var visitor = this;
// key is of types Expression
if (node.computed)
node["key"] = visitor.accept(node["key"], scope, path.concat(["key"]));
// value is of types Expression
node["value"] = visitor.accept(node["value"], scope, path.concat(["value"]));
return node;
}
visitThisExpression(node, scope, path) {
scope.thisRefs.push(node);
return super.visitThisExpression(node, scope, path);
}
visitTryStatement (node, scope, path) {
var visitor = this;
// block is of types BlockStatement
node["block"] = visitor.accept(node["block"], scope, path.concat(["block"]));
// handler is of types CatchClause
if (node["handler"]) {
node["handler"] = visitor.accept(node["handler"], scope, path.concat(["handler"]));
scope.catches.push(node.handler.param);
}
// finalizer is of types BlockStatement
if (node["finalizer"]) {
node["finalizer"] = visitor.accept(node["finalizer"], scope, path.concat(["finalizer"]));
}
return node;
}
visitLabeledStatement (node, scope, path) {
var visitor = this;
// ignore label
// // label is of types Identifier
// node["label"] = visitor.accept(node["label"], scope, path.concat(["label"]));
// body is of types Statement
node["body"] = visitor.accept(node["body"], scope, path.concat(["body"]));
return node;
}
visitClassDeclaration(node, scope, path) {
scope.classDecls.push(node);
scope.classDeclPaths.push(path);
var visitor = this;
// ignore id
// // id is of types Identifier
// node["id"] = visitor.accept(node["id"], scope, path.concat(["id"]));
// superClass is of types Expression
if (node["superClass"]) {
node["superClass"] = visitor.accept(node["superClass"], scope, path.concat(["superClass"]));
}
// body is of types ClassBody
node["body"] = visitor.accept(node["body"], scope, path.concat(["body"]));
return node;
}
visitClassExpression(node, scope, path) {
if (node.id) {
scope.classExprs.push(node);
scope.classExprPaths.push(path);
}
var visitor = this;
// ignore id
// // id is of types Identifier
// node["id"] = visitor.accept(node["id"], scope, path.concat(["id"]));
// superClass is of types Expression
if (node["superClass"]) {
node["superClass"] = visitor.accept(node["superClass"], scope, path.concat(["superClass"]));
}
// body is of types ClassBody
node["body"] = visitor.accept(node["body"], scope, path.concat(["body"]));
return node;
}
visitMethodDefinition(node, scope, path) {
var visitor = this;
// don't visit key Identifier for now
// // key is of types Expression
// node["key"] = visitor.accept(node["key"], scope, path.concat(["key"]));
// value is of types FunctionExpression
node["value"] = visitor.accept(node["value"], scope, path.concat(["value"]));
return node;
}
visitMetaProperty(node, scope, path) {
// this is the new.target thing
var visitor = this;
// node['meta'] = visitor.accept(node['meta'], scope, path.concat(['meta']));
// node['property'] = visitor.accept(node['property'],scope, path.concat(['property']));
return node;
}
visitBreakStatement(node, scope, path) { return node; }
visitContinueStatement(node, scope, path) { return node; }
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// es6 modules
visitImportSpecifier(node, scope, path) {
scope.importDecls.push(node.local);
scope.importDeclPaths.push(path);
var visitor = this;
// // imported is of types Identifier
// node["imported"] = visitor.accept(node["imported"], scope, path.concat(["imported"]));
// local is of types Identifier
node["local"] = visitor.accept(node["local"], scope, path.concat(["local"]));
return node;
}
visitImportDefaultSpecifier(node, scope, path) {
scope.importDecls.push(node.local);
scope.importDeclPaths.push(path);
var visitor = this;
// // local is of types Identifier
// node["local"] = visitor.accept(node["local"], scope, path.concat(["local"]));
return node;
}
visitImportNamespaceSpecifier(node, scope, path) {
scope.importDecls.push(node.local);
scope.importDeclPaths.push(path);
var visitor = this;
// // local is of types Identifier
// node["local"] = visitor.accept(node["local"], scope, path.concat(["local"]));
return node;
}
visitExportSpecifier(node, scope, path) {
var visitor = this;
// // exported is of types Identifier
// node["exported"] = visitor.accept(node["exported"], scope, path.concat(["exported"]));
// local is of types Identifier
node["local"] = visitor.accept(node["local"], scope, path.concat(["local"]));
return node;
}
visitExportNamedDeclaration(node, scope, path) {
scope.exportDecls.push(node);
scope.exportDeclPaths.push(path);
// only descend if it's not an export {...} from "..."
if (!node.source) super.visitExportNamedDeclaration(node, scope, path);
return node;
}
visitExportDefaultDeclaration(node, scope, path) {
scope.exportDecls.push(node);
scope.exportDeclPaths.push(path);
return super.visitExportDefaultDeclaration(node, scope, path);
}
visitExportAllDeclaration(node, scope, path) {
scope.exportDecls.push(node);
scope.exportDeclPaths.push(path);
return super.visitExportAllDeclaration(node, scope, path);
}
}
export {
Visitor as BaseVisitor,
PrinterVisitor,
ComparisonVisitor,
ScopeVisitor
}