make visitor validation more anal, add node type wrappers, add mixin support to visitor explosion, allow visitor entrance/exit to provide an array of callbacks to use rather than limiting it to a single callback
This commit is contained in:
@@ -23,10 +23,12 @@ export const MESSAGES = {
|
||||
missingTemplatesDirectory: "no templates directory - this is most likely the result of a broken `npm publish`. Please report to https://github.com/babel/babel/issues",
|
||||
unsupportedOutputType: "Unsupported output type $1",
|
||||
illegalMethodName: "Illegal method name $1",
|
||||
|
||||
traverseNeedsParent: "Must pass a scope and parentPath unless traversing a Program/File got a $1 node",
|
||||
traverseVerifyRootFunction: "You passed `traverse()` a function when it expected a visitor object, are you sure you didn't mean `{ enter: Function }`?",
|
||||
traverseVerifyVisitorFunction: "Hey! You passed \`traverse()\` a visitor object with the key $1 that's a straight up `Function` instead of `{ enter: Function }`. You need to normalise it with `traverse.explode(visitor)`.",
|
||||
traverseVerifyVisitorFunction: "You passed \`traverse()\` a visitor object with the key $1 that's a `Function` instead of `{ enter: Function }`. You need to normalise your visitor with `traverse.explode(visitor)`.",
|
||||
traverseVerifyVisitorProperty: "You passed `traverse()` a visitor object with the property $1 that has the invalid property $2",
|
||||
traverseVerifyNodeType: "You gave us a visitor for the node type $1 but it's not a valid type",
|
||||
|
||||
pluginIllegalKind: "Illegal kind $1 for plugin $2",
|
||||
pluginIllegalPosition: "Illegal position $1 for plugin $2",
|
||||
|
||||
@@ -10,7 +10,7 @@ import * as react from "./react";
|
||||
import * as t from "../../types";
|
||||
|
||||
export default function (exports, opts) {
|
||||
exports.check = function (node) {
|
||||
exports.shouldVisit = function (node) {
|
||||
if (t.isJSX(node)) return true;
|
||||
if (react.isCreateClass(node)) return true;
|
||||
return false;
|
||||
|
||||
@@ -5,8 +5,14 @@ import object from "../../helpers/object";
|
||||
import * as util from "../../util";
|
||||
import * as t from "../../types";
|
||||
|
||||
var remapVisitor = {
|
||||
var remapVisitor = traverse.explode({
|
||||
enter(node, parent, scope, formatter) {
|
||||
if (node._skipModulesRemap) {
|
||||
return this.skip();
|
||||
}
|
||||
},
|
||||
|
||||
Identifier(node, parent, scope, formatter) {
|
||||
var remap = formatter.internalRemap[node.name];
|
||||
|
||||
if (this.isReferencedIdentifier() && remap && node !== remap) {
|
||||
@@ -14,53 +20,50 @@ var remapVisitor = {
|
||||
return remap;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
if (t.isUpdateExpression(node)) {
|
||||
var exported = formatter.getExport(node.argument, scope);
|
||||
|
||||
if (exported) {
|
||||
this.skip();
|
||||
|
||||
// expand to long file assignment expression
|
||||
var assign = t.assignmentExpression(node.operator[0] + "=", node.argument, t.literal(1));
|
||||
|
||||
// remap this assignment expression
|
||||
var remapped = formatter.remapExportAssignment(assign, exported);
|
||||
|
||||
// we don't need to change the result
|
||||
if (t.isExpressionStatement(parent) || node.prefix) {
|
||||
return remapped;
|
||||
AssignmentExpression: {
|
||||
exit(node, parent, scope, formatter) {
|
||||
if (!node._ignoreModulesRemap) {
|
||||
var exported = formatter.getExport(node.left, scope);
|
||||
if (exported) {
|
||||
return formatter.remapExportAssignment(node, exported);
|
||||
}
|
||||
|
||||
var nodes = [];
|
||||
nodes.push(remapped);
|
||||
|
||||
var operator;
|
||||
if (node.operator === "--") {
|
||||
operator = "+";
|
||||
} else { // "++"
|
||||
operator = "-";
|
||||
}
|
||||
nodes.push(t.binaryExpression(operator, node.argument, t.literal(1)));
|
||||
|
||||
return t.sequenceExpression(nodes);
|
||||
}
|
||||
}
|
||||
|
||||
if (node._skipModulesRemap) {
|
||||
return this.skip();
|
||||
}
|
||||
},
|
||||
|
||||
exit(node, parent, scope, formatter) {
|
||||
if (t.isAssignmentExpression(node) && !node._ignoreModulesRemap) {
|
||||
var exported = formatter.getExport(node.left, scope);
|
||||
if (exported) {
|
||||
return formatter.remapExportAssignment(node, exported);
|
||||
}
|
||||
UpdateExpression(node, parent, scope, formatter) {
|
||||
var exported = formatter.getExport(node.argument, scope);
|
||||
if (!exported) return;
|
||||
|
||||
this.skip();
|
||||
|
||||
// expand to long file assignment expression
|
||||
var assign = t.assignmentExpression(node.operator[0] + "=", node.argument, t.literal(1));
|
||||
|
||||
// remap this assignment expression
|
||||
var remapped = formatter.remapExportAssignment(assign, exported);
|
||||
|
||||
// we don't need to change the result
|
||||
if (t.isExpressionStatement(parent) || node.prefix) {
|
||||
return remapped;
|
||||
}
|
||||
|
||||
var nodes = [];
|
||||
nodes.push(remapped);
|
||||
|
||||
var operator;
|
||||
if (node.operator === "--") {
|
||||
operator = "+";
|
||||
} else { // "++"
|
||||
operator = "-";
|
||||
}
|
||||
nodes.push(t.binaryExpression(operator, node.argument, t.literal(1)));
|
||||
|
||||
return t.sequenceExpression(nodes);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
var importsVisitor = {
|
||||
ImportDeclaration: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as t from "../../../types";
|
||||
|
||||
export { check } from "../internal/modules";
|
||||
export { shouldVisit } from "../internal/modules";
|
||||
|
||||
function keepBlockHoist(node, nodes) {
|
||||
if (node._blockHoist) {
|
||||
|
||||
@@ -2,7 +2,9 @@ import { _ForOfStatementArray } from "../es6/for-of";
|
||||
import * as t from "../../../types";
|
||||
|
||||
export var shouldVisit = t.isForOfStatement;
|
||||
export var optional = true;
|
||||
export var metadata = {
|
||||
optional: true
|
||||
};
|
||||
|
||||
export function ForOfStatement(node, parent, scope, file) {
|
||||
if (this.get("right").isTypeGeneric("Array")) {
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import * as t from "../types";
|
||||
|
||||
export default function (obj) {
|
||||
for (var type in obj) {
|
||||
var fns = obj[type];
|
||||
if (typeof fns === "function") {
|
||||
obj[type] = fns = { enter: fns };
|
||||
}
|
||||
|
||||
var aliases = t.FLIPPED_ALIAS_KEYS[type];
|
||||
if (aliases) {
|
||||
for (var i = 0; i < aliases.length; i++) {
|
||||
var alias = aliases[i];
|
||||
obj[alias] = obj[alias] || fns;
|
||||
}
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import TraversalContext from "./context";
|
||||
import explode from "./explode";
|
||||
import { explode, verify } from "./visitors";
|
||||
import * as messages from "../messages";
|
||||
import includes from "lodash/collection/includes";
|
||||
import * as t from "../types";
|
||||
@@ -14,7 +14,7 @@ export default function traverse(parent, opts, scope, state, parentPath) {
|
||||
}
|
||||
|
||||
if (!opts) opts = {};
|
||||
traverse.verify(opts);
|
||||
verify(opts);
|
||||
|
||||
// array of nodes
|
||||
if (Array.isArray(parent)) {
|
||||
@@ -26,42 +26,8 @@ export default function traverse(parent, opts, scope, state, parentPath) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Quickly iterate over some traversal options and validate them.
|
||||
*/
|
||||
|
||||
traverse.verify = function (opts) {
|
||||
if (opts._verified) return;
|
||||
|
||||
if (typeof opts === "function") {
|
||||
throw new Error(messages.get("traverseVerifyRootFunction"));
|
||||
}
|
||||
|
||||
if (!opts.enter) opts.enter = function () { };
|
||||
if (!opts.exit) opts.exit = function () { };
|
||||
if (!opts.shouldSkip) opts.shouldSkip = function () { return false; };
|
||||
|
||||
for (var key in opts) {
|
||||
// it's all good
|
||||
if (key === "blacklist") continue;
|
||||
|
||||
var opt = opts[key];
|
||||
|
||||
if (typeof opt === "function") {
|
||||
// it's all good, it's fine for this key to be a function
|
||||
if (key === "enter" || key === "exit" || key === "shouldSkip") continue;
|
||||
|
||||
throw new Error(messages.get("traverseVerifyVisitorFunction", key));
|
||||
} else if (typeof opt === "object") {
|
||||
for (var key2 in opt) {
|
||||
if (key2 === "enter" || key2 === "exit") continue;
|
||||
throw new Error(messages.get("traverseVerifyVisitorProperty", key, key2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
opts._verified = true;
|
||||
};
|
||||
traverse.verify = verify;
|
||||
traverse.explode = explode;
|
||||
|
||||
traverse.node = function (node, opts, scope, state, parentPath) {
|
||||
var keys = t.VISITOR_KEYS[node.type];
|
||||
@@ -113,8 +79,6 @@ traverse.removeProperties = function (tree) {
|
||||
return tree;
|
||||
};
|
||||
|
||||
traverse.explode = explode;
|
||||
|
||||
function hasBlacklistedType(node, parent, scope, state) {
|
||||
if (node.type === state.type) {
|
||||
state.has = true;
|
||||
|
||||
@@ -662,12 +662,21 @@ export default class TraversalPath {
|
||||
if (!node) return;
|
||||
|
||||
var opts = this.opts;
|
||||
var fn = opts[key] || opts;
|
||||
if (opts[node.type]) fn = opts[node.type][key] || fn;
|
||||
var fns = [opts[key]];
|
||||
|
||||
// call the function with the params (node, parent, scope, state)
|
||||
var replacement = fn.call(this, node, this.parent, this.scope, this.state);
|
||||
if (replacement) this.replaceWith(replacement, true);
|
||||
if (opts[node.type]) {
|
||||
fns = fns.concat(opts[node.type][key]);
|
||||
}
|
||||
|
||||
for (var fn of (fns: Array)) {
|
||||
if (!fn) continue;
|
||||
|
||||
// call the function with the params (node, parent, scope, state)
|
||||
var replacement = fn.call(this, node, this.parent, this.scope, this.state);
|
||||
if (replacement) this.replaceWith(replacement, true);
|
||||
|
||||
if (this.shouldStop) break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import includes from "lodash/collection/includes";
|
||||
import explode from "./explode";
|
||||
import { explode } from "./visitors";
|
||||
import traverse from "./index";
|
||||
import defaults from "lodash/object/defaults";
|
||||
import * as messages from "../messages";
|
||||
@@ -38,26 +38,32 @@ var functionVariableVisitor = {
|
||||
}
|
||||
};
|
||||
|
||||
var programReferenceVisitor = {
|
||||
enter(node, parent, scope, state) {
|
||||
if (t.isReferencedIdentifier(node, parent)) {
|
||||
var bindingInfo = scope.getBinding(node.name);
|
||||
if (bindingInfo) {
|
||||
bindingInfo.reference();
|
||||
} else {
|
||||
state.addGlobal(node);
|
||||
}
|
||||
} else if (t.isLabeledStatement(node)) {
|
||||
var programReferenceVisitor = explode({
|
||||
ReferencedIdentifier(node, parent, scope, state) {
|
||||
var bindingInfo = scope.getBinding(node.name);
|
||||
if (bindingInfo) {
|
||||
bindingInfo.reference();
|
||||
} else {
|
||||
state.addGlobal(node);
|
||||
} else if (t.isAssignmentExpression(node)) {
|
||||
scope.registerConstantViolation(this.get("left"), this.get("right"));
|
||||
} else if (t.isUpdateExpression(node)) {
|
||||
scope.registerConstantViolation(this.get("argument"), null);
|
||||
} else if (t.isUnaryExpression(node) && node.operator === "delete") {
|
||||
scope.registerConstantViolation(this.get("left"), null);
|
||||
}
|
||||
},
|
||||
|
||||
LabeledStatement(node, parent, scope, state) {
|
||||
state.addGlobal(node);
|
||||
},
|
||||
|
||||
AssignmentExpression(node, parent, scope, state) {
|
||||
scope.registerConstantViolation(this.get("left"), this.get("right"));
|
||||
},
|
||||
|
||||
UpdateExpression(node, parent, scope, state) {
|
||||
scope.registerConstantViolation(this.get("argument"), null);
|
||||
},
|
||||
|
||||
UnaryExpression(node, parent, scope, state) {
|
||||
if (node.operator === "delete") scope.registerConstantViolation(this.get("left"), null);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
var blockVariableVisitor = {
|
||||
enter(node, parent, scope, state) {
|
||||
|
||||
114
src/babel/traversal/visitors/index.js
Normal file
114
src/babel/traversal/visitors/index.js
Normal file
@@ -0,0 +1,114 @@
|
||||
import * as typeWrappers from "./wrappers";
|
||||
import * as messages from "../../messages";
|
||||
import * as t from "../../types";
|
||||
|
||||
export function explode(visitor, mergeConflicts) {
|
||||
// make sure there's no __esModule type since this is because we're using loose mode
|
||||
// and it sets __esModule to be enumerable on all modules :(
|
||||
delete visitor.__esModule;
|
||||
|
||||
for (let nodeType in visitor) {
|
||||
if (shouldIgnoreKey(nodeType)) continue;
|
||||
|
||||
var fns = visitor[nodeType];
|
||||
|
||||
if (typeof fns === "function") {
|
||||
visitor[nodeType] = fns = { enter: fns };
|
||||
}
|
||||
|
||||
var aliases = t.FLIPPED_ALIAS_KEYS[nodeType];
|
||||
if (!aliases) continue;
|
||||
|
||||
// clear it form the visitor
|
||||
delete visitor[nodeType];
|
||||
|
||||
for (var alias of (aliases: Array)) {
|
||||
var existing = visitor[alias];
|
||||
if (existing) {
|
||||
if (mergeConflicts) {
|
||||
merge(fns, existing);
|
||||
}
|
||||
} else {
|
||||
visitor[alias] = fns;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handle type wrappers
|
||||
for (let nodeType in visitor) {
|
||||
if (shouldIgnoreKey(nodeType)) continue;
|
||||
|
||||
var wrapper = typeWrappers[nodeType];
|
||||
if (!wrapper) continue;
|
||||
|
||||
// wrap all the functions
|
||||
var fns = visitor[nodeType];
|
||||
for (var type in fns) {
|
||||
fns[type] = wrapper.wrap(fns[type]);
|
||||
}
|
||||
|
||||
// clear it from the visitor
|
||||
delete visitor[nodeType];
|
||||
|
||||
// merge the visitor if necessary or just put it back in
|
||||
if (visitor[wrapper.type]) {
|
||||
merge(visitor[wrapper.type], fns);
|
||||
} else {
|
||||
visitor[wrapper.type] = fns;
|
||||
}
|
||||
}
|
||||
|
||||
return visitor;
|
||||
}
|
||||
|
||||
export function verify(visitor) {
|
||||
if (visitor._verified) return;
|
||||
|
||||
if (typeof visitor === "function") {
|
||||
throw new Error(messages.get("traverseVerifyRootFunction"));
|
||||
}
|
||||
|
||||
if (!visitor.enter) visitor.enter = function () { };
|
||||
if (!visitor.exit) visitor.exit = function () { };
|
||||
if (!visitor.shouldSkip) visitor.shouldSkip = function () { return false; };
|
||||
|
||||
for (var nodeType in visitor) {
|
||||
if (shouldIgnoreKey(nodeType)) continue;
|
||||
|
||||
if (!t.VISITOR_KEYS[nodeType]) {
|
||||
throw new Error(messages.get("traverseVerifyNodeType", nodeType));
|
||||
}
|
||||
|
||||
var visitors = visitor[nodeType];
|
||||
|
||||
if (typeof visitors === "function") {
|
||||
throw new Error(messages.get("traverseVerifyVisitorFunction", nodeType));
|
||||
} else if (typeof visitors === "object") {
|
||||
for (var visitorKey in visitors) {
|
||||
if (visitorKey === "enter" || visitorKey === "exit") continue;
|
||||
throw new Error(messages.get("traverseVerifyVisitorProperty", nodeType, visitorKey));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visitor._verified = true;
|
||||
}
|
||||
|
||||
function shouldIgnoreKey(key) {
|
||||
// internal/hidden key
|
||||
if (key[0] === "_") return true;
|
||||
|
||||
// ignore function keys
|
||||
if (key === "enter" || key === "exit" || key === "shouldSkip") return true;
|
||||
|
||||
// ignore other options
|
||||
if (key === "blacklist" || key === "noScope") return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function merge(visitor1, visitor2) {
|
||||
for (var key in visitor1) {
|
||||
visitor2[key] = (visitor2[alias] || []).concat(visitor1[key]);
|
||||
}
|
||||
}
|
||||
10
src/babel/traversal/visitors/wrappers.js
Normal file
10
src/babel/traversal/visitors/wrappers.js
Normal file
@@ -0,0 +1,10 @@
|
||||
export var ReferencedIdentifier = {
|
||||
type: "Identifier",
|
||||
wrap(fn) {
|
||||
return function () {
|
||||
if (this.isReferencedIdentifier()) {
|
||||
return fn.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user