UNPKG

8.27 kBTypeScriptView Raw
1import Maybe from '../tsutils/Maybe';
2import { TypeInfo } from '../utilities/TypeInfo';
3import { ASTNode, ASTKindToNode } from './ast';
4
5/**
6 * A visitor is provided to visit, it contains the collection of
7 * relevant functions to be called during the visitor's traversal.
8 */
9export type ASTVisitor = Visitor<ASTKindToNode>;
10export type Visitor<KindToNode, Nodes = KindToNode[keyof KindToNode]> =
11 | EnterLeaveVisitor<KindToNode, Nodes>
12 | ShapeMapVisitor<KindToNode, Nodes>;
13
14interface EnterLeave<T> {
15 readonly enter?: T;
16 readonly leave?: T;
17}
18
19type EnterLeaveVisitor<KindToNode, Nodes> = EnterLeave<
20 VisitFn<Nodes> | { [K in keyof KindToNode]?: VisitFn<Nodes, KindToNode[K]> }
21>;
22
23type ShapeMapVisitor<KindToNode, Nodes> = {
24 [K in keyof KindToNode]?:
25 | VisitFn<Nodes, KindToNode[K]>
26 | EnterLeave<VisitFn<Nodes, KindToNode[K]>>;
27};
28
29/**
30 * A visitor is comprised of visit functions, which are called on each node
31 * during the visitor's traversal.
32 */
33export type VisitFn<TAnyNode, TVisitedNode = TAnyNode> = (
34 /** The current node being visiting.*/
35 node: TVisitedNode,
36 /** The index or key to this node from the parent node or Array. */
37 key: string | number | undefined,
38 /** The parent immediately above this node, which may be an Array. */
39 parent: TAnyNode | ReadonlyArray<TAnyNode> | undefined,
40 /** The key path to get to this node from the root node. */
41 path: ReadonlyArray<string | number>,
42 /** All nodes and Arrays visited before reaching parent of this node.
43 * These correspond to array indices in `path`.
44 * Note: ancestors includes arrays which contain the parent of visited node.
45 */
46 ancestors: ReadonlyArray<TAnyNode | ReadonlyArray<TAnyNode>>,
47) => any;
48
49/**
50 * A KeyMap describes each the traversable properties of each kind of node.
51 */
52export type VisitorKeyMap<T> = { [P in keyof T]: ReadonlyArray<keyof T[P]> };
53
54// TODO: Should be `[]`, but that requires TypeScript@3
55type EmptyTuple = never[];
56
57export const QueryDocumentKeys: {
58 Name: EmptyTuple;
59
60 Document: ['definitions'];
61 // Prettier forces trailing commas, but TS pre 3.2 doesn't allow them.
62 // prettier-ignore
63 OperationDefinition: [
64 'name',
65 'variableDefinitions',
66 'directives',
67 'selectionSet'
68 ];
69 VariableDefinition: ['variable', 'type', 'defaultValue', 'directives'];
70 Variable: ['name'];
71 SelectionSet: ['selections'];
72 Field: ['alias', 'name', 'arguments', 'directives', 'selectionSet'];
73 Argument: ['name', 'value'];
74
75 FragmentSpread: ['name', 'directives'];
76 InlineFragment: ['typeCondition', 'directives', 'selectionSet'];
77 // prettier-ignore
78 FragmentDefinition: [
79 'name',
80 // Note: fragment variable definitions are experimental and may be changed
81 // or removed in the future.
82 'variableDefinitions',
83 'typeCondition',
84 'directives',
85 'selectionSet'
86 ];
87
88 IntValue: EmptyTuple;
89 FloatValue: EmptyTuple;
90 StringValue: EmptyTuple;
91 BooleanValue: EmptyTuple;
92 NullValue: EmptyTuple;
93 EnumValue: EmptyTuple;
94 ListValue: ['values'];
95 ObjectValue: ['fields'];
96 ObjectField: ['name', 'value'];
97
98 Directive: ['name', 'arguments'];
99
100 NamedType: ['name'];
101 ListType: ['type'];
102 NonNullType: ['type'];
103
104 SchemaDefinition: ['directives', 'operationTypes'];
105 OperationTypeDefinition: ['type'];
106
107 ScalarTypeDefinition: ['description', 'name', 'directives'];
108 // prettier-ignore
109 ObjectTypeDefinition: [
110 'description',
111 'name',
112 'interfaces',
113 'directives',
114 'fields'
115 ];
116 FieldDefinition: ['description', 'name', 'arguments', 'type', 'directives'];
117 // prettier-ignore
118 InputValueDefinition: [
119 'description',
120 'name',
121 'type',
122 'defaultValue',
123 'directives'
124 ];
125 InterfaceTypeDefinition: ['description', 'name', 'directives', 'fields'];
126 UnionTypeDefinition: ['description', 'name', 'directives', 'types'];
127 EnumTypeDefinition: ['description', 'name', 'directives', 'values'];
128 EnumValueDefinition: ['description', 'name', 'directives'];
129 InputObjectTypeDefinition: ['description', 'name', 'directives', 'fields'];
130
131 DirectiveDefinition: ['description', 'name', 'arguments', 'locations'];
132
133 SchemaExtension: ['directives', 'operationTypes'];
134
135 ScalarTypeExtension: ['name', 'directives'];
136 ObjectTypeExtension: ['name', 'interfaces', 'directives', 'fields'];
137 InterfaceTypeExtension: ['name', 'directives', 'fields'];
138 UnionTypeExtension: ['name', 'directives', 'types'];
139 EnumTypeExtension: ['name', 'directives', 'values'];
140 InputObjectTypeExtension: ['name', 'directives', 'fields'];
141};
142
143export const BREAK: any;
144
145/**
146 * visit() will walk through an AST using a depth first traversal, calling
147 * the visitor's enter function at each node in the traversal, and calling the
148 * leave function after visiting that node and all of its child nodes.
149 *
150 * By returning different values from the enter and leave functions, the
151 * behavior of the visitor can be altered, including skipping over a sub-tree of
152 * the AST (by returning false), editing the AST by returning a value or null
153 * to remove the value, or to stop the whole traversal by returning BREAK.
154 *
155 * When using visit() to edit an AST, the original AST will not be modified, and
156 * a new version of the AST with the changes applied will be returned from the
157 * visit function.
158 *
159 * const editedAST = visit(ast, {
160 * enter(node, key, parent, path, ancestors) {
161 * // @return
162 * // undefined: no action
163 * // false: skip visiting this node
164 * // visitor.BREAK: stop visiting altogether
165 * // null: delete this node
166 * // any value: replace this node with the returned value
167 * },
168 * leave(node, key, parent, path, ancestors) {
169 * // @return
170 * // undefined: no action
171 * // false: no action
172 * // visitor.BREAK: stop visiting altogether
173 * // null: delete this node
174 * // any value: replace this node with the returned value
175 * }
176 * });
177 *
178 * Alternatively to providing enter() and leave() functions, a visitor can
179 * instead provide functions named the same as the kinds of AST nodes, or
180 * enter/leave visitors at a named key, leading to four permutations of
181 * visitor API:
182 *
183 * 1) Named visitors triggered when entering a node a specific kind.
184 *
185 * visit(ast, {
186 * Kind(node) {
187 * // enter the "Kind" node
188 * }
189 * })
190 *
191 * 2) Named visitors that trigger upon entering and leaving a node of
192 * a specific kind.
193 *
194 * visit(ast, {
195 * Kind: {
196 * enter(node) {
197 * // enter the "Kind" node
198 * }
199 * leave(node) {
200 * // leave the "Kind" node
201 * }
202 * }
203 * })
204 *
205 * 3) Generic visitors that trigger upon entering and leaving any node.
206 *
207 * visit(ast, {
208 * enter(node) {
209 * // enter any node
210 * },
211 * leave(node) {
212 * // leave any node
213 * }
214 * })
215 *
216 * 4) Parallel visitors for entering and leaving nodes of a specific kind.
217 *
218 * visit(ast, {
219 * enter: {
220 * Kind(node) {
221 * // enter the "Kind" node
222 * }
223 * },
224 * leave: {
225 * Kind(node) {
226 * // leave the "Kind" node
227 * }
228 * }
229 * })
230 */
231export function visit(
232 root: ASTNode,
233 visitor: Visitor<ASTKindToNode>,
234 visitorKeys?: VisitorKeyMap<ASTKindToNode>, // default: QueryDocumentKeys
235): any;
236
237/**
238 * Creates a new visitor instance which delegates to many visitors to run in
239 * parallel. Each visitor will be visited for each node before moving on.
240 *
241 * If a prior visitor edits a node, no following visitors will see that node.
242 */
243export function visitInParallel(
244 visitors: ReadonlyArray<Visitor<ASTKindToNode>>,
245): Visitor<ASTKindToNode>;
246
247/**
248 * Creates a new visitor instance which maintains a provided TypeInfo instance
249 * along with visiting visitor.
250 */
251export function visitWithTypeInfo(
252 typeInfo: TypeInfo,
253 visitor: Visitor<ASTKindToNode>,
254): Visitor<ASTKindToNode>;
255
256/**
257 * Given a visitor instance, if it is leaving or not, and a node kind, return
258 * the function the visitor runtime should call.
259 */
260export function getVisitFn(
261 visitor: Visitor<any>,
262 kind: string,
263 isLeaving: boolean,
264): Maybe<VisitFn<any>>;