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;