further develop ast paths that represent a single location in the ast as an abstraction around a node-parent relationship

This commit is contained in:
Sebastian McKenzie 2015-03-06 02:26:04 +11:00
parent 7a6e568940
commit f62a3ef394
23 changed files with 197 additions and 88 deletions

View File

@ -6,7 +6,7 @@ var visitor = {
enter(node, parent, scope, state) {
// check if this node is a referenced identifier that matches the same as our
// function id
if (!t.isReferencedIdentifier(node, parent, { name: state.name })) return;
if (!this.isReferencedIdentifier({ name: state.name })) return;
// check that we don't have a local variable declared as that removes the need
// for the wrapper

View File

@ -24,7 +24,7 @@ module.exports = function (node, callId, scope) {
var call = t.callExpression(callId, [node]);
var id = node.id;
delete node.id;
node.id = null;
if (t.isFunctionDeclaration(node)) {
var declar = t.variableDeclaration("let", [

View File

@ -2,7 +2,7 @@ import t from "../../../types";
var visitor = {
enter(node, parent, scope, state) {
if (!t.isReferencedIdentifier(node, parent)) return;
if (!this.isReferencedIdentifier()) return;
var declared = state.letRefs[node.name];
if (!declared) return;

View File

@ -109,7 +109,7 @@ function traverseReplace(node, parent, scope, remaps) {
var letReferenceBlockVisitor = {
enter(node, parent, scope, state) {
if (t.isFunction(node)) {
if (this.isFunction()) {
scope.traverse(node, letReferenceFunctionVisitor, state);
return this.skip();
}
@ -119,7 +119,7 @@ var letReferenceBlockVisitor = {
var letReferenceFunctionVisitor = {
enter(node, parent, scope, state) {
// not a direct reference
if (!t.isReferencedIdentifier(node, parent)) return;
if (!this.isReferencedIdentifier()) return;
// this scope has a variable with the same name so it couldn't belong
// to our let scope
@ -134,17 +134,17 @@ var letReferenceFunctionVisitor = {
var hoistVarDeclarationsVisitor = {
enter(node, parent, scope, self) {
if (t.isForStatement(node)) {
if (this.isForStatement()) {
if (isVar(node.init, node)) {
node.init = t.sequenceExpression(self.pushDeclar(node.init));
}
} else if (t.isFor(node)) {
} else if (this.isFor()) {
if (isVar(node.left, node)) {
node.left = node.left.declarations[0].id;
}
} else if (isVar(node, parent)) {
return self.pushDeclar(node).map(t.expressionStatement);
} else if (t.isFunction(node)) {
} else if (this.isFunction()) {
return this.skip();
}
}
@ -152,7 +152,7 @@ var hoistVarDeclarationsVisitor = {
var loopLabelVisitor = {
enter(node, parent, scope, state) {
if (t.isLabeledStatement(node)) {
if (this.isLabeledStatement()) {
state.innerLabels.push(node.label.name);
}
}
@ -170,13 +170,13 @@ var loopVisitor = {
enter(node, parent, scope, state) {
var replace;
if (t.isLoop(node)) {
if (this.isLoop()) {
state.ignoreLabeless = true;
scope.traverse(node, loopVisitor, state);
state.ignoreLabeless = false;
}
if (t.isFunction(node) || t.isLoop(node)) {
if (this.isFunction() || this.isLoop()) {
return this.skip();
}
@ -204,7 +204,7 @@ var loopVisitor = {
replace = t.literal(loopText);
}
if (t.isReturnStatement(node)) {
if (this.isReturnStatement()) {
state.hasReturn = true;
replace = t.objectExpression([
t.property("init", t.identifier("v"), node.argument || t.identifier("undefined"))

View File

@ -7,8 +7,8 @@ export function check(node) {
var visitor = {
enter(node, parent, scope, state) {
if (t.isAssignmentExpression(node) || t.isUpdateExpression(node)) {
var ids = t.getBindingIdentifiers(node);
if (this.isAssignmentExpression() || this.isUpdateExpression()) {
var ids = this.getBindingIdentifiers();
for (var name in ids) {
var id = ids[name];
@ -30,7 +30,7 @@ var visitor = {
throw state.file.errorWithNode(id, messages.get("readOnly", name));
}
} else if (t.isScope(node, parent)) {
} else if (this.isScope()) {
this.skip();
}
}

View File

@ -219,7 +219,7 @@ export function VariableDeclaration(node, parent, scope, file) {
}
return nodes;
};
}
var hasRest = function (pattern) {
for (var i = 0; i < pattern.elements.length; i++) {

View File

@ -41,14 +41,14 @@ export function ForOfStatement(node, parent, scope, file) {
var breakVisitor = {
enter(node, parent, scope, state) {
if (t.isLoop(node)) {
if (this.isLoop()) {
state.ignoreLabeless = true;
scope.traverse(node, breakVisitor, state);
state.ignoreLabeless = false;
return this.skip();
}
if (t.isBreakStatement(node)) {
if (this.isBreakStatement()) {
if (!node.label && state.ignoreLabeless) return;
if (node.label && node.label.name !== state.label) return;

View File

@ -14,7 +14,7 @@ var hasDefaults = function (node) {
var iifeVisitor = {
enter(node, parent, scope, state) {
if (!t.isReferencedIdentifier(node, parent)) return;
if (!this.isReferencedIdentifier()) return;
if (!state.scope.hasOwnBinding(node.name)) return;
if (state.scope.bindingIdentifierEquals(node.name, node)) return;

View File

@ -7,13 +7,13 @@ export var check = t.isRestElement;
var memberExpressionOptimisationVisitor = {
enter(node, parent, scope, state) {
// check if this scope has a local binding that will shadow the rest parameter
if (t.isScope(node, parent) && !scope.bindingIdentifierEquals(state.name, state.outerBinding)) {
if (this.isScope() && !scope.bindingIdentifierEquals(state.name, state.outerBinding)) {
return this.skip();
}
// skip over functions as whatever `arguments` we reference inside will refer
// to the wrong function
if (t.isFunctionDeclaration(node) || t.isFunctionExpression(node)) {
if (this.isFunctionDeclaration() || this.isFunctionExpression()) {
state.noOptimise = true;
scope.traverse(node, memberExpressionOptimisationVisitor, state);
state.noOptimise = false;
@ -21,7 +21,7 @@ var memberExpressionOptimisationVisitor = {
}
// is this a referenced identifier and is it referencing the rest parameter?
if (!t.isReferencedIdentifier(node, parent, { name: state.name })) return;
if (!this.isReferencedIdentifier({ name: state.name })) return;
if (!state.noOptimise && t.isMemberExpression(parent) && parent.computed) {
// if we know that this member expression is referencing a number then we can safely

View File

@ -1,4 +1,3 @@
import clone from "lodash/lang/clone";
import t from "../../../types";
export function check(node) {
@ -12,6 +11,6 @@ export function Property(node) {
if (node.shorthand) {
node.shorthand = false;
node.key = t.removeComments(clone(node.key));
node.key = t.removeComments(t.clone(node.key));
}
}

View File

@ -17,7 +17,7 @@ function returnBlock(expr) {
// looks for and replaces tail recursion calls
var firstPass = {
enter(node, parent, scope, state) {
if (t.isIfStatement(node)) {
if (this.isIfStatement()) {
if (t.isReturnStatement(node.alternate)) {
t.ensureBlock(node, "alternate");
}
@ -25,7 +25,7 @@ var firstPass = {
if (t.isReturnStatement(node.consequent)) {
t.ensureBlock(node, "consequent");
}
} else if (t.isReturnStatement(node)) {
} else if (this.isReturnStatement()) {
this.skip();
return state.subTransform(node.argument);
} else if (t.isTryStatement(parent)) {
@ -34,9 +34,9 @@ var firstPass = {
} else if (parent.finalizer && node !== parent.finalizer) {
this.skip();
}
} else if (t.isFunction(node)) {
} else if (this.isFunction()) {
this.skip();
} else if (t.isVariableDeclaration(node)) {
} else if (this.isVariableDeclaration()) {
this.skip();
state.vars.push(node);
}
@ -47,15 +47,15 @@ var firstPass = {
// them as needed
var secondPass = {
enter(node, parent, scope, state) {
if (t.isThisExpression(node)) {
if (this.isThisExpression()) {
state.needsThis = true;
return state.getThisId();
} else if (t.isReferencedIdentifier(node, parent, { name: "arguments" })) {
} else if (this.isReferencedIdentifier({ name: "arguments" })) {
state.needsArguments = true;
return state.getArgumentsId();
} else if (t.isFunction(node)) {
} else if (this.isFunction()) {
this.skip();
if (t.isFunctionDeclaration(node)) {
if (this.isFunctionDeclaration()) {
node = t.variableDeclaration("var", [
t.variableDeclarator(node.id, t.toExpression(node))
]);
@ -69,7 +69,7 @@ var secondPass = {
// optimizes recursion by removing `this` and `arguments` if they aren't used
var thirdPass = {
enter(node, parent, scope, state) {
if (!t.isExpressionStatement(node)) return;
if (!this.isExpressionStatement()) return;
var expr = node.expression;
if (!t.isAssignmentExpression(expr)) return;

View File

@ -2,7 +2,7 @@ import t from "../../../types";
var functionChildrenVisitor = {
enter(node, parent, scope, state) {
if (t.isFunction(node) && !node._aliasFunction) {
if (this.isFunction() && !node._aliasFunction) {
return this.skip();
}
@ -10,22 +10,22 @@ var functionChildrenVisitor = {
var getId;
if (t.isIdentifier(node) && node.name === "arguments") {
if (this.isIdentifier() && node.name === "arguments") {
getId = state.getArgumentsId;
} else if (t.isThisExpression(node)) {
} else if (this.isThisExpression()) {
getId = state.getThisId;
} else {
return;
}
if (t.isReferenced(node, parent)) return getId();
if (this.isReferenced()) return getId();
}
};
var functionVisitor = {
enter(node, parent, scope, state) {
if (!node._aliasFunction) {
if (t.isFunction(node)) {
if (this.isFunction()) {
// stop traversal of this node as it'll be hit again by this transformer
return this.skip();
} else {

View File

@ -16,14 +16,15 @@ var ALIASABLE_CONSTRUCTORS = [
"Map",
"WeakMap",
"Set",
"WeakSet"
"WeakSet",
"Number"
];
var astVisitor = {
enter(node, parent, scope, file) {
var prop;
if (t.isMemberExpression(node) && t.isReferenced(node, parent)) {
if (this.isMemberExpression() && this.isReferenced()) {
// Array.from -> _core.Array.from
var obj = node.object;
prop = node.property;
@ -34,10 +35,10 @@ var astVisitor = {
this.skip();
return t.prependToMemberExpression(node, file.get("coreIdentifier"));
}
} else if (t.isReferencedIdentifier(node, parent) && !t.isMemberExpression(parent) && includes(ALIASABLE_CONSTRUCTORS, node.name) && !scope.getBindingIdentifier(node.name)) {
} else if (this.isReferencedIdentifier() && !t.isMemberExpression(parent) && includes(ALIASABLE_CONSTRUCTORS, node.name) && !scope.getBindingIdentifier(node.name)) {
// Symbol() -> _core.Symbol(); new Promise -> new _core.Promise
return t.memberExpression(file.get("coreIdentifier"), node);
} else if (t.isCallExpression(node)) {
} else if (this.isCallExpression()) {
// arr[Symbol.iterator]() -> _core.$for.getIterator(arr)
var callee = node.callee;
@ -53,7 +54,7 @@ var astVisitor = {
CORE_ID: file.get("coreIdentifier"),
VALUE: callee.object
});
} else if (t.isBinaryExpression(node)) {
} else if (this.isBinaryExpression()) {
// Symbol.iterator in arr -> core.$for.isIterable(arr)
if (node.operator !== "in") return;
@ -94,7 +95,7 @@ export function pre(file) {
}
export function Identifier(node, parent, scope, file) {
if (t.isReferencedIdentifier(node, parent, { name: "regeneratorRuntime" })) {
if (this.isReferencedIdentifier({ name: "regeneratorRuntime" })) {
return file.get("regeneratorIdentifier");
}
}

View File

@ -6,8 +6,7 @@ export var playground = true;
build(exports, {
is(node, file) {
var is = t.isAssignmentExpression(node) && node.operator === "||=";
if (is) {
if (t.isAssignmentExpression(node, { operator: "||=" })) {
var left = node.left;
if (!t.isMemberExpression(left) && !t.isIdentifier(left)) {
throw file.errorWithNode(left, messages.get("expectedMemberExpressionOrIdentifier"));

View File

@ -5,7 +5,7 @@ export var playground = true;
build(exports, {
is(node) {
var is = t.isAssignmentExpression(node) && node.operator === "?=";
var is = t.isAssignmentExpression(node, { operator: "?=" });
if (is) t.assertMemberExpression(node.left);
return is;
},

View File

@ -4,9 +4,9 @@ export var playground = true;
var visitor = {
enter(node, parent, scope, state) {
if (t.isFunction(node)) return this.skip();
if (this.isFunction()) return this.skip();
if (t.isReturnStatement(node) && node.argument) {
if (this.isReturnStatement() && node.argument) {
node.argument = t.memberExpression(t.callExpression(state.file.addHelper("define-property"), [
t.thisExpression(),
state.key,

View File

@ -3,7 +3,7 @@ import t from "../../../types";
export var optional = true;
export function Identifier(node, parent) {
if (node.name === "undefined" && t.isReferenced(node, parent)) {
if (node.name === "undefined" && this.isReferenced()) {
return t.unaryExpression("void", t.literal(0), true);
}
}

View File

@ -3,7 +3,7 @@ import t from "../../../types";
export var optional = true;
export function ExpressionStatement(node) {
if (t.isIdentifier(node.expression, { name: "debugger" })) {
if (this.get("expression").isIdentifier({ name: "debugger" })) {
this.remove();
}
}

View File

@ -1,11 +1,10 @@
import levenshtein from "leven";
import * as messages from "../../../messages";
import t from "../../../types";
export var optional = true;
export function Identifier(node, parent, scope, file) {
if (!t.isReferenced(node, parent)) return;
if (!this.isReferenced()) return;
if (scope.hasBinding(node.name)) return;
// get the closest declaration to offer as a suggestion

View File

@ -2,7 +2,7 @@ import TraversalPath from "./path";
import flatten from "lodash/array/flatten";
import compact from "lodash/array/compact";
export default class TraversalConext {
export default class TraversalContext {
constructor(scope, opts, state, parentPath) {
this.shouldFlatten = false;
this.parentPath = parentPath;
@ -17,7 +17,7 @@ export default class TraversalConext {
}
visitNode(node, obj, key) {
var iteration = new TraversalPath(this, node, obj, key);
var iteration = TraversalPath.get(this.parentPath, this, node, obj, key);
return iteration.visit();
}

View File

@ -42,13 +42,14 @@ traverse.node = function (node, opts, scope, state, parentPath) {
function clearNode(node) {
node._declarations = null;
node.extendedRange = null;
node._scopeInfo = null;
node.tokens = null;
node.range = null;
node.start = null;
node.end = null;
node.loc = null;
node.raw = null;
node._scopeInfo = null;
node._paths = null;
node.tokens = null;
node.range = null;
node.start = null;
node.end = null;
node.loc = null;
node.raw = null;
if (Array.isArray(node.trailingComments)) {
clearComments(node.trailingComments);
@ -57,11 +58,18 @@ function clearNode(node) {
if (Array.isArray(node.leadingComments)) {
clearComments(node.leadingComments);
}
for (var key in node) {
var val = node[key];
if (Array.isArray(val)) {
delete val._paths;
}
}
}
var clearVisitor = {
noScope: true,
enter: clearNode
exit: clearNode
};
function clearComments(comments) {
@ -71,8 +79,8 @@ function clearComments(comments) {
}
traverse.removeProperties = function (tree) {
clearNode(tree);
traverse(tree, clearVisitor);
clearNode(tree);
return tree;
};

View File

@ -4,23 +4,34 @@ import Scope from "./scope";
import t from "../types";
export default class TraversalPath {
constructor(context, parent, container, key) {
this.shouldRemove = false;
this.shouldSkip = false;
this.shouldStop = false;
constructor(parentPath, parent, container) {
this.parentPath = parentPath;
this.container = container;
this.parent = parent;
this.data = {};
}
this.parentPath = context.parentPath;
this.context = context;
this.state = this.context.state;
this.opts = this.context.opts;
static get(parentPath, context, parent, container, key) {
var targetNode = container[key];
var paths = container._paths ||= [];
var path;
this.container = container;
this.key = key;
for (var i = 0; i < paths.length; i++) {
var pathCheck = paths[i];
if (pathCheck.node === targetNode) {
path = pathCheck;
break;
}
}
this.parent = parent;
this.state = context.state;
if (!path) {
path = new TraversalPath(parentPath, parent, container);
paths.push(path);
}
this.setScope();
path.setContext(context, key);
return path;
}
static getScope(node, parent, scope) {
@ -34,10 +45,31 @@ export default class TraversalPath {
return ourScope;
}
setData(key, val) {
return this.data[key] = val;
}
getData(key) {
return this.data[key];
}
setScope() {
this.scope = TraversalPath.getScope(this.node, this.parent, this.context.scope);
}
setContext(context, key) {
this.shouldRemove = false;
this.shouldSkip = false;
this.shouldStop = false;
this.context = context;
this.state = context.state;
this.opts = context.opts;
this.key = key;
this.setScope();
}
remove() {
this.shouldRemove = true;
this.shouldSkip = true;
@ -114,14 +146,13 @@ export default class TraversalPath {
}
}
visit() {
var opts = this.opts;
var node = this.node;
isBlacklisted() {
var blacklist = this.opts.blacklist;
return blacklist && blacklist.indexOf(this.node.type) > -1;
}
// type is blacklisted
if (opts.blacklist && opts.blacklist.indexOf(node.type) > -1) {
return false;
}
visit() {
if (this.isBlacklisted()) return false;
this.call("enter");
@ -129,7 +160,8 @@ export default class TraversalPath {
return this.shouldStop;
}
node = this.node;
var node = this.node;
var opts = this.opts;
if (Array.isArray(node)) {
// traverse over these replacement nodes we purposely don't call exitNode
@ -139,13 +171,38 @@ export default class TraversalPath {
}
} else {
traverse.node(node, opts, this.scope, this.state, this);
this.call("exit");
}
return this.shouldStop;
}
isReferencedIdentifier() {
return t.isReferencedIdentifier(this.node);
get(key) {
return TraversalPath.get(this, this.context, this.node, this.node, key);
}
isReferencedIdentifier(opts) {
return t.isReferencedIdentifier(this.node, this.parent, opts);
}
isReferenced() {
return t.isReferenced(this.node, this.parent);
}
isScope() {
return t.isScope(this.node, this.parent);
}
getBindingIdentifiers() {
return t.getBindingIdentifiers(this.node);
}
}
for (var i = 0; i < t.TYPES.length; i++) {
let type = t.TYPES[i];
let typeKey = `is${type}`;
TraversalPath.prototype[typeKey] = function (opts) {
return t[typeKey](this.node, opts);
};
}

View File

@ -6,6 +6,7 @@ import isString from "lodash/lang/isString";
import compact from "lodash/array/compact";
import esutils from "esutils";
import object from "../helpers/object";
import clone from "lodash/lang/clone";
import each from "lodash/collection/each";
import uniq from "lodash/array/uniq";
@ -48,7 +49,7 @@ each(t.VISITOR_KEYS, function (keys, type) {
each(t.ALIAS_KEYS, function (aliases, type) {
each(aliases, function (alias) {
var types = t.FLIPPED_ALIAS_KEYS[alias] = t.FLIPPED_ALIAS_KEYS[alias] || [];
var types = t.FLIPPED_ALIAS_KEYS[alias] ||= [];
types.push(type);
});
});
@ -58,6 +59,8 @@ each(t.FLIPPED_ALIAS_KEYS, function (types, type) {
registerType(type, false);
});
t.TYPES = Object.keys(t.VISITOR_KEYS).concat(Object.keys(t.FLIPPED_ALIAS_KEYS));
/**
* Returns whether `node` is of given `type`.
*
@ -413,6 +416,49 @@ t.ensureBlock = function (node, key) {
return node[key] = t.toBlock(node[key], node);
};
/**
* Description
*
* @param {Object} node
* @returns {Object}
*/
t.clone = function (node) {
var newNode = {};
for (var key in node) {
if (key[0] === "_") continue;
newNode[key] = node[key];
}
return newNode;
};
/**
* Description
*
* @param {Object} node
* @returns {Object}
*/
t.cloneDeep = function (node) {
var newNode = {};
for (var key in node) {
var val = node[key];
if (val) {
if (val.type) {
val = t.cloneDeep(val);
} else if (Array.isArray(val)) {
val = val.map(t.cloneDeep);
}
}
newNode[key] = val;
}
return newNode;
};
/**
* Build a function that when called will return whether or not the
* input `node` `MemberExpression` matches the input `match`.