1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128 | 1
1
1
157264
157263
157263
157263
157250
157250
157250
155046
155046
143878
152010
152010
151984
151981
151981
292145
151981
151981
151965
151746
151746
151731
140399
143878
43403
51535
43391
51587
100475
100456
1
1
1
1
6271
1
607
1
1
7
7
7
7
6
5
42
2
2
5
| var VISITOR_KEYS = require("./visitor-keys");
var _ = require("lodash");
var traverse = module.exports = function (parent, callbacks, blacklistTypes) {
// falsy node
if (!parent) return;
// array of nodes
Iif (_.isArray(parent)) {
_.each(parent, function (node) {
traverse(node, callbacks, blacklistTypes);
});
return;
}
// unknown node type to traverse
var keys = VISITOR_KEYS[parent.type];
if (!keys) return;
// blacklist these node types from being traversed
blacklistTypes = blacklistTypes || [];
// normalise callbacks
if (_.isFunction(callbacks)) callbacks = { enter: callbacks };
_.each(keys, function (key) {
var nodes = parent[key];
if (!nodes) return;
var handle = function (obj, key) {
var node = obj[key];
if (!node) return;
// type is blacklisted
if (_.contains(blacklistTypes, node.type)) return;
var result;
// replace node
var maybeReplace = function (result) {
if (result != null) obj[key] = result;
};
// enter
Eif (callbacks.enter) {
result = callbacks.enter(node, parent, obj, key);
// stop iteration
if (result === false) return;
maybeReplace(result);
}
// traverse node
traverse(node, callbacks, blacklistTypes);
// exit
if (callbacks.exit) {
maybeReplace(callbacks.exit(node, parent, obj, key));
}
};
if (_.isArray(nodes)) {
_.each(nodes, function (node, i) {
handle(nodes, i);
});
// remove deleted nodes
parent[key] = _.flatten(parent[key]).filter(function (node) {
return node !== traverse.Delete;
});
} else {
handle(parent, key);
if (parent[key] === traverse.Delete) {
throw new Error("trying to delete property " + key + " from " +
parent.type + " but can't because it's required");
}
}
});
};
traverse.FUNCTION_TYPES = ["ArrowFunctionExpression", "FunctionDeclaration", "FunctionExpression"];
traverse.aliases = {
ArrowFunctionExpression: ["Function"],
FunctionDeclaration: ["Function"],
FunctionExpression: ["Function"]
};
traverse.isFunction = function (node) {
return _.contains(traverse.FUNCTION_TYPES, node.type);
};
traverse.isPattern = function (node) {
return node.type === "ArrayPattern" || node.type === "ObjectPattern";
};
traverse.Delete = {};
traverse.hasType = function (tree, type, blacklistTypes) {
blacklistTypes = [].concat(blacklistTypes || []);
var has = false;
Iif (_.isArray(tree)) {
// array of nodes, find the first
return !!_.find(tree, function (node) {
return traverse.hasType(node, type, blacklistTypes);
});
} else {
// the node we're searching in is blacklisted
if (_.contains(blacklistTypes, tree.type)) return false;
// the type we're looking for is the same as the passed node
if (tree.type === type) return true;
traverse(tree, function (node) {
if (node.type === type) {
has = true;
return false;
}
}, blacklistTypes);
}
return has;
};
|