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 = 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 === undefined) { 52 newValue.push(value[index]); 53 } else if (result === null) { 54 hasEdited = true; 55 } else { 56 hasEdited = hasEdited || result !== value[index]; 57 newValue.push(result); 58 } 59 } 60 } 61 value = newValue; 62 } else if (value != null && typeof value.kind === 'string') { 63 result = traverse(value, nodeKey, node); 64 if (result !== undefined) { 65 hasEdited = hasEdited || value !== result; 66 value = result; 67 } 68 } 69 70 path.pop(); 71 if (hasEdited) copy[nodeKey] = value; 72 } 73 74 if (parent) ancestors.pop(); 75 const leave = 76 (visitor[node.kind] && visitor[node.kind].leave) || 77 (visitor as EnterLeaveVisitor<ASTNode>).leave; 78 const resultLeave = leave && leave.call(visitor, node, key, parent, path, ancestors); 79 if (resultLeave === BREAK) { 80 throw BREAK; 81 } else if (resultLeave !== undefined) { 82 return resultLeave; 83 } else if (resultEnter !== undefined) { 84 return hasEdited ? copy : resultEnter; 85 } else { 86 return hasEdited ? copy : node; 87 } 88 } 89 90 try { 91 const result = traverse(node); 92 return result !== undefined && result !== false ? result : node; 93 } catch (error) { 94 if (error !== BREAK) throw error; 95 return node; 96 } 97} 98 99export type ASTVisitor = EnterLeaveVisitor<ASTNode> | KindVisitor; 100 101type KindVisitor = { 102 readonly [NodeT in ASTNode as NodeT['kind']]?: ASTVisitFn<NodeT> | EnterLeaveVisitor<NodeT>; 103}; 104 105interface EnterLeaveVisitor<TVisitedNode extends ASTNode> { 106 readonly enter?: ASTVisitFn<TVisitedNode> | undefined; 107 readonly leave?: ASTVisitFn<TVisitedNode> | undefined; 108} 109 110export type ASTVisitFn<Node extends ASTNode> = ( 111 node: Node, 112 key: string | number | undefined, 113 parent: ASTNode | ReadonlyArray<ASTNode> | undefined, 114 path: ReadonlyArray<string | number>, 115 ancestors: ReadonlyArray<ASTNode | ReadonlyArray<ASTNode>> 116) => any; 117 118export type ASTReducer<R> = { 119 readonly [NodeT in ASTNode as NodeT['kind']]?: { 120 readonly enter?: ASTVisitFn<NodeT>; 121 readonly leave: ASTReducerFn<NodeT, R>; 122 }; 123}; 124 125type ASTReducerFn<TReducedNode extends ASTNode, R> = ( 126 node: { [K in keyof TReducedNode]: ReducedField<TReducedNode[K], R> }, 127 key: string | number | undefined, 128 parent: ASTNode | ReadonlyArray<ASTNode> | undefined, 129 path: ReadonlyArray<string | number>, 130 ancestors: ReadonlyArray<ASTNode | ReadonlyArray<ASTNode>> 131) => R; 132 133type ReducedField<T, R> = T extends null | undefined 134 ? T 135 : T extends ReadonlyArray<any> 136 ? ReadonlyArray<R> 137 : R;