I'm extremely stupid and didn't commit as I go. To anyone reading this
I'm extremely sorry. A lot of these changes are very broad and I plan on
releasing Babel 6.0.0 today live on stage at Ember Camp London so I'm
afraid I couldn't wait. If you're ever in London I'll buy you a beer
(or assorted beverage!) to make up for it, also I'll kiss your feet and
give you a back massage, maybe.
This commit is contained in:
Sebastian McKenzie
2015-10-29 17:51:24 +00:00
parent 3974dd762d
commit ae7d5367f1
1501 changed files with 16477 additions and 19786 deletions

View File

@@ -1,6 +1,10 @@
/* @flow */
import NodePath from "./path";
import * as t from "babel-types";
let testing = process.env.NODE_ENV === "test";
export default class TraversalContext {
constructor(scope, opts, state, parentPath) {
this.parentPath = parentPath;
@@ -9,57 +13,101 @@ export default class TraversalContext {
this.opts = opts;
}
queue = null;
parentPath: NodePath;
scope;
state;
opts;
queue: ?Array<NodePath> = null;
shouldVisit(node) {
var opts = this.opts;
/**
* This method does a simple check to determine whether or not we really need to attempt
* visit a node. This will prevent us from constructing a NodePath.
*/
shouldVisit(node): boolean {
let opts = this.opts;
if (opts.enter || opts.exit) return true;
// check if we have a visitor for this node
if (opts[node.type]) return true;
var keys = t.VISITOR_KEYS[node.type];
// check if we're going to traverse into this node
let keys: ?Array<string> = t.VISITOR_KEYS[node.type];
if (!keys || !keys.length) return false;
for (var key of (keys: Array)) {
// we need to traverse into this node so ensure that it has children to traverse into!
for (let key of keys) {
if (node[key]) return true;
}
return false;
}
create(node, obj, key, listKey) {
var path = NodePath.get({
create(node, obj, key, listKey): NodePath {
return NodePath.get({
parentPath: this.parentPath,
parent: node,
container: obj,
key: key,
listKey
});
path.unshiftContext(this);
return path;
}
maybeQueue(path) {
if (this.trap) {
throw new Error("Infinite cycle detected");
}
if (this.queue) {
this.priorityQueue.push(path);
}
}
visitMultiple(container, parent, listKey) {
// nothing to traverse!
if (container.length === 0) return false;
var visited = [];
var queue = this.queue = [];
var stop = false;
let queue = [];
// build up initial queue
for (let key = 0; key < container.length; key++) {
var self = container[key];
if (self && this.shouldVisit(self)) {
let node = container[key];
if (node && this.shouldVisit(node)) {
queue.push(this.create(parent, container, key, listKey));
}
}
// visit the queue
for (let path of (queue: Array)) {
path.resync();
return this.visitQueue(queue);
}
visitSingle(node, key): boolean {
if (this.shouldVisit(node[key])) {
return this.visitQueue([
this.create(node, node, key)
]);
} else {
return false;
}
}
visitQueue(queue: Array<NodePath>) {
// set queue
this.queue = queue;
this.priorityQueue = [];
let visited = [];
let stop = false;
// visit the queue
for (let path of queue) {
path.resync();
path.pushContext(this);
if (testing && queue.length >= 1000) {
this.trap = true;
}
// ensure we don't visit the same node twice
if (visited.indexOf(path.node) >= 0) continue;
visited.push(path.node);
@@ -67,28 +115,29 @@ export default class TraversalContext {
stop = true;
break;
}
if (this.priorityQueue.length) {
stop = this.visitQueue(this.priorityQueue);
this.priorityQueue = [];
this.queue = queue;
if (stop) break;
}
}
for (let path of (queue: Array)) {
path.shiftContext();
// clear queue
for (let path of queue) {
path.popContext();
}
// clear queue
this.queue = null;
return stop;
}
visitSingle(node, key) {
if (this.shouldVisit(node[key])) {
var path = this.create(node, node, key);
path.visit();
path.shiftContext();
}
}
visit(node, key) {
var nodes = node[key];
if (!nodes) return;
let nodes = node[key];
if (!nodes) return false;
if (Array.isArray(nodes)) {
return this.visitMultiple(nodes, node, key);

View File

@@ -1,6 +1,4 @@
/**
* [Please add a description.]
*/
/* @flow */
export default class Hub {
constructor(file, options) {

View File

@@ -1,10 +1,23 @@
/* @flow */
import TraversalContext from "./context";
import * as visitors from "./visitors";
import * as messages from "babel-messages";
import includes from "lodash/collection/includes";
import * as t from "babel-types";
export default function traverse(parent: Object, opts?: Object, scope?: Object, state: Object, parentPath: Object) {
export { default as NodePath } from "./path";
export { default as Scope } from "./scope";
export { default as Hub } from "./hub";
export { visitors };
export default function traverse(
parent: Object | Array<Object>,
opts?: Object,
scope?: Object,
state: Object,
parentPath: Object,
) {
if (!parent) return;
if (!opts) opts = {};
@@ -16,14 +29,7 @@ export default function traverse(parent: Object, opts?: Object, scope?: Object,
visitors.explode(opts);
// array of nodes
if (Array.isArray(parent)) {
for (var i = 0; i < parent.length; i++) {
traverse.node(parent[i], opts, scope, state, parentPath);
}
} else {
traverse.node(parent, opts, scope, state, parentPath);
}
traverse.node(parent, opts, scope, state, parentPath);
}
traverse.visitors = visitors;
@@ -35,22 +41,32 @@ traverse.Scope = require("./scope");
traverse.Hub = require("./hub");
traverse.cheap = function (node, enter) {
var keys = t.VISITOR_KEYS[node.type];
if (!node) return;
let keys = t.VISITOR_KEYS[node.type];
if (!keys) return;
enter(node);
for (var key of keys) {
traverse.cheap(node[key], enter);
for (let key of keys) {
let subNode = node[key];
if (Array.isArray(subNode)) {
for (let node of subNode) {
traverse.cheap(node, enter);
}
} else {
traverse.cheap(subNode, enter);
}
}
};
traverse.node = function (node: Object, opts: Object, scope: Object, state: Object, parentPath: Object, skipKeys?) {
var keys: Array = t.VISITOR_KEYS[node.type];
let keys: Array = t.VISITOR_KEYS[node.type];
if (!keys) return;
var context = new TraversalContext(scope, opts, state, parentPath);
for (var key of keys) {
let context = new TraversalContext(scope, opts, state, parentPath);
for (let key of keys) {
if (skipKeys && skipKeys[key]) continue;
if (context.visit(node, key)) return;
}
@@ -64,7 +80,7 @@ const CLEAR_KEYS: Array = t.COMMENT_KEYS.concat([
]);
traverse.clearNode = function (node) {
for (var key of CLEAR_KEYS) {
for (let key of CLEAR_KEYS) {
if (node[key] != null) node[key] = undefined;
}
};
@@ -88,7 +104,7 @@ traverse.hasType = function (tree: Object, scope: Object, type: Object, blacklis
// the type we're looking for is the same as the passed node
if (tree.type === type) return true;
var state = {
let state = {
has: false,
type: type
};

View File

@@ -1,3 +1,5 @@
/* @flow */
// This file contains that retrieve or validate anything related to the current paths ancestry.
import * as t from "babel-types";
@@ -9,13 +11,25 @@ import NodePath from "./index";
*/
export function findParent(callback) {
var path = this;
let path = this;
while (path = path.parentPath) {
if (callback(path)) return path;
}
return null;
}
/**
* Description
*/
export function find(callback) {
let path = this;
do {
if (callback(path)) return path;
} while (path = path.parentPath);
return null;
}
/**
* Get the parent function of the current path.
*/
@@ -29,7 +43,7 @@ export function getFunctionParent() {
*/
export function getStatementParent() {
var path = this;
let path = this;
do {
if (Array.isArray(path.container)) {
return path;
@@ -47,11 +61,11 @@ export function getStatementParent() {
export function getEarliestCommonAncestorFrom(paths: Array<NodePath>): NodePath {
return this.getDeepestCommonAncestorFrom(paths, function (deepest, i, ancestries) {
var earliest;
var keys = t.VISITOR_KEYS[deepest.type];
let earliest;
let keys = t.VISITOR_KEYS[deepest.type];
for (var ancestry of (ancestries: Array)) {
var path = ancestry[i + 1];
for (let ancestry of (ancestries: Array)) {
let path = ancestry[i + 1];
// first path
if (!earliest) {
@@ -69,8 +83,8 @@ export function getEarliestCommonAncestorFrom(paths: Array<NodePath>): NodePath
}
// handle keys
var earliestKeyIndex = keys.indexOf(earliest.parentKey);
var currentKeyIndex = keys.indexOf(path.parentKey);
let earliestKeyIndex = keys.indexOf(earliest.parentKey);
let currentKeyIndex = keys.indexOf(path.parentKey);
if (earliestKeyIndex > currentKeyIndex) {
// key appears before so it's earlier
earliest = path;
@@ -97,14 +111,14 @@ export function getDeepestCommonAncestorFrom(paths: Array<NodePath>, filter?: Fu
}
// minimum depth of the tree so we know the highest node
var minDepth = Infinity;
let minDepth = Infinity;
// last common ancestor
var lastCommonIndex, lastCommon;
let lastCommonIndex, lastCommon;
// get the ancestors of the path, breaking when the parent exceeds ourselves
var ancestries = paths.map((path) => {
var ancestry = [];
let ancestries = paths.map((path) => {
let ancestry = [];
do {
ancestry.unshift(path);
@@ -119,13 +133,13 @@ export function getDeepestCommonAncestorFrom(paths: Array<NodePath>, filter?: Fu
});
// get the first ancestry so we have a seed to assess all other ancestries with
var first = ancestries[0];
let first = ancestries[0];
// check ancestor equality
depthLoop: for (var i = 0; i < minDepth; i++) {
var shouldMatch = first[i];
depthLoop: for (let i = 0; i < minDepth; i++) {
let shouldMatch = first[i];
for (var ancestry of (ancestries: Array)) {
for (let ancestry of (ancestries: Array)) {
if (ancestry[i] !== shouldMatch) {
// we've hit a snag
break depthLoop;
@@ -155,8 +169,8 @@ export function getDeepestCommonAncestorFrom(paths: Array<NodePath>, filter?: Fu
*/
export function getAncestry() {
var path = this;
var paths = [];
let path = this;
let paths = [];
do {
paths.push(path);
} while(path = path.parentPath);
@@ -164,9 +178,9 @@ export function getAncestry() {
}
export function inType() {
var path = this;
let path = this;
while (path) {
for (var type of (arguments: Array)) {
for (let type of (arguments: Array)) {
if (path.node.type === type) return true;
}
path = path.parentPath;
@@ -180,10 +194,10 @@ export function inType() {
*/
export function inShadow(key?) {
var path = this;
let path = this;
do {
if (path.isFunction()) {
var shadow = path.node.shadow;
let shadow = path.node.shadow;
if (shadow) {
// this is because sometimes we may have a `shadow` value of:
//

View File

@@ -1,3 +1,5 @@
/* @flow */
// This file contains methods responsible for dealing with comments.
/**
@@ -5,15 +7,15 @@
*/
export function shareCommentsWithSiblings() {
var node = this.node;
let node = this.node;
if (!node) return;
var trailing = node.trailingComments;
var leading = node.leadingComments;
let trailing = node.trailingComments;
let leading = node.leadingComments;
if (!trailing && !leading) return;
var prev = this.getSibling(this.key - 1);
var next = this.getSibling(this.key + 1);
let prev = this.getSibling(this.key - 1);
let next = this.getSibling(this.key + 1);
if (!prev.node) prev = next;
if (!next.node) next = prev;
@@ -36,10 +38,10 @@ export function addComment(type, content, line?) {
export function addComments(type: string, comments: Array) {
if (!comments) return;
var node = this.node;
let node = this.node;
if (!node) return;
var key = `${type}Comments`;
let key = `${type}Comments`;
if (node[key]) {
node[key] = node[key].concat(comments);

View File

@@ -0,0 +1 @@
export const PATH_CACHE_KEY = "_paths"; //Symbol();

View File

@@ -1,71 +1,69 @@
/* @flow */
// This file contains methods responsible for maintaining a TraversalContext.
import traverse from "../index";
export function call(key) {
var node = this.node;
if (!node) return;
export function call(key): boolean {
let opts = this.opts;
var opts = this.opts;
for (var fns of [opts[key], opts[node.type] && opts[node.type][key]]){
if (!fns) continue;
for (var fn of (fns: Array)) {
if (!fn) continue;
let node = this.node;
if (!node) return;
var previousType = this.type;
// call the function with the params (node, parent, scope, state)
var replacement = fn.call(this, this, this.state);
if (replacement) {
throw new Error("Unexpected return value from visitor method");
}
if (this.shouldStop || this.shouldSkip || this.removed) return;
if (previousType !== this.type) {
this.queueNode(this);
return;
}
}
if (this.node) {
if (this._call(opts[key])) return true;
}
if (this.node) {
return this._call(opts[this.node.type] && opts[this.node.type][key]);
}
return false;
}
export function _call(fns?: Array<Function>): boolean {
if (!fns) return false;
for (let fn of fns) {
if (!fn) continue;
let node = this.node;
if (!node) return true;
let ret = fn.call(this.state, this, this.state);
if (ret) throw new Error("Unexpected return value from visitor method " + fn);
// node has been replaced, it will have been requeued
if (this.node !== node) return true;
if (this.shouldStop || this.shouldSkip || this.removed) return true;
}
return false;
}
export function isBlacklisted(): boolean {
var blacklist = this.opts.blacklist;
let blacklist = this.opts.blacklist;
return blacklist && blacklist.indexOf(this.node.type) > -1;
}
export function visit(): boolean {
if (this.isBlacklisted()) return false;
if (this.opts.shouldSkip && this.opts.shouldSkip(this)) return false;
if (!this.node) {
return false;
}
this.call("enter");
if (this.isBlacklisted()) {
return false;
}
if (this.shouldSkip) {
if (this.opts.shouldSkip && this.opts.shouldSkip(this)) {
return false;
}
if (this.call("enter") || this.shouldSkip) {
return this.shouldStop;
}
var node = this.node;
var opts = this.opts;
traverse.node(this.node, this.opts, this.scope, this.state, this, this.skipKeys);
if (node) {
if (Array.isArray(node)) {
// traverse over these replacement nodes we purposely don't call exitNode
// as the original node has been destroyed
for (var i = 0; i < node.length; i++) {
traverse.node(node[i], opts, this.scope, this.state, this, this.skipKeys);
}
} else {
traverse.node(node, opts, this.scope, this.state, this, this.skipKeys);
this.call("exit");
}
}
this.call("exit");
return this.shouldStop;
}
@@ -86,8 +84,19 @@ export function stop() {
export function setScope() {
if (this.opts && this.opts.noScope) return;
var target = this.context || this.parentPath;
this.scope = this.getScope(target && target.scope);
let target = this.context && this.context.scope;
if (!target) {
let path = this.parentPath;
while (path && !target) {
if (path.opts && path.opts.noScope) return;
target = path.scope;
path = path.parentPath;
}
}
this.scope = this.getScope(target);
if (this.scope) this.scope.init();
}
@@ -138,37 +147,31 @@ export function _resyncKey() {
// not done through our path APIs
if (Array.isArray(this.container)) {
for (var i = 0; i < this.container.length; i++) {
for (let i = 0; i < this.container.length; i++) {
if (this.container[i] === this.node) {
return this.setKey(i);
}
}
} else {
for (var key in this.container) {
for (let key in this.container) {
if (this.container[key] === this.node) {
return this.setKey(key);
}
}
}
// ¯\_(ツ)_/¯ who knows where it's gone lol
this.key = null;
}
export function _resyncList() {
var listKey = this.listKey;
var parentPath = this.parentPath;
if (!listKey || !parentPath || !parentPath.node) return;
if (!this.parent || !this.inList) return;
var newContainer = parentPath.node[listKey];
let newContainer = this.parent[this.listKey];
if (this.container === newContainer) return;
// container is out of sync. this is likely the result of it being reassigned
if (newContainer) {
this.container = newContainer;
} else {
this.container = null;
}
this.container = newContainer || null;
}
export function _resyncRemoved() {
@@ -177,13 +180,13 @@ export function _resyncRemoved() {
}
}
export function shiftContext() {
this.contexts.shift();
this.setContext(this.contexts[0]);
export function popContext() {
this.contexts.pop();
this.setContext(this.contexts[this.contexts.length - 1]);
}
export function unshiftContext(context) {
this.contexts.unshift(context);
export function pushContext(context) {
this.contexts.push(context);
this.setContext(context);
}
@@ -203,10 +206,10 @@ export function setKey(key) {
this.type = this.node && this.node.type;
}
export function queueNode(path) {
for (var context of (this.contexts: Array)) {
if (context.queue) {
context.queue.push(path);
}
export function requeue(path = this) {
if (path.removed) return;
for (let context of this.contexts) {
context.maybeQueue(path);
}
}

View File

@@ -1,21 +1,23 @@
/* @flow */
// This file contains methods that convert the path node into another node or some other type of data.
import * as t from "babel-types";
export function toComputedKey(): Object {
var node = this.node;
let node = this.node;
var key;
let key;
if (this.isMemberExpression()) {
key = node.property;
} else if (this.isProperty()) {
} else if (this.isProperty() || this.isMethod()) {
key = node.key;
} else {
throw new ReferenceError("todo");
}
if (!node.computed) {
if (t.isIdentifier(key)) key = t.literal(key.name);
if (t.isIdentifier(key)) key = t.stringLiteral(key.name);
}
return key;
@@ -24,3 +26,15 @@ export function toComputedKey(): Object {
export function ensureBlock() {
return t.ensureBlock(this.node);
}
export function arrowFunctionToShadowed() {
// todo: maybe error
if (!this.isArrowFunctionExpression()) return;
this.ensureBlock();
let { node } = this;
node.expression = false;
node.type = "FunctionExpression";
node.shadow = node.shadow || true;
}

View File

@@ -1,3 +1,7 @@
/* @flow */
import type NodePath from "./index";
// This file contains Babels metainterpreter that can evaluate static code.
/* eslint eqeqeq: 0 */
@@ -24,7 +28,7 @@ const INVALID_METHODS = ["random"];
*/
export function evaluateTruthy(): boolean {
var res = this.evaluate();
let res = this.evaluate();
if (res.confident) return !!res.value;
}
@@ -44,40 +48,48 @@ export function evaluateTruthy(): boolean {
*/
export function evaluate(): { confident: boolean; value: any } {
var confident = true;
let confident = true;
let deoptPath: ?NodePath;
var value = evaluate(this);
function deopt(path) {
if (!confident) return;
deoptPath = path;
confident = false;
}
let value = evaluate(this);
if (!confident) value = undefined;
return {
confident: confident,
deopt: deoptPath,
value: value
};
function evaluate(path) {
if (!confident) return;
var node = path.node;
let { node } = path;
if (path.isSequenceExpression()) {
let exprs = path.get("expressions");
return evaluate(exprs[exprs.length - 1]);
}
if (path.isLiteral()) {
if (node.regex) {
// we have a regex and we can't represent it natively
} else {
return node.value;
}
if (path.isStringLiteral() || path.isNumberLiteral() || path.isBooleanLiteral()) {
return node.value;
}
if (path.isNullLiteral()) {
return null;
}
if (path.isTemplateLiteral()) {
var str = "";
let str = "";
var i = 0;
let i = 0;
let exprs = path.get("expressions");
for (let elem of (node.quasis: Array)) {
for (let elem of (node.quasis: Array<Object>)) {
// not confident, evaluated an expression we don't like
if (!confident) break;
@@ -85,7 +97,7 @@ export function evaluate(): { confident: boolean; value: any } {
str += elem.value.cooked;
// add on interpolated expression if it's present
var expr = exprs[i++];
let expr = exprs[i++];
if (expr) str += String(evaluate(expr));
}
@@ -100,20 +112,10 @@ export function evaluate(): { confident: boolean; value: any } {
}
}
if (path.isTypeCastExpression()) {
if (path.isExpressionWrapper()) { // TypeCastExpression, ExpressionStatement etc
return evaluate(path.get("expression"));
}
if (path.isIdentifier() && !path.scope.hasBinding(node.name, true)) {
if (node.name === "undefined") {
return undefined;
} else if (node.name === "Infinity") {
return Infinity;
} else if (node.name === "NaN") {
return NaN;
}
}
// "foo".length
if (path.isMemberExpression() && !path.parentPath.isCallExpression({ callee: node })) {
let property = path.get("property");
@@ -129,13 +131,21 @@ export function evaluate(): { confident: boolean; value: any } {
}
if (path.isReferencedIdentifier()) {
var binding = path.scope.getBinding(node.name);
let binding = path.scope.getBinding(node.name);
if (binding && binding.hasValue) {
return binding.value;
} else {
var resolved = path.resolve();
if (node.name === "undefined") {
return undefined;
} else if (node.name === "Infinity") {
return Infinity;
} else if (node.name === "NaN") {
return NaN;
}
let resolved = path.resolve();
if (resolved === path) {
return confident = false;
return deopt(path);
} else {
return evaluate(resolved);
}
@@ -143,27 +153,68 @@ export function evaluate(): { confident: boolean; value: any } {
}
if (path.isUnaryExpression({ prefix: true })) {
var arg = evaluate(path.get("argument"));
if (node.operator === "void") {
// we don't need to evaluate the argument to know what this will return
return undefined;
}
let argument = path.get("argument");
if (node.operator === "typeof" && (argument.isFunction() || argument.isClass())) {
return "function";
}
let arg = evaluate(argument);
switch (node.operator) {
case "void": return undefined;
case "!": return !arg;
case "+": return +arg;
case "-": return -arg;
case "~": return ~arg;
case "typeof": return typeof arg;
}
}
if (path.isArrayExpression() || path.isObjectExpression()) {
// we could evaluate these but it's probably impractical and not very useful
if (path.isArrayExpression()) {
let arr = [];
let elems: Array<NodePath> = path.get("elements");
for (let elem of elems) {
elem = elem.evaluate();
if (elem.confident) {
arr.push(elem.value);
} else {
return deopt(elem);
}
}
return arr;
}
if (path.isObjectExpression()) {
// todo
}
if (path.isLogicalExpression()) {
// If we are confident that one side of an && is false, or one side of
// an || is true, we can be confident about the entire expression
let wasConfident = confident;
let left = evaluate(path.get("left"));
let leftConfident = confident;
confident = wasConfident;
let right = evaluate(path.get("right"));
let rightConfident = confident;
let uncertain = leftConfident !== rightConfident;
confident = leftConfident && rightConfident;
switch (node.operator) {
case "||": return left || right;
case "&&": return left && right;
case "||":
if ((left || right) && uncertain) {
confident = true;
}
return left || right;
case "&&":
if ((!left && leftConfident) || (!right && rightConfident)) {
confident = true;
}
return left && right;
}
}
@@ -186,13 +237,19 @@ export function evaluate(): { confident: boolean; value: any } {
case "!=": return left != right;
case "===": return left === right;
case "!==": return left !== right;
case "|": return left | right;
case "&": return left & right;
case "^": return left ^ right;
case "<<": return left << right;
case ">>": return left >> right;
case ">>>": return left >>> right;
}
}
if (path.isCallExpression()) {
var callee = path.get("callee");
var context;
var func;
let callee = path.get("callee");
let context;
let func;
// Number(1);
if (callee.isIdentifier() && !path.scope.getBinding(callee.node.name, true) && VALID_CALLEES.indexOf(callee.node.name) >= 0) {
@@ -201,7 +258,7 @@ export function evaluate(): { confident: boolean; value: any } {
if (callee.isMemberExpression()) {
let object = callee.get("object");
var property = callee.get("property");
let property = callee.get("property");
// Math.min(1, 2)
if (object.isIdentifier() && property.isIdentifier() && VALID_CALLEES.indexOf(object.node.name) >= 0 && INVALID_METHODS.indexOf(property.node.name) < 0) {
@@ -220,13 +277,13 @@ export function evaluate(): { confident: boolean; value: any } {
}
if (func) {
var args = path.get("arguments").map(evaluate);
let args = path.get("arguments").map(evaluate);
if (!confident) return;
return func.apply(context, args);
}
}
confident = false;
deopt(path);
}
}

View File

@@ -1,3 +1,5 @@
/* @flow */
// This file contains methods responsible for dealing with/retrieving children or siblings.
import type TraversalContext from "../index";
@@ -5,7 +7,7 @@ import NodePath from "./index";
import * as t from "babel-types";
export function getStatementParent(): ?NodePath {
var path = this;
let path = this;
do {
if (!path.parentPath || (Array.isArray(path.container) && path.isStatement())) {
@@ -31,9 +33,9 @@ export function getOpposite() {
}
export function getCompletionRecords(): Array {
var paths = [];
let paths = [];
var add = function (path) {
let add = function (path) {
if (path) paths = paths.concat(path.getCompletionRecords());
};
@@ -69,7 +71,7 @@ export function getSibling(key) {
export function get(key: string, context?: boolean | TraversalContext): NodePath {
if (context === true) context = this.context;
var parts = key.split(".");
let parts = key.split(".");
if (parts.length === 1) { // "foo"
return this._getKey(key, context);
} else { // "foo.bar"
@@ -78,8 +80,8 @@ export function get(key: string, context?: boolean | TraversalContext): NodePath
}
export function _getKey(key, context?) {
var node = this.node;
var container = node[key];
let node = this.node;
let container = node[key];
if (Array.isArray(container)) {
// requested a container so give them all the paths
@@ -103,8 +105,8 @@ export function _getKey(key, context?) {
}
export function _getPattern(parts, context) {
var path = this;
for (var part of (parts: Array)) {
let path = this;
for (let part of (parts: Array)) {
if (part === ".") {
path = path.parentPath;
} else {

View File

@@ -1,4 +1,9 @@
/* @flow */
import type Hub from "../hub";
import type TraversalContext from "../context";
import * as virtualTypes from "./lib/virtual-types";
import { PATH_CACHE_KEY } from "./constants";
import invariant from "invariant";
import traverse from "../index";
import assign from "lodash/object/assign";
@@ -6,45 +11,66 @@ import Scope from "../scope";
import * as t from "babel-types";
export default class NodePath {
constructor(hub, parent) {
constructor(hub: Hub, parent: Object) {
this.parent = parent;
this.hub = hub;
this.contexts = [];
this.parent = parent;
this.data = {};
this.hub = hub;
this.shouldSkip = false;
this.shouldStop = false;
this.removed = false;
this.state = null;
this.opts = null;
this.skipKeys = null;
this.parentPath = null;
this.context = null;
this.container = null;
this.listKey = null;
this.inList = false;
this.parentKey = null;
this.key = null;
this.node = null;
this.scope = null;
this.type = null;
this.typeAnnotation = null;
this.data = {};
this.shouldSkip = false;
this.shouldStop = false;
this.removed = false;
this.state = null;
this.opts = null;
this.skipKeys = null;
this.parentPath = null;
this.context = null;
this.container = null;
this.listKey = null;
this.inList = false;
this.parentKey = null;
this.key = null;
this.node = null;
this.scope = null;
this.type = null;
this.typeAnnotation = null;
}
static get({ hub, parentPath, parent, container, listKey, key }) {
parent: Object;
hub: Hub;
contexts: Array<TraversalContext>;
data: Object;
shouldSkip: boolean;
shouldStop: boolean;
removed: boolean;
state: any;
opts: ?Object;
skipKeys: ?Object;
parentPath: ?NodePath;
context: TraversalContext;
container: ?Object | Array<Object>;
listKey: ?string;
inList: boolean;
parentKey: ?string;
key: ?string;
node: ?Object;
scope: Scope;
type: ?string;
typeAnnotation: ?Object;
static get({ hub, parentPath, parent, container, listKey, key }): NodePath {
if (!hub && parentPath) {
hub = parentPath.hub;
}
invariant(parent, "To get a node path the parent needs to exist");
var targetNode = container[key];
var paths = parent._paths = parent._paths || [];
var path;
let targetNode = container[key];
for (var i = 0; i < paths.length; i++) {
var pathCheck = paths[i];
let paths = parent[PATH_CACHE_KEY] = parent[PATH_CACHE_KEY] || [];
let path;
for (let i = 0; i < paths.length; i++) {
let pathCheck = paths[i];
if (pathCheck.node === targetNode) {
path = pathCheck;
break;
@@ -56,13 +82,17 @@ export default class NodePath {
paths.push(path);
}
if (!(path instanceof NodePath)) {
throw new Error("We found a path that isn't a NodePath instance");
}
path.setup(parentPath, container, listKey, key);
return path;
}
getScope(scope: Scope) {
var ourScope = scope;
let ourScope = scope;
// we're entering a new scope so let's construct it!
if (this.isScope()) {
@@ -72,25 +102,25 @@ export default class NodePath {
return ourScope;
}
setData(key, val) {
setData(key: string, val: any): any {
return this.data[key] = val;
}
getData(key, def) {
var val = this.data[key];
getData(key: string, def?: any): any {
let val = this.data[key];
if (!val && def) val = this.data[key] = def;
return val;
}
errorWithNode(msg, Error = SyntaxError) {
return this.hub.file.errorWithNode(this.node, msg, Error);
buildCodeFrameError(msg: string, Error: typeof Error = SyntaxError): Error {
return this.hub.file.buildCodeFrameError(this.node, msg, Error);
}
traverse(visitor, state) {
traverse(visitor: Object, state?: any) {
traverse(this.node, visitor, this.scope, state, this);
}
mark(type, message) {
mark(type: string, message: string) {
this.hub.file.metadata.marked.push({
type,
message,
@@ -98,14 +128,21 @@ export default class NodePath {
});
}
/**
* Description
*/
set(key, node) {
t.validate(key, this.node, node);
set(key: string, node: Object) {
t.validate(this.node, key, node);
this.node[key] = node;
}
dump() {
let parts = [];
let path = this;
do {
let key = path.key;
if (path.inList) key = `${path.listKey}[${key}]`;
parts.unshift(key);
} while(path = path.parentPath);
console.log(parts.join("."));
}
}
assign(NodePath.prototype, require("./ancestry"));
@@ -120,18 +157,26 @@ assign(NodePath.prototype, require("./modification"));
assign(NodePath.prototype, require("./family"));
assign(NodePath.prototype, require("./comments"));
for (let type of (t.TYPES: Array)) {
for (let type of (t.TYPES: Array<string>)) {
let typeKey = `is${type}`;
NodePath.prototype[typeKey] = function (opts) {
return t[typeKey](this.node, opts);
};
NodePath.prototype[`assert${type}`] = function (opts) {
if (!this[typeKey](opts)) {
throw new TypeError(`Expected node path of type ${type}`);
}
};
}
for (let type in virtualTypes) {
if (type[0] === "_") continue;
if (t.TYPES.indexOf(type) < 0) t.TYPES.push(type);
let virtualType = virtualTypes[type];
NodePath.prototype[`is${type}`] = function (opts) {
return virtualTypes[type].checkPath(this, opts);
return virtualType.checkPath(this, opts);
};
}

View File

@@ -1,3 +1,5 @@
/* @flow */
import type NodePath from "./index";
import * as inferers from "./inferers";
import * as t from "babel-types";
@@ -6,10 +8,10 @@ import * as t from "babel-types";
* Infer the type of the current `NodePath`.
*/
export function getTypeAnnotation() {
export function getTypeAnnotation(): Object {
if (this.typeAnnotation) return this.typeAnnotation;
var type = this._getTypeAnnotation() || t.anyTypeAnnotation();
let type = this._getTypeAnnotation() || t.anyTypeAnnotation();
if (t.isTypeAnnotation(type)) type = type.typeAnnotation;
return this.typeAnnotation = type;
}
@@ -19,20 +21,20 @@ export function getTypeAnnotation() {
*/
export function _getTypeAnnotation(): ?Object {
var node = this.node;
let node = this.node;
if (!node) {
// handle initializerless variables, add in checks for loop initializers too
if (this.key === "init" && this.parentPath.isVariableDeclarator()) {
var declar = this.parentPath.parentPath;
var declarParent = declar.parentPath;
let declar = this.parentPath.parentPath;
let declarParent = declar.parentPath;
// for (var NODE in bar) {}
// for (let NODE in bar) {}
if (declar.key === "left" && declarParent.isForInStatement()) {
return t.stringTypeAnnotation();
}
// for (var NODE of bar) {}
// for (let NODE of bar) {}
if (declar.key === "left" && declarParent.isForOfStatement()) {
return t.anyTypeAnnotation();
}
@@ -47,7 +49,7 @@ export function _getTypeAnnotation(): ?Object {
return node.typeAnnotation;
}
var inferer = inferers[node.type];
let inferer = inferers[node.type];
if (inferer) {
return inferer.call(this, node);
}
@@ -58,7 +60,7 @@ export function _getTypeAnnotation(): ?Object {
}
}
export function isBaseType(baseName: string, soft?): boolean {
export function isBaseType(baseName: string, soft?: boolean): boolean {
return _isBaseType(baseName, this.getTypeAnnotation(), soft);
}
@@ -85,11 +87,11 @@ function _isBaseType(baseName: string, type?, soft?): boolean {
}
export function couldBeBaseType(name: string): boolean {
var type = this.getTypeAnnotation();
let type = this.getTypeAnnotation();
if (t.isAnyTypeAnnotation(type)) return true;
if (t.isUnionTypeAnnotation(type)) {
for (var type2 of (type.types: Array)) {
for (let type2 of (type.types: Array<Object>)) {
if (t.isAnyTypeAnnotation(type2) || _isBaseType(name, type2, true)) {
return true;
}
@@ -101,7 +103,7 @@ export function couldBeBaseType(name: string): boolean {
}
export function baseTypeStrictlyMatches(right: NodePath) {
var left = this.getTypeAnnotation();
let left = this.getTypeAnnotation();
right = right.getTypeAnnotation();
if (!t.isAnyTypeAnnotation(left) && t.isFlowBaseAnnotation(left)) {
@@ -110,6 +112,6 @@ export function baseTypeStrictlyMatches(right: NodePath) {
}
export function isGenericType(genericName: string): boolean {
var type = this.getTypeAnnotation();
let type = this.getTypeAnnotation();
return t.isGenericTypeAnnotation(type) && t.isIdentifier(type.id, { name: genericName });
}

View File

@@ -1,11 +1,14 @@
/* @flow */
import type NodePath from "../index";
import * as t from "babel-types";
export default function (node) {
export default function (node: Object) {
if (!this.isReferenced()) return;
// check if a binding exists of this value and if so then return a union type of all
// possible types that the binding could be
var binding = this.scope.getBinding(node.name);
let binding = this.scope.getBinding(node.name);
if (binding) {
if (binding.identifier.typeAnnotation) {
return binding.identifier.typeAnnotation;
@@ -25,17 +28,17 @@ export default function (node) {
}
function getTypeAnnotationBindingConstantViolations(path, name) {
var binding = path.scope.getBinding(name);
let binding = path.scope.getBinding(name);
var types = [];
let types = [];
path.typeAnnotation = t.unionTypeAnnotation(types);
var functionConstantViolations = [];
var constantViolations = getConstantViolationsBefore(binding, path, functionConstantViolations);
let functionConstantViolations = [];
let constantViolations = getConstantViolationsBefore(binding, path, functionConstantViolations);
var testType = getConditionalAnnotation(path, name);
let testType = getConditionalAnnotation(path, name);
if (testType) {
var testConstantViolations = getConstantViolationsBefore(binding, testType.ifStatement);
let testConstantViolations = getConstantViolationsBefore(binding, testType.ifStatement);
// remove constant violations observed before the IfStatement
constantViolations = constantViolations.filter((path) => testConstantViolations.indexOf(path) < 0);
@@ -47,11 +50,11 @@ function getTypeAnnotationBindingConstantViolations(path, name) {
if (constantViolations.length) {
// pick one constant from each scope which will represent the last possible
// control flow path that it could've taken/been
var rawConstantViolations = constantViolations.reverse();
var visitedScopes = [];
let rawConstantViolations = constantViolations.reverse();
let visitedScopes = [];
constantViolations = [];
for (let violation of (rawConstantViolations: Array)) {
var violationScope = violation.scope;
for (let violation of (rawConstantViolations: Array<NodePath>)) {
let violationScope = violation.scope;
if (visitedScopes.indexOf(violationScope) >= 0) continue;
visitedScopes.push(violationScope);
@@ -67,7 +70,7 @@ function getTypeAnnotationBindingConstantViolations(path, name) {
constantViolations = constantViolations.concat(functionConstantViolations);
// push on inferred types of violated paths
for (let violation of (constantViolations: Array)) {
for (let violation of (constantViolations: Array<NodePath>)) {
types.push(violation.getTypeAnnotation());
}
}
@@ -78,23 +81,23 @@ function getTypeAnnotationBindingConstantViolations(path, name) {
}
function getConstantViolationsBefore(binding, path, functions) {
var violations = binding.constantViolations.slice();
let violations = binding.constantViolations.slice();
violations.unshift(binding.path);
return violations.filter((violation) => {
violation = violation.resolve();
var status = violation._guessExecutionStatusRelativeTo(path);
let status = violation._guessExecutionStatusRelativeTo(path);
if (functions && status === "function") functions.push(violation);
return status === "before";
});
}
function inferAnnotationFromBinaryExpression(name, path) {
var operator = path.node.operator;
let operator = path.node.operator;
var right = path.get("right").resolve();
var left = path.get("left").resolve();
let right = path.get("right").resolve();
let left = path.get("left").resolve();
var target;
let target;
if (left.isIdentifier({ name })) {
target = right;
} else if (right.isIdentifier({ name })) {
@@ -113,8 +116,8 @@ function inferAnnotationFromBinaryExpression(name, path) {
}
//
var typeofPath;
var typePath;
let typeofPath;
let typePath;
if (left.isUnaryExpression({ operator: "typeof" })) {
typeofPath = left;
typePath = right;
@@ -129,7 +132,7 @@ function inferAnnotationFromBinaryExpression(name, path) {
if (!typePath.isLiteral()) return;
// and that it's a string so we can infer it
var typeValue = typePath.node.value;
let typeValue = typePath.node.value;
if (typeof typeValue !== "string") return;
// and that the argument of the typeof path references us!
@@ -140,7 +143,7 @@ function inferAnnotationFromBinaryExpression(name, path) {
}
function getParentConditionalPath(path) {
var parentPath;
let parentPath;
while (parentPath = path.parentPath) {
if (parentPath.isIfStatement() || parentPath.isConditionalExpression()) {
if (path.key === "test") {
@@ -155,12 +158,12 @@ function getParentConditionalPath(path) {
}
function getConditionalAnnotation(path, name) {
var ifStatement = getParentConditionalPath(path);
let ifStatement = getParentConditionalPath(path);
if (!ifStatement) return;
var test = ifStatement.get("test");
var paths = [test];
var types = [];
let test = ifStatement.get("test");
let paths = [test];
let types = [];
do {
let path = paths.shift().resolve();
@@ -171,7 +174,7 @@ function getConditionalAnnotation(path, name) {
}
if (path.isBinaryExpression()) {
var type = inferAnnotationFromBinaryExpression(name, path);
let type = inferAnnotationFromBinaryExpression(name, path);
if (type) types.push(type);
}
} while(paths.length);

View File

@@ -1,9 +1,11 @@
/* @flow */
import * as t from "babel-types";
export { default as Identifier } from "./inferer-reference";
export function VariableDeclarator() {
var id = this.get("id");
let id = this.get("id");
if (id.isIdentifier()) {
return this.get("init").getTypeAnnotation();
@@ -51,8 +53,8 @@ export function BinaryExpression(node) {
} else if (t.BOOLEAN_BINARY_OPERATORS.indexOf(operator) >= 0) {
return t.booleanTypeAnnotation();
} else if (operator === "+") {
var right = this.get("right");
var left = this.get("left");
let right = this.get("right");
let left = this.get("left");
if (left.isBaseType("number") && right.isBaseType("number")) {
// both numbers so this will be a number
@@ -99,13 +101,24 @@ export function UpdateExpression(node) {
}
}
export function Literal(node) {
var value = node.value;
if (typeof value === "string") return t.stringTypeAnnotation();
if (typeof value === "number") return t.numberTypeAnnotation();
if (typeof value === "boolean") return t.booleanTypeAnnotation();
if (value === null) return t.voidTypeAnnotation();
if (node.regex) return t.genericTypeAnnotation(t.identifier("RegExp"));
export function StringLiteral() {
return t.stringTypeAnnotation();
}
export function NumberLiteral() {
return t.numberTypeAnnotation();
}
export function BooleanLiteral() {
return t.booleanTypeAnnotation();
}
export function NullLiteral() {
return t.voidTypeAnnotation();
}
export function RegexLiteral() {
return t.genericTypeAnnotation(t.identifier("RegExp"));
}
export function ObjectExpression() {

View File

@@ -1,3 +1,5 @@
/* @flow */
// This file contains methods responsible for introspecting the current path for certain values.
import type NodePath from "./index";
@@ -15,17 +17,17 @@ export function matchesPattern(pattern: string, allowPartial?: boolean): boolean
// not a member expression
if (!this.isMemberExpression()) return false;
var parts = pattern.split(".");
var search = [this.node];
var i = 0;
let parts = pattern.split(".");
let search = [this.node];
let i = 0;
function matches(name) {
var part = parts[i];
let part = parts[i];
return part === "*" || name === part;
}
while (search.length) {
var node = search.shift();
let node = search.shift();
if (allowPartial && i === parts.length) {
return true;
@@ -68,7 +70,7 @@ export function matchesPattern(pattern: string, allowPartial?: boolean): boolean
*/
export function has(key): boolean {
var val = this.node[key];
let val = this.node[key];
if (val && Array.isArray(val)) {
return !!val.length;
} else {
@@ -88,7 +90,7 @@ export function isStatic() {
* Alias of `has`.
*/
export var is = has;
export let is = has;
/**
* Opposite of `has`.
@@ -134,11 +136,11 @@ export function isNodeType(type: string): boolean {
*/
export function isCompletionRecord(allowInsideFunction?) {
var path = this;
var first = true;
let path = this;
let first = true;
do {
var container = path.container;
let container = path.container;
// we're in a function so can't be a completion record
if (path.isFunction() && !first) {
@@ -177,11 +179,11 @@ export function isStatementOrBlock() {
export function referencesImport(moduleSource, importName) {
if (!this.isReferencedIdentifier()) return false;
var binding = this.scope.getBinding(this.node.name);
let binding = this.scope.getBinding(this.node.name);
if (!binding || binding.kind !== "module") return false;
var path = binding.path;
var parent = path.parentPath;
let path = binding.path;
let parent = path.parentPath;
if (!parent.isImportDeclaration()) return false;
// check moduleSource
@@ -211,7 +213,7 @@ export function referencesImport(moduleSource, importName) {
*/
export function getSource() {
var node = this.node;
let node = this.node;
if (node.end) {
return this.hub.file.code.slice(node.start, node.end);
} else {
@@ -232,57 +234,29 @@ export function willIMaybeExecuteBefore(target) {
export function _guessExecutionStatusRelativeTo(target) {
// check if the two paths are in different functions, we can't track execution of these
var targetFuncParent = target.scope.getFunctionParent();
var selfFuncParent = this.scope.getFunctionParent();
let targetFuncParent = target.scope.getFunctionParent();
let selfFuncParent = this.scope.getFunctionParent();
if (targetFuncParent !== selfFuncParent) {
var targetFuncPath = targetFuncParent.path;
if (!targetFuncPath.isFunctionDeclaration()) return "function";
// so we're in a completely different function, if this is a function declaration
// then we can be a bit smarter and handle cases where the function is either
// a. not called at all (part of an export)
// b. called directly
var binding = targetFuncPath.scope.getBinding(targetFuncPath.node.id.name);
// no references!
if (!binding.references) return "before";
var referencePaths: Array = binding.referencePaths;
// verify that all of the references are calls
for (let path of referencePaths) {
if (path.key !== "callee" || !path.parentPath.isCallExpression()) {
return "function";
}
let status = this._guessExecutionStatusRelativeToDifferentFunctions(targetFuncParent);
if (status) {
return status;
} else {
target = targetFuncParent.path;
}
var allStatus;
// verify that all the calls have the same execution status
for (let path of referencePaths) {
var status = this._guessExecutionStatusRelativeTo(path);
if (allStatus) {
if (allStatus !== status) return "function";
} else {
allStatus = status;
}
}
return allStatus || "function";
}
var targetPaths = target.getAncestry();
let targetPaths = target.getAncestry();
if (targetPaths.indexOf(this) >= 0) return "after";
var selfPaths = this.getAncestry();
let selfPaths = this.getAncestry();
// get ancestor where the branches intersect
var commonPath;
var targetIndex;
var selfIndex;
let commonPath;
let targetIndex;
let selfIndex;
for (selfIndex = 0; selfIndex < selfPaths.length; selfIndex++) {
var selfPath = selfPaths[selfIndex];
let selfPath = selfPaths[selfIndex];
targetIndex = targetPaths.indexOf(selfPath);
if (targetIndex >= 0) {
commonPath = selfPath;
@@ -294,8 +268,8 @@ export function _guessExecutionStatusRelativeTo(target) {
}
// get the relationship paths that associate these nodes to their common ancestor
var targetRelationship = targetPaths[targetIndex - 1];
var selfRelationship = selfPaths[selfIndex - 1];
let targetRelationship = targetPaths[targetIndex - 1];
let selfRelationship = selfPaths[selfIndex - 1];
if (!targetRelationship || !selfRelationship) {
return "before";
}
@@ -306,11 +280,48 @@ export function _guessExecutionStatusRelativeTo(target) {
}
// otherwise we're associated by a parent node, check which key comes before the other
var targetKeyPosition = t.VISITOR_KEYS[targetRelationship.type].indexOf(targetRelationship.key);
var selfKeyPosition = t.VISITOR_KEYS[selfRelationship.type].indexOf(selfRelationship.key);
let targetKeyPosition = t.VISITOR_KEYS[targetRelationship.type].indexOf(targetRelationship.key);
let selfKeyPosition = t.VISITOR_KEYS[selfRelationship.type].indexOf(selfRelationship.key);
return targetKeyPosition > selfKeyPosition ? "before" : "after";
}
export function _guessExecutionStatusRelativeToDifferentFunctions(targetFuncParent) {
let targetFuncPath = targetFuncParent.path;
if (!targetFuncPath.isFunctionDeclaration()) return;
// so we're in a completely different function, if this is a function declaration
// then we can be a bit smarter and handle cases where the function is either
// a. not called at all (part of an export)
// b. called directly
let binding = targetFuncPath.scope.getBinding(targetFuncPath.node.id.name);
// no references!
if (!binding.references) return "before";
let referencePaths: Array = binding.referencePaths;
// verify that all of the references are calls
for (let path of referencePaths) {
if (path.key !== "callee" || !path.parentPath.isCallExpression()) {
return;
}
}
let allStatus;
// verify that all the calls have the same execution status
for (let path of referencePaths) {
let status = this._guessExecutionStatusRelativeTo(path);
if (allStatus) {
if (allStatus !== status) return;
} else {
allStatus = status;
}
}
return allStatus;
}
/**
* Resolve a "pointer" `NodePath` to it's absolute path.
*/
@@ -335,7 +346,7 @@ export function _resolve(dangerous?, resolved?): ?NodePath {
// otherwise it's a request for a pattern and that's a bit more tricky
}
} else if (this.isReferencedIdentifier()) {
var binding = this.scope.getBinding(this.node.name);
let binding = this.scope.getBinding(this.node.name);
if (!binding) return;
// reassigned so we can't really resolve it
@@ -353,22 +364,22 @@ export function _resolve(dangerous?, resolved?): ?NodePath {
// this is dangerous, as non-direct target assignments will mutate it's state
// making this resolution inaccurate
var targetKey = this.toComputedKey();
let targetKey = this.toComputedKey();
if (!t.isLiteral(targetKey)) return;
var targetName = targetKey.value;
let targetName = targetKey.value;
var target = this.get("object").resolve(dangerous, resolved);
let target = this.get("object").resolve(dangerous, resolved);
if (target.isObjectExpression()) {
var props = target.get("properties");
for (var prop of (props: Array)) {
let props = target.get("properties");
for (let prop of (props: Array)) {
if (!prop.isProperty()) continue;
var key = prop.get("key");
let key = prop.get("key");
// { foo: obj }
var match = prop.isnt("computed") && key.isIdentifier({ name: targetName });
let match = prop.isnt("computed") && key.isIdentifier({ name: targetName });
// { "foo": "obj" } or { ["foo"]: "obj" }
match = match || key.isLiteral({ value: targetName });
@@ -376,8 +387,8 @@ export function _resolve(dangerous?, resolved?): ?NodePath {
if (match) return prop.get("value").resolve(dangerous, resolved);
}
} else if (target.isArrayExpression() && !isNaN(+targetName)) {
var elems = target.get("elements");
var elem = elems[targetName];
let elems = target.get("elements");
let elem = elems[targetName];
if (elem) return elem.resolve(dangerous, resolved);
}
}

View File

@@ -1,25 +1,26 @@
/* @flow */
import { react } from "babel-types";
import * as t from "babel-types";
var referenceVisitor = {
ReferencedIdentifier(node, parent, scope, state) {
if (this.isJSXIdentifier() && react.isCompatTag(node.name)) {
let referenceVisitor = {
ReferencedIdentifier(path, state) {
if (path.isJSXIdentifier() && react.isCompatTag(path.node.name)) {
return;
}
// direct references that we need to track to hoist this to the highest scope we can
var binding = scope.getBinding(node.name);
let binding = path.scope.getBinding(path.node.name);
if (!binding) return;
// this binding isn't accessible from the parent scope so we can safely ignore it
// eg. it's in a closure etc
if (binding !== state.scope.getBinding(node.name)) return;
if (binding !== state.scope.getBinding(path.node.name)) return;
if (binding.constant) {
state.bindings[node.name] = binding;
state.bindings[path.node.name] = binding;
} else {
for (var violationPath of (binding.constantViolations: Array)) {
for (let violationPath of (binding.constantViolations: Array)) {
state.breakOnScopePaths = state.breakOnScopePaths.concat(violationPath.getAncestry());
}
}
@@ -36,8 +37,8 @@ export default class PathHoister {
}
isCompatibleScope(scope) {
for (var key in this.bindings) {
var binding = this.bindings[key];
for (let key in this.bindings) {
let binding = this.bindings[key];
if (!scope.bindingIdentifierEquals(key, binding.identifier)) {
return false;
}
@@ -47,7 +48,7 @@ export default class PathHoister {
}
getCompatibleScopes() {
var scope = this.path.scope;
let scope = this.path.scope;
do {
if (this.isCompatibleScope(scope)) {
this.scopes.push(scope);
@@ -62,9 +63,9 @@ export default class PathHoister {
}
getAttachmentPath() {
var scopes = this.scopes;
let scopes = this.scopes;
var scope = scopes.pop();
let scope = scopes.pop();
if (!scope) return;
if (scope.path.isFunction()) {
@@ -84,22 +85,22 @@ export default class PathHoister {
}
getNextScopeStatementParent() {
var scope = this.scopes.pop();
let scope = this.scopes.pop();
if (scope) return scope.path.getStatementParent();
}
hasOwnParamBindings(scope) {
for (var name in this.bindings) {
for (let name in this.bindings) {
if (!scope.hasOwnBinding(name)) continue;
var binding = this.bindings[name];
let binding = this.bindings[name];
if (binding.kind === "param") return true;
}
return false;
}
run() {
var node = this.path.node;
let node = this.path.node;
if (node._hoisted) return;
node._hoisted = true;
@@ -107,13 +108,13 @@ export default class PathHoister {
this.getCompatibleScopes();
var attachTo = this.getAttachmentPath();
let attachTo = this.getAttachmentPath();
if (!attachTo) return;
// don't bother hoisting to the same function as this will cause multiple branches to be evaluated more than once leading to a bad optimisation
if (attachTo.getFunctionParent() === this.path.getFunctionParent()) return;
var uid = attachTo.scope.generateUidIdentifier("ref");
let uid = attachTo.scope.generateUidIdentifier("ref");
attachTo.insertBefore([
t.variableDeclaration("var", [
@@ -121,7 +122,7 @@ export default class PathHoister {
])
]);
var parent = this.path.parentPath;
let parent = this.path.parentPath;
if (parent.isJSXElement() && this.path.container === parent.node.children) {
// turning the `span` in `<div><span /></div>` to an expression so we need to wrap it with

View File

@@ -1,50 +1,21 @@
// this file contains hooks that handle ancestry cleanup of parent nodes when removing children
/* @flow */
import * as t from "babel-types";
// this file contains hooks that handle ancestry cleanup of parent nodes when removing children
/**
* Pre hooks should be used for either rejecting removal or delegating removal
*/
export var pre = [
function (self) {
if (self.key === "body" && (self.isBlockStatement() || self.isClassBody())) {
// function () NODE
// class NODE
// attempting to remove a block statement that's someones body so let's just clear all the inner
// statements instead
self.node.body = [];
export let hooks = [
function (self, parent) {
if (self.key === "body" && parent.isArrowFunctionExpression()) {
self.replaceWith(self.scope.buildUndefinedNode());
return true;
}
},
function (self, parent) {
var replace = false;
// () => NODE;
// removing the body of an arrow function
replace = replace || (self.key === "body" && parent.isArrowFunctionExpression());
// throw NODE;
// removing a throw statement argument
replace = replace || (self.key === "argument" && parent.isThrowStatement());
if (replace) {
self.replaceWith(t.identifier("undefined"));
return true;
}
}
];
/**
* Post hooks should be used for cleaning up parents
*/
export var post = [
function (self, parent) {
var removeParent = false;
let removeParent = false;
// while (NODE);
// removing the test of a while/switch, we can either just remove it entirely *or* turn the `test` into `true`
@@ -59,21 +30,16 @@ export var post = [
// stray labeled statement with no body
removeParent = removeParent || (self.key === "body" && parent.isLabeledStatement());
// var NODE;
// let NODE;
// remove an entire declaration if there are no declarators left
removeParent = removeParent || (self.listKey === "declarations" && parent.isVariableDeclaration() && parent.node.declarations.length === 0);
removeParent = removeParent || (self.listKey === "declarations" && parent.isVariableDeclaration() && parent.node.declarations.length === 1);
// NODE;
// remove the entire expression statement if there's no expression
removeParent = removeParent || (self.key === "expression" && parent.isExpressionStatement());
// if (NODE);
// remove the entire if since the consequent is never going to be hit, if there's an alternate then it's already been
// handled with the `pre` hook
removeParent = removeParent || (self.key === "test" && parent.isIfStatement());
if (removeParent) {
parent.dangerouslyRemove();
parent.remove();
return true;
}
},

View File

@@ -1,9 +1,12 @@
/* @flow */
import type NodePath from "../index";
import { react } from "babel-types";
import * as t from "babel-types";
export var ReferencedIdentifier = {
export let ReferencedIdentifier = {
types: ["Identifier", "JSXIdentifier"],
checkPath({ node, parent }, opts) {
checkPath({ node, parent }: NodePath, opts?: Object): boolean {
if (!t.isIdentifier(node, opts)) {
if (t.isJSXIdentifier(node, opts)) {
if (react.isCompatTag(node.name)) return false;
@@ -18,16 +21,23 @@ export var ReferencedIdentifier = {
}
};
export var BindingIdentifier = {
types: ["Identifier"],
export let ReferencedMemberExpression = {
types: ["MemberExpression"],
checkPath({ node, parent }) {
return t.isBinding(node, parent);
return t.isMemberExpression(node) && t.isReferenced(node, parent);
}
};
export var Statement = {
export let BindingIdentifier = {
types: ["Identifier"],
checkPath({ node, parent }: NodePath): boolean {
return t.isIdentifier(node) && t.isBinding(node, parent);
}
};
export let Statement = {
types: ["Statement"],
checkPath({ node, parent }) {
checkPath({ node, parent }: NodePath): boolean {
if (t.isStatement(node)) {
if (t.isVariableDeclaration(node)) {
if (t.isForXStatement(parent, { left: node })) return false;
@@ -41,9 +51,9 @@ export var Statement = {
}
};
export var Expression = {
export let Expression = {
types: ["Expression"],
checkPath(path) {
checkPath(path: NodePath): boolean {
if (path.isIdentifier()) {
return path.isReferencedIdentifier();
} else {
@@ -52,75 +62,53 @@ export var Expression = {
}
};
export var Scope = {
export let Scope = {
types: ["Scopable"],
checkPath(path) {
return t.isScope(path.node, path.parent);
}
};
export var Referenced = {
checkPath(path) {
export let Referenced = {
checkPath(path: NodePath): boolean {
return t.isReferenced(path.node, path.parent);
}
};
export var BlockScoped = {
checkPath(path) {
export let BlockScoped = {
checkPath(path: NodePath): boolean {
return t.isBlockScoped(path.node);
}
};
export var Var = {
export let Var = {
types: ["VariableDeclaration"],
checkPath(path) {
checkPath(path: NodePath): boolean {
return t.isVar(path.node);
}
};
export var DirectiveLiteral = {
types: ["Literal"],
checkPath(path) {
return path.parentPath.isDirective();
}
};
export var Directive = {
types: ["ExpressionStatement"],
checkPath({ inList, container, key }) {
// needs to be in a statement list
if (!inList) return false;
// get the last directive node in this list
var lastDirective = -1;
for (var i = 0; i < container.length; i++) {
var node = container[i];
if (t.isExpressionStatement(node) && t.isLiteral(node.expression)) {
lastDirective = i;
} else {
break;
}
}
return key <= lastDirective;
}
};
export var User = {
checkPath(path) {
export let User = {
checkPath(path: NodePath): boolean {
return path.node && !!path.node.loc;
}
};
export var Generated = {
checkPath(path) {
export let Generated = {
checkPath(path: NodePath): boolean {
return !path.isUser();
}
};
export var Flow = {
export let Pure = {
checkPath(path: NodePath, opts?): boolean {
return path.scope.isPure(path.node, opts);
}
};
export let Flow = {
types: ["Flow", "ImportDeclaration", "ExportDeclaration"],
checkPath({ node }) {
checkPath({ node }: NodePath): boolean {
if (t.isFlow(node)) {
return true;
} else if (t.isImportDeclaration(node)) {

View File

@@ -1,5 +1,8 @@
/* @flow */
// This file contains methods that modify the path/node in some ways.
import { PATH_CACHE_KEY } from "./constants";
import PathHoister from "./lib/hoister";
import NodePath from "./index";
import * as t from "babel-types";
@@ -36,17 +39,16 @@ export function insertBefore(nodes) {
export function _containerInsert(from, nodes) {
this.updateSiblingKeys(from, nodes.length);
var paths = [];
let paths = [];
for (var i = 0; i < nodes.length; i++) {
var to = from + i;
var node = nodes[i];
for (let i = 0; i < nodes.length; i++) {
let to = from + i;
let node = nodes[i];
this.container.splice(to, 0, node);
if (this.context) {
var path = this.context.create(this.parent, this.container, to, this.listKey);
let path = this.context.create(this.parent, this.container, to, this.listKey);
paths.push(path);
this.queueNode(path);
} else {
paths.push(NodePath.get({
parentPath: this,
@@ -58,6 +60,21 @@ export function _containerInsert(from, nodes) {
}
}
let contexts = this.contexts;
let path = this;
while (!contexts.length) {
path = path.parentPath;
contexts = path.contexts;
}
for (let path of paths) {
path.setScope();
for (let context of contexts) {
context.maybeQueue(path);
}
}
return paths;
}
@@ -70,8 +87,10 @@ export function _containerInsertAfter(nodes) {
}
export function _maybePopFromStatements(nodes) {
var last = nodes[nodes.length - 1];
if (t.isExpressionStatement(last) && t.isIdentifier(last.expression) && !this.isCompletionRecord()) {
let last = nodes[nodes.length - 1];
let isIdentifier = t.isIdentifier(last) || (t.isExpressionStatement(last) && t.isIdentifier(last.expression));
if (isIdentifier && !this.isCompletionRecord()) {
nodes.pop();
}
}
@@ -90,7 +109,7 @@ export function insertAfter(nodes) {
return this.parentPath.insertAfter(nodes);
} else if (this.isNodeType("Expression") || (this.parentPath.isForStatement() && this.key === "init")) {
if (this.node) {
var temp = this.scope.generateDeclaredUidIdentifier();
let temp = this.scope.generateDeclaredUidIdentifier();
nodes.unshift(t.expressionStatement(t.assignmentExpression("=", temp, this.node)));
nodes.push(t.expressionStatement(temp));
}
@@ -117,8 +136,8 @@ export function insertAfter(nodes) {
export function updateSiblingKeys(fromIndex, incrementBy) {
if (!this.parent) return;
var paths = this.parent._paths;
for (var i = 0; i < paths.length; i++) {
let paths = this.parent[PATH_CACHE_KEY];
for (let i = 0; i < paths.length; i++) {
let path = paths[i];
if (path.key >= fromIndex) {
path.key += incrementBy;
@@ -127,12 +146,16 @@ export function updateSiblingKeys(fromIndex, incrementBy) {
}
export function _verifyNodeList(nodes) {
if (!nodes) {
return [];
}
if (nodes.constructor !== Array) {
nodes = [nodes];
}
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
for (let i = 0; i < nodes.length; i++) {
let node = nodes[i];
if (!node) {
throw new Error(`Node list has falsy node with the index of ${i}`);
} else if (typeof node !== "object") {
@@ -154,12 +177,10 @@ export function unshiftContainer(listKey, nodes) {
// get the first path and insert our nodes before it, if it doesn't exist then it
// doesn't matter, our nodes will be inserted anyway
var container = this.node[listKey];
var path = NodePath.get({
let path = NodePath.get({
parentPath: this,
parent: this.node,
container: container,
container: this.node[listKey],
listKey,
key: 0
});
@@ -175,17 +196,16 @@ export function pushContainer(listKey, nodes) {
// get an invisible path that represents the last node + 1 and replace it with our
// nodes, effectively inlining it
var container = this.node[listKey];
var i = container.length;
var path = NodePath.get({
let container = this.node[listKey];
let path = NodePath.get({
parentPath: this,
parent: this.node,
container: container,
listKey,
key: i
key: container.length
});
return path.replaceWith(nodes, true);
return path.replaceWithMultiple(nodes);
}
/**
@@ -194,6 +214,6 @@ export function pushContainer(listKey, nodes) {
*/
export function hoist(scope = this.scope) {
var hoister = new PathHoister(this, scope);
let hoister = new PathHoister(this, scope);
return hoister.run();
}

View File

@@ -1,24 +1,15 @@
/* @flow */
// This file contains methods responsible for removing a node.
import * as removalHooks from "./lib/removal-hooks";
/**
* This is now safe.
*/
export var dangerouslyRemove = remove;
/**
* Dangerously remove the current node. This may sometimes result in a tainted
* invalid AST so use with caution.
*/
import { hooks } from "./lib/removal-hooks";
export function remove() {
this._assertUnremoved();
this.resync();
if (this._callRemovalHooks("pre")) {
if (this._callRemovalHooks()) {
this._markRemoved();
return;
}
@@ -26,12 +17,10 @@ export function remove() {
this.shareCommentsWithSiblings();
this._remove();
this._markRemoved();
this._callRemovalHooks("post");
}
export function _callRemovalHooks(position) {
for (var fn of (removalHooks[position]: Array)) {
export function _callRemovalHooks() {
for (let fn of (hooks: Array<Function>)) {
if (fn(this, this.parentPath)) return true;
}
}
@@ -53,6 +42,6 @@ export function _markRemoved() {
export function _assertUnremoved() {
if (this.removed) {
throw this.errorWithNode("NodePath has been removed so is read-only.");
throw this.buildCodeFrameError("NodePath has been removed so is read-only.");
}
}

View File

@@ -1,3 +1,5 @@
/* @flow */
// This file contains methods responsible for replacing a node with another.
import codeFrame from "babel-code-frame";
@@ -6,23 +8,22 @@ import NodePath from "./index";
import { parse } from "babylon";
import * as t from "babel-types";
var hoistVariablesVisitor = {
Function() {
this.skip();
let hoistVariablesVisitor = {
Function(path) {
path.skip();
},
VariableDeclaration(node, parent, scope) {
if (node.kind !== "var") return;
VariableDeclaration(path) {
if (path.node.kind !== "var") return;
var bindings = this.getBindingIdentifiers();
for (var key in bindings) {
scope.push({ id: bindings[key] });
let bindings = path.getBindingIdentifiers();
for (let key in bindings) {
path.scope.push({ id: bindings[key] });
}
var exprs = [];
let exprs = [];
for (var declar of (node.declarations: Array)) {
for (let declar of (path.node.declarations: Array<Object>)) {
if (declar.init) {
exprs.push(t.expressionStatement(
t.assignmentExpression("=", declar.id, declar.init)
@@ -30,7 +31,7 @@ var hoistVariablesVisitor = {
}
}
return exprs;
path.replaceWithMultiple(exprs);
}
};
@@ -50,7 +51,12 @@ export function replaceWithMultiple(nodes: Array<Object>) {
t.inheritTrailingComments(nodes[nodes.length - 1], this.node);
this.node = this.container[this.key] = null;
this.insertAfter(nodes);
if (!this.node) this.dangerouslyRemove();
if (this.node) {
this.requeue();
} else {
this.remove();
}
}
/**
@@ -68,7 +74,7 @@ export function replaceWithSourceString(replacement) {
replacement = `(${replacement})`;
replacement = parse(replacement);
} catch (err) {
var loc = err.loc;
let loc = err.loc;
if (loc) {
err.message += " - make sure this is an expression.";
err.message += "\n" + codeFrame(replacement, loc.line, loc.column + 1);
@@ -85,7 +91,7 @@ export function replaceWithSourceString(replacement) {
* Replace the current node with another.
*/
export function replaceWith(replacement, whateverAllowed) {
export function replaceWith(replacement) {
this.resync();
if (this.removed) {
@@ -97,7 +103,7 @@ export function replaceWith(replacement, whateverAllowed) {
}
if (!replacement) {
throw new Error("You passed `path.replaceWith()` a falsy node, use `path.dangerouslyRemove()` instead");
throw new Error("You passed `path.replaceWith()` a falsy node, use `path.remove()` instead");
}
if (this.node === replacement) {
@@ -108,23 +114,12 @@ export function replaceWith(replacement, whateverAllowed) {
throw new Error("You can only replace a Program root node with another Program node");
}
// normalise inserting an entire AST
if (t.isProgram(replacement) && !this.isProgram()) {
replacement = replacement.body;
whateverAllowed = true;
}
if (Array.isArray(replacement)) {
if (whateverAllowed) {
return this.replaceWithMultiple(replacement);
} else {
throw new Error("Don't use `path.replaceWith()` with an array of nodes, use `path.replaceWithMultiple()`");
}
throw new Error("Don't use `path.replaceWith()` with an array of nodes, use `path.replaceWithMultiple()`");
}
if (typeof replacement === "string") {
// triggers an error
return this.replaceWithSourceString();
throw new Error("Don't use `path.replaceWith()` with a source string, use `path.replaceWithSourceString()`");
}
// replacing a statement with an expression so wrap it in an expression statement
@@ -149,6 +144,9 @@ export function replaceWith(replacement, whateverAllowed) {
// potentially create new scope
this.setScope();
// requeue for visiting
this.requeue();
}
/**
@@ -156,10 +154,14 @@ export function replaceWith(replacement, whateverAllowed) {
*/
export function _replaceWith(node) {
if (!this.container) {
throw new ReferenceError("Container is falsy");
}
if (this.inList) {
t.validate(this.key, this.parent, [node]);
t.validate(this.parent, this.key, [node]);
} else {
t.validate(this.key, this.parent, node);
t.validate(this.parent, this.key, node);
}
this.node = this.container[this.key] = node;
@@ -171,34 +173,50 @@ export function _replaceWith(node) {
* extremely important to retain original semantics.
*/
export function replaceExpressionWithStatements(nodes: Array) {
export function replaceExpressionWithStatements(nodes: Array<Object>) {
this.resync();
var toSequenceExpression = t.toSequenceExpression(nodes, this.scope);
let toSequenceExpression = t.toSequenceExpression(nodes, this.scope);
if (toSequenceExpression) {
return this.replaceWith(toSequenceExpression);
if (t.isSequenceExpression(toSequenceExpression)) {
let exprs = toSequenceExpression.expressions;
if (exprs.length >= 2 && this.parentPath.isExpressionStatement()) {
this._maybePopFromStatements(exprs);
}
// could be just one element due to the previous maybe popping
if (exprs.length === 1) {
this.replaceWith(exprs[0]);
} else {
this.replaceWith(toSequenceExpression);
}
} else if (toSequenceExpression) {
this.replaceWith(toSequenceExpression);
} else {
var container = t.functionExpression(null, [], t.blockStatement(nodes));
let container = t.functionExpression(null, [], t.blockStatement(nodes));
container.shadow = true;
this.replaceWith(t.callExpression(container, []));
this.traverse(hoistVariablesVisitor);
// add implicit returns to all ending expression statements
var last = this.get("callee").getCompletionRecords();
for (var lastNode of (last: Array)) {
if (!lastNode.isExpressionStatement()) continue;
let completionRecords: Array<NodePath> = this.get("callee").getCompletionRecords();
for (let path of completionRecords) {
if (!path.isExpressionStatement()) continue;
var loop = lastNode.findParent((path) => path.isLoop());
let loop = path.findParent((path) => path.isLoop());
if (loop) {
var uid = this.get("callee").scope.generateDeclaredUidIdentifier("ret");
this.get("callee.body").pushContainer("body", t.returnStatement(uid));
lastNode.get("expression").replaceWith(
t.assignmentExpression("=", uid, lastNode.node.expression)
let callee = this.get("callee");
let uid = callee.scope.generateDeclaredUidIdentifier("ret");
callee.get("body").pushContainer("body", t.returnStatement(uid));
path.get("expression").replaceWith(
t.assignmentExpression("=", uid, path.node.expression)
);
} else {
lastNode.replaceWith(t.returnStatement(lastNode.node.expression));
path.replaceWith(t.returnStatement(path.node.expression));
}
}
@@ -206,14 +224,14 @@ export function replaceExpressionWithStatements(nodes: Array) {
}
}
export function replaceInline(nodes) {
export function replaceInline(nodes: Object | Array<Object>) {
this.resync();
if (Array.isArray(nodes)) {
if (Array.isArray(this.container)) {
nodes = this._verifyNodeList(nodes);
this._containerInsertAfter(nodes);
return this.dangerouslyRemove();
return this.remove();
} else {
return this.replaceWithMultiple(nodes);
}

View File

@@ -1,3 +1,7 @@
/* @flow */
import type NodePath from "../path";
/**
* This class is responsible for a binding inside of a scope.
*
@@ -11,21 +15,17 @@
export default class Binding {
constructor({ existing, identifier, scope, path, kind }) {
this.constantViolations = [];
this.constant = true;
this.identifier = identifier;
this.scope = scope;
this.path = path;
this.kind = kind;
this.constantViolations = [];
this.constant = true;
this.referencePaths = [];
this.references = 0;
this.referenced = false;
this.scope = scope;
this.path = path;
this.kind = kind;
this.hasValue = false;
this.hasDeoptedValue = false;
this.value = null;
this.references = 0;
this.clearValue();
@@ -38,6 +38,18 @@ export default class Binding {
}
}
constantViolations: Array<NodePath>;
constant: boolean;
referencePaths: Array<NodePath>;
referenced: boolean;
references: number;
hasDeoptedValue: boolean;
hasValue: boolean;
value: any;
deoptValue() {
this.clearValue();
this.hasDeoptedValue = true;
@@ -68,7 +80,7 @@ export default class Binding {
* Increment the amount of references to this binding.
*/
reference(path) {
reference(path: NodePath) {
this.referenced = true;
this.references++;
this.referencePaths.push(path)

View File

@@ -1,138 +1,113 @@
/* @flow */
import includes from "lodash/collection/includes";
import repeating from "repeating";
import Renamer from "./lib/renamer";
import type NodePath from "../path";
import traverse from "../index";
import defaults from "lodash/object/defaults";
import * as messages from "babel-messages";
import Binding from "./binding";
import globals from "globals";
import flatten from "lodash/array/flatten";
import extend from "lodash/object/extend";
import * as t from "babel-types";
var collectorVisitor = {
const SCOPE_INFO_CACHE_KEY = "_scopeInfo"; //Symbol();
For(node, parent, scope) {
for (var key of (t.FOR_INIT_KEYS: Array)) {
var declar = this.get(key);
if (declar.isVar()) scope.getFunctionParent().registerBinding("var", declar);
let collectorVisitor = {
For(path) {
for (let key of (t.FOR_INIT_KEYS: Array)) {
let declar = path.get(key);
if (declar.isVar()) path.scope.getFunctionParent().registerBinding("var", declar);
}
},
Declaration(node, parent, scope) {
Declaration(path) {
// delegate block scope handling to the `blockVariableVisitor`
if (this.isBlockScoped()) return;
if (path.isBlockScoped()) return;
// this will be hit again once we traverse into it after this iteration
if (this.isExportDeclaration() && this.get("declaration").isDeclaration()) return;
if (path.isExportDeclaration() && path.get("declaration").isDeclaration()) return;
// we've ran into a declaration!
scope.getFunctionParent().registerDeclaration(this);
path.scope.getFunctionParent().registerDeclaration(path);
},
ReferencedIdentifier(node) {
var binding = this.scope.getBinding(node.name);
if (binding) {
binding.reference(this);
} else {
this.scope.getProgramParent().addGlobal(node);
}
ReferencedIdentifier(path, state) {
state.references.push(path);
},
ForXStatement() {
var left = this.get("left");
ForXStatement(path, state) {
let left = path.get("left");
if (left.isPattern() || left.isIdentifier()) {
this.scope.registerConstantViolation(left, left);
state.constantViolations.push(left);
}
},
ExportDeclaration: {
exit(node) {
var declar = node.declaration;
exit({ node, scope }) {
let declar = node.declaration;
if (t.isClassDeclaration(declar) || t.isFunctionDeclaration(declar)) {
this.scope.getBinding(declar.id.name).reference();
let id = declar.id;
if (!id) return;
let binding = scope.getBinding(id.name);
if (binding) binding.reference();
} else if (t.isVariableDeclaration(declar)) {
for (var decl of (declar.declarations: Array)) {
var ids = t.getBindingIdentifiers(decl);
for (var name in ids) {
this.scope.getBinding(name).reference();
for (let decl of (declar.declarations: Array<Object>)) {
let ids = t.getBindingIdentifiers(decl);
for (let name in ids) {
let binding = scope.getBinding(name);
if (binding) binding.reference();
}
}
}
}
},
LabeledStatement(node) {
this.scope.getProgramParent().addGlobal(node);
this.scope.getBlockParent().registerDeclaration(this);
LabeledStatement(path) {
path.scope.getProgramParent().addGlobal(path.node);
path.scope.getBlockParent().registerDeclaration(path);
},
AssignmentExpression() {
// register undeclared bindings as globals
var ids = this.getBindingIdentifiers();
var programParent;
for (var name in ids) {
if (this.scope.getBinding(name)) continue;
AssignmentExpression(path, state) {
state.assignments.push(path);
},
programParent = programParent || this.scope.getProgramParent();
programParent.addGlobal(ids[name]);
UpdateExpression(path, state) {
state.constantViolations.push(path.get("argument"));
},
UnaryExpression(path, state) {
if (path.node.operator === "delete") {
state.constantViolations.push(path.get("argument"));
}
// register as constant violation
this.scope.registerConstantViolation(this, this.get("left"), this.get("right"));
},
UpdateExpression(node, parent, scope) {
scope.registerConstantViolation(this, this.get("argument"), null);
BlockScoped(path) {
let scope = path.scope;
if (scope.path === path) scope = scope.parent;
scope.getBlockParent().registerDeclaration(path);
},
UnaryExpression(node, parent, scope) {
if (node.operator === "delete") scope.registerConstantViolation(this, this.get("left"), null);
ClassDeclaration(path) {
let id = path.node.id;
if (!id) return;
let name = id.name;
path.scope.bindings[name] = path.scope.getBinding(name);
},
BlockScoped(node, parent, scope) {
if (scope.path === this) scope = scope.parent;
scope.getBlockParent().registerDeclaration(this);
},
ClassDeclaration(node, parent, scope) {
var name = node.id.name;
scope.bindings[name] = scope.getBinding(name);
},
Block(node, parent, scope) {
var paths = this.get("body");
for (var path of (paths: Array)) {
if (path.isFunctionDeclaration()) {
scope.getBlockParent().registerDeclaration(path);
Block(path) {
let paths = path.get("body");
for (let bodyPath of (paths: Array)) {
if (bodyPath.isFunctionDeclaration()) {
path.scope.getBlockParent().registerDeclaration(bodyPath);
}
}
}
};
var renameVisitor = {
ReferencedIdentifier(node, parent, scope, state) {
if (node.name === state.oldName) {
node.name = state.newName;
}
},
Scope(node, parent, scope, state) {
if (!scope.bindingIdentifierEquals(state.oldName, state.binding)) {
this.skip();
}
},
"AssignmentExpression|Declaration"(node, parent, scope, state) {
var ids = this.getBindingIdentifiers();
for (var name in ids) {
if (name === state.oldName) ids[name].name = state.newName;
}
}
};
export default class Scope {
/**
@@ -145,7 +120,7 @@ export default class Scope {
return parent;
}
var cached = path.getData("scope");
let cached = path.getData("scope");
if (cached && cached.parent === parent && cached.block === path.node) {
return cached;
} else {
@@ -164,7 +139,7 @@ export default class Scope {
* Globals.
*/
static globals = flatten([globals.builtin, globals.browser, globals.node].map(Object.keys));
static globals = Object.keys(globals.builtin);
/**
* Variables available in current context.
@@ -190,7 +165,7 @@ export default class Scope {
*/
generateDeclaredUidIdentifier(name: string = "temp") {
var id = this.generateUidIdentifier(name);
let id = this.generateUidIdentifier(name);
this.push({ id });
return id;
}
@@ -208,17 +183,16 @@ export default class Scope {
*/
generateUid(name: string) {
name = t.toIdentifier(name).replace(/^_+/, "");
name = t.toIdentifier(name).replace(/^_+/, "").replace(/[0-9]+$/g, "");
var uid;
var i = 0;
let uid;
let i = 0;
do {
uid = this._generateUid(name, i);
i++;
} while (this.hasBinding(uid) || this.hasGlobal(uid) || this.hasReference(uid));
var program = this.getProgramParent();
let program = this.getProgramParent();
program.references[uid] = true;
program.uids[uid] = true;
@@ -230,7 +204,7 @@ export default class Scope {
*/
_generateUid(name, i) {
var id = name;
let id = name;
if (i > 1) id += i;
return `_${id}`;
}
@@ -240,24 +214,24 @@ export default class Scope {
*/
generateUidIdentifierBasedOnNode(parent: Object, defaultName?: String): Object {
var node = parent;
let node = parent;
if (t.isAssignmentExpression(parent)) {
node = parent.left;
} else if (t.isVariableDeclarator(parent)) {
node = parent.id;
} else if (t.isProperty(node)) {
} else if (t.isObjectProperty(node) || t.isObjectMethod(node)) {
node = node.key;
}
var parts = [];
let parts = [];
var add = function (node) {
let add = function (node) {
if (t.isModuleDeclaration(node)) {
if (node.source) {
add(node.source);
} else if (node.specifiers && node.specifiers.length) {
for (var specifier of (node.specifiers: Array)) {
for (let specifier of (node.specifiers: Array)) {
add(specifier);
}
} else if (node.declaration) {
@@ -275,7 +249,7 @@ export default class Scope {
} else if (t.isCallExpression(node)) {
add(node.callee);
} else if (t.isObjectExpression(node) || t.isObjectPattern(node)) {
for (var prop of (node.properties: Array)) {
for (let prop of (node.properties: Array)) {
add(prop.key || prop.argument);
}
}
@@ -283,7 +257,7 @@ export default class Scope {
add(node);
var id = parts.join("$");
let id = parts.join("$");
id = id.replace(/^_/, "") || defaultName || "ref";
return this.generateUidIdentifier(id.slice(0, 20));
@@ -305,7 +279,7 @@ export default class Scope {
}
if (t.isIdentifier(node)) {
var binding = this.getBinding(node.name);
let binding = this.getBinding(node.name);
if (binding) {
return binding.constant;
} else {
@@ -324,7 +298,7 @@ export default class Scope {
if (this.isStatic(node)) {
return null;
} else {
var id = this.generateUidIdentifierBasedOnNode(node);
let id = this.generateUidIdentifierBasedOnNode(node);
if (!dontPush) this.push({ id });
return id;
}
@@ -337,7 +311,7 @@ export default class Scope {
// ignore hoisted functions if there's also a local let
if (kind === "hoisted" && local.kind === "let") return;
var duplicate = false;
let duplicate = false;
// don't allow duplicate bindings to exist alongside
if (!duplicate) duplicate = kind === "let" || local.kind === "let" || local.kind === "const" || local.kind === "module";
@@ -346,7 +320,7 @@ export default class Scope {
if (!duplicate) duplicate = local.kind === "param" && (kind === "let" || kind === "const");
if (duplicate) {
throw this.hub.file.errorWithNode(id, messages.get("scopeDuplicateDeclaration", name), TypeError);
throw this.hub.file.buildCodeFrameError(id, messages.get("scopeDuplicateDeclaration", name), TypeError);
}
}
@@ -366,16 +340,17 @@ export default class Scope {
}
dump() {
var sep = repeating("-", 60);
let sep = repeating("-", 60);
console.log(sep);
var scope = this;
let scope = this;
do {
console.log("#", scope.block.type);
for (var name in scope.bindings) {
var binding = scope.bindings[name];
for (let name in scope.bindings) {
let binding = scope.bindings[name];
console.log(" -", name, {
constant: binding.constant,
references: binding.references,
violations: binding.constantViolations.length,
kind: binding.kind
});
}
@@ -384,10 +359,10 @@ export default class Scope {
}
toArray(node: Object, i?: number) {
var file = this.hub.file;
let file = this.hub.file;
if (t.isIdentifier(node)) {
var binding = this.getBinding(node.name);
let binding = this.getBinding(node.name);
if (binding && binding.constant && binding.path.isGenericType("Array")) return node;
}
@@ -396,17 +371,29 @@ export default class Scope {
}
if (t.isIdentifier(node, { name: "arguments" })) {
return t.callExpression(t.memberExpression(file.addHelper("slice"), t.identifier("call")), [node]);
return t.callExpression(
t.memberExpression(
t.memberExpression(
t.memberExpression(
t.identifier("Array"),
t.identifier("prototype")
),
t.identifier("slice")
),
t.identifier("call")
),
[node]
);
}
var helperName = "to-array";
var args = [node];
let helperName = "toArray";
let args = [node];
if (i === true) {
helperName = "to-consumable-array";
helperName = "toConsumableArray";
} else if (i) {
args.push(t.literal(i));
helperName = "sliced-to-array";
if (this.hub.file.isLoose("es6.forOf")) helperName += "-loose";
args.push(t.numberLiteral(i));
helperName = "slicedToArray";
// TODO if (this.hub.file.isLoose("es6.forOf")) helperName += "-loose";
}
return t.callExpression(file.addHelper(helperName), args);
}
@@ -415,17 +402,17 @@ export default class Scope {
if (path.isLabeledStatement()) {
this.registerBinding("label", path);
} else if (path.isFunctionDeclaration()) {
this.registerBinding("hoisted", path);
this.registerBinding("hoisted", path.get("id"));
} else if (path.isVariableDeclaration()) {
var declarations = path.get("declarations");
let declarations = path.get("declarations");
for (let declar of (declarations: Array)) {
this.registerBinding(path.node.kind, declar);
}
} else if (path.isClassDeclaration()) {
this.registerBinding("let", path);
} else if (path.isImportDeclaration()) {
var specifiers = path.get("specifiers");
for (var specifier of (specifiers: Array)) {
let specifiers = path.get("specifiers");
for (let specifier of (specifiers: Array)) {
this.registerBinding("module", specifier);
}
} else if (path.isExportDeclaration()) {
@@ -446,11 +433,11 @@ export default class Scope {
}
}
registerConstantViolation(root: NodePath, left: NodePath, right: NodePath) {
var ids = left.getBindingIdentifiers();
for (var name in ids) {
var binding = this.getBinding(name);
if (binding) binding.reassign(root, left, right);
registerConstantViolation(path: NodePath) {
let ids = path.getBindingIdentifiers();
for (let name in ids) {
let binding = this.getBinding(name);
if (binding) binding.reassign(path);
}
}
@@ -458,19 +445,19 @@ export default class Scope {
if (!kind) throw new ReferenceError("no `kind`");
if (path.isVariableDeclaration()) {
var declarators = path.get("declarations");
for (var declar of (declarators: Array)) {
let declarators: Array<NodePath> = path.get("declarations");
for (let declar of declarators) {
this.registerBinding(kind, declar);
}
return;
}
var parent = this.getProgramParent();
var ids = path.getBindingIdentifiers(true);
let parent = this.getProgramParent();
let ids = path.getBindingIdentifiers(true);
for (var name in ids) {
for (var id of (ids[name]: Array)) {
var local = this.getOwnBinding(name);
for (let name in ids) {
for (let id of (ids[name]: Array<Object>)) {
let local = this.getOwnBinding(name);
if (local) {
// same identifier so continue safely as we're likely trying to register it
// multiple times
@@ -497,7 +484,7 @@ export default class Scope {
}
hasUid(name): boolean {
var scope = this;
let scope = this;
do {
if (scope.uids[name]) return true;
@@ -507,7 +494,7 @@ export default class Scope {
}
hasGlobal(name: string): boolean {
var scope = this;
let scope = this;
do {
if (scope.globals[name]) return true;
@@ -517,7 +504,7 @@ export default class Scope {
}
hasReference(name: string): boolean {
var scope = this;
let scope = this;
do {
if (scope.references[name]) return true;
@@ -528,29 +515,39 @@ export default class Scope {
isPure(node, constantsOnly?: boolean) {
if (t.isIdentifier(node)) {
var binding = this.getBinding(node.name);
let binding = this.getBinding(node.name);
if (!binding) return false;
if (constantsOnly) return binding.constant;
return true;
} else if (t.isClass(node)) {
return !node.superClass || this.isPure(node.superClass, constantsOnly);
if (node.superClass && !this.isPure(node.superClass, constantsOnly)) return false;
return this.isPure(node.body, constantsOnly);
} else if (t.isClassBody(node)) {
for (let method of node.body) {
if (!this.isPure(method, constantsOnly)) return false;
}
return true;
} else if (t.isBinary(node)) {
return this.isPure(node.left, constantsOnly) && this.isPure(node.right, constantsOnly);
} else if (t.isArrayExpression(node)) {
for (var elem of (node.elements: Array)) {
for (let elem of (node.elements: Array<Object>)) {
if (!this.isPure(elem, constantsOnly)) return false;
}
return true;
} else if (t.isObjectExpression(node)) {
for (var prop of (node.properties: Array)) {
for (let prop of (node.properties: Array<Object>)) {
if (!this.isPure(prop, constantsOnly)) return false;
}
return true;
} else if (t.isProperty(node)) {
} else if (t.isClassMethod(node)) {
if (node.computed && !this.isPure(node.key, constantsOnly)) return false;
if (node.kind === "get" || node.kind === "set") return false;
return true;
} else if (t.isClassProperty(node)) {
if (node.computed && !this.isPure(node.key, constantsOnly)) return false;
return this.isPure(node.value, constantsOnly);
} else {
return t.isPure(node);
return t.isPureish(node);
}
}
@@ -567,9 +564,9 @@ export default class Scope {
*/
getData(key) {
var scope = this;
let scope = this;
do {
var data = scope.data[key];
let data = scope.data[key];
if (data != null) return data;
} while(scope = scope.parent);
}
@@ -580,9 +577,9 @@ export default class Scope {
*/
removeData(key) {
var scope = this;
let scope = this;
do {
var data = scope.data[key];
let data = scope.data[key];
if (data != null) scope.data[key] = null;
} while(scope = scope.parent);
}
@@ -592,14 +589,14 @@ export default class Scope {
}
crawl() {
var path = this.path;
let path = this.path;
//
var info = this.block._scopeInfo;
let info = this.block[SCOPE_INFO_CACHE_KEY];
if (info) return extend(this, info);
info = this.block._scopeInfo = {
info = this.block[SCOPE_INFO_CACHE_KEY] = {
references: Object.create(null),
bindings: Object.create(null),
globals: Object.create(null),
@@ -607,13 +604,13 @@ export default class Scope {
data: Object.create(null),
};
extend(this, info);
Object.assign(this, info);
// ForStatement - left, init
if (path.isLoop()) {
for (let key of (t.FOR_INIT_KEYS: Array)) {
var node = path.get(key);
for (let key of (t.FOR_INIT_KEYS: Array<string>)) {
let node = path.get(key);
if (node.isBlockScoped()) this.registerBinding(node.node.kind, node);
}
}
@@ -621,22 +618,20 @@ export default class Scope {
// FunctionExpression - id
if (path.isFunctionExpression() && path.has("id")) {
if (!t.isProperty(path.parent, { method: true })) {
this.registerBinding("var", path);
}
this.registerBinding("local", path);
}
// Class
if (path.isClassExpression() && path.has("id")) {
this.registerBinding("var", path);
this.registerBinding("local", path);
}
// Function - params, rest
if (path.isFunction()) {
var params = path.get("params");
for (let param of (params: Array)) {
let params: Array<NodePath> = path.get("params");
for (let param of params) {
this.registerBinding("param", param);
}
}
@@ -647,24 +642,61 @@ export default class Scope {
this.registerBinding("let", path);
}
// ComprehensionExpression - blocks
if (path.isComprehensionExpression()) {
this.registerBinding("let", path);
}
// Program
var parent = this.getProgramParent();
let parent = this.getProgramParent();
if (parent.crawling) return;
let state = {
references: [],
constantViolations: [],
assignments: [],
};
this.crawling = true;
path.traverse(collectorVisitor);
path.traverse(collectorVisitor, state);
this.crawling = false;
// register assignments
for (let path of state.assignments) {
// register undeclared bindings as globals
let ids = path.getBindingIdentifiers();
let programParent;
for (let name in ids) {
if (path.scope.getBinding(name)) continue;
programParent = programParent || path.scope.getProgramParent();
programParent.addGlobal(ids[name]);
}
// register as constant violation
path.scope.registerConstantViolation(path);
}
// register references
for (let ref of state.references) {
let binding = ref.scope.getBinding(ref.node.name);
if (binding) {
binding.reference(ref);
} else {
ref.scope.getProgramParent().addGlobal(ref.node);
}
}
// register constant violations
for (let path of state.constantViolations) {
path.scope.registerConstantViolation(path);
}
}
push(opts: Object) {
var path = this.path;
push(opts: {
id: Object;
init: ?Object;
unique: ?boolean;
_blockHoist: ?number;
kind: "var" | "let";
}) {
let path = this.path;
if (path.isSwitchStatement()) {
path = this.getFunctionParent().path;
@@ -679,25 +711,23 @@ export default class Scope {
path = this.getBlockParent().path;
}
var unique = opts.unique;
var kind = opts.kind || "var";
var blockHoist = opts._blockHoist == null ? 2 : opts._blockHoist;
let unique = opts.unique;
let kind = opts.kind || "var";
let blockHoist = opts._blockHoist == null ? 2 : opts._blockHoist;
var dataKey = `declaration:${kind}:${blockHoist}`;
var declarPath = !unique && path.getData(dataKey);
let dataKey = `declaration:${kind}:${blockHoist}`;
let declarPath = !unique && path.getData(dataKey);
if (!declarPath) {
var declar = t.variableDeclaration(kind, []);
let declar = t.variableDeclaration(kind, []);
declar._generated = true;
declar._blockHoist = blockHoist;
this.hub.file.attachAuxiliaryComment(declar);
[declarPath] = path.unshiftContainer("body", [declar]);
if (!unique) path.setData(dataKey, declarPath);
}
var declarator = t.variableDeclarator(opts.id, opts.init);
let declarator = t.variableDeclarator(opts.id, opts.init);
declarPath.node.declarations.push(declarator);
this.registerBinding(kind, declarPath.get("declarations").pop());
}
@@ -707,7 +737,7 @@ export default class Scope {
*/
getProgramParent() {
var scope = this;
let scope = this;
do {
if (scope.path.isProgram()) {
return scope;
@@ -722,7 +752,7 @@ export default class Scope {
*/
getFunctionParent() {
var scope = this;
let scope = this;
do {
if (scope.path.isFunctionParent()) {
return scope;
@@ -737,7 +767,7 @@ export default class Scope {
*/
getBlockParent() {
var scope = this;
let scope = this;
do {
if (scope.path.isBlockParent()) {
return scope;
@@ -751,9 +781,9 @@ export default class Scope {
*/
getAllBindings(): Object {
var ids = Object.create(null);
let ids = Object.create(null);
var scope = this;
let scope = this;
do {
defaults(ids, scope.bindings);
scope = scope.parent;
@@ -767,13 +797,13 @@ export default class Scope {
*/
getAllBindingsOfKind(): Object {
var ids = Object.create(null);
let ids = Object.create(null);
for (let kind of (arguments: Array)) {
var scope = this;
let scope = this;
do {
for (var name in scope.bindings) {
var binding = scope.bindings[name];
for (let name in scope.bindings) {
let binding = scope.bindings[name];
if (binding.kind === kind) ids[name] = binding;
}
scope = scope.parent;
@@ -788,10 +818,10 @@ export default class Scope {
}
getBinding(name: string) {
var scope = this;
let scope = this;
do {
var binding = scope.getOwnBinding(name);
let binding = scope.getOwnBinding(name);
if (binding) return binding;
} while (scope = scope.parent);
}
@@ -801,12 +831,12 @@ export default class Scope {
}
getBindingIdentifier(name: string) {
var info = this.getBinding(name);
let info = this.getBinding(name);
return info && info.identifier;
}
getOwnBindingIdentifier(name: string) {
var binding = this.bindings[name];
let binding = this.bindings[name];
return binding && binding.identifier;
}
@@ -833,7 +863,7 @@ export default class Scope {
*/
moveBindingTo(name, scope) {
var info = this.getBinding(name);
let info = this.getBinding(name);
if (info) {
info.scope.removeOwnBinding(name);
info.scope = scope;
@@ -847,13 +877,13 @@ export default class Scope {
removeBinding(name: string) {
// clear literal binding
var info = this.getBinding(name);
let info = this.getBinding(name);
if (info) {
info.scope.removeOwnBinding(name);
}
// clear uids with this name - https://github.com/babel/babel/issues/2101
var scope = this;
let scope = this;
do {
if (scope.uids[name]) {
scope.uids[name] = false;

View File

@@ -37,7 +37,7 @@ export default class Renamer {
binding: Binding;
maybeConvertFromExportDeclaration(parentDeclar) {
let exportDeclar = parentDeclar && parentDeclar.parentPath.isExportDeclaration() && parentDeclar.parentPath;
let exportDeclar = parentDeclar.parentPath.isExportDeclaration() && parentDeclar.parentPath;
if (!exportDeclar) return;
// build specifiers that point back to this export declaration
@@ -63,6 +63,8 @@ export default class Renamer {
}
maybeConvertFromClassFunctionDeclaration(path) {
return; // TODO
// retain the `name` of a class/function declaration
if (!path.isFunctionDeclaration() && !path.isClassDeclaration()) return;
@@ -77,6 +79,8 @@ export default class Renamer {
}
maybeConvertFromClassFunctionExpression(path) {
return; // TODO
// retain the `name` of a class/function expression
if (!path.isFunctionExpression() && !path.isClassExpression()) return;
@@ -96,7 +100,9 @@ export default class Renamer {
let { scope, path } = binding;
let parentDeclar = path.find((path) => path.isDeclaration() || path.isFunctionExpression());
this.maybeConvertFromExportDeclaration(parentDeclar);
if (parentDeclar) {
this.maybeConvertFromExportDeclaration(parentDeclar);
}
scope.traverse(block || scope.block, renameVisitor, this);
@@ -111,7 +117,9 @@ export default class Renamer {
// todo: hoist and convert function to a let
}
this.maybeConvertFromClassFunctionDeclaration(parentDeclar);
this.maybeConvertFromClassFunctionExpression(parentDeclar);
if (parentDeclar) {
this.maybeConvertFromClassFunctionDeclaration(parentDeclar);
this.maybeConvertFromClassFunctionExpression(parentDeclar);
}
}
}

View File

@@ -1,3 +1,5 @@
/* @flow */
import * as virtualTypes from "./path/lib/virtual-types";
import * as messages from "babel-messages";
import * as t from "babel-types";
@@ -22,22 +24,6 @@ export function explode(visitor) {
}
}
// normalise colons
for (let nodeType in visitor) {
if (shouldIgnoreKey(nodeType)) continue;
let parts = nodeType.split(":");
if (parts.length === 1) continue;
let fns = visitor[nodeType];
delete visitor[nodeType];
nodeType = parts[0];
visitor[nodeType] = visitor[nodeType] || {};
visitor[nodeType][parts[1]] = fns;
}
// verify data structure
verify(visitor);
@@ -55,7 +41,7 @@ export function explode(visitor) {
for (let nodeType of (Object.keys(visitor): Array)) {
if (shouldIgnoreKey(nodeType)) continue;
var wrapper = virtualTypes[nodeType];
let wrapper = virtualTypes[nodeType];
if (!wrapper) continue;
// wrap all the functions
@@ -87,14 +73,14 @@ export function explode(visitor) {
let fns = visitor[nodeType];
var aliases = t.FLIPPED_ALIAS_KEYS[nodeType];
let aliases = t.FLIPPED_ALIAS_KEYS[nodeType];
if (!aliases) continue;
// clear it from the visitor
delete visitor[nodeType];
for (var alias of (aliases: Array)) {
var existing = visitor[alias];
for (let alias of (aliases: Array)) {
let existing = visitor[alias];
if (existing) {
mergePair(existing, fns);
} else {
@@ -119,16 +105,16 @@ export function verify(visitor) {
throw new Error(messages.get("traverseVerifyRootFunction"));
}
for (var nodeType in visitor) {
for (let nodeType in visitor) {
if (shouldIgnoreKey(nodeType)) continue;
if (t.TYPES.indexOf(nodeType) < 0) {
throw new Error(messages.get("traverseVerifyNodeType", nodeType));
}
var visitors = visitor[nodeType];
let visitors = visitor[nodeType];
if (typeof visitors === "object") {
for (var visitorKey in visitors) {
for (let visitorKey in visitors) {
if (visitorKey === "enter" || visitorKey === "exit") continue;
throw new Error(messages.get("traverseVerifyVisitorProperty", nodeType, visitorKey));
}
@@ -138,15 +124,47 @@ export function verify(visitor) {
visitor._verified = true;
}
export function merge(visitors) {
var rootVisitor = {};
export function merge(visitors: Array, states: Array = []) {
let rootVisitor = {};
for (let i = 0; i < visitors.length; i++) {
let visitor = visitors[i];
let state = states[i];
for (var visitor of (visitors: Array)) {
explode(visitor);
for (var type in visitor) {
var nodeVisitor = rootVisitor[type] = rootVisitor[type] || {};
mergePair(nodeVisitor, visitor[type]);
for (let type in visitor) {
let visitorType = visitor[type];
// if we have state then overload the callbacks to take it
if (state) {
let oldVisitorType = visitorType;
visitorType = {};
for (let key in oldVisitorType) {
let fns = oldVisitorType[key];
// not an enter/exit array of callbacks
if (!Array.isArray(fns)) continue;
fns = fns.map(function (fn) {
if (typeof fn === "function") {
let newFn = function (path) {
return fn.call(state, path, state);
};
newFn.toString = () => fn.toString();
return newFn;
} else {
return fn;
}
});
visitorType[key] = fns;
}
}
let nodeVisitor = rootVisitor[type] = rootVisitor[type] || {};
mergePair(nodeVisitor, visitorType);
}
}
@@ -157,7 +175,7 @@ function ensureEntranceObjects(obj) {
for (let key in obj) {
if (shouldIgnoreKey(key)) continue;
var fns = obj[key];
let fns = obj[key];
if (typeof fns === "function") {
obj[key] = { enter: fns };
}
@@ -170,11 +188,13 @@ function ensureCallbackArrays(obj){
}
function wrapCheck(wrapper, fn) {
return function () {
if (wrapper.checkPath(this)) {
let newFn = function (path) {
if (wrapper.checkPath(path)) {
return fn.apply(this, arguments);
}
};
newFn.toString = () => fn.toString();
return newFn;
}
function shouldIgnoreKey(key) {
@@ -191,7 +211,7 @@ function shouldIgnoreKey(key) {
}
function mergePair(dest, src) {
for (var key in src) {
for (let key in src) {
dest[key] = [].concat(dest[key] || [], src[key]);
}
}