UNPKG

11.9 kBJavaScriptView Raw
1import inspect from '../jsutils/inspect';
2export var QueryDocumentKeys = {
3 Name: [],
4 Document: ['definitions'],
5 OperationDefinition: ['name', 'variableDefinitions', 'directives', 'selectionSet'],
6 VariableDefinition: ['variable', 'type', 'defaultValue', 'directives'],
7 Variable: ['name'],
8 SelectionSet: ['selections'],
9 Field: ['alias', 'name', 'arguments', 'directives', 'selectionSet'],
10 Argument: ['name', 'value'],
11 FragmentSpread: ['name', 'directives'],
12 InlineFragment: ['typeCondition', 'directives', 'selectionSet'],
13 FragmentDefinition: ['name', // Note: fragment variable definitions are experimental and may be changed
14 // or removed in the future.
15 'variableDefinitions', 'typeCondition', 'directives', 'selectionSet'],
16 IntValue: [],
17 FloatValue: [],
18 StringValue: [],
19 BooleanValue: [],
20 NullValue: [],
21 EnumValue: [],
22 ListValue: ['values'],
23 ObjectValue: ['fields'],
24 ObjectField: ['name', 'value'],
25 Directive: ['name', 'arguments'],
26 NamedType: ['name'],
27 ListType: ['type'],
28 NonNullType: ['type'],
29 SchemaDefinition: ['directives', 'operationTypes'],
30 OperationTypeDefinition: ['type'],
31 ScalarTypeDefinition: ['description', 'name', 'directives'],
32 ObjectTypeDefinition: ['description', 'name', 'interfaces', 'directives', 'fields'],
33 FieldDefinition: ['description', 'name', 'arguments', 'type', 'directives'],
34 InputValueDefinition: ['description', 'name', 'type', 'defaultValue', 'directives'],
35 InterfaceTypeDefinition: ['description', 'name', 'directives', 'fields'],
36 UnionTypeDefinition: ['description', 'name', 'directives', 'types'],
37 EnumTypeDefinition: ['description', 'name', 'directives', 'values'],
38 EnumValueDefinition: ['description', 'name', 'directives'],
39 InputObjectTypeDefinition: ['description', 'name', 'directives', 'fields'],
40 DirectiveDefinition: ['description', 'name', 'arguments', 'locations'],
41 SchemaExtension: ['directives', 'operationTypes'],
42 ScalarTypeExtension: ['name', 'directives'],
43 ObjectTypeExtension: ['name', 'interfaces', 'directives', 'fields'],
44 InterfaceTypeExtension: ['name', 'directives', 'fields'],
45 UnionTypeExtension: ['name', 'directives', 'types'],
46 EnumTypeExtension: ['name', 'directives', 'values'],
47 InputObjectTypeExtension: ['name', 'directives', 'fields']
48};
49export var BREAK = Object.freeze({});
50/**
51 * visit() will walk through an AST using a depth first traversal, calling
52 * the visitor's enter function at each node in the traversal, and calling the
53 * leave function after visiting that node and all of its child nodes.
54 *
55 * By returning different values from the enter and leave functions, the
56 * behavior of the visitor can be altered, including skipping over a sub-tree of
57 * the AST (by returning false), editing the AST by returning a value or null
58 * to remove the value, or to stop the whole traversal by returning BREAK.
59 *
60 * When using visit() to edit an AST, the original AST will not be modified, and
61 * a new version of the AST with the changes applied will be returned from the
62 * visit function.
63 *
64 * const editedAST = visit(ast, {
65 * enter(node, key, parent, path, ancestors) {
66 * // @return
67 * // undefined: no action
68 * // false: skip visiting this node
69 * // visitor.BREAK: stop visiting altogether
70 * // null: delete this node
71 * // any value: replace this node with the returned value
72 * },
73 * leave(node, key, parent, path, ancestors) {
74 * // @return
75 * // undefined: no action
76 * // false: no action
77 * // visitor.BREAK: stop visiting altogether
78 * // null: delete this node
79 * // any value: replace this node with the returned value
80 * }
81 * });
82 *
83 * Alternatively to providing enter() and leave() functions, a visitor can
84 * instead provide functions named the same as the kinds of AST nodes, or
85 * enter/leave visitors at a named key, leading to four permutations of
86 * visitor API:
87 *
88 * 1) Named visitors triggered when entering a node a specific kind.
89 *
90 * visit(ast, {
91 * Kind(node) {
92 * // enter the "Kind" node
93 * }
94 * })
95 *
96 * 2) Named visitors that trigger upon entering and leaving a node of
97 * a specific kind.
98 *
99 * visit(ast, {
100 * Kind: {
101 * enter(node) {
102 * // enter the "Kind" node
103 * }
104 * leave(node) {
105 * // leave the "Kind" node
106 * }
107 * }
108 * })
109 *
110 * 3) Generic visitors that trigger upon entering and leaving any node.
111 *
112 * visit(ast, {
113 * enter(node) {
114 * // enter any node
115 * },
116 * leave(node) {
117 * // leave any node
118 * }
119 * })
120 *
121 * 4) Parallel visitors for entering and leaving nodes of a specific kind.
122 *
123 * visit(ast, {
124 * enter: {
125 * Kind(node) {
126 * // enter the "Kind" node
127 * }
128 * },
129 * leave: {
130 * Kind(node) {
131 * // leave the "Kind" node
132 * }
133 * }
134 * })
135 */
136
137export function visit(root, visitor) {
138 var visitorKeys = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : QueryDocumentKeys;
139
140 /* eslint-disable no-undef-init */
141 var stack = undefined;
142 var inArray = Array.isArray(root);
143 var keys = [root];
144 var index = -1;
145 var edits = [];
146 var node = undefined;
147 var key = undefined;
148 var parent = undefined;
149 var path = [];
150 var ancestors = [];
151 var newRoot = root;
152 /* eslint-enable no-undef-init */
153
154 do {
155 index++;
156 var isLeaving = index === keys.length;
157 var isEdited = isLeaving && edits.length !== 0;
158
159 if (isLeaving) {
160 key = ancestors.length === 0 ? undefined : path[path.length - 1];
161 node = parent;
162 parent = ancestors.pop();
163
164 if (isEdited) {
165 if (inArray) {
166 node = node.slice();
167 } else {
168 var clone = {};
169
170 for (var _i2 = 0, _Object$keys2 = Object.keys(node); _i2 < _Object$keys2.length; _i2++) {
171 var k = _Object$keys2[_i2];
172 clone[k] = node[k];
173 }
174
175 node = clone;
176 }
177
178 var editOffset = 0;
179
180 for (var ii = 0; ii < edits.length; ii++) {
181 var editKey = edits[ii][0];
182 var editValue = edits[ii][1];
183
184 if (inArray) {
185 editKey -= editOffset;
186 }
187
188 if (inArray && editValue === null) {
189 node.splice(editKey, 1);
190 editOffset++;
191 } else {
192 node[editKey] = editValue;
193 }
194 }
195 }
196
197 index = stack.index;
198 keys = stack.keys;
199 edits = stack.edits;
200 inArray = stack.inArray;
201 stack = stack.prev;
202 } else {
203 key = parent ? inArray ? index : keys[index] : undefined;
204 node = parent ? parent[key] : newRoot;
205
206 if (node === null || node === undefined) {
207 continue;
208 }
209
210 if (parent) {
211 path.push(key);
212 }
213 }
214
215 var result = void 0;
216
217 if (!Array.isArray(node)) {
218 if (!isNode(node)) {
219 throw new Error('Invalid AST Node: ' + inspect(node));
220 }
221
222 var visitFn = getVisitFn(visitor, node.kind, isLeaving);
223
224 if (visitFn) {
225 result = visitFn.call(visitor, node, key, parent, path, ancestors);
226
227 if (result === BREAK) {
228 break;
229 }
230
231 if (result === false) {
232 if (!isLeaving) {
233 path.pop();
234 continue;
235 }
236 } else if (result !== undefined) {
237 edits.push([key, result]);
238
239 if (!isLeaving) {
240 if (isNode(result)) {
241 node = result;
242 } else {
243 path.pop();
244 continue;
245 }
246 }
247 }
248 }
249 }
250
251 if (result === undefined && isEdited) {
252 edits.push([key, node]);
253 }
254
255 if (isLeaving) {
256 path.pop();
257 } else {
258 stack = {
259 inArray: inArray,
260 index: index,
261 keys: keys,
262 edits: edits,
263 prev: stack
264 };
265 inArray = Array.isArray(node);
266 keys = inArray ? node : visitorKeys[node.kind] || [];
267 index = -1;
268 edits = [];
269
270 if (parent) {
271 ancestors.push(parent);
272 }
273
274 parent = node;
275 }
276 } while (stack !== undefined);
277
278 if (edits.length !== 0) {
279 newRoot = edits[edits.length - 1][1];
280 }
281
282 return newRoot;
283}
284
285function isNode(maybeNode) {
286 return Boolean(maybeNode && typeof maybeNode.kind === 'string');
287}
288/**
289 * Creates a new visitor instance which delegates to many visitors to run in
290 * parallel. Each visitor will be visited for each node before moving on.
291 *
292 * If a prior visitor edits a node, no following visitors will see that node.
293 */
294
295
296export function visitInParallel(visitors) {
297 var skipping = new Array(visitors.length);
298 return {
299 enter: function enter(node) {
300 for (var i = 0; i < visitors.length; i++) {
301 if (!skipping[i]) {
302 var fn = getVisitFn(visitors[i], node.kind,
303 /* isLeaving */
304 false);
305
306 if (fn) {
307 var result = fn.apply(visitors[i], arguments);
308
309 if (result === false) {
310 skipping[i] = node;
311 } else if (result === BREAK) {
312 skipping[i] = BREAK;
313 } else if (result !== undefined) {
314 return result;
315 }
316 }
317 }
318 }
319 },
320 leave: function leave(node) {
321 for (var i = 0; i < visitors.length; i++) {
322 if (!skipping[i]) {
323 var fn = getVisitFn(visitors[i], node.kind,
324 /* isLeaving */
325 true);
326
327 if (fn) {
328 var result = fn.apply(visitors[i], arguments);
329
330 if (result === BREAK) {
331 skipping[i] = BREAK;
332 } else if (result !== undefined && result !== false) {
333 return result;
334 }
335 }
336 } else if (skipping[i] === node) {
337 skipping[i] = null;
338 }
339 }
340 }
341 };
342}
343/**
344 * Creates a new visitor instance which maintains a provided TypeInfo instance
345 * along with visiting visitor.
346 */
347
348export function visitWithTypeInfo(typeInfo, visitor) {
349 return {
350 enter: function enter(node) {
351 typeInfo.enter(node);
352 var fn = getVisitFn(visitor, node.kind,
353 /* isLeaving */
354 false);
355
356 if (fn) {
357 var result = fn.apply(visitor, arguments);
358
359 if (result !== undefined) {
360 typeInfo.leave(node);
361
362 if (isNode(result)) {
363 typeInfo.enter(result);
364 }
365 }
366
367 return result;
368 }
369 },
370 leave: function leave(node) {
371 var fn = getVisitFn(visitor, node.kind,
372 /* isLeaving */
373 true);
374 var result;
375
376 if (fn) {
377 result = fn.apply(visitor, arguments);
378 }
379
380 typeInfo.leave(node);
381 return result;
382 }
383 };
384}
385/**
386 * Given a visitor instance, if it is leaving or not, and a node kind, return
387 * the function the visitor runtime should call.
388 */
389
390export function getVisitFn(visitor, kind, isLeaving) {
391 var kindVisitor = visitor[kind];
392
393 if (kindVisitor) {
394 if (!isLeaving && typeof kindVisitor === 'function') {
395 // { Kind() {} }
396 return kindVisitor;
397 }
398
399 var kindSpecificVisitor = isLeaving ? kindVisitor.leave : kindVisitor.enter;
400
401 if (typeof kindSpecificVisitor === 'function') {
402 // { Kind: { enter() {}, leave() {} } }
403 return kindSpecificVisitor;
404 }
405 } else {
406 var specificVisitor = isLeaving ? visitor.leave : visitor.enter;
407
408 if (specificVisitor) {
409 if (typeof specificVisitor === 'function') {
410 // { enter() {}, leave() {} }
411 return specificVisitor;
412 }
413
414 var specificKindVisitor = specificVisitor[kind];
415
416 if (typeof specificKindVisitor === 'function') {
417 // { enter: { Kind() {} }, leave: { Kind() {} } }
418 return specificKindVisitor;
419 }
420 }
421 }
422}