source: node_modules/@swagger-api/apidom-ast/cjs/traversal/visitor.cjs@ 65b6638

main
Last change on this file since 65b6638 was d24f17c, checked in by Aleksandar Panovski <apano77@…>, 15 months ago

Initial commit

  • Property mode set to 100644
File size: 14.8 KB
Line 
1"use strict";
2
3exports.__esModule = true;
4exports.visit = exports.mergeAll = exports.isNode = exports.getVisitFn = exports.getNodeType = exports.cloneNode = exports.BREAK = void 0;
5var _apidomError = require("@swagger-api/apidom-error");
6/**
7 * SPDX-FileCopyrightText: Copyright (c) GraphQL Contributors
8 *
9 * SPDX-License-Identifier: MIT
10 */
11
12// getVisitFn :: (Visitor, String, Boolean) -> Function
13const getVisitFn = (visitor, type, isLeaving) => {
14 const typeVisitor = visitor[type];
15 if (typeVisitor != null) {
16 if (!isLeaving && typeof typeVisitor === 'function') {
17 // { Type() {} }
18 return typeVisitor;
19 }
20 const typeSpecificVisitor = isLeaving ? typeVisitor.leave : typeVisitor.enter;
21 if (typeof typeSpecificVisitor === 'function') {
22 // { Type: { enter() {}, leave() {} } }
23 return typeSpecificVisitor;
24 }
25 } else {
26 const specificVisitor = isLeaving ? visitor.leave : visitor.enter;
27 if (specificVisitor != null) {
28 if (typeof specificVisitor === 'function') {
29 // { enter() {}, leave() {} }
30 return specificVisitor;
31 }
32 const specificTypeVisitor = specificVisitor[type];
33 if (typeof specificTypeVisitor === 'function') {
34 // { enter: { Type() {} }, leave: { Type() {} } }
35 return specificTypeVisitor;
36 }
37 }
38 }
39 return null;
40};
41exports.getVisitFn = getVisitFn;
42const BREAK = exports.BREAK = {};
43
44// getNodeType :: Node -> String
45const getNodeType = node => node == null ? void 0 : node.type;
46
47// isNode :: Node -> Boolean
48exports.getNodeType = getNodeType;
49const isNode = node => typeof getNodeType(node) === 'string';
50
51// cloneNode :: a -> a
52exports.isNode = isNode;
53const cloneNode = node => Object.create(Object.getPrototypeOf(node), Object.getOwnPropertyDescriptors(node));
54
55/**
56 * Creates a new visitor instance which delegates to many visitors to run in
57 * parallel. Each visitor will be visited for each node before moving on.
58 *
59 * If a prior visitor edits a node, no following visitors will see that node.
60 * `exposeEdits=true` can be used to exoise the edited node from the previous visitors.
61 */
62exports.cloneNode = cloneNode;
63const mergeAll = (visitors, {
64 visitFnGetter = getVisitFn,
65 nodeTypeGetter = getNodeType,
66 breakSymbol = BREAK,
67 deleteNodeSymbol = null,
68 skipVisitingNodeSymbol = false,
69 exposeEdits = false
70} = {}) => {
71 const skipSymbol = Symbol('skip');
72 const skipping = new Array(visitors.length).fill(skipSymbol);
73 return {
74 enter(node, ...rest) {
75 let currentNode = node;
76 let hasChanged = false;
77 for (let i = 0; i < visitors.length; i += 1) {
78 if (skipping[i] === skipSymbol) {
79 const visitFn = visitFnGetter(visitors[i], nodeTypeGetter(currentNode), false);
80 if (typeof visitFn === 'function') {
81 const result = visitFn.call(visitors[i], currentNode, ...rest);
82 if (result === skipVisitingNodeSymbol) {
83 skipping[i] = node;
84 } else if (result === breakSymbol) {
85 skipping[i] = breakSymbol;
86 } else if (result === deleteNodeSymbol) {
87 return result;
88 } else if (result !== undefined) {
89 if (exposeEdits) {
90 currentNode = result;
91 hasChanged = true;
92 } else {
93 return result;
94 }
95 }
96 }
97 }
98 }
99 return hasChanged ? currentNode : undefined;
100 },
101 leave(node, ...rest) {
102 for (let i = 0; i < visitors.length; i += 1) {
103 if (skipping[i] === skipSymbol) {
104 const visitFn = visitFnGetter(visitors[i], nodeTypeGetter(node), true);
105 if (typeof visitFn === 'function') {
106 const result = visitFn.call(visitors[i], node, ...rest);
107 if (result === breakSymbol) {
108 skipping[i] = breakSymbol;
109 } else if (result !== undefined && result !== skipVisitingNodeSymbol) {
110 return result;
111 }
112 }
113 } else if (skipping[i] === node) {
114 skipping[i] = skipSymbol;
115 }
116 }
117 return undefined;
118 }
119 };
120};
121
122/* eslint-disable no-continue, no-param-reassign */
123/**
124 * visit() will walk through an AST using a preorder depth first traversal, calling
125 * the visitor's enter function at each node in the traversal, and calling the
126 * leave function after visiting that node and all of its child nodes.
127 *
128 * By returning different values from the enter and leave functions, the
129 * behavior of the visitor can be altered, including skipping over a sub-tree of
130 * the AST (by returning false), editing the AST by returning a value or null
131 * to remove the value, or to stop the whole traversal by returning BREAK.
132 *
133 * When using visit() to edit an AST, the original AST will not be modified, and
134 * a new version of the AST with the changes applied will be returned from the
135 * visit function.
136 *
137 * const editedAST = visit(ast, {
138 * enter(node, key, parent, path, ancestors) {
139 * // @return
140 * // undefined: no action
141 * // false: skip visiting this node
142 * // BREAK: stop visiting altogether
143 * // null: delete this node
144 * // any value: replace this node with the returned value
145 * },
146 * leave(node, key, parent, path, ancestors) {
147 * // @return
148 * // undefined: no action
149 * // false: no action
150 * // BREAK: stop visiting altogether
151 * // null: delete this node
152 * // any value: replace this node with the returned value
153 * }
154 * });
155 *
156 * Alternatively to providing enter() and leave() functions, a visitor can
157 * instead provide functions named the same as the kinds of AST nodes, or
158 * enter/leave visitors at a named key, leading to four permutations of
159 * visitor API:
160 *
161 * 1) Named visitors triggered when entering a node a specific kind.
162 *
163 * visit(ast, {
164 * Kind(node) {
165 * // enter the "Kind" node
166 * }
167 * })
168 *
169 * 2) Named visitors that trigger upon entering and leaving a node of
170 * a specific kind.
171 *
172 * visit(ast, {
173 * Kind: {
174 * enter(node) {
175 * // enter the "Kind" node
176 * }
177 * leave(node) {
178 * // leave the "Kind" node
179 * }
180 * }
181 * })
182 *
183 * 3) Generic visitors that trigger upon entering and leaving any node.
184 *
185 * visit(ast, {
186 * enter(node) {
187 * // enter any node
188 * },
189 * leave(node) {
190 * // leave any node
191 * }
192 * })
193 *
194 * 4) Parallel visitors for entering and leaving nodes of a specific kind.
195 *
196 * visit(ast, {
197 * enter: {
198 * Kind(node) {
199 * // enter the "Kind" node
200 * }
201 * },
202 * leave: {
203 * Kind(node) {
204 * // leave the "Kind" node
205 * }
206 * }
207 * })
208 *
209 * @sig visit :: (Node, Visitor, Options)
210 * @sig Options = { keyMap: Object, state: Object }
211 */
212exports.mergeAll = mergeAll;
213const visit = (
214// @ts-ignore
215root,
216// @ts-ignore
217visitor, {
218 keyMap = null,
219 state = {},
220 breakSymbol = BREAK,
221 deleteNodeSymbol = null,
222 skipVisitingNodeSymbol = false,
223 visitFnGetter = getVisitFn,
224 nodeTypeGetter = getNodeType,
225 nodePredicate = isNode,
226 nodeCloneFn = cloneNode,
227 detectCycles = true
228} = {}) => {
229 const visitorKeys = keyMap || {};
230 let stack;
231 let inArray = Array.isArray(root);
232 let keys = [root];
233 let index = -1;
234 let parent;
235 let edits = [];
236 let node = root;
237 const path = [];
238 // @ts-ignore
239 const ancestors = [];
240 do {
241 index += 1;
242 const isLeaving = index === keys.length;
243 let key;
244 const isEdited = isLeaving && edits.length !== 0;
245 if (isLeaving) {
246 key = ancestors.length === 0 ? undefined : path.pop();
247 node = parent;
248 // @ts-ignore
249 parent = ancestors.pop();
250 if (isEdited) {
251 if (inArray) {
252 // @ts-ignore; creating clone
253 node = node.slice();
254 let editOffset = 0;
255 for (const [editKey, editValue] of edits) {
256 const arrayKey = editKey - editOffset;
257 if (editValue === deleteNodeSymbol) {
258 node.splice(arrayKey, 1);
259 editOffset += 1;
260 } else {
261 node[arrayKey] = editValue;
262 }
263 }
264 } else {
265 // creating clone
266 node = nodeCloneFn(node);
267 for (const [editKey, editValue] of edits) {
268 node[editKey] = editValue;
269 }
270 }
271 }
272 index = stack.index;
273 keys = stack.keys;
274 // @ts-ignore
275 edits = stack.edits;
276 // @ts-ignore
277 inArray = stack.inArray;
278 // @ts-ignore
279 stack = stack.prev;
280 } else if (parent !== deleteNodeSymbol && parent !== undefined) {
281 key = inArray ? index : keys[index];
282 node = parent[key];
283 if (node === deleteNodeSymbol || node === undefined) {
284 continue;
285 }
286 path.push(key);
287 }
288 let result;
289 if (!Array.isArray(node)) {
290 if (!nodePredicate(node)) {
291 throw new _apidomError.ApiDOMStructuredError(`Invalid AST Node: ${String(node)}`, {
292 node
293 });
294 }
295
296 // cycle detected; skipping over a sub-tree to avoid recursion
297 if (detectCycles && ancestors.includes(node)) {
298 path.pop();
299 continue;
300 }
301 // call appropriate visitor function if available
302 const visitFn = visitFnGetter(visitor, nodeTypeGetter(node), isLeaving);
303 if (visitFn) {
304 // assign state
305 for (const [stateKey, stateValue] of Object.entries(state)) {
306 visitor[stateKey] = stateValue;
307 }
308 // retrieve result
309 result = visitFn.call(visitor, node, key, parent, path, ancestors);
310 }
311 if (result === breakSymbol) {
312 break;
313 }
314 if (result === skipVisitingNodeSymbol) {
315 if (!isLeaving) {
316 path.pop();
317 continue;
318 }
319 } else if (result !== undefined) {
320 edits.push([key, result]);
321 if (!isLeaving) {
322 if (nodePredicate(result)) {
323 node = result;
324 } else {
325 path.pop();
326 continue;
327 }
328 }
329 }
330 }
331 if (result === undefined && isEdited) {
332 edits.push([key, node]);
333 }
334 if (!isLeaving) {
335 var _visitorKeys$nodeType;
336 stack = {
337 inArray,
338 index,
339 keys,
340 edits,
341 prev: stack
342 };
343 inArray = Array.isArray(node);
344 // @ts-ignore
345 keys = inArray ? node : (_visitorKeys$nodeType = visitorKeys[nodeTypeGetter(node)]) != null ? _visitorKeys$nodeType : [];
346 index = -1;
347 edits = [];
348 if (parent !== deleteNodeSymbol && parent !== undefined) {
349 ancestors.push(parent);
350 }
351 parent = node;
352 }
353 } while (stack !== undefined);
354 if (edits.length !== 0) {
355 return edits[edits.length - 1][1]; // @TODO(vladimir.gorej@gmail.com): can be replaced by Array.prototype.at in future
356 }
357 return root;
358};
359
360/**
361 * Asynchronous version of visit.
362 */
363// @ts-ignore
364exports.visit = visit;
365visit[Symbol.for('nodejs.util.promisify.custom')] = async (
366// @ts-ignore
367root,
368// @ts-ignore
369visitor, {
370 keyMap = null,
371 state = {},
372 breakSymbol = BREAK,
373 deleteNodeSymbol = null,
374 skipVisitingNodeSymbol = false,
375 visitFnGetter = getVisitFn,
376 nodeTypeGetter = getNodeType,
377 nodePredicate = isNode,
378 nodeCloneFn = cloneNode,
379 detectCycles = true
380} = {}) => {
381 const visitorKeys = keyMap || {};
382 let stack;
383 let inArray = Array.isArray(root);
384 let keys = [root];
385 let index = -1;
386 let parent;
387 let edits = [];
388 let node = root;
389 const path = [];
390 // @ts-ignore
391 const ancestors = [];
392 do {
393 index += 1;
394 const isLeaving = index === keys.length;
395 let key;
396 const isEdited = isLeaving && edits.length !== 0;
397 if (isLeaving) {
398 key = ancestors.length === 0 ? undefined : path.pop();
399 node = parent;
400 // @ts-ignore
401 parent = ancestors.pop();
402 if (isEdited) {
403 if (inArray) {
404 // @ts-ignore; creating clone
405 node = node.slice();
406 let editOffset = 0;
407 for (const [editKey, editValue] of edits) {
408 const arrayKey = editKey - editOffset;
409 if (editValue === deleteNodeSymbol) {
410 node.splice(arrayKey, 1);
411 editOffset += 1;
412 } else {
413 node[arrayKey] = editValue;
414 }
415 }
416 } else {
417 // creating clone
418 node = nodeCloneFn(node);
419 for (const [editKey, editValue] of edits) {
420 node[editKey] = editValue;
421 }
422 }
423 }
424 index = stack.index;
425 keys = stack.keys;
426 // @ts-ignore
427 edits = stack.edits;
428 // @ts-ignore
429 inArray = stack.inArray;
430 // @ts-ignore
431 stack = stack.prev;
432 } else if (parent !== deleteNodeSymbol && parent !== undefined) {
433 key = inArray ? index : keys[index];
434 node = parent[key];
435 if (node === deleteNodeSymbol || node === undefined) {
436 continue;
437 }
438 path.push(key);
439 }
440 let result;
441 if (!Array.isArray(node)) {
442 if (!nodePredicate(node)) {
443 throw new _apidomError.ApiDOMStructuredError(`Invalid AST Node: ${String(node)}`, {
444 node
445 });
446 }
447
448 // cycle detected; skipping over a sub-tree to avoid recursion
449 if (detectCycles && ancestors.includes(node)) {
450 path.pop();
451 continue;
452 }
453 const visitFn = visitFnGetter(visitor, nodeTypeGetter(node), isLeaving);
454 if (visitFn) {
455 // assign state
456 for (const [stateKey, stateValue] of Object.entries(state)) {
457 visitor[stateKey] = stateValue;
458 }
459
460 // retrieve result
461 result = await visitFn.call(visitor, node, key, parent, path, ancestors); // eslint-disable-line no-await-in-loop
462 }
463 if (result === breakSymbol) {
464 break;
465 }
466 if (result === skipVisitingNodeSymbol) {
467 if (!isLeaving) {
468 path.pop();
469 continue;
470 }
471 } else if (result !== undefined) {
472 edits.push([key, result]);
473 if (!isLeaving) {
474 if (nodePredicate(result)) {
475 node = result;
476 } else {
477 path.pop();
478 continue;
479 }
480 }
481 }
482 }
483 if (result === undefined && isEdited) {
484 edits.push([key, node]);
485 }
486 if (!isLeaving) {
487 var _visitorKeys$nodeType2;
488 stack = {
489 inArray,
490 index,
491 keys,
492 edits,
493 prev: stack
494 };
495 inArray = Array.isArray(node);
496 // @ts-ignore
497 keys = inArray ? node : (_visitorKeys$nodeType2 = visitorKeys[nodeTypeGetter(node)]) != null ? _visitorKeys$nodeType2 : [];
498 index = -1;
499 edits = [];
500 if (parent !== deleteNodeSymbol && parent !== undefined) {
501 ancestors.push(parent);
502 }
503 parent = node;
504 }
505 } while (stack !== undefined);
506 if (edits.length !== 0) {
507 return edits[edits.length - 1][1]; // @TODO(vladimir.gorej@gmail.com): can be replaced by Array.prototype.at in future
508 }
509 return root;
510};
511
512/* eslint-enable */
Note: See TracBrowser for help on using the repository browser.