···
const nameRe = /[_A-Za-z]\w*/y;
69
-
function name(): ast.NameNode | undefined {
70
-
let match: string | undefined;
71
-
if ((match = advance(nameRe))) {
73
-
kind: 'Name' as Kind.NAME,
79
-
// NOTE(Safari10 Quirk): This needs to be wrapped in a non-capturing group
80
-
const constRe = /(?:null|true|false)/y;
70
+
// NOTE: This should be compressed by our build step
71
+
// This merges all possible value parsing into one regular expression
72
+
const valueRe = new RegExp(
74
+
// `null`, `true`, and `false` literals (BooleanValue & NullValue)
75
+
'(null|true|false)|' +
76
+
// Variables starting with `$` then having a name (VariableNode)
80
+
// Numbers, starting with int then optionally following with a float part (IntValue and FloatValue)
81
+
'(-?\\d+)((?:\\.\\d+)?[eE][+-]?\\d+|\\.\\d+)?|' +
82
+
// Block strings starting with `"""` until the next unescaped `"""` (StringValue)
83
+
'("""(?:"""|(?:[\\s\\S]*?[^\\\\])"""))|' +
84
+
// Strings starting with `"` must be on one line (StringValue)
85
+
'("(?:"|[^\\r\\n]*?[^\\\\]"))|' + // string
86
+
// Enums are simply names except for our literals (EnumValue)
82
-
const variableRe = /\$[_A-Za-z]\w*/y;
83
-
const intRe = /-?\d+/y;
93
+
// NOTE: Each of the groups above end up in the RegExpExecArray at the specified indices (starting with 1)
94
+
const enum ValueGroup {
85
-
// NOTE(Safari10 Quirk): This cannot be further simplified
86
-
const floatPartRe = /(?:\.\d+)?[eE][+-]?\d+|\.\d+/y;
104
+
type ValueExec = RegExpExecArray & {
105
+
[Prop in ValueGroup]: string | undefined;
const complexStringRe = /\\/g;
89
-
const blockStringRe = /"""(?:"""|(?:[\s\S]*?[^\\])""")/y;
90
-
const stringRe = /"(?:"|[^\r\n]*?[^\\]")/y;
function value(constant: true): ast.ConstValueNode;
function value(constant: boolean): ast.ValueNode;
95
-
function value(constant: boolean): ast.ValueNode | undefined {
96
-
let out: ast.ValueNode | undefined;
113
+
function value(constant: boolean): ast.ValueNode {
let match: string | undefined;
98
-
if ((match = advance(constRe))) {
102
-
kind: 'NullValue' as Kind.NULL,
105
-
kind: 'BooleanValue' as Kind.BOOLEAN,
106
-
value: match === 'true',
108
-
} else if (!constant && (match = advance(variableRe))) {
110
-
kind: 'Variable' as Kind.VARIABLE,
112
-
kind: 'Name' as Kind.NAME,
113
-
value: match.slice(1),
116
-
} else if ((match = advance(intRe))) {
117
-
const intPart = match;
118
-
if ((match = advance(floatPartRe))) {
120
-
kind: 'FloatValue' as Kind.FLOAT,
121
-
value: intPart + match,
125
-
kind: 'IntValue' as Kind.INT,
129
-
} else if ((match = advance(nameRe))) {
131
-
kind: 'EnumValue' as Kind.ENUM,
134
-
} else if ((match = advance(blockStringRe))) {
136
-
kind: 'StringValue' as Kind.STRING,
137
-
value: blockString(match.slice(3, -3)),
140
-
} else if ((match = advance(stringRe))) {
142
-
kind: 'StringValue' as Kind.STRING,
143
-
value: complexStringRe.test(match) ? (JSON.parse(match) as string) : match.slice(1, -1),
146
-
} else if ((out = list(constant) || object(constant))) {
154
-
function list(constant: boolean): ast.ListValueNode | undefined {
155
-
let match: ast.ValueNode | undefined;
115
+
let exec: ValueExec | null;
116
+
valueRe.lastIndex = idx;
if (input.charCodeAt(idx) === 91 /*'['*/) {
118
+
// Lists are checked ahead of time with `[` chars
const values: ast.ValueNode[] = [];
160
-
while ((match = value(constant))) values.push(match);
161
-
if (input.charCodeAt(idx++) !== 93 /*']'*/) throw error('ListValue');
122
+
while (input.charCodeAt(idx) !== 93 /*']'*/) values.push(value(constant));
kind: 'ListValue' as Kind.LIST,
170
-
function object(constant: boolean): ast.ObjectValueNode | undefined {
171
-
if (input.charCodeAt(idx) === 123 /*'{'*/) {
129
+
} else if (input.charCodeAt(idx) === 123 /*'{'*/) {
130
+
// Objects are checked ahead of time with `{` chars
const fields: ast.ObjectFieldNode[] = [];
175
-
let _name: ast.NameNode | undefined;
176
-
while ((_name = name())) {
134
+
while (input.charCodeAt(idx) !== 125 /*'}'*/) {
135
+
if ((match = advance(nameRe)) == null) throw error('ObjectField');
178
-
if (input.charCodeAt(idx++) !== 58 /*':'*/) throw error('ObjectField' as Kind.OBJECT_FIELD);
137
+
if (input.charCodeAt(idx++) !== 58 /*':'*/) throw error('ObjectField');
180
-
const _value = value(constant);
181
-
if (!_value) throw error('ObjectField');
kind: 'ObjectField' as Kind.OBJECT_FIELD,
141
+
name: { kind: 'Name' as Kind.NAME, value: match },
142
+
value: value(constant),
188
-
if (input.charCodeAt(idx++) !== 125 /*'}'*/) throw error('ObjectValue');
kind: 'ObjectValue' as Kind.OBJECT,
151
+
} else if ((exec = valueRe.exec(input) as ValueExec) != null) {
152
+
// Starting from here, the merged `valueRe` is used
153
+
idx = valueRe.lastIndex;
155
+
if ((match = exec[ValueGroup.Const]) != null) {
156
+
return match === 'null'
157
+
? { kind: 'NullValue' as Kind.NULL }
159
+
kind: 'BooleanValue' as Kind.BOOLEAN,
160
+
value: match === 'true',
162
+
} else if ((match = exec[ValueGroup.Var]) != null) {
164
+
throw error('Variable');
167
+
kind: 'Variable' as Kind.VARIABLE,
169
+
kind: 'Name' as Kind.NAME,
174
+
} else if ((match = exec[ValueGroup.Int]) != null) {
175
+
let floatPart: string | undefined;
176
+
if ((floatPart = exec[ValueGroup.Float]) != null) {
178
+
kind: 'FloatValue' as Kind.FLOAT,
179
+
value: match + floatPart,
183
+
kind: 'IntValue' as Kind.INT,
187
+
} else if ((match = exec[ValueGroup.BlockString]) != null) {
189
+
kind: 'StringValue' as Kind.STRING,
190
+
value: blockString(match.slice(3, -3)),
193
+
} else if ((match = exec[ValueGroup.String]) != null) {
195
+
kind: 'StringValue' as Kind.STRING,
196
+
// When strings don't contain escape codes, a simple slice will be enough, otherwise
197
+
// `JSON.parse` matches GraphQL's string parsing perfectly
198
+
value: complexStringRe.test(match) ? (JSON.parse(match) as string) : match.slice(1, -1),
201
+
} else if ((match = exec[ValueGroup.Enum]) != null) {
203
+
kind: 'EnumValue' as Kind.ENUM,
209
+
throw error('Value');
197
-
function arguments_(constant: boolean): ast.ArgumentNode[] {
198
-
const args: ast.ArgumentNode[] = [];
212
+
function arguments_(constant: boolean): ast.ArgumentNode[] | undefined {
if (input.charCodeAt(idx) === 40 /*'('*/) {
214
+
const args: ast.ArgumentNode[] = [];
203
-
let _name: ast.NameNode | undefined;
204
-
while ((_name = name())) {
217
+
let _name: string | undefined;
219
+
if ((_name = advance(nameRe)) == null) throw error('Argument');
if (input.charCodeAt(idx++) !== 58 /*':'*/) throw error('Argument');
208
-
const _value = value(constant);
209
-
if (!_value) throw error('Argument');
kind: 'Argument' as Kind.ARGUMENT,
225
+
name: { kind: 'Name' as Kind.NAME, value: _name },
226
+
value: value(constant),
216
-
if (!args.length || input.charCodeAt(idx++) !== 41 /*')'*/) throw error('Argument');
222
-
function directives(constant: true): ast.ConstDirectiveNode[];
223
-
function directives(constant: boolean): ast.DirectiveNode[];
225
-
function directives(constant: boolean): ast.DirectiveNode[] {
226
-
const directives: ast.DirectiveNode[] = [];
228
-
while (input.charCodeAt(idx) === 64 /*'@'*/) {
228
+
} while (input.charCodeAt(idx) !== 41 /*')'*/);
230
-
const _name = name();
231
-
if (!_name) throw error('Directive');
234
-
kind: 'Directive' as Kind.DIRECTIVE,
236
-
arguments: arguments_(constant),
242
-
function field(): ast.FieldNode | undefined {
243
-
let _name = name();
246
-
let _alias: ast.NameNode | undefined;
247
-
if (input.charCodeAt(idx) === 58 /*':'*/) {
235
+
function directives(constant: true): ast.ConstDirectiveNode[] | undefined;
236
+
function directives(constant: boolean): ast.DirectiveNode[] | undefined;
238
+
function directives(constant: boolean): ast.DirectiveNode[] | undefined {
239
+
if (input.charCodeAt(idx) === 64 /*'@'*/) {
240
+
const directives: ast.DirectiveNode[] = [];
241
+
let _name: string | undefined;
244
+
if ((_name = advance(nameRe)) == null) throw error('Directive');
252
-
if (!_name) throw error('Field');
256
-
kind: 'Field' as Kind.FIELD,
259
-
arguments: arguments_(false),
260
-
directives: directives(false),
261
-
selectionSet: selectionSet(),
247
+
kind: 'Directive' as Kind.DIRECTIVE,
248
+
name: { kind: 'Name' as Kind.NAME, value: _name },
249
+
arguments: arguments_(constant),
251
+
} while (input.charCodeAt(idx) === 64 /*'@'*/);
function type(): ast.TypeNode {
267
-
let match: ast.NameNode | ast.TypeNode | undefined;
269
-
if (input.charCodeAt(idx) === 91 /*'['*/) {
257
+
let match: string | undefined;
259
+
while (input.charCodeAt(idx) === 91 /*'['*/) {
272
-
const _type = type();
273
-
if (!_type || input.charCodeAt(idx++) !== 93 /*']'*/) throw error('ListType');
275
-
kind: 'ListType' as Kind.LIST_TYPE,
278
-
} else if ((match = name())) {
280
-
kind: 'NamedType' as Kind.NAMED_TYPE,
284
-
throw error('NamedType');
264
+
if ((match = advance(nameRe)) == null) throw error('NamedType');
288
-
if (input.charCodeAt(idx) === 33 /*'!'*/) {
292
-
kind: 'NonNullType' as Kind.NON_NULL_TYPE,
266
+
let type: ast.TypeNode = {
267
+
kind: 'NamedType' as Kind.NAMED_TYPE,
268
+
name: { kind: 'Name' as Kind.NAME, value: match },
271
+
if (input.charCodeAt(idx) === 33 /*'!'*/) {
275
+
kind: 'NonNullType' as Kind.NON_NULL_TYPE,
276
+
type: type as ast.NamedTypeNode | ast.ListTypeNode,
277
+
} satisfies ast.NonNullTypeNode;
280
+
if (input.charCodeAt(idx++) !== 93 /*']'*/) throw error('NamedType');
283
+
kind: 'ListType' as Kind.LIST_TYPE,
284
+
type: type as ast.NamedTypeNode | ast.ListTypeNode,
285
+
} satisfies ast.ListTypeNode;
300
-
const typeConditionRe = /on/y;
301
-
function typeCondition(): ast.NamedTypeNode | undefined {
302
-
if (advance(typeConditionRe)) {
304
-
const _name = name();
305
-
if (!_name) throw error('NamedType');
308
-
kind: 'NamedType' as Kind.NAMED_TYPE,
291
+
// NOTE: This should be compressed by our build step
292
+
// This merges the two possible selection parsing branches into one regular expression
293
+
const selectionRe = new RegExp(
295
+
// fragment spreads (FragmentSpread or InlineFragment nodes)
297
+
// field aliases or names (FieldNode)
304
+
// NOTE: Each of the groups above end up in the RegExpExecArray at the indices 1&2
305
+
const enum SelectionGroup {
314
-
const fragmentSpreadRe = /\.\.\./y;
310
+
type SelectionExec = RegExpExecArray & {
311
+
[Prop in SelectionGroup]: string | undefined;
316
-
function fragmentSpread(): ast.FragmentSpreadNode | ast.InlineFragmentNode | undefined {
317
-
if (advance(fragmentSpreadRe)) {
320
-
let _name: ast.NameNode | undefined;
321
-
if ((_name = name()) && _name.value !== 'on') {
323
-
kind: 'FragmentSpread' as Kind.FRAGMENT_SPREAD,
325
-
directives: directives(false),
314
+
function selectionSet(): ast.SelectionSetNode {
315
+
const selections: ast.SelectionNode[] = [];
316
+
let match: string | undefined;
317
+
let exec: SelectionExec | null;
319
+
selectionRe.lastIndex = idx;
320
+
if ((exec = selectionRe.exec(input) as SelectionExec) != null) {
321
+
idx = selectionRe.lastIndex;
322
+
if (exec[SelectionGroup.Spread] != null) {
324
+
let match = advance(nameRe);
325
+
if (match != null && match !== 'on') {
326
+
// A simple `...Name` spread with optional directives
329
+
kind: 'FragmentSpread' as Kind.FRAGMENT_SPREAD,
330
+
name: { kind: 'Name' as Kind.NAME, value: match },
331
+
directives: directives(false),
335
+
if (match === 'on') {
336
+
// An inline `... on Name` spread; if this doesn't match, the type condition has been omitted
337
+
if ((match = advance(nameRe)) == null) throw error('NamedType');
340
+
const _directives = directives(false);
341
+
if (input.charCodeAt(idx++) !== 123 /*'{'*/) throw error('InlineFragment');
344
+
kind: 'InlineFragment' as Kind.INLINE_FRAGMENT,
345
+
typeCondition: match
347
+
kind: 'NamedType' as Kind.NAMED_TYPE,
348
+
name: { kind: 'Name' as Kind.NAME, value: match },
351
+
directives: _directives,
352
+
selectionSet: selectionSet(),
355
+
} else if ((match = exec[SelectionGroup.Name]) != null) {
356
+
let _alias: string | undefined;
358
+
// Parse the optional alias, by reassigning and then getting the name
359
+
if (input.charCodeAt(idx) === 58 /*':'*/) {
363
+
if ((match = advance(nameRe)) == null) throw error('Field');
365
+
const _arguments = arguments_(false);
367
+
const _directives = directives(false);
368
+
let _selectionSet: ast.SelectionSetNode | undefined;
369
+
if (input.charCodeAt(idx) === 123 /*'{'*/) {
372
+
_selectionSet = selectionSet();
375
+
kind: 'Field' as Kind.FIELD,
376
+
alias: _alias ? { kind: 'Name' as Kind.NAME, value: _alias } : undefined,
377
+
name: { kind: 'Name' as Kind.NAME, value: match },
378
+
arguments: _arguments,
379
+
directives: _directives,
380
+
selectionSet: _selectionSet,
329
-
const _typeCondition = typeCondition();
330
-
const _directives = directives(false);
331
-
const _selectionSet = selectionSet();
332
-
if (!_selectionSet) throw error('InlineFragment');
334
-
kind: 'InlineFragment' as Kind.INLINE_FRAGMENT,
335
-
typeCondition: _typeCondition,
336
-
directives: _directives,
337
-
selectionSet: _selectionSet,
384
+
throw error('SelectionSet');
343
-
function selectionSet(): ast.SelectionSetNode | undefined {
344
-
let match: ast.SelectionNode | undefined;
386
+
} while (input.charCodeAt(idx) !== 125 /*'}'*/);
346
-
if (input.charCodeAt(idx) === 123 /*'{'*/) {
349
-
const selections: ast.SelectionNode[] = [];
350
-
while ((match = fragmentSpread() || field())) selections.push(match);
351
-
if (!selections.length || input.charCodeAt(idx++) !== 125 /*'}'*/) throw error('SelectionSet');
354
-
kind: 'SelectionSet' as Kind.SELECTION_SET,
390
+
kind: 'SelectionSet' as Kind.SELECTION_SET,
360
-
function variableDefinitions(): ast.VariableDefinitionNode[] {
361
-
let match: string | undefined;
362
-
const vars: ast.VariableDefinitionNode[] = [];
395
+
function variableDefinitions(): ast.VariableDefinitionNode[] | undefined {
if (input.charCodeAt(idx) === 40 /*'('*/) {
398
+
const vars: ast.VariableDefinitionNode[] = [];
367
-
while ((match = advance(variableRe))) {
401
+
let _name: string | undefined;
403
+
if (input.charCodeAt(idx++) !== 36 /*'$'*/) throw error('Variable');
404
+
if ((_name = advance(nameRe)) == null) throw error('Variable');
if (input.charCodeAt(idx++) !== 58 /*':'*/) throw error('VariableDefinition');
371
-
let _defaultValue: ast.ValueNode | undefined;
409
+
let _defaultValue: ast.ConstValueNode | undefined;
if (input.charCodeAt(idx) === 61 /*'='*/) {
_defaultValue = value(true);
376
-
if (!_defaultValue) throw error('VariableDefinition');
kind: 'VariableDefinition' as Kind.VARIABLE_DEFINITION,
kind: 'Variable' as Kind.VARIABLE,
384
-
kind: 'Name' as Kind.NAME,
385
-
value: match.slice(1),
420
+
name: { kind: 'Name' as Kind.NAME, value: _name },
389
-
defaultValue: _defaultValue as ast.ConstValueNode,
423
+
defaultValue: _defaultValue,
directives: directives(true),
393
-
if (input.charCodeAt(idx++) !== 41 /*')'*/) throw error('VariableDefinition');
426
+
} while (input.charCodeAt(idx) !== 41 /*')'*/);
399
-
const fragmentDefinitionRe = /fragment/y;
400
-
function fragmentDefinition(): ast.FragmentDefinitionNode | undefined {
401
-
if (advance(fragmentDefinitionRe)) {
403
-
const _name = name();
404
-
if (!_name) throw error('FragmentDefinition');
406
-
const _typeCondition = typeCondition();
407
-
if (!_typeCondition) throw error('FragmentDefinition');
408
-
const _directives = directives(false);
409
-
const _selectionSet = selectionSet();
410
-
if (!_selectionSet) throw error('FragmentDefinition');
412
-
kind: 'FragmentDefinition' as Kind.FRAGMENT_DEFINITION,
414
-
typeCondition: _typeCondition,
415
-
directives: _directives,
416
-
selectionSet: _selectionSet,
433
+
function fragmentDefinition(): ast.FragmentDefinitionNode {
434
+
let _name: string | undefined;
435
+
let _condition: string | undefined;
436
+
if ((_name = advance(nameRe)) == null) throw error('FragmentDefinition');
438
+
if (advance(nameRe) !== 'on') throw error('FragmentDefinition');
440
+
if ((_condition = advance(nameRe)) == null) throw error('FragmentDefinition');
442
+
const _directives = directives(false);
443
+
if (input.charCodeAt(idx++) !== 123 /*'{'*/) throw error('FragmentDefinition');
446
+
kind: 'FragmentDefinition' as Kind.FRAGMENT_DEFINITION,
447
+
name: { kind: 'Name' as Kind.NAME, value: _name },
449
+
kind: 'NamedType' as Kind.NAMED_TYPE,
450
+
name: { kind: 'Name' as Kind.NAME, value: _condition },
452
+
directives: _directives,
453
+
selectionSet: selectionSet(),
421
-
// NOTE(Safari10 Quirk): This *might* need to be wrapped in a group, but worked without it too
422
-
const operationDefinitionRe = /(?:query|mutation|subscription)/y;
457
+
const definitionRe = /(?:query|mutation|subscription|fragment)/y;
424
-
function operationDefinition(): ast.OperationDefinitionNode | undefined {
425
-
let _operation: string | undefined;
426
-
let _name: ast.NameNode | undefined;
427
-
let _variableDefinitions: ast.VariableDefinitionNode[] = [];
428
-
let _directives: ast.DirectiveNode[] = [];
429
-
if ((_operation = advance(operationDefinitionRe))) {
459
+
function operationDefinition(
460
+
operation: OperationTypeNode | undefined
461
+
): ast.OperationDefinitionNode | undefined {
462
+
let _name: string | undefined;
463
+
let _variableDefinitions: ast.VariableDefinitionNode[] | undefined;
464
+
let _directives: ast.DirectiveNode[] | undefined;
467
+
_name = advance(nameRe);
_variableDefinitions = variableDefinitions();
_directives = directives(false);
435
-
const _selectionSet = selectionSet();
436
-
if (_selectionSet) {
471
+
if (input.charCodeAt(idx) === 123 /*'{'*/) {
kind: 'OperationDefinition' as Kind.OPERATION_DEFINITION,
439
-
operation: (_operation || 'query') as OperationTypeNode,
476
+
operation: operation || ('query' as OperationTypeNode.QUERY),
477
+
name: _name ? { kind: 'Name' as Kind.NAME, value: _name } : undefined,
variableDefinitions: _variableDefinitions,
443
-
selectionSet: _selectionSet,
480
+
selectionSet: selectionSet(),
function document(): ast.DocumentNode {
449
-
let match: ast.ExecutableDefinitionNode | void;
486
+
let match: string | undefined;
487
+
let definition: ast.OperationDefinitionNode | undefined;
const definitions: ast.ExecutableDefinitionNode[] = [];
452
-
while ((match = fragmentDefinition() || operationDefinition())) definitions.push(match);
491
+
if ((match = advance(definitionRe)) === 'fragment') {
493
+
definitions.push(fragmentDefinition());
494
+
} else if ((definition = operationDefinition(match as OperationTypeNode)) != null) {
495
+
definitions.push(definition);
497
+
throw error('Document');
499
+
} while (idx < input.length);
kind: 'Document' as Kind.DOCUMENT,
···
input = typeof string.body === 'string' ? string.body : string;
479
-
const _value = value(false);
480
-
if (!_value) throw error('ValueNode');
526
+
return value(false);
export function parseType(