+2
-2
benchmark/suite.js
+2
-2
benchmark/suite.js
···
6
6
const document = graphql.parse(kitchenSink, { noLocation: true });
7
7
8
8
suite('parse kitchen sink query', () => {
9
-
benchmark('0no-co/graphql.web', () => {
9
+
benchmark('0no-co/graphql.web', () => {
10
10
graphqlWeb.parse(kitchenSink);
11
11
});
12
12
···
16
16
});
17
17
18
18
suite('print kitchen sink query', () => {
19
-
benchmark('0no-co/graphql.web', () => {
19
+
benchmark('0no-co/graphql.web', () => {
20
20
graphqlWeb.print(document);
21
21
});
22
22
+3
-1
src/__tests__/parser.test.ts
+3
-1
src/__tests__/parser.test.ts
···
6
6
7
7
describe('print', () => {
8
8
it('prints the kitchen sink document like graphql.js does', () => {
9
-
const sink = readFileSync(__dirname + '/../../benchmark/kitchen_sink.graphql', { encoding: 'utf8' });
9
+
const sink = readFileSync(__dirname + '/../../benchmark/kitchen_sink.graphql', {
10
+
encoding: 'utf8',
11
+
});
10
12
const doc = parse(sink);
11
13
expect(doc).toMatchSnapshot();
12
14
expect(doc).toEqual(graphql_parse(sink, { noLocation: true }));
+3
-10
src/ast.ts
+3
-10
src/ast.ts
···
52
52
readonly loc?: Location;
53
53
}
54
54
55
-
export type DefinitionNode =
56
-
| OperationDefinitionNode
57
-
| FragmentDefinitionNode;
58
-
export type ExecutableDefinitionNode =
59
-
| OperationDefinitionNode
60
-
| FragmentDefinitionNode;
55
+
export type DefinitionNode = OperationDefinitionNode | FragmentDefinitionNode;
56
+
export type ExecutableDefinitionNode = OperationDefinitionNode | FragmentDefinitionNode;
61
57
62
58
export interface OperationDefinitionNode {
63
59
readonly kind: Kind.OPERATION_DEFINITION;
···
92
88
selections: ReadonlyArray<SelectionNode>;
93
89
}
94
90
95
-
export declare type SelectionNode =
96
-
| FieldNode
97
-
| FragmentSpreadNode
98
-
| InlineFragmentNode;
91
+
export declare type SelectionNode = FieldNode | FragmentSpreadNode | InlineFragmentNode;
99
92
100
93
export interface FieldNode {
101
94
readonly kind: Kind.FIELD;
+1
-1
src/error.ts
+1
-1
src/error.ts
+38
-63
src/parser.ts
+38
-63
src/parser.ts
···
18
18
function advance(pattern: RegExp) {
19
19
pattern.lastIndex = idx;
20
20
if (pattern.test(input)) {
21
-
const match = input.slice(idx, idx = pattern.lastIndex);
21
+
const match = input.slice(idx, (idx = pattern.lastIndex));
22
22
return match;
23
23
}
24
24
}
···
56
56
const nameRe = /[_\w][_\d\w]*/y;
57
57
function name(): ast.NameNode | undefined {
58
58
let match: string | undefined;
59
-
if (match = advance(nameRe)) {
59
+
if ((match = advance(nameRe))) {
60
60
return {
61
61
kind: Kind.NAME,
62
62
value: match,
···
81
81
let match: string | undefined;
82
82
if (advance(nullRe)) {
83
83
out = { kind: Kind.NULL };
84
-
} else if (match = advance(boolRe)) {
84
+
} else if ((match = advance(boolRe))) {
85
85
out = {
86
86
kind: Kind.BOOLEAN,
87
87
value: match === 'true',
···
94
94
value: match.slice(1),
95
95
},
96
96
};
97
-
} else if (match = advance(floatRe)) {
97
+
} else if ((match = advance(floatRe))) {
98
98
out = {
99
99
kind: Kind.FLOAT,
100
100
value: match,
101
101
};
102
-
} else if (match = advance(intRe)) {
102
+
} else if ((match = advance(intRe))) {
103
103
out = {
104
104
kind: Kind.INT,
105
105
value: match,
106
106
};
107
-
} else if (match = advance(nameRe)) {
107
+
} else if ((match = advance(nameRe))) {
108
108
out = {
109
109
kind: Kind.ENUM,
110
110
value: match,
111
111
};
112
-
} else if (match = advance(blockStringRe)) {
112
+
} else if ((match = advance(blockStringRe))) {
113
113
out = {
114
114
kind: Kind.STRING,
115
115
value: blockString(match.slice(3, -3)),
116
116
block: true,
117
117
};
118
-
} else if (match = advance(stringRe)) {
118
+
} else if ((match = advance(stringRe))) {
119
119
out = {
120
120
kind: Kind.STRING,
121
-
value: complexStringRe.test(match)
122
-
? JSON.parse(match) as string
123
-
: match.slice(1, -1),
121
+
value: complexStringRe.test(match) ? (JSON.parse(match) as string) : match.slice(1, -1),
124
122
block: false,
125
123
};
126
-
} else if (out = list(constant) || object(constant)) {
124
+
} else if ((out = list(constant) || object(constant))) {
127
125
return out;
128
126
}
129
127
···
137
135
idx++;
138
136
ignored();
139
137
const values: ast.ValueNode[] = [];
140
-
while (match = value(constant))
141
-
values.push(match);
142
-
if (input.charCodeAt(idx++) !== 93 /*']'*/)
143
-
throw error(Kind.LIST);
138
+
while ((match = value(constant))) values.push(match);
139
+
if (input.charCodeAt(idx++) !== 93 /*']'*/) throw error(Kind.LIST);
144
140
ignored();
145
141
return {
146
142
kind: Kind.LIST,
···
155
151
ignored();
156
152
const fields: ast.ObjectFieldNode[] = [];
157
153
let _name: ast.NameNode | undefined;
158
-
while (_name = name()) {
154
+
while ((_name = name())) {
159
155
ignored();
160
-
if (input.charCodeAt(idx++) !== 58 /*':'*/)
161
-
throw error(Kind.OBJECT_FIELD);
156
+
if (input.charCodeAt(idx++) !== 58 /*':'*/) throw error(Kind.OBJECT_FIELD);
162
157
ignored();
163
158
const _value = value(constant);
164
-
if (!_value)
165
-
throw error(Kind.OBJECT_FIELD);
159
+
if (!_value) throw error(Kind.OBJECT_FIELD);
166
160
fields.push({
167
161
kind: Kind.OBJECT_FIELD,
168
162
name: _name,
169
163
value: _value,
170
164
});
171
165
}
172
-
if (input.charCodeAt(idx++) !== 125 /*'}'*/)
173
-
throw error(Kind.OBJECT);
166
+
if (input.charCodeAt(idx++) !== 125 /*'}'*/) throw error(Kind.OBJECT);
174
167
ignored();
175
168
return {
176
169
kind: Kind.OBJECT,
···
186
179
idx++;
187
180
ignored();
188
181
let _name: ast.NameNode | undefined;
189
-
while (_name = name()) {
182
+
while ((_name = name())) {
190
183
ignored();
191
-
if (input.charCodeAt(idx++) !== 58 /*':'*/)
192
-
throw error(Kind.ARGUMENT);
184
+
if (input.charCodeAt(idx++) !== 58 /*':'*/) throw error(Kind.ARGUMENT);
193
185
ignored();
194
186
const _value = value(constant);
195
-
if (!_value)
196
-
throw error(Kind.ARGUMENT);
187
+
if (!_value) throw error(Kind.ARGUMENT);
197
188
args.push({
198
189
kind: Kind.ARGUMENT,
199
190
name: _name,
200
191
value: _value,
201
192
});
202
193
}
203
-
if (!args.length || input.charCodeAt(idx++) !== 41 /*')'*/)
204
-
throw error(Kind.ARGUMENT);
194
+
if (!args.length || input.charCodeAt(idx++) !== 41 /*')'*/) throw error(Kind.ARGUMENT);
205
195
ignored();
206
196
}
207
197
return args;
···
216
206
while (input.charCodeAt(idx) === 64 /*'@'*/) {
217
207
idx++;
218
208
const _name = name();
219
-
if (!_name)
220
-
throw error(Kind.DIRECTIVE);
209
+
if (!_name) throw error(Kind.DIRECTIVE);
221
210
ignored();
222
211
directives.push({
223
212
kind: Kind.DIRECTIVE,
···
239
228
ignored();
240
229
_alias = _name;
241
230
_name = name();
242
-
if (!_name)
243
-
throw error(Kind.FIELD);
231
+
if (!_name) throw error(Kind.FIELD);
244
232
ignored();
245
233
}
246
234
return {
···
261
249
idx++;
262
250
ignored();
263
251
const _type = type();
264
-
if (!_type || input.charCodeAt(idx++) !== 93 /*']'*/)
265
-
throw error(Kind.LIST_TYPE);
252
+
if (!_type || input.charCodeAt(idx++) !== 93 /*']'*/) throw error(Kind.LIST_TYPE);
266
253
match = {
267
254
kind: Kind.LIST_TYPE,
268
255
type: _type,
269
256
};
270
-
} else if (match = name()) {
257
+
} else if ((match = name())) {
271
258
match = {
272
259
kind: Kind.NAMED_TYPE,
273
260
name: match,
···
294
281
if (advance(typeConditionRe)) {
295
282
ignored();
296
283
const _name = name();
297
-
if (!_name)
298
-
throw error(Kind.NAMED_TYPE);
284
+
if (!_name) throw error(Kind.NAMED_TYPE);
299
285
ignored();
300
286
return {
301
287
kind: Kind.NAMED_TYPE,
···
323
309
const _typeCondition = typeCondition();
324
310
const _directives = directives(false);
325
311
const _selectionSet = selectionSet();
326
-
if (!_selectionSet)
327
-
throw error(Kind.INLINE_FRAGMENT);
312
+
if (!_selectionSet) throw error(Kind.INLINE_FRAGMENT);
328
313
return {
329
314
kind: Kind.INLINE_FRAGMENT,
330
315
typeCondition: _typeCondition,
···
342
327
idx++;
343
328
ignored();
344
329
const selections: ast.SelectionNode[] = [];
345
-
while (match = fragmentSpread() || field())
346
-
selections.push(match);
330
+
while ((match = fragmentSpread() || field())) selections.push(match);
347
331
if (!selections.length || input.charCodeAt(idx++) !== 125 /*'}'*/)
348
332
throw error(Kind.SELECTION_SET);
349
333
ignored();
···
361
345
if (input.charCodeAt(idx) === 40 /*'('*/) {
362
346
idx++;
363
347
ignored();
364
-
while (match = advance(variableRe)) {
348
+
while ((match = advance(variableRe))) {
365
349
ignored();
366
-
if (input.charCodeAt(idx++) !== 58 /*':'*/)
367
-
throw error(Kind.VARIABLE_DEFINITION);
350
+
if (input.charCodeAt(idx++) !== 58 /*':'*/) throw error(Kind.VARIABLE_DEFINITION);
368
351
const _type = type();
369
-
if (!_type)
370
-
throw error(Kind.VARIABLE_DEFINITION);
352
+
if (!_type) throw error(Kind.VARIABLE_DEFINITION);
371
353
let _defaultValue: ast.ValueNode | undefined;
372
354
if (input.charCodeAt(idx) === 61 /*'='*/) {
373
355
idx++;
374
356
ignored();
375
357
_defaultValue = value(true);
376
-
if (!_defaultValue)
377
-
throw error(Kind.VARIABLE_DEFINITION);
358
+
if (!_defaultValue) throw error(Kind.VARIABLE_DEFINITION);
378
359
}
379
360
ignored();
380
361
vars.push({
···
391
372
directives: directives(true),
392
373
});
393
374
}
394
-
if (input.charCodeAt(idx++) !== 41 /*')'*/)
395
-
throw error(Kind.VARIABLE_DEFINITION);
375
+
if (input.charCodeAt(idx++) !== 41 /*')'*/) throw error(Kind.VARIABLE_DEFINITION);
396
376
ignored();
397
377
}
398
378
return vars;
···
403
383
if (advance(fragmentDefinitionRe)) {
404
384
ignored();
405
385
const _name = name();
406
-
if (!_name)
407
-
throw error(Kind.FRAGMENT_DEFINITION);
386
+
if (!_name) throw error(Kind.FRAGMENT_DEFINITION);
408
387
ignored();
409
388
const _typeCondition = typeCondition();
410
-
if (!_typeCondition)
411
-
throw error(Kind.FRAGMENT_DEFINITION);
389
+
if (!_typeCondition) throw error(Kind.FRAGMENT_DEFINITION);
412
390
const _directives = directives(false);
413
391
const _selectionSet = selectionSet();
414
-
if (!_selectionSet)
415
-
throw error(Kind.FRAGMENT_DEFINITION);
392
+
if (!_selectionSet) throw error(Kind.FRAGMENT_DEFINITION);
416
393
return {
417
394
kind: Kind.FRAGMENT_DEFINITION,
418
395
name: _name,
···
429
406
let _name: ast.NameNode | undefined;
430
407
let _variableDefinitions: ast.VariableDefinitionNode[] = [];
431
408
let _directives: ast.DirectiveNode[] = [];
432
-
if (_operation = advance(operationDefinitionRe)) {
409
+
if ((_operation = advance(operationDefinitionRe))) {
433
410
ignored();
434
411
_name = name();
435
412
_variableDefinitions = variableDefinitions();
···
452
429
let match: ast.DefinitionNode | void;
453
430
ignored();
454
431
const definitions: ast.DefinitionNode[] = [];
455
-
while (match = fragmentDefinition() || operationDefinition())
456
-
definitions.push(match);
457
-
if (idx !== input.length)
458
-
throw error(Kind.DOCUMENT);
432
+
while ((match = fragmentDefinition() || operationDefinition())) definitions.push(match);
433
+
if (idx !== input.length) throw error(Kind.DOCUMENT);
459
434
return {
460
435
kind: Kind.DOCUMENT,
461
436
definitions,
+29
-44
src/printer.ts
+29
-44
src/printer.ts
···
9
9
return '"""\n' + string.replace(/"""/g, '\\"""') + '\n"""';
10
10
}
11
11
12
-
const hasItems = <T>(
13
-
array: ReadonlyArray<T> | undefined | null
14
-
): array is ReadonlyArray<T> => !!(array && array.length);
12
+
const hasItems = <T>(array: ReadonlyArray<T> | undefined | null): array is ReadonlyArray<T> =>
13
+
!!(array && array.length);
15
14
16
15
const MAX_LINE_LENGTH = 80;
17
16
···
19
18
let out: string;
20
19
switch (node.kind) {
21
20
case Kind.OPERATION_DEFINITION:
22
-
if (node.operation === 'query' && !node.name && !hasItems(node.variableDefinitions) && !hasItems(node.directives)) {
21
+
if (
22
+
node.operation === 'query' &&
23
+
!node.name &&
24
+
!hasItems(node.variableDefinitions) &&
25
+
!hasItems(node.directives)
26
+
) {
23
27
return print(node.selectionSet);
24
28
}
25
29
out = node.operation;
26
-
if (node.name)
27
-
out += ' ' + node.name.value;
30
+
if (node.name) out += ' ' + node.name.value;
28
31
if (hasItems(node.variableDefinitions)) {
29
32
if (!node.name) out += ' ';
30
33
out += '(' + node.variableDefinitions.map(print).join(', ') + ')';
31
34
}
32
-
if (hasItems(node.directives))
33
-
out += ' ' + node.directives.map(print).join(' ');
35
+
if (hasItems(node.directives)) out += ' ' + node.directives.map(print).join(' ');
34
36
return out + ' ' + print(node.selectionSet);
35
37
36
38
case Kind.VARIABLE_DEFINITION:
37
-
out = print(node.variable) +
38
-
': ' +
39
-
print(node.type);
40
-
if (node.defaultValue)
41
-
out += ' = ' + print(node.defaultValue);
42
-
if (hasItems(node.directives))
43
-
out += ' ' + node.directives.map(print).join(' ');
39
+
out = print(node.variable) + ': ' + print(node.type);
40
+
if (node.defaultValue) out += ' = ' + print(node.defaultValue);
41
+
if (hasItems(node.directives)) out += ' ' + node.directives.map(print).join(' ');
44
42
return out;
45
43
46
44
case Kind.FIELD:
47
-
out = (node.alias ? print(node.alias) + ': ' : '') + node.name.value
45
+
out = (node.alias ? print(node.alias) + ': ' : '') + node.name.value;
48
46
if (hasItems(node.arguments)) {
49
47
const args = node.arguments.map(print);
50
48
const argsLine = out + '(' + args.join(', ') + ')';
51
-
out = argsLine.length > MAX_LINE_LENGTH
52
-
? out + '(\n ' + args.join('\n').replace(/\n/g, '\n ') + '\n)'
53
-
: argsLine;
49
+
out =
50
+
argsLine.length > MAX_LINE_LENGTH
51
+
? out + '(\n ' + args.join('\n').replace(/\n/g, '\n ') + '\n)'
52
+
: argsLine;
54
53
}
55
-
if (hasItems(node.directives))
56
-
out += ' ' + node.directives.map(print).join(' ');
57
-
return node.selectionSet
58
-
? out + ' ' + print(node.selectionSet)
59
-
: out;
54
+
if (hasItems(node.directives)) out += ' ' + node.directives.map(print).join(' ');
55
+
return node.selectionSet ? out + ' ' + print(node.selectionSet) : out;
60
56
61
57
case Kind.STRING:
62
-
return node.block
63
-
? printBlockString(node.value)
64
-
: printString(node.value);
58
+
return node.block ? printBlockString(node.value) : printString(node.value);
65
59
66
60
case Kind.BOOLEAN:
67
61
return '' + node.value;
···
88
82
return '$' + node.name.value;
89
83
90
84
case Kind.DOCUMENT:
91
-
return hasItems(node.definitions)
92
-
? node.definitions.map(print).join('\n\n')
93
-
: '';
85
+
return hasItems(node.definitions) ? node.definitions.map(print).join('\n\n') : '';
94
86
95
87
case Kind.SELECTION_SET:
96
-
return '{\n ' +
97
-
node.selections.map(print).join('\n').replace(/\n/g, '\n ') +
98
-
'\n}';
88
+
return '{\n ' + node.selections.map(print).join('\n').replace(/\n/g, '\n ') + '\n}';
99
89
100
90
case Kind.ARGUMENT:
101
91
return node.name.value + ': ' + print(node.value);
102
92
103
93
case Kind.FRAGMENT_SPREAD:
104
94
out = '...' + node.name.value;
105
-
if (hasItems(node.directives))
106
-
out += ' ' + node.directives.map(print).join(' ');
95
+
if (hasItems(node.directives)) out += ' ' + node.directives.map(print).join(' ');
107
96
return out;
108
97
109
98
case Kind.INLINE_FRAGMENT:
110
99
out = '...';
111
-
if (node.typeCondition)
112
-
out += ' on ' + node.typeCondition.name.value;
113
-
if (hasItems(node.directives))
114
-
out += ' ' + node.directives.map(print).join(' ');
100
+
if (node.typeCondition) out += ' on ' + node.typeCondition.name.value;
101
+
if (hasItems(node.directives)) out += ' ' + node.directives.map(print).join(' ');
115
102
return out + ' ' + print(node.selectionSet);
116
103
117
104
case Kind.FRAGMENT_DEFINITION:
118
105
out = 'fragment ' + node.name.value;
119
106
out += ' on ' + node.typeCondition.name.value;
120
-
if (hasItems(node.directives))
121
-
out += ' ' + node.directives.map(print).join(' ');
107
+
if (hasItems(node.directives)) out += ' ' + node.directives.map(print).join(' ');
122
108
return out + ' ' + print(node.selectionSet);
123
-
109
+
124
110
case Kind.DIRECTIVE:
125
111
out = '@' + node.name.value;
126
-
if (hasItems(node.arguments))
127
-
out += '(' + node.arguments.map(print).join(', ') + ')';
112
+
if (hasItems(node.arguments)) out += '(' + node.arguments.map(print).join(', ') + ')';
128
113
return out;
129
114
130
115
case Kind.NAMED_TYPE:
+5
-10
src/values.ts
+5
-10
src/values.ts
···
4
4
5
5
export function valueFromASTUntyped(
6
6
node: ValueNode,
7
-
variables?: Maybe<Record<string, any>>,
7
+
variables?: Maybe<Record<string, any>>
8
8
): unknown {
9
9
switch (node.kind) {
10
10
case Kind.NULL:
···
19
19
return node.value;
20
20
case Kind.LIST: {
21
21
const values: unknown[] = [];
22
-
for (const value of node.values)
23
-
values.push(valueFromASTUntyped(value, variables));
22
+
for (const value of node.values) values.push(valueFromASTUntyped(value, variables));
24
23
return values;
25
24
}
26
25
case Kind.OBJECT: {
···
37
36
export function valueFromTypeNode(
38
37
node: ValueNode,
39
38
type: TypeNode,
40
-
variables?: Maybe<Record<string, any>>,
39
+
variables?: Maybe<Record<string, any>>
41
40
): unknown {
42
41
if (node.kind === Kind.VARIABLE) {
43
42
const variableName = node.name.value;
44
-
return variables
45
-
? valueFromTypeNode(variables[variableName], type, variables)
46
-
: undefined;
43
+
return variables ? valueFromTypeNode(variables[variableName], type, variables) : undefined;
47
44
} else if (type.kind === Kind.NON_NULL_TYPE) {
48
-
return node.kind !== Kind.NULL
49
-
? valueFromTypeNode(node, type, variables)
50
-
: undefined
45
+
return node.kind !== Kind.NULL ? valueFromTypeNode(node, type, variables) : undefined;
51
46
} else if (node.kind === Kind.NULL) {
52
47
return null;
53
48
} else if (type.kind === Kind.LIST_TYPE) {
+8
-12
src/visitor.ts
+8
-12
src/visitor.ts
···
6
6
export function visit<R>(root: ASTNode, visitor: ASTReducer<R>): R;
7
7
8
8
export function visit(node: ASTNode, visitor: ASTVisitor | ASTReducer<any>) {
9
-
const ancestors: Array<ASTNode | ReadonlyArray<ASTNode>>= [];
9
+
const ancestors: Array<ASTNode | ReadonlyArray<ASTNode>> = [];
10
10
const path: Array<string | number> = [];
11
11
12
12
function traverse(
13
13
node: ASTNode,
14
14
key?: string | number | undefined,
15
-
parent?: ASTNode | ReadonlyArray<ASTNode> | undefined,
15
+
parent?: ASTNode | ReadonlyArray<ASTNode> | undefined
16
16
) {
17
17
let hasEdited = false;
18
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);
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);
22
21
if (resultEnter === false) {
23
22
return node;
24
23
} else if (resultEnter === null) {
···
71
70
72
71
if (parent) ancestors.pop();
73
72
const leave = visitor[node.kind] && visitor[node.kind].leave;
74
-
const resultLeave =
75
-
leave && leave.call(visitor, node, key, parent, path, ancestors);
73
+
const resultLeave = leave && leave.call(visitor, node, key, parent, path, ancestors);
76
74
if (resultLeave === BREAK) {
77
75
throw BREAK;
78
76
} else if (resultLeave !== undefined) {
···
96
94
export type ASTVisitor = EnterLeaveVisitor<ASTNode> | KindVisitor;
97
95
98
96
type KindVisitor = {
99
-
readonly [NodeT in ASTNode as NodeT['kind']]?:
100
-
| ASTVisitFn<NodeT>
101
-
| EnterLeaveVisitor<NodeT>;
97
+
readonly [NodeT in ASTNode as NodeT['kind']]?: ASTVisitFn<NodeT> | EnterLeaveVisitor<NodeT>;
102
98
};
103
99
104
100
interface EnterLeaveVisitor<TVisitedNode extends ASTNode> {
···
111
107
key: string | number | undefined,
112
108
parent: ASTNode | ReadonlyArray<ASTNode> | undefined,
113
109
path: ReadonlyArray<string | number>,
114
-
ancestors: ReadonlyArray<ASTNode | ReadonlyArray<ASTNode>>,
110
+
ancestors: ReadonlyArray<ASTNode | ReadonlyArray<ASTNode>>
115
111
) => any;
116
112
117
113
export type ASTReducer<R> = {
···
126
122
key: string | number | undefined,
127
123
parent: ASTNode | ReadonlyArray<ASTNode> | undefined,
128
124
path: ReadonlyArray<string | number>,
129
-
ancestors: ReadonlyArray<ASTNode | ReadonlyArray<ASTNode>>,
125
+
ancestors: ReadonlyArray<ASTNode | ReadonlyArray<ASTNode>>
130
126
) => R;
131
127
132
128
type ReducedField<T, R> = T extends null | undefined