Mirror: The spec-compliant minimum of client-side GraphQL.
1import { ASTNode } from './ast';
2
3export const BREAK = {};
4
5export function visit<N extends ASTNode>(root: N, visitor: ASTVisitor): N;
6export function visit<R>(root: ASTNode, visitor: ASTReducer<R>): R;
7
8export function visit(node: ASTNode, visitor: ASTVisitor | ASTReducer<any>) {
9 const ancestors: Array<ASTNode | ReadonlyArray<ASTNode>> = [];
10 const path: Array<string | number> = [];
11
12 function traverse(
13 node: ASTNode,
14 key?: string | number | undefined,
15 parent?: ASTNode | ReadonlyArray<ASTNode> | undefined
16 ) {
17 let hasEdited = false;
18
19 const enter = (visitor[node.kind] && visitor[node.kind].enter) || visitor[node.kind];
20 const resultEnter = enter && enter.call(visitor, node, key, parent, path, ancestors);
21 if (resultEnter === false) {
22 return node;
23 } else if (resultEnter === null) {
24 return null;
25 } else if (resultEnter === BREAK) {
26 throw BREAK;
27 } else if (resultEnter && typeof resultEnter.kind === 'string') {
28 hasEdited = resultEnter !== node;
29 node = resultEnter;
30 }
31
32 if (parent) ancestors.push(parent);
33
34 let result: any;
35 const copy = { ...node };
36 for (const nodeKey in node) {
37 path.push(nodeKey);
38 let value = node[nodeKey];
39 if (Array.isArray(value)) {
40 const newValue: any[] = [];
41 for (let index = 0; index < value.length; index++) {
42 if (value[index] != null && typeof value[index].kind === 'string') {
43 ancestors.push(node);
44 path.push(index);
45 result = traverse(value[index], index, value);
46 path.pop();
47 ancestors.pop();
48 if (result === undefined) {
49 newValue.push(value[index]);
50 } else if (result === null) {
51 hasEdited = true;
52 } else {
53 hasEdited = hasEdited || result !== value[index];
54 newValue.push(result);
55 }
56 }
57 }
58 value = newValue;
59 } else if (value != null && typeof value.kind === 'string') {
60 result = traverse(value, nodeKey, node);
61 if (result !== undefined) {
62 hasEdited = hasEdited || value !== result;
63 value = result;
64 }
65 }
66
67 path.pop();
68 if (hasEdited) copy[nodeKey] = value;
69 }
70
71 if (parent) ancestors.pop();
72 const leave = visitor[node.kind] && visitor[node.kind].leave;
73 const resultLeave = leave && leave.call(visitor, node, key, parent, path, ancestors);
74 if (resultLeave === BREAK) {
75 throw BREAK;
76 } else if (resultLeave !== undefined) {
77 return resultLeave;
78 } else if (resultEnter !== undefined) {
79 return hasEdited ? copy : resultEnter;
80 } else {
81 return hasEdited ? copy : node;
82 }
83 }
84
85 try {
86 const result = traverse(node);
87 return result !== undefined && result !== false ? result : node;
88 } catch (error) {
89 if (error !== BREAK) throw error;
90 return node;
91 }
92}
93
94export type ASTVisitor = EnterLeaveVisitor<ASTNode> | KindVisitor;
95
96type KindVisitor = {
97 readonly [NodeT in ASTNode as NodeT['kind']]?: ASTVisitFn<NodeT> | EnterLeaveVisitor<NodeT>;
98};
99
100interface EnterLeaveVisitor<TVisitedNode extends ASTNode> {
101 readonly enter?: ASTVisitFn<TVisitedNode> | undefined;
102 readonly leave?: ASTVisitFn<TVisitedNode> | undefined;
103}
104
105export type ASTVisitFn<Node extends ASTNode> = (
106 node: Node,
107 key: string | number | undefined,
108 parent: ASTNode | ReadonlyArray<ASTNode> | undefined,
109 path: ReadonlyArray<string | number>,
110 ancestors: ReadonlyArray<ASTNode | ReadonlyArray<ASTNode>>
111) => any;
112
113export type ASTReducer<R> = {
114 readonly [NodeT in ASTNode as NodeT['kind']]?: {
115 readonly enter?: ASTVisitFn<NodeT>;
116 readonly leave: ASTReducerFn<NodeT, R>;
117 };
118};
119
120type ASTReducerFn<TReducedNode extends ASTNode, R> = (
121 node: { [K in keyof TReducedNode]: ReducedField<TReducedNode[K], R> },
122 key: string | number | undefined,
123 parent: ASTNode | ReadonlyArray<ASTNode> | undefined,
124 path: ReadonlyArray<string | number>,
125 ancestors: ReadonlyArray<ASTNode | ReadonlyArray<ASTNode>>
126) => R;
127
128type ReducedField<T, R> = T extends null | undefined
129 ? T
130 : T extends ReadonlyArray<any>
131 ? ReadonlyArray<R>
132 : R;