1 | var hasOwnProperty = Object.prototype.hasOwnProperty;
|
---|
2 | var noop = function() {};
|
---|
3 |
|
---|
4 | function ensureFunction(value) {
|
---|
5 | return typeof value === 'function' ? value : noop;
|
---|
6 | }
|
---|
7 |
|
---|
8 | function invokeForType(fn, type) {
|
---|
9 | return function(node, item, list) {
|
---|
10 | if (node.type === type) {
|
---|
11 | fn.call(this, node, item, list);
|
---|
12 | }
|
---|
13 | };
|
---|
14 | }
|
---|
15 |
|
---|
16 | function getWalkersFromStructure(name, nodeType) {
|
---|
17 | var structure = nodeType.structure;
|
---|
18 | var walkers = [];
|
---|
19 |
|
---|
20 | for (var key in structure) {
|
---|
21 | if (hasOwnProperty.call(structure, key) === false) {
|
---|
22 | continue;
|
---|
23 | }
|
---|
24 |
|
---|
25 | var fieldTypes = structure[key];
|
---|
26 | var walker = {
|
---|
27 | name: key,
|
---|
28 | type: false,
|
---|
29 | nullable: false
|
---|
30 | };
|
---|
31 |
|
---|
32 | if (!Array.isArray(structure[key])) {
|
---|
33 | fieldTypes = [structure[key]];
|
---|
34 | }
|
---|
35 |
|
---|
36 | for (var i = 0; i < fieldTypes.length; i++) {
|
---|
37 | var fieldType = fieldTypes[i];
|
---|
38 | if (fieldType === null) {
|
---|
39 | walker.nullable = true;
|
---|
40 | } else if (typeof fieldType === 'string') {
|
---|
41 | walker.type = 'node';
|
---|
42 | } else if (Array.isArray(fieldType)) {
|
---|
43 | walker.type = 'list';
|
---|
44 | }
|
---|
45 | }
|
---|
46 |
|
---|
47 | if (walker.type) {
|
---|
48 | walkers.push(walker);
|
---|
49 | }
|
---|
50 | }
|
---|
51 |
|
---|
52 | if (walkers.length) {
|
---|
53 | return {
|
---|
54 | context: nodeType.walkContext,
|
---|
55 | fields: walkers
|
---|
56 | };
|
---|
57 | }
|
---|
58 |
|
---|
59 | return null;
|
---|
60 | }
|
---|
61 |
|
---|
62 | function getTypesFromConfig(config) {
|
---|
63 | var types = {};
|
---|
64 |
|
---|
65 | for (var name in config.node) {
|
---|
66 | if (hasOwnProperty.call(config.node, name)) {
|
---|
67 | var nodeType = config.node[name];
|
---|
68 |
|
---|
69 | if (!nodeType.structure) {
|
---|
70 | throw new Error('Missed `structure` field in `' + name + '` node type definition');
|
---|
71 | }
|
---|
72 |
|
---|
73 | types[name] = getWalkersFromStructure(name, nodeType);
|
---|
74 | }
|
---|
75 | }
|
---|
76 |
|
---|
77 | return types;
|
---|
78 | }
|
---|
79 |
|
---|
80 | function createTypeIterator(config, reverse) {
|
---|
81 | var fields = config.fields.slice();
|
---|
82 | var contextName = config.context;
|
---|
83 | var useContext = typeof contextName === 'string';
|
---|
84 |
|
---|
85 | if (reverse) {
|
---|
86 | fields.reverse();
|
---|
87 | }
|
---|
88 |
|
---|
89 | return function(node, context, walk, walkReducer) {
|
---|
90 | var prevContextValue;
|
---|
91 |
|
---|
92 | if (useContext) {
|
---|
93 | prevContextValue = context[contextName];
|
---|
94 | context[contextName] = node;
|
---|
95 | }
|
---|
96 |
|
---|
97 | for (var i = 0; i < fields.length; i++) {
|
---|
98 | var field = fields[i];
|
---|
99 | var ref = node[field.name];
|
---|
100 |
|
---|
101 | if (!field.nullable || ref) {
|
---|
102 | if (field.type === 'list') {
|
---|
103 | var breakWalk = reverse
|
---|
104 | ? ref.reduceRight(walkReducer, false)
|
---|
105 | : ref.reduce(walkReducer, false);
|
---|
106 |
|
---|
107 | if (breakWalk) {
|
---|
108 | return true;
|
---|
109 | }
|
---|
110 | } else if (walk(ref)) {
|
---|
111 | return true;
|
---|
112 | }
|
---|
113 | }
|
---|
114 | }
|
---|
115 |
|
---|
116 | if (useContext) {
|
---|
117 | context[contextName] = prevContextValue;
|
---|
118 | }
|
---|
119 | };
|
---|
120 | }
|
---|
121 |
|
---|
122 | function createFastTraveralMap(iterators) {
|
---|
123 | return {
|
---|
124 | Atrule: {
|
---|
125 | StyleSheet: iterators.StyleSheet,
|
---|
126 | Atrule: iterators.Atrule,
|
---|
127 | Rule: iterators.Rule,
|
---|
128 | Block: iterators.Block
|
---|
129 | },
|
---|
130 | Rule: {
|
---|
131 | StyleSheet: iterators.StyleSheet,
|
---|
132 | Atrule: iterators.Atrule,
|
---|
133 | Rule: iterators.Rule,
|
---|
134 | Block: iterators.Block
|
---|
135 | },
|
---|
136 | Declaration: {
|
---|
137 | StyleSheet: iterators.StyleSheet,
|
---|
138 | Atrule: iterators.Atrule,
|
---|
139 | Rule: iterators.Rule,
|
---|
140 | Block: iterators.Block,
|
---|
141 | DeclarationList: iterators.DeclarationList
|
---|
142 | }
|
---|
143 | };
|
---|
144 | }
|
---|
145 |
|
---|
146 | module.exports = function createWalker(config) {
|
---|
147 | var types = getTypesFromConfig(config);
|
---|
148 | var iteratorsNatural = {};
|
---|
149 | var iteratorsReverse = {};
|
---|
150 | var breakWalk = Symbol('break-walk');
|
---|
151 | var skipNode = Symbol('skip-node');
|
---|
152 |
|
---|
153 | for (var name in types) {
|
---|
154 | if (hasOwnProperty.call(types, name) && types[name] !== null) {
|
---|
155 | iteratorsNatural[name] = createTypeIterator(types[name], false);
|
---|
156 | iteratorsReverse[name] = createTypeIterator(types[name], true);
|
---|
157 | }
|
---|
158 | }
|
---|
159 |
|
---|
160 | var fastTraversalIteratorsNatural = createFastTraveralMap(iteratorsNatural);
|
---|
161 | var fastTraversalIteratorsReverse = createFastTraveralMap(iteratorsReverse);
|
---|
162 |
|
---|
163 | var walk = function(root, options) {
|
---|
164 | function walkNode(node, item, list) {
|
---|
165 | var enterRet = enter.call(context, node, item, list);
|
---|
166 |
|
---|
167 | if (enterRet === breakWalk) {
|
---|
168 | debugger;
|
---|
169 | return true;
|
---|
170 | }
|
---|
171 |
|
---|
172 | if (enterRet === skipNode) {
|
---|
173 | return false;
|
---|
174 | }
|
---|
175 |
|
---|
176 | if (iterators.hasOwnProperty(node.type)) {
|
---|
177 | if (iterators[node.type](node, context, walkNode, walkReducer)) {
|
---|
178 | return true;
|
---|
179 | }
|
---|
180 | }
|
---|
181 |
|
---|
182 | if (leave.call(context, node, item, list) === breakWalk) {
|
---|
183 | return true;
|
---|
184 | }
|
---|
185 |
|
---|
186 | return false;
|
---|
187 | }
|
---|
188 |
|
---|
189 | var walkReducer = (ret, data, item, list) => ret || walkNode(data, item, list);
|
---|
190 | var enter = noop;
|
---|
191 | var leave = noop;
|
---|
192 | var iterators = iteratorsNatural;
|
---|
193 | var context = {
|
---|
194 | break: breakWalk,
|
---|
195 | skip: skipNode,
|
---|
196 |
|
---|
197 | root: root,
|
---|
198 | stylesheet: null,
|
---|
199 | atrule: null,
|
---|
200 | atrulePrelude: null,
|
---|
201 | rule: null,
|
---|
202 | selector: null,
|
---|
203 | block: null,
|
---|
204 | declaration: null,
|
---|
205 | function: null
|
---|
206 | };
|
---|
207 |
|
---|
208 | if (typeof options === 'function') {
|
---|
209 | enter = options;
|
---|
210 | } else if (options) {
|
---|
211 | enter = ensureFunction(options.enter);
|
---|
212 | leave = ensureFunction(options.leave);
|
---|
213 |
|
---|
214 | if (options.reverse) {
|
---|
215 | iterators = iteratorsReverse;
|
---|
216 | }
|
---|
217 |
|
---|
218 | if (options.visit) {
|
---|
219 | if (fastTraversalIteratorsNatural.hasOwnProperty(options.visit)) {
|
---|
220 | iterators = options.reverse
|
---|
221 | ? fastTraversalIteratorsReverse[options.visit]
|
---|
222 | : fastTraversalIteratorsNatural[options.visit];
|
---|
223 | } else if (!types.hasOwnProperty(options.visit)) {
|
---|
224 | throw new Error('Bad value `' + options.visit + '` for `visit` option (should be: ' + Object.keys(types).join(', ') + ')');
|
---|
225 | }
|
---|
226 |
|
---|
227 | enter = invokeForType(enter, options.visit);
|
---|
228 | leave = invokeForType(leave, options.visit);
|
---|
229 | }
|
---|
230 | }
|
---|
231 |
|
---|
232 | if (enter === noop && leave === noop) {
|
---|
233 | throw new Error('Neither `enter` nor `leave` walker handler is set or both aren\'t a function');
|
---|
234 | }
|
---|
235 |
|
---|
236 | walkNode(root);
|
---|
237 | };
|
---|
238 |
|
---|
239 | walk.break = breakWalk;
|
---|
240 | walk.skip = skipNode;
|
---|
241 |
|
---|
242 | walk.find = function(ast, fn) {
|
---|
243 | var found = null;
|
---|
244 |
|
---|
245 | walk(ast, function(node, item, list) {
|
---|
246 | if (fn.call(this, node, item, list)) {
|
---|
247 | found = node;
|
---|
248 | return breakWalk;
|
---|
249 | }
|
---|
250 | });
|
---|
251 |
|
---|
252 | return found;
|
---|
253 | };
|
---|
254 |
|
---|
255 | walk.findLast = function(ast, fn) {
|
---|
256 | var found = null;
|
---|
257 |
|
---|
258 | walk(ast, {
|
---|
259 | reverse: true,
|
---|
260 | enter: function(node, item, list) {
|
---|
261 | if (fn.call(this, node, item, list)) {
|
---|
262 | found = node;
|
---|
263 | return breakWalk;
|
---|
264 | }
|
---|
265 | }
|
---|
266 | });
|
---|
267 |
|
---|
268 | return found;
|
---|
269 | };
|
---|
270 |
|
---|
271 | walk.findAll = function(ast, fn) {
|
---|
272 | var found = [];
|
---|
273 |
|
---|
274 | walk(ast, function(node, item, list) {
|
---|
275 | if (fn.call(this, node, item, list)) {
|
---|
276 | found.push(node);
|
---|
277 | }
|
---|
278 | });
|
---|
279 |
|
---|
280 | return found;
|
---|
281 | };
|
---|
282 |
|
---|
283 | return walk;
|
---|
284 | };
|
---|