UNPKG

9.41 kBJavaScriptView Raw
1import { isNode } from '../language/ast.mjs';
2import { Kind } from '../language/kinds.mjs';
3import { getEnterLeaveForKind } from '../language/visitor.mjs';
4import {
5 getNamedType,
6 getNullableType,
7 isCompositeType,
8 isEnumType,
9 isInputObjectType,
10 isInputType,
11 isInterfaceType,
12 isListType,
13 isObjectType,
14 isOutputType,
15} from '../type/definition.mjs';
16import {
17 SchemaMetaFieldDef,
18 TypeMetaFieldDef,
19 TypeNameMetaFieldDef,
20} from '../type/introspection.mjs';
21import { typeFromAST } from './typeFromAST.mjs';
22/**
23 * TypeInfo is a utility class which, given a GraphQL schema, can keep track
24 * of the current field and type definitions at any point in a GraphQL document
25 * AST during a recursive descent by calling `enter(node)` and `leave(node)`.
26 */
27
28export class TypeInfo {
29 constructor(
30 schema,
31 /**
32 * Initial type may be provided in rare cases to facilitate traversals
33 * beginning somewhere other than documents.
34 */
35 initialType,
36 /** @deprecated will be removed in 17.0.0 */
37 getFieldDefFn,
38 ) {
39 this._schema = schema;
40 this._typeStack = [];
41 this._parentTypeStack = [];
42 this._inputTypeStack = [];
43 this._fieldDefStack = [];
44 this._defaultValueStack = [];
45 this._directive = null;
46 this._argument = null;
47 this._enumValue = null;
48 this._getFieldDef =
49 getFieldDefFn !== null && getFieldDefFn !== void 0
50 ? getFieldDefFn
51 : getFieldDef;
52
53 if (initialType) {
54 if (isInputType(initialType)) {
55 this._inputTypeStack.push(initialType);
56 }
57
58 if (isCompositeType(initialType)) {
59 this._parentTypeStack.push(initialType);
60 }
61
62 if (isOutputType(initialType)) {
63 this._typeStack.push(initialType);
64 }
65 }
66 }
67
68 get [Symbol.toStringTag]() {
69 return 'TypeInfo';
70 }
71
72 getType() {
73 if (this._typeStack.length > 0) {
74 return this._typeStack[this._typeStack.length - 1];
75 }
76 }
77
78 getParentType() {
79 if (this._parentTypeStack.length > 0) {
80 return this._parentTypeStack[this._parentTypeStack.length - 1];
81 }
82 }
83
84 getInputType() {
85 if (this._inputTypeStack.length > 0) {
86 return this._inputTypeStack[this._inputTypeStack.length - 1];
87 }
88 }
89
90 getParentInputType() {
91 if (this._inputTypeStack.length > 1) {
92 return this._inputTypeStack[this._inputTypeStack.length - 2];
93 }
94 }
95
96 getFieldDef() {
97 if (this._fieldDefStack.length > 0) {
98 return this._fieldDefStack[this._fieldDefStack.length - 1];
99 }
100 }
101
102 getDefaultValue() {
103 if (this._defaultValueStack.length > 0) {
104 return this._defaultValueStack[this._defaultValueStack.length - 1];
105 }
106 }
107
108 getDirective() {
109 return this._directive;
110 }
111
112 getArgument() {
113 return this._argument;
114 }
115
116 getEnumValue() {
117 return this._enumValue;
118 }
119
120 enter(node) {
121 const schema = this._schema; // Note: many of the types below are explicitly typed as "unknown" to drop
122 // any assumptions of a valid schema to ensure runtime types are properly
123 // checked before continuing since TypeInfo is used as part of validation
124 // which occurs before guarantees of schema and document validity.
125
126 switch (node.kind) {
127 case Kind.SELECTION_SET: {
128 const namedType = getNamedType(this.getType());
129
130 this._parentTypeStack.push(
131 isCompositeType(namedType) ? namedType : undefined,
132 );
133
134 break;
135 }
136
137 case Kind.FIELD: {
138 const parentType = this.getParentType();
139 let fieldDef;
140 let fieldType;
141
142 if (parentType) {
143 fieldDef = this._getFieldDef(schema, parentType, node);
144
145 if (fieldDef) {
146 fieldType = fieldDef.type;
147 }
148 }
149
150 this._fieldDefStack.push(fieldDef);
151
152 this._typeStack.push(isOutputType(fieldType) ? fieldType : undefined);
153
154 break;
155 }
156
157 case Kind.DIRECTIVE:
158 this._directive = schema.getDirective(node.name.value);
159 break;
160
161 case Kind.OPERATION_DEFINITION: {
162 const rootType = schema.getRootType(node.operation);
163
164 this._typeStack.push(isObjectType(rootType) ? rootType : undefined);
165
166 break;
167 }
168
169 case Kind.INLINE_FRAGMENT:
170 case Kind.FRAGMENT_DEFINITION: {
171 const typeConditionAST = node.typeCondition;
172 const outputType = typeConditionAST
173 ? typeFromAST(schema, typeConditionAST)
174 : getNamedType(this.getType());
175
176 this._typeStack.push(isOutputType(outputType) ? outputType : undefined);
177
178 break;
179 }
180
181 case Kind.VARIABLE_DEFINITION: {
182 const inputType = typeFromAST(schema, node.type);
183
184 this._inputTypeStack.push(
185 isInputType(inputType) ? inputType : undefined,
186 );
187
188 break;
189 }
190
191 case Kind.ARGUMENT: {
192 var _this$getDirective;
193
194 let argDef;
195 let argType;
196 const fieldOrDirective =
197 (_this$getDirective = this.getDirective()) !== null &&
198 _this$getDirective !== void 0
199 ? _this$getDirective
200 : this.getFieldDef();
201
202 if (fieldOrDirective) {
203 argDef = fieldOrDirective.args.find(
204 (arg) => arg.name === node.name.value,
205 );
206
207 if (argDef) {
208 argType = argDef.type;
209 }
210 }
211
212 this._argument = argDef;
213
214 this._defaultValueStack.push(argDef ? argDef.defaultValue : undefined);
215
216 this._inputTypeStack.push(isInputType(argType) ? argType : undefined);
217
218 break;
219 }
220
221 case Kind.LIST: {
222 const listType = getNullableType(this.getInputType());
223 const itemType = isListType(listType) ? listType.ofType : listType; // List positions never have a default value.
224
225 this._defaultValueStack.push(undefined);
226
227 this._inputTypeStack.push(isInputType(itemType) ? itemType : undefined);
228
229 break;
230 }
231
232 case Kind.OBJECT_FIELD: {
233 const objectType = getNamedType(this.getInputType());
234 let inputFieldType;
235 let inputField;
236
237 if (isInputObjectType(objectType)) {
238 inputField = objectType.getFields()[node.name.value];
239
240 if (inputField) {
241 inputFieldType = inputField.type;
242 }
243 }
244
245 this._defaultValueStack.push(
246 inputField ? inputField.defaultValue : undefined,
247 );
248
249 this._inputTypeStack.push(
250 isInputType(inputFieldType) ? inputFieldType : undefined,
251 );
252
253 break;
254 }
255
256 case Kind.ENUM: {
257 const enumType = getNamedType(this.getInputType());
258 let enumValue;
259
260 if (isEnumType(enumType)) {
261 enumValue = enumType.getValue(node.value);
262 }
263
264 this._enumValue = enumValue;
265 break;
266 }
267
268 default: // Ignore other nodes
269 }
270 }
271
272 leave(node) {
273 switch (node.kind) {
274 case Kind.SELECTION_SET:
275 this._parentTypeStack.pop();
276
277 break;
278
279 case Kind.FIELD:
280 this._fieldDefStack.pop();
281
282 this._typeStack.pop();
283
284 break;
285
286 case Kind.DIRECTIVE:
287 this._directive = null;
288 break;
289
290 case Kind.OPERATION_DEFINITION:
291 case Kind.INLINE_FRAGMENT:
292 case Kind.FRAGMENT_DEFINITION:
293 this._typeStack.pop();
294
295 break;
296
297 case Kind.VARIABLE_DEFINITION:
298 this._inputTypeStack.pop();
299
300 break;
301
302 case Kind.ARGUMENT:
303 this._argument = null;
304
305 this._defaultValueStack.pop();
306
307 this._inputTypeStack.pop();
308
309 break;
310
311 case Kind.LIST:
312 case Kind.OBJECT_FIELD:
313 this._defaultValueStack.pop();
314
315 this._inputTypeStack.pop();
316
317 break;
318
319 case Kind.ENUM:
320 this._enumValue = null;
321 break;
322
323 default: // Ignore other nodes
324 }
325 }
326}
327
328/**
329 * Not exactly the same as the executor's definition of getFieldDef, in this
330 * statically evaluated environment we do not always have an Object type,
331 * and need to handle Interface and Union types.
332 */
333function getFieldDef(schema, parentType, fieldNode) {
334 const name = fieldNode.name.value;
335
336 if (
337 name === SchemaMetaFieldDef.name &&
338 schema.getQueryType() === parentType
339 ) {
340 return SchemaMetaFieldDef;
341 }
342
343 if (name === TypeMetaFieldDef.name && schema.getQueryType() === parentType) {
344 return TypeMetaFieldDef;
345 }
346
347 if (name === TypeNameMetaFieldDef.name && isCompositeType(parentType)) {
348 return TypeNameMetaFieldDef;
349 }
350
351 if (isObjectType(parentType) || isInterfaceType(parentType)) {
352 return parentType.getFields()[name];
353 }
354}
355/**
356 * Creates a new visitor instance which maintains a provided TypeInfo instance
357 * along with visiting visitor.
358 */
359
360export function visitWithTypeInfo(typeInfo, visitor) {
361 return {
362 enter(...args) {
363 const node = args[0];
364 typeInfo.enter(node);
365 const fn = getEnterLeaveForKind(visitor, node.kind).enter;
366
367 if (fn) {
368 const result = fn.apply(visitor, args);
369
370 if (result !== undefined) {
371 typeInfo.leave(node);
372
373 if (isNode(result)) {
374 typeInfo.enter(result);
375 }
376 }
377
378 return result;
379 }
380 },
381
382 leave(...args) {
383 const node = args[0];
384 const fn = getEnterLeaveForKind(visitor, node.kind).leave;
385 let result;
386
387 if (fn) {
388 result = fn.apply(visitor, args);
389 }
390
391 typeInfo.leave(node);
392 return result;
393 },
394 };
395}