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