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