-
Notifications
You must be signed in to change notification settings - Fork 2
/
index.js
199 lines (173 loc) · 5.42 KB
/
index.js
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
/*!
* RegJSTraverse
* Copyright 2014 Juilan Viereck <http://jviereck.github.io/>
* Available under MIT license (see LICENSE file)
*/
(function() {
// Unique id
var BREAK = {};
var SKIP = {};
function TraverseState() {
this.__doBreak = false;
this.__doSkip = false;
}
TraverseState.prototype.skip = function() {
this.__doSkip = true;
}
TraverseState.prototype.didCallSkip = function() {
return this.__doSkip;
}
TraverseState.prototype.break = function() {
this.__doBreak = true;
}
TraverseState.prototype.didCallBreak = function() {
return this.__doBreak;
}
function ReplaceState() {
TraverseState.call(this);
this.__replaceWith = null;
}
ReplaceState.prototype = new TraverseState();
ReplaceState.prototype.replace = function(node) {
this.__replaceWith = node;
}
/**
* Process a single node.
* @returns {Boolean} Indicates if break was not called while processing `node`.
*/
function processNode(node, parent, enterFn, leaveFn, state) {
var i, res, doSkip = false, body, replaceWith = null, isCharacterClassRange;
// Call the `enter` function on the `node` if defined.
if (enterFn) {
res = enterFn(/* this=state here (see traverse fn) ,*/ node, parent);
if (state.__replaceWith) {
node = replaceWith = state.__replaceWith;
state.__replaceWith = null;
}
if (res === BREAK || state.__doBreak === true) {
state.__doBreak = true;
return false /* break was called */;
}
if (res === SKIP || state.__doSkip === true) {
doSkip = true;
}
}
// If the consumer did not skip the node during the call to `enter`, then
// process all the child nodes of the current node if there are any.
if (!doSkip) {
// The body to process is either the body on the node OR the min/max
// entry on the `characterClassRange`.
body = node.body;
isCharacterClassRange = node.type === 'characterClassRange';
if (isCharacterClassRange) {
body = [node.min, node.max];
}
if (body /* there are child nodes to inspect */) {
for (i = 0; i < body.length; i++) {
res = processNode(body[i], node, enterFn, leaveFn, state);
if (state.__replaceWith) {
if (isCharacterClassRange) {
if (i === 0) {
node.min = state.__replaceWith;
} else {
node.max = state.__replaceWith;
}
} else {
body[i] = state.__replaceWith;
}
state.__replaceWith = null;
}
if (!res) {
return false;
}
}
}
}
// Call the `leave` function on the `node` if defined.
if (leaveFn) {
res = leaveFn(/* this=state here (see traverse fn) ,*/ node, parent);
if (state.__replaceWith) {
replaceWith = state.__replaceWith;
}
if (res === BREAK || state.__doBreak === true) {
state.__doBreak = true;
return false /* break was called */;
}
}
// Reset the skip flag. Do this at the very end after calling `funcs.leave`
// as the leave function might invoke skip, which should have no effect.
state.__doSkip = false;
state.__replaceWith = replaceWith;
return true /* break was not called */;
}
function traverse(ast, funcs, customState) {
var enterFn, leaveFn;
var state = customState || new TraverseState();
// Bind the `state` as the first argument of the function. Binding the
// `this` variable instead of using `funcs.enterFn.call(state,...)` for
// performance reason.
if (funcs.enter) enterFn = funcs.enter.bind(state);
if (funcs.leave) leaveFn = funcs.leave.bind(state);
// Kick off the traversal from the top node.
processNode(ast, null, enterFn, leaveFn, state);
}
/**
* Replace a single node.
* @returns {Boolean} Indicates if break was not called while processing `node`.
*/
function replace(ast, funcs, customState) {
var enterFn, leaveFn, userEnterFn, userLeaveFn;
// Using a different kind of state here as in the `traverse` function.
// This state allows the call to `replace` and has a new property
// `this.__replaceWith`.
var state = customState || new ReplaceState();
// Wrap the enter and leave functions to move the return values onto the
// state. This is necessary, as the `processNode` function assumes to find
// the replacement node on the state object.
if (funcs.enter) {
userEnterFn = funcs.enter.bind(state);
enterFn = function(node, parent) {
var res = userEnterFn(node, parent);
// Move the return value onto the state.
if (res !== null && res !== undefined && res !== BREAK && res !== SKIP) {
this.__replaceWith = res;
}
return res;
}
enterFn = enterFn.bind(state);
}
if (funcs.leave) {
userLeaveFn = funcs.leave.bind(state);
leaveFn = function(node, parent) {
var res = userLeaveFn(node, parent);
// Move the return value onto the state.
if (res !== null && res !== undefined && res !== BREAK && res !== SKIP) {
this.__replaceWith = res;
}
return res;
}
leaveFn = leaveFn.bind(state);
}
processNode(ast, null, enterFn, leaveFn, state);
if (state.__replaceWith) {
return state.__replaceWith;
} else {
return ast;
}
}
var regjstraverse = {
traverse: traverse,
replace: replace,
VisitorOption: {
Break: BREAK,
Skip: SKIP
},
ReplaceState: ReplaceState,
TraverseState: TraverseState
}
if (typeof module !== 'undefined' && module.exports) {
module.exports = regjstraverse;
} else {
window.regjstraverse = regjstraverse;
}
}());