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 == 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;