Passed
Pull Request — master (#190)
by Sebastian
03:29
created

ASTBuilder   F

Complexity

Total Complexity 157

Size/Duplication

Total Lines 1513
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 157
dl 0
loc 1513
rs 0.6314
c 0
b 0
f 0

61 Methods

Rating   Name   Duplication   Size   Complexity  
A parseOperationType() 0 10 2
A buildName() 0 8 1
B buildOperationDefinition() 0 30 3
A buildDirectives() 0 12 2
A buildDirective() 0 13 1
A buildVariableDefinitions() 0 6 2
A buildDocument() 0 16 2
A isOperation() 0 3 1
A buildArguments() 0 13 3
D buildDefinition() 0 29 17
A buildSelectionSet() 0 13 1
B buildExecutableDefinition() 0 18 7
A buildObjectField() 0 17 1
A buildList() 0 13 2
A buildScalarTypeDefinition() 0 14 1
A buildOperationTypeDefinition() 0 13 1
A buildDirectiveLocation() 0 11 2
A buildEnumTypeDefinition() 0 15 1
A buildUnionTypeExtension() 0 21 3
A buildSchemaDefinition() 0 16 1
A buildVariable() 0 10 1
A buildStringLiteral() 0 11 1
A buildInputObjectTypeDefinition() 0 16 1
A buildFragmentDefinition() 0 20 1
A buildObjectTypeDefinition() 0 16 1
A buildArgumentsDefinition() 0 14 2
A buildFieldDefinition() 0 18 1
A buildInputFieldsDefinition() 0 14 2
A buildDirectiveLocations() 0 11 2
B buildFragment() 0 29 4
A buildFieldsDefinition() 0 10 2
B buildObjectTypeExtension() 0 23 4
B buildVariableDefinition() 0 24 2
B parseTypeReference() 0 27 3
A buildInputObjectTypeExtension() 0 22 3
A buildNamedType() 0 8 1
A buildValueLiteral() 0 6 1
A buildDirectiveDefinition() 0 23 1
C parseValueLiteral() 0 67 13
A buildObject() 0 16 2
A buildConstArgument() 0 21 1
A buildInputValueDefinition() 0 19 2
A buildFragmentName() 0 7 2
A buildInterfaceTypeDefinition() 0 16 1
A buildEnumValuesDefinition() 0 10 2
A buildImplementsInterfaces() 0 16 3
A buildUnionMemberTypes() 0 14 3
A buildTypeReference() 0 3 1
C buildTypeSystemDefinition() 0 29 12
A buildValue() 0 3 1
A buildArgument() 0 21 1
A buildEnumTypeExtension() 0 21 3
A buildScalarTypeExtension() 0 22 2
A buildSelection() 0 5 2
A buildInterfaceTypeExtension() 0 22 3
A buildDescription() 0 5 2
A buildEnumValueDefinition() 0 10 1
A buildConstValue() 0 3 1
C buildTypeSystemExtension() 0 22 8
A buildUnionTypeDefinition() 0 15 1
A buildField() 0 23 3

How to fix   Complexity   

Complex Class

Complex classes like ASTBuilder often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ASTBuilder, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Digia\GraphQL\Language;
4
5
use Digia\GraphQL\Error\SyntaxErrorException;
6
use Digia\GraphQL\Language\Node\NodeKindEnum;
7
8
class ASTBuilder implements ASTBuilderInterface
9
{
10
11
    use LexerUtilsTrait;
12
13
    /**
14
     * @param LexerInterface $lexer
15
     *
16
     * @return array|null
17
     * @throws SyntaxErrorException
18
     * @throws \ReflectionException
19
     */
20
    public function buildDocument(LexerInterface $lexer): ?array
21
    {
22
        $start = $lexer->getToken();
23
24
        $this->expect($lexer, TokenKindEnum::SOF);
25
26
        $definitions = [];
27
28
        do {
29
            $definitions[] = $this->buildDefinition($lexer);
30
        } while (!$this->skip($lexer, TokenKindEnum::EOF));
31
32
        return [
33
            'kind' => NodeKindEnum::DOCUMENT,
34
            'definitions' => $definitions,
35
            'loc' => $this->buildLocation($lexer, $start),
36
        ];
37
    }
38
39
    /**
40
     * @param LexerInterface $lexer
41
     *
42
     * @return array
43
     * @throws SyntaxErrorException
44
     * @throws \ReflectionException
45
     */
46
    protected function buildDefinition(LexerInterface $lexer): array
47
    {
48
        if ($this->peek($lexer, TokenKindEnum::NAME)) {
49
            switch ($lexer->getTokenValue()) {
50
                case KeywordEnum::QUERY:
51
                case KeywordEnum::MUTATION:
52
                case KeywordEnum::SUBSCRIPTION:
53
                case KeywordEnum::FRAGMENT:
54
                    return $this->buildExecutableDefinition($lexer);
55
                case KeywordEnum::SCHEMA:
56
                case KeywordEnum::SCALAR:
57
                case KeywordEnum::TYPE:
58
                case KeywordEnum::INTERFACE:
59
                case KeywordEnum::UNION:
60
                case KeywordEnum::ENUM:
61
                case KeywordEnum::INPUT:
62
                case KeywordEnum::EXTEND:
63
                case KeywordEnum::DIRECTIVE:
64
                    // Note: The schema definition language is an experimental addition.
65
                    return $this->buildTypeSystemDefinition($lexer);
66
            }
67
        } elseif ($this->peek($lexer, TokenKindEnum::BRACE_L)) {
68
            return $this->buildExecutableDefinition($lexer);
69
        } elseif ($this->peekDescription($lexer)) {
70
            // Note: The schema definition language is an experimental addition.
71
            return $this->buildTypeSystemDefinition($lexer);
72
        }
73
74
        throw $this->unexpected($lexer);
75
    }
76
77
    /**
78
     * @param LexerInterface $lexer
79
     *
80
     * @return array
81
     * @throws SyntaxErrorException
82
     */
83
    protected function buildExecutableDefinition(LexerInterface $lexer): array
84
    {
85
        if ($this->peek($lexer, TokenKindEnum::NAME)) {
86
            // valid names are: query, mutation, subscription and fragment
87
            switch ($lexer->getToken()->getValue()) {
88
                case KeywordEnum::QUERY:
89
                case KeywordEnum::MUTATION:
90
                case KeywordEnum::SUBSCRIPTION:
91
                    return $this->buildOperationDefinition($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildOperationDefinition($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
92
                case KeywordEnum::FRAGMENT:
93
                    return $this->buildFragmentDefinition($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildFragmentDefinition($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
94
            }
95
        } elseif ($this->peek($lexer, TokenKindEnum::BRACE_L)) {
96
            // Anonymous query
97
            return $this->buildOperationDefinition($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildOperationDefinition($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
98
        }
99
100
        throw $this->unexpected($lexer);
101
    }
102
103
    /**
104
     * @param LexerInterface $lexer
105
     *
106
     * @return array|null
107
     * @throws SyntaxErrorException
108
     */
109
    protected function buildOperationDefinition(LexerInterface $lexer): ?array
110
    {
111
        $start = $lexer->getToken();
112
113
        if ($this->peek($lexer, TokenKindEnum::BRACE_L)) {
114
            return [
115
                'kind' => NodeKindEnum::OPERATION_DEFINITION,
116
                'operation' => 'query',
117
                'name' => null,
118
                'variableDefinitions' => [],
119
                'directives' => [],
120
                'selectionSet' => $this->buildSelectionSet($lexer),
121
                'loc' => $this->buildLocation($lexer, $start),
122
            ];
123
        }
124
125
        $operation = $this->parseOperationType($lexer);
126
127
        if ($this->peek($lexer, TokenKindEnum::NAME)) {
128
            $name = $this->buildName($lexer);
129
        }
130
131
        return [
132
            'kind' => NodeKindEnum::OPERATION_DEFINITION,
133
            'operation' => $operation,
134
            'name' => $name ?? null,
135
            'variableDefinitions' => $this->buildVariableDefinitions($lexer),
136
            'directives' => $this->buildDirectives($lexer),
137
            'selectionSet' => $this->buildSelectionSet($lexer),
138
            'loc' => $this->buildLocation($lexer, $start),
139
        ];
140
    }
141
142
    /**
143
     * @param LexerInterface $lexer
144
     *
145
     * @return array|null
146
     * @throws SyntaxErrorException
147
     */
148
    protected function buildSelectionSet(LexerInterface $lexer): ?array
149
    {
150
        $start = $lexer->getToken();
151
152
        return [
153
            'kind' => NodeKindEnum::SELECTION_SET,
154
            'selections' => $this->many(
155
                $lexer,
156
                TokenKindEnum::BRACE_L,
157
                [$this, 'buildSelection'],
158
                TokenKindEnum::BRACE_R
159
            ),
160
            'loc' => $this->buildLocation($lexer, $start),
161
        ];
162
    }
163
164
    /**
165
     * @param LexerInterface $lexer
166
     *
167
     * @return string
168
     * @throws SyntaxErrorException
169
     */
170
    protected function parseOperationType(LexerInterface $lexer): string
171
    {
172
        $token = $this->expect($lexer, TokenKindEnum::NAME);
173
        $value = $token->getValue();
174
175
        if ($this->isOperation($value)) {
176
            return $value;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $value could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
177
        }
178
179
        throw $this->unexpected($lexer, $token);
180
    }
181
182
    /**
183
     * @param string $value
184
     *
185
     * @return bool
186
     */
187
    protected function isOperation(string $value): bool
188
    {
189
        return \in_array($value, ['query', 'mutation', 'subscription'], true);
190
    }
191
192
    /**
193
     * @param LexerInterface $lexer
194
     *
195
     * @return array|null
196
     * @throws SyntaxErrorException
197
     */
198
    protected function buildName(LexerInterface $lexer): ?array
199
    {
200
        $token = $this->expect($lexer, TokenKindEnum::NAME);
201
202
        return [
203
            'kind' => NodeKindEnum::NAME,
204
            'value' => $token->getValue(),
205
            'loc' => $this->buildLocation($lexer, $token),
206
        ];
207
    }
208
209
    /**
210
     * @param LexerInterface $lexer
211
     *
212
     * @return array|null
213
     * @throws SyntaxErrorException
214
     */
215
    protected function buildVariableDefinitions(LexerInterface $lexer): ?array
216
    {
217
        return $this->peek($lexer, TokenKindEnum::PAREN_L)
218
            ? $this->many($lexer, TokenKindEnum::PAREN_L,
219
                [$this, 'buildVariableDefinition'], TokenKindEnum::PAREN_R)
220
            : [];
221
    }
222
223
    /**
224
     * @param LexerInterface $lexer
225
     * @param bool $isConst
226
     *
227
     * @return array|null
228
     * @throws SyntaxErrorException
229
     */
230
    protected function buildDirectives(
231
        LexerInterface $lexer,
232
        bool $isConst = false
233
    ): ?array
234
    {
235
        $directives = [];
236
237
        while ($this->peek($lexer, TokenKindEnum::AT)) {
238
            $directives[] = $this->buildDirective($lexer, $isConst);
239
        }
240
241
        return $directives;
242
    }
243
244
    /**
245
     * @param LexerInterface $lexer
246
     * @param bool $isConst
247
     *
248
     * @return array
249
     * @throws SyntaxErrorException
250
     */
251
    protected function buildDirective(
252
        LexerInterface $lexer,
253
        bool $isConst
254
    ): array {
255
        $start = $lexer->getToken();
256
257
        $this->expect($lexer, TokenKindEnum::AT);
258
259
        return [
260
            'kind' => NodeKindEnum::DIRECTIVE,
261
            'name' => $this->buildName($lexer),
262
            'arguments' => $this->buildArguments($lexer, $isConst),
263
            'loc' => $this->buildLocation($lexer, $start),
264
        ];
265
    }
266
267
    /**
268
     * @param LexerInterface $lexer
269
     * @param bool $isConst
270
     *
271
     * @return array
272
     * @throws SyntaxErrorException
273
     */
274
    protected function buildArguments(
275
        LexerInterface $lexer,
276
        bool $isConst = false
277
    ): ?array
278
    {
279
        return $this->peek($lexer, TokenKindEnum::PAREN_L)
280
            ? $this->many(
281
                $lexer,
282
                TokenKindEnum::PAREN_L,
283
                [$this, $isConst ? 'buildConstArgument' : 'buildArgument'],
284
                TokenKindEnum::PAREN_R
285
            )
286
            : [];
287
    }
288
289
    /**
290
     * @param LexerInterface $lexer
291
     *
292
     * @return array|null
293
     * @throws SyntaxErrorException
294
     */
295
    protected function buildFragmentDefinition(LexerInterface $lexer): ?array
296
    {
297
        $start = $lexer->getToken();
298
299
        $this->expectKeyword($lexer, KeywordEnum::FRAGMENT);
300
301
        $buildTypeCondition = function (LexerInterface $lexer) {
302
            $this->expectKeyword($lexer, 'on');
303
304
            return $this->buildNamedType($lexer);
305
        };
306
307
        return [
308
            'kind' => NodeKindEnum::FRAGMENT_DEFINITION,
309
            'name' => $this->buildFragmentName($lexer),
310
            'variableDefinitions' => $this->buildVariableDefinitions($lexer),
311
            'typeCondition' => $buildTypeCondition($lexer),
312
            'directives' => $this->buildDirectives($lexer),
313
            'selectionSet' => $this->buildSelectionSet($lexer),
314
            'loc' => $this->buildLocation($lexer, $start),
315
        ];
316
    }
317
318
    /**
319
     * @param LexerInterface $lexer
320
     *
321
     * @return array|null
322
     * @throws SyntaxErrorException
323
     */
324
    protected function buildNamedType(LexerInterface $lexer): ?array
325
    {
326
        $start = $lexer->getToken();
327
328
        return [
329
            'kind' => NodeKindEnum::NAMED_TYPE,
330
            'name' => $this->buildName($lexer),
331
            'loc' => $this->buildLocation($lexer, $start),
332
        ];
333
    }
334
335
    /**
336
     * @param LexerInterface $lexer
337
     *
338
     * @return array
339
     * @throws SyntaxErrorException
340
     */
341
    protected function buildFragmentName(LexerInterface $lexer): array
342
    {
343
        if ($lexer->getTokenValue() === 'on') {
344
            throw $this->unexpected($lexer);
345
        }
346
347
        return $this->buildName($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildName($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
348
    }
349
350
    /**
351
     * @param LexerInterface $lexer
352
     *
353
     * @return array
354
     * @throws SyntaxErrorException
355
     * @throws \ReflectionException
356
     */
357
    protected function buildTypeSystemDefinition(LexerInterface $lexer): array
358
    {
359
        // Many definitions begin with a description and require a lookahead.
360
        $keywordToken = $this->peekDescription($lexer) ? $lexer->lookahead() : $lexer->getToken();
361
362
        if ($keywordToken->getKind() === TokenKindEnum::NAME) {
363
            switch ($keywordToken->getValue()) {
364
                case KeywordEnum::SCHEMA:
365
                    return $this->buildSchemaDefinition($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildSchemaDefinition($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
366
                case KeywordEnum::SCALAR:
367
                    return $this->buildScalarTypeDefinition($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildScalarTypeDefinition($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
368
                case KeywordEnum::TYPE:
369
                    return $this->buildObjectTypeDefinition($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildObjectTypeDefinition($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
370
                case KeywordEnum::INTERFACE:
371
                    return $this->buildInterfaceTypeDefinition($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildInter...eTypeDefinition($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
372
                case KeywordEnum::UNION:
373
                    return $this->buildUnionTypeDefinition($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildUnionTypeDefinition($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
374
                case KeywordEnum::ENUM:
375
                    return $this->buildEnumTypeDefinition($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildEnumTypeDefinition($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
376
                case KeywordEnum::INPUT:
377
                    return $this->buildInputObjectTypeDefinition($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildInput...tTypeDefinition($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
378
                case KeywordEnum::DIRECTIVE:
379
                    return $this->buildDirectiveDefinition($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildDirectiveDefinition($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
380
                case KeywordEnum::EXTEND:
381
                    return $this->buildTypeSystemExtension($lexer);
382
            }
383
        }
384
385
        throw $this->unexpected($lexer, $keywordToken);
386
    }
387
388
    /**
389
     * @param LexerInterface $lexer
390
     *
391
     * @return array|null
392
     * @throws SyntaxErrorException
393
     */
394
    protected function buildSchemaDefinition(LexerInterface $lexer): ?array
395
    {
396
        $start = $lexer->getToken();
397
398
        $this->expectKeyword($lexer, KeywordEnum::SCHEMA);
399
400
        return [
401
            'kind' => NodeKindEnum::SCHEMA_DEFINITION,
402
            'directives' => $this->buildDirectives($lexer),
403
            'operationTypes' => $this->many(
404
                $lexer,
405
                TokenKindEnum::BRACE_L,
406
                [$this, 'buildOperationTypeDefinition'],
407
                TokenKindEnum::BRACE_R
408
            ),
409
            'loc' => $this->buildLocation($lexer, $start),
410
        ];
411
    }
412
413
    /**
414
     * @param LexerInterface $lexer
415
     *
416
     * @return array|null
417
     * @throws SyntaxErrorException
418
     */
419
    protected function buildScalarTypeDefinition(LexerInterface $lexer): ?array
420
    {
421
        $start = $lexer->getToken();
422
423
        $description = $this->buildDescription($lexer);
424
425
        $this->expectKeyword($lexer, KeywordEnum::SCALAR);
426
427
        return [
428
            'kind' => NodeKindEnum::SCALAR_TYPE_DEFINITION,
429
            'description' => $description,
430
            'name' => $this->buildName($lexer),
431
            'directives' => $this->buildDirectives($lexer),
432
            'loc' => $this->buildLocation($lexer, $start),
433
        ];
434
    }
435
436
    /**
437
     * @inheritdoc
438
     */
439
    public function buildDescription(LexerInterface $lexer): ?array
440
    {
441
        return $this->peekDescription($lexer)
442
            ? $this->buildStringLiteral($lexer)
443
            : null;
444
    }
445
446
    protected function buildStringLiteral(LexerInterface $lexer): ?array
447
    {
448
        $token = $lexer->getToken();
449
450
        $lexer->advance();
451
452
        return [
453
            'kind' => NodeKindEnum::STRING,
454
            'value' => $token->getValue(),
455
            'block' => $token->getKind() === TokenKindEnum::BLOCK_STRING,
456
            'loc' => $this->buildLocation($lexer, $token),
457
        ];
458
    }
459
460
    /**
461
     * @param LexerInterface $lexer
462
     *
463
     * @return array|null
464
     * @throws SyntaxErrorException
465
     */
466
    protected function buildObjectTypeDefinition(LexerInterface $lexer): ?array
467
    {
468
        $start = $lexer->getToken();
469
470
        $description = $this->buildDescription($lexer);
471
472
        $this->expectKeyword($lexer, KeywordEnum::TYPE);
473
474
        return [
475
            'kind' => NodeKindEnum::OBJECT_TYPE_DEFINITION,
476
            'description' => $description,
477
            'name' => $this->buildName($lexer),
478
            'interfaces' => $this->buildImplementsInterfaces($lexer),
479
            'directives' => $this->buildDirectives($lexer),
480
            'fields' => $this->buildFieldsDefinition($lexer),
481
            'loc' => $this->buildLocation($lexer, $start),
482
        ];
483
    }
484
485
    /**
486
     * @param LexerInterface $lexer
487
     *
488
     * @return array|null
489
     * @throws SyntaxErrorException
490
     */
491
    protected function buildImplementsInterfaces(LexerInterface $lexer): ?array
492
    {
493
        $types = [];
494
495
        if ($lexer->getTokenValue() === 'implements') {
496
            $lexer->advance();
497
498
            // Optional leading ampersand
499
            $this->skip($lexer, TokenKindEnum::AMP);
500
501
            do {
502
                $types[] = $this->buildNamedType($lexer);
503
            } while ($this->skip($lexer, TokenKindEnum::AMP));
504
        }
505
506
        return $types;
507
    }
508
509
    /**
510
     * @param LexerInterface $lexer
511
     *
512
     * @return array|null
513
     * @throws SyntaxErrorException
514
     */
515
    protected function buildFieldsDefinition(LexerInterface $lexer): ?array
516
    {
517
        return $this->peek($lexer, TokenKindEnum::BRACE_L)
518
            ? $this->many(
519
                $lexer,
520
                TokenKindEnum::BRACE_L,
521
                [$this, 'buildFieldDefinition'],
522
                TokenKindEnum::BRACE_R
523
            )
524
            : [];
525
    }
526
527
    /**
528
     * @param LexerInterface $lexer
529
     *
530
     * @return array|null
531
     * @throws SyntaxErrorException
532
     */
533
    protected function buildInterfaceTypeDefinition(LexerInterface $lexer
534
    ): ?array
535
    {
536
        $start = $lexer->getToken();
537
538
        $description = $this->buildDescription($lexer);
539
540
        $this->expectKeyword($lexer, KeywordEnum::INTERFACE);
541
542
        return [
543
            'kind' => NodeKindEnum::INTERFACE_TYPE_DEFINITION,
544
            'description' => $description,
545
            'name' => $this->buildName($lexer),
546
            'directives' => $this->buildDirectives($lexer),
547
            'fields' => $this->buildFieldsDefinition($lexer),
548
            'loc' => $this->buildLocation($lexer, $start),
549
        ];
550
    }
551
552
    /**
553
     * @param LexerInterface $lexer
554
     *
555
     * @return array|null
556
     * @throws SyntaxErrorException
557
     */
558
    protected function buildUnionTypeDefinition(LexerInterface $lexer): ?array
559
    {
560
        $start = $lexer->getToken();
561
562
        $description = $this->buildDescription($lexer);
563
564
        $this->expectKeyword($lexer, KeywordEnum::UNION);
565
566
        return [
567
            'kind' => NodeKindEnum::UNION_TYPE_DEFINITION,
568
            'description' => $description,
569
            'name' => $this->buildName($lexer),
570
            'directives' => $this->buildDirectives($lexer),
571
            'types' => $this->buildUnionMemberTypes($lexer),
572
            'loc' => $this->buildLocation($lexer, $start),
573
        ];
574
    }
575
576
    /**
577
     * @param LexerInterface $lexer
578
     *
579
     * @return array|null
580
     * @throws SyntaxErrorException
581
     */
582
    protected function buildUnionMemberTypes(LexerInterface $lexer): ?array
583
    {
584
        $types = [];
585
586
        if ($this->skip($lexer, TokenKindEnum::EQUALS)) {
587
            // Optional leading pipe
588
            $this->skip($lexer, TokenKindEnum::PIPE);
589
590
            do {
591
                $types[] = $this->buildNamedType($lexer);
592
            } while ($this->skip($lexer, TokenKindEnum::PIPE));
593
        }
594
595
        return $types;
596
    }
597
598
    /**
599
     * @param LexerInterface $lexer
600
     *
601
     * @return array|null
602
     * @throws SyntaxErrorException
603
     */
604
    protected function buildEnumTypeDefinition(LexerInterface $lexer): ?array
605
    {
606
        $start = $lexer->getToken();
607
608
        $description = $this->buildDescription($lexer);
609
610
        $this->expectKeyword($lexer, KeywordEnum::ENUM);
611
612
        return [
613
            'kind' => NodeKindEnum::ENUM_TYPE_DEFINITION,
614
            'description' => $description,
615
            'name' => $this->buildName($lexer),
616
            'directives' => $this->buildDirectives($lexer),
617
            'values' => $this->buildEnumValuesDefinition($lexer),
618
            'loc' => $this->buildLocation($lexer, $start),
619
        ];
620
    }
621
622
    /**
623
     * @param LexerInterface $lexer
624
     *
625
     * @return array|null
626
     * @throws SyntaxErrorException
627
     */
628
    protected function buildEnumValuesDefinition(LexerInterface $lexer): ?array
629
    {
630
        return $this->peek($lexer, TokenKindEnum::BRACE_L)
631
            ? $this->many(
632
                $lexer,
633
                TokenKindEnum::BRACE_L,
634
                [$this, 'buildEnumValueDefinition'],
635
                TokenKindEnum::BRACE_R
636
            )
637
            : [];
638
    }
639
640
    /**
641
     * @param LexerInterface $lexer
642
     *
643
     * @return array|null
644
     * @throws SyntaxErrorException
645
     */
646
    protected function buildInputObjectTypeDefinition(LexerInterface $lexer
647
    ): ?array
648
    {
649
        $start = $lexer->getToken();
650
651
        $description = $this->buildDescription($lexer);
652
653
        $this->expectKeyword($lexer, KeywordEnum::INPUT);
654
655
        return [
656
            'kind' => NodeKindEnum::INPUT_OBJECT_TYPE_DEFINITION,
657
            'description' => $description,
658
            'name' => $this->buildName($lexer),
659
            'directives' => $this->buildDirectives($lexer, true),
660
            'fields' => $this->buildInputFieldsDefinition($lexer),
661
            'loc' => $this->buildLocation($lexer, $start),
662
        ];
663
    }
664
665
    /**
666
     * @param LexerInterface $lexer
667
     *
668
     * @return array|null
669
     * @throws SyntaxErrorException
670
     */
671
    protected function buildInputFieldsDefinition(LexerInterface $lexer): ?array
672
    {
673
        $buildFunction = function (LexerInterface $lexer): array {
674
            return $this->buildInputValueDefinition($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildInputValueDefinition($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
675
        };
676
677
        return $this->peek($lexer, TokenKindEnum::BRACE_L)
678
            ? $this->many(
679
                $lexer,
680
                TokenKindEnum::BRACE_L,
681
                $buildFunction,
682
                TokenKindEnum::BRACE_R
683
            )
684
            : [];
685
    }
686
687
    /**
688
     * @param LexerInterface $lexer
689
     *
690
     * @return array|null
691
     * @throws SyntaxErrorException
692
     */
693
    protected function buildInputValueDefinition(LexerInterface $lexer): ?array
694
    {
695
        $start = $lexer->getToken();
696
697
        $description = $this->buildDescription($lexer);
698
        $name = $this->buildName($lexer);
699
700
        $this->expect($lexer, TokenKindEnum::COLON);
701
702
        return [
703
            'kind' => NodeKindEnum::INPUT_VALUE_DEFINITION,
704
            'description' => $description,
705
            'name' => $name,
706
            'type' => $this->buildTypeReference($lexer),
707
            'defaultValue' => $this->skip($lexer, TokenKindEnum::EQUALS)
708
                ? $this->buildValueLiteral($lexer, true)
709
                : null,
710
            'directives' => $this->buildDirectives($lexer, true),
711
            'loc' => $this->buildLocation($lexer, $start),
712
        ];
713
    }
714
715
    /**
716
     * @param LexerInterface $lexer
717
     *
718
     * @return array|null
719
     * @throws SyntaxErrorException
720
     */
721
    public function buildTypeReference(LexerInterface $lexer): ?array
722
    {
723
        return $this->parseTypeReference($lexer);
724
    }
725
726
    /**
727
     * @param LexerInterface $lexer
728
     *
729
     * @return array
730
     * @throws SyntaxErrorException
731
     */
732
    protected function parseTypeReference(LexerInterface $lexer): array
733
    {
734
        $start = $lexer->getToken();
735
736
        if ($this->skip($lexer, TokenKindEnum::BRACKET_L)) {
737
            $type = $this->buildTypeReference($lexer);
738
739
            $this->expect($lexer, TokenKindEnum::BRACKET_R);
740
741
            $type = [
742
                'kind' => NodeKindEnum::LIST_TYPE,
743
                'type' => $type,
744
                'loc' => $this->buildLocation($lexer, $start),
745
            ];
746
        } else {
747
            $type = $this->buildNamedType($lexer);
748
        }
749
750
        if ($this->skip($lexer, TokenKindEnum::BANG)) {
751
            return [
752
                'kind' => NodeKindEnum::NON_NULL_TYPE,
753
                'type' => $type,
754
                'loc' => $this->buildLocation($lexer, $start),
755
            ];
756
        }
757
758
        return $type;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $type could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
759
    }
760
761
    /**
762
     * @param LexerInterface $lexer
763
     * @param bool $isConst
764
     *
765
     * @return array|null
766
     * @throws SyntaxErrorException
767
     */
768
    public function buildValueLiteral(
769
        LexerInterface $lexer,
770
        bool $isConst = false
771
    ): ?array
772
    {
773
        return $this->parseValueLiteral($lexer, $isConst);
774
    }
775
776
    /**
777
     * @param LexerInterface $lexer
778
     * @param bool $isConst
779
     *
780
     * @return array
781
     * @throws SyntaxErrorException
782
     */
783
    protected function parseValueLiteral(
784
        LexerInterface $lexer,
785
        bool $isConst
786
    ): array {
787
        $token = $lexer->getToken();
788
789
        switch ($token->getKind()) {
790
            case TokenKindEnum::BRACKET_L:
791
                return $this->buildList($lexer, $isConst);
792
            case TokenKindEnum::BRACE_L:
793
                return $this->buildObject($lexer, $isConst);
794
            case TokenKindEnum::INT:
795
                $lexer->advance();
796
797
                return [
798
                    'kind' => NodeKindEnum::INT,
799
                    'value' => $token->getValue(),
800
                    'loc' => $this->buildLocation($lexer, $token),
801
                ];
802
            case TokenKindEnum::FLOAT:
803
                $lexer->advance();
804
805
                return [
806
                    'kind' => NodeKindEnum::FLOAT,
807
                    'value' => $token->getValue(),
808
                    'loc' => $this->buildLocation($lexer, $token),
809
                ];
810
            case TokenKindEnum::STRING:
811
            case TokenKindEnum::BLOCK_STRING:
812
                return $this->buildStringLiteral($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildStringLiteral($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
813
            case TokenKindEnum::NAME:
814
                $value = $token->getValue();
815
816
                if ($value === 'true' || $value === 'false') {
817
                    $lexer->advance();
818
819
                    return [
820
                        'kind' => NodeKindEnum::BOOLEAN,
821
                        'value' => $value === 'true',
822
                        'loc' => $this->buildLocation($lexer, $token),
823
                    ];
824
                }
825
826
                if ($value === 'null') {
827
                    $lexer->advance();
828
829
                    return [
830
                        'kind' => NodeKindEnum::NULL,
831
                        'loc' => $this->buildLocation($lexer, $token),
832
                    ];
833
                }
834
835
                $lexer->advance();
836
837
                return [
838
                    'kind' => NodeKindEnum::ENUM,
839
                    'value' => $token->getValue(),
840
                    'loc' => $this->buildLocation($lexer, $token),
841
                ];
842
            case TokenKindEnum::DOLLAR:
843
                if (!$isConst) {
844
                    return $this->buildVariable($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildVariable($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
845
                }
846
                break;
847
        }
848
849
        throw $this->unexpected($lexer);
850
    }
851
852
    /**
853
     * @param LexerInterface $lexer
854
     * @param bool $isConst
855
     *
856
     * @return array
857
     * @throws SyntaxErrorException
858
     */
859
    protected function buildList(LexerInterface $lexer, bool $isConst): array
860
    {
861
        $start = $lexer->getToken();
862
863
        return [
864
            'kind' => NodeKindEnum::LIST,
865
            'values' => $this->any(
866
                $lexer,
867
                TokenKindEnum::BRACKET_L,
868
                [$this, $isConst ? 'buildConstValue' : 'buildValue'],
869
                TokenKindEnum::BRACKET_R
870
            ),
871
            'loc' => $this->buildLocation($lexer, $start),
872
        ];
873
    }
874
875
    /**
876
     * @param LexerInterface $lexer
877
     * @param bool $isConst
878
     *
879
     * @return array
880
     * @throws SyntaxErrorException
881
     */
882
    protected function buildObject(LexerInterface $lexer, bool $isConst): array
883
    {
884
        $start = $lexer->getToken();
885
886
        $this->expect($lexer, TokenKindEnum::BRACE_L);
887
888
        $fields = [];
889
890
        while (!$this->skip($lexer, TokenKindEnum::BRACE_R)) {
891
            $fields[] = $this->buildObjectField($lexer, $isConst);
892
        }
893
894
        return [
895
            'kind' => NodeKindEnum::OBJECT,
896
            'fields' => $fields,
897
            'loc' => $this->buildLocation($lexer, $start),
898
        ];
899
    }
900
901
    /**
902
     * @param LexerInterface $lexer
903
     * @param bool $isConst
904
     *
905
     * @return array
906
     * @throws SyntaxErrorException
907
     */
908
    protected function buildObjectField(
909
        LexerInterface $lexer,
910
        bool $isConst
911
    ): array {
912
        $start = $lexer->getToken();
913
914
        $buildValue = function (LexerInterface $lexer, bool $isConst) {
915
            $this->expect($lexer, TokenKindEnum::COLON);
916
917
            return $this->buildValueLiteral($lexer, $isConst);
918
        };
919
920
        return [
921
            'kind' => NodeKindEnum::OBJECT_FIELD,
922
            'name' => $this->buildName($lexer),
923
            'value' => $buildValue($lexer, $isConst),
924
            'loc' => $this->buildLocation($lexer, $start),
925
        ];
926
    }
927
928
    /**
929
     * @param LexerInterface $lexer
930
     *
931
     * @return array|null
932
     * @throws SyntaxErrorException
933
     */
934
    protected function buildVariable(LexerInterface $lexer): ?array
935
    {
936
        $start = $lexer->getToken();
937
938
        $this->expect($lexer, TokenKindEnum::DOLLAR);
939
940
        return [
941
            'kind' => NodeKindEnum::VARIABLE,
942
            'name' => $this->buildName($lexer),
943
            'loc' => $this->buildLocation($lexer, $start),
944
        ];
945
    }
946
947
    /**
948
     * @param LexerInterface $lexer
949
     *
950
     * @return array|null
951
     * @throws SyntaxErrorException
952
     * @throws \ReflectionException
953
     */
954
    protected function buildDirectiveDefinition(LexerInterface $lexer): ?array
955
    {
956
        $start = $lexer->getToken();
957
958
        $description = $this->buildDescription($lexer);
959
960
        $this->expectKeyword($lexer, KeywordEnum::DIRECTIVE);
961
        $this->expect($lexer, TokenKindEnum::AT);
962
963
        $name = $this->buildName($lexer);
964
        $arguments = $this->buildArgumentsDefinition($lexer);
965
966
        $this->expectKeyword($lexer, KeywordEnum::ON);
967
968
        $locations = $this->buildDirectiveLocations($lexer);
969
970
        return [
971
            'kind' => NodeKindEnum::DIRECTIVE_DEFINITION,
972
            'description' => $description,
973
            'name' => $name,
974
            'arguments' => $arguments,
975
            'locations' => $locations,
976
            'loc' => $this->buildLocation($lexer, $start),
977
        ];
978
    }
979
980
    /**
981
     * @param LexerInterface $lexer
982
     *
983
     * @return array|null
984
     * @throws SyntaxErrorException
985
     */
986
    protected function buildArgumentsDefinition(LexerInterface $lexer): ?array
987
    {
988
        $buildFunction = function (LexerInterface $lexer): array {
989
            return $this->buildInputValueDefinition($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildInputValueDefinition($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
990
        };
991
992
        return $this->peek($lexer, TokenKindEnum::PAREN_L)
993
            ? $this->many(
994
                $lexer,
995
                TokenKindEnum::PAREN_L,
996
                $buildFunction,
997
                TokenKindEnum::PAREN_R
998
            )
999
            : [];
1000
    }
1001
1002
    /**
1003
     * @param LexerInterface $lexer
1004
     *
1005
     * @return array
1006
     * @throws SyntaxErrorException
1007
     * @throws \ReflectionException
1008
     */
1009
    protected function buildDirectiveLocations(LexerInterface $lexer): array
1010
    {
1011
        $this->skip($lexer, TokenKindEnum::PIPE);
1012
1013
        $locations = [];
1014
1015
        do {
1016
            $locations[] = $this->buildDirectiveLocation($lexer);
1017
        } while ($this->skip($lexer, TokenKindEnum::PIPE));
1018
1019
        return $locations;
1020
    }
1021
1022
    /**
1023
     * @param LexerInterface $lexer
1024
     *
1025
     * @return array
1026
     * @throws SyntaxErrorException
1027
     * @throws \ReflectionException
1028
     */
1029
    protected function buildDirectiveLocation(LexerInterface $lexer): array
1030
    {
1031
        $start = $lexer->getToken();
1032
1033
        $name = $this->buildName($lexer);
1034
1035
        if (\in_array($name['value'], DirectiveLocationEnum::values(), true)) {
1036
            return $name;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $name could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
1037
        }
1038
1039
        throw $this->unexpected($lexer, $start);
1040
    }
1041
1042
    /**
1043
     * @param LexerInterface $lexer
1044
     *
1045
     * @return array
1046
     * @throws SyntaxErrorException
1047
     */
1048
    protected function buildTypeSystemExtension(LexerInterface $lexer): array
1049
    {
1050
        $keywordToken = $lexer->lookahead();
1051
1052
        if ($keywordToken->getKind() === TokenKindEnum::NAME) {
1053
            switch ($keywordToken->getValue()) {
1054
                case KeywordEnum::SCALAR:
1055
                    return $this->buildScalarTypeExtension($lexer, false);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildScala...xtension($lexer, false) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
1056
                case KeywordEnum::TYPE:
1057
                    return $this->buildObjectTypeExtension($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildObjectTypeExtension($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
1058
                case KeywordEnum::INTERFACE:
1059
                    return $this->buildInterfaceTypeExtension($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildInterfaceTypeExtension($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
1060
                case KeywordEnum::UNION:
1061
                    return $this->buildUnionTypeExtension($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildUnionTypeExtension($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
1062
                case KeywordEnum::ENUM:
1063
                    return $this->buildEnumTypeExtension($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildEnumTypeExtension($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
1064
                case KeywordEnum::INPUT:
1065
                    return $this->buildInputObjectTypeExtension($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildInput...ctTypeExtension($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
1066
            }
1067
        }
1068
1069
        throw $this->unexpected($lexer, $keywordToken);
1070
    }
1071
1072
    /**
1073
     * @param LexerInterface $lexer
1074
     * @param bool $isConst
1075
     *
1076
     * @return array|null
1077
     * @throws SyntaxErrorException
1078
     */
1079
    protected function buildScalarTypeExtension(
1080
        LexerInterface $lexer,
1081
        bool $isConst = false
1082
    ): ?array
1083
    {
1084
        $start = $lexer->getToken();
1085
1086
        $this->expectKeyword($lexer, KeywordEnum::EXTEND);
1087
        $this->expectKeyword($lexer, KeywordEnum::SCALAR);
1088
1089
        $name = $this->buildName($lexer);
1090
        $directives = $this->buildDirectives($lexer, $isConst);
1091
1092
        if (empty($directives)) {
1093
            throw $this->unexpected($lexer);
1094
        }
1095
1096
        return [
1097
            'kind' => NodeKindEnum::SCALAR_TYPE_EXTENSION,
1098
            'name' => $name,
1099
            'directives' => $directives,
1100
            'loc' => $this->buildLocation($lexer, $start),
1101
        ];
1102
    }
1103
1104
    /**
1105
     * @param LexerInterface $lexer
1106
     *
1107
     * @return array|null
1108
     * @throws SyntaxErrorException
1109
     */
1110
    protected function buildObjectTypeExtension(LexerInterface $lexer): ?array
1111
    {
1112
        $start = $lexer->getToken();
1113
1114
        $this->expectKeyword($lexer, KeywordEnum::EXTEND);
1115
        $this->expectKeyword($lexer, KeywordEnum::TYPE);
1116
1117
        $name = $this->buildName($lexer);
1118
        $interfaces = $this->buildImplementsInterfaces($lexer);
1119
        $directives = $this->buildDirectives($lexer);
1120
        $fields = $this->buildFieldsDefinition($lexer);
1121
1122
        if (empty($interfaces) && empty($directives) && empty($fields)) {
1123
            throw $this->unexpected($lexer);
1124
        }
1125
1126
        return [
1127
            'kind' => NodeKindEnum::OBJECT_TYPE_EXTENSION,
1128
            'name' => $name,
1129
            'interfaces' => $interfaces,
1130
            'directives' => $directives,
1131
            'fields' => $fields,
1132
            'loc' => $this->buildLocation($lexer, $start),
1133
        ];
1134
    }
1135
1136
    /**
1137
     * @param LexerInterface $lexer
1138
     *
1139
     * @return array|null
1140
     * @throws SyntaxErrorException
1141
     */
1142
    protected function buildInterfaceTypeExtension(LexerInterface $lexer
1143
    ): ?array
1144
    {
1145
        $start = $lexer->getToken();
1146
1147
        $this->expectKeyword($lexer, KeywordEnum::EXTEND);
1148
        $this->expectKeyword($lexer, KeywordEnum::INTERFACE);
1149
1150
        $name = $this->buildName($lexer);
1151
        $directives = $this->buildDirectives($lexer);
1152
        $fields = $this->buildFieldsDefinition($lexer);
1153
1154
        if (empty($directives) && empty($fields)) {
1155
            throw $this->unexpected($lexer);
1156
        }
1157
1158
        return [
1159
            'kind' => NodeKindEnum::INTERFACE_TYPE_EXTENSION,
1160
            'name' => $name,
1161
            'directives' => $directives,
1162
            'fields' => $fields,
1163
            'loc' => $this->buildLocation($lexer, $start),
1164
        ];
1165
    }
1166
1167
    /**
1168
     * @param LexerInterface $lexer
1169
     *
1170
     * @return array|null
1171
     * @throws SyntaxErrorException
1172
     */
1173
    protected function buildUnionTypeExtension(LexerInterface $lexer): ?array
1174
    {
1175
        $start = $lexer->getToken();
1176
1177
        $this->expectKeyword($lexer, KeywordEnum::EXTEND);
1178
        $this->expectKeyword($lexer, KeywordEnum::UNION);
1179
1180
        $name = $this->buildName($lexer);
1181
        $directives = $this->buildDirectives($lexer);
1182
        $types = $this->buildUnionMemberTypes($lexer);
1183
1184
        if (empty($directives) && empty($types)) {
1185
            throw $this->unexpected($lexer);
1186
        }
1187
1188
        return [
1189
            'kind' => NodeKindEnum::UNION_TYPE_EXTENSION,
1190
            'name' => $name,
1191
            'directives' => $directives,
1192
            'types' => $types,
1193
            'loc' => $this->buildLocation($lexer, $start),
1194
        ];
1195
    }
1196
1197
    /**
1198
     * @param LexerInterface $lexer
1199
     *
1200
     * @return array|null
1201
     * @throws SyntaxErrorException
1202
     */
1203
    protected function buildEnumTypeExtension(LexerInterface $lexer): ?array
1204
    {
1205
        $start = $lexer->getToken();
1206
1207
        $this->expectKeyword($lexer, KeywordEnum::EXTEND);
1208
        $this->expectKeyword($lexer, KeywordEnum::ENUM);
1209
1210
        $name = $this->buildName($lexer);
1211
        $directives = $this->buildDirectives($lexer);
1212
        $values = $this->buildEnumValuesDefinition($lexer);
1213
1214
        if (empty($directives) && empty($values)) {
1215
            throw $this->unexpected($lexer);
1216
        }
1217
1218
        return [
1219
            'kind' => NodeKindEnum::ENUM_TYPE_EXTENSION,
1220
            'name' => $name,
1221
            'directives' => $directives,
1222
            'values' => $values,
1223
            'loc' => $this->buildLocation($lexer, $start),
1224
        ];
1225
    }
1226
1227
    /**
1228
     * @param LexerInterface $lexer
1229
     *
1230
     * @return array|null
1231
     * @throws SyntaxErrorException
1232
     */
1233
    protected function buildInputObjectTypeExtension(LexerInterface $lexer
1234
    ): ?array
1235
    {
1236
        $start = $lexer->getToken();
1237
1238
        $this->expectKeyword($lexer, KeywordEnum::EXTEND);
1239
        $this->expectKeyword($lexer, KeywordEnum::INPUT);
1240
1241
        $name = $this->buildName($lexer);
1242
        $directives = $this->buildDirectives($lexer, true);
1243
        $fields = $this->buildInputFieldsDefinition($lexer);
1244
1245
        if (empty($directives) && empty($fields)) {
1246
            throw $this->unexpected($lexer);
1247
        }
1248
1249
        return [
1250
            'kind' => NodeKindEnum::INPUT_OBJECT_TYPE_EXTENSION,
1251
            'name' => $name,
1252
            'directives' => $directives,
1253
            'fields' => $fields,
1254
            'loc' => $this->buildLocation($lexer, $start),
1255
        ];
1256
    }
1257
1258
    /**
1259
     * @param LexerInterface $lexer
1260
     *
1261
     * @return array
1262
     * @throws SyntaxErrorException
1263
     */
1264
    protected function buildArgument(LexerInterface $lexer): array
1265
    {
1266
        $start = $lexer->getToken();
1267
1268
        /**
1269
         * @param LexerInterface $lexer
1270
         *
1271
         * @return mixed
1272
         * @throws SyntaxErrorException
1273
         */
1274
        $buildValue = function (LexerInterface $lexer) {
1275
            $this->expect($lexer, TokenKindEnum::COLON);
1276
1277
            return $this->buildValueLiteral($lexer);
1278
        };
1279
1280
        return [
1281
            'kind' => NodeKindEnum::ARGUMENT,
1282
            'name' => $this->buildName($lexer),
1283
            'value' => $buildValue($lexer),
1284
            'loc' => $this->buildLocation($lexer, $start),
1285
        ];
1286
    }
1287
1288
    /**
1289
     * @param LexerInterface $lexer
1290
     *
1291
     * @return array
1292
     * @throws SyntaxErrorException
1293
     */
1294
    protected function buildConstArgument(LexerInterface $lexer): array
1295
    {
1296
        $start = $lexer->getToken();
1297
1298
        /**
1299
         * @param LexerInterface $lexer
1300
         *
1301
         * @return mixed
1302
         * @throws SyntaxErrorException
1303
         */
1304
        $buildValue = function (LexerInterface $lexer) {
1305
            $this->expect($lexer, TokenKindEnum::COLON);
1306
1307
            return $this->buildValueLiteral($lexer);
1308
        };
1309
1310
        return [
1311
            'kind' => NodeKindEnum::ARGUMENT,
1312
            'name' => $this->buildName($lexer),
1313
            'value' => $buildValue($lexer),
1314
            'loc' => $this->buildLocation($lexer, $start),
1315
        ];
1316
    }
1317
1318
    /**
1319
     * @param LexerInterface $lexer
1320
     *
1321
     * @return array
1322
     * @throws SyntaxErrorException
1323
     */
1324
    protected function buildEnumValueDefinition(LexerInterface $lexer): array
1325
    {
1326
        $start = $lexer->getToken();
1327
1328
        return [
1329
            'kind' => NodeKindEnum::ENUM_VALUE_DEFINITION,
1330
            'description' => $this->buildDescription($lexer),
1331
            'name' => $this->buildName($lexer),
1332
            'directives' => $this->buildDirectives($lexer),
1333
            'loc' => $this->buildLocation($lexer, $start),
1334
        ];
1335
    }
1336
1337
    /**
1338
     * @param LexerInterface $lexer
1339
     *
1340
     * @return array
1341
     * @throws SyntaxErrorException
1342
     */
1343
    protected function buildFieldDefinition(LexerInterface $lexer): array
1344
    {
1345
        $start = $lexer->getToken();
1346
1347
        $description = $this->buildDescription($lexer);
1348
        $name = $this->buildName($lexer);
1349
        $arguments = $this->buildArgumentsDefinition($lexer);
1350
1351
        $this->expect($lexer, TokenKindEnum::COLON);
1352
1353
        return [
1354
            'kind' => NodeKindEnum::FIELD_DEFINITION,
1355
            'description' => $description,
1356
            'name' => $name,
1357
            'arguments' => $arguments,
1358
            'type' => $this->buildTypeReference($lexer),
1359
            'directives' => $this->buildDirectives($lexer),
1360
            'loc' => $this->buildLocation($lexer, $start),
1361
        ];
1362
    }
1363
1364
    /**
1365
     * @param LexerInterface $lexer
1366
     *
1367
     * @return array
1368
     * @throws SyntaxErrorException
1369
     */
1370
    protected function buildOperationTypeDefinition(LexerInterface $lexer
1371
    ): array {
1372
        $start = $lexer->getToken();
1373
1374
        $operation = $this->parseOperationType($lexer);
1375
1376
        $this->expect($lexer, TokenKindEnum::COLON);
1377
1378
        return [
1379
            'kind' => NodeKindEnum::OPERATION_TYPE_DEFINITION,
1380
            'operation' => $operation,
1381
            'type' => $this->buildNamedType($lexer),
1382
            'loc' => $this->buildLocation($lexer, $start),
1383
        ];
1384
    }
1385
1386
    /**
1387
     * @param LexerInterface $lexer
1388
     *
1389
     * @return array
1390
     * @throws SyntaxErrorException
1391
     */
1392
    protected function buildSelection(LexerInterface $lexer): array
1393
    {
1394
        return $this->peek($lexer, TokenKindEnum::SPREAD)
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->peek($lexe...his->buildField($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
1395
            ? $this->buildFragment($lexer)
1396
            : $this->buildField($lexer);
1397
    }
1398
1399
    /**
1400
     * @param LexerInterface $lexer
1401
     *
1402
     * @return array|null
1403
     * @throws SyntaxErrorException
1404
     */
1405
    protected function buildFragment(LexerInterface $lexer): ?array
1406
    {
1407
        $start = $lexer->getToken();
1408
1409
        $this->expect($lexer, TokenKindEnum::SPREAD);
1410
1411
        $tokenValue = $lexer->getTokenValue();
1412
1413
        if ($tokenValue !== 'on' && $this->peek($lexer, TokenKindEnum::NAME)) {
1414
            return [
1415
                'kind' => NodeKindEnum::FRAGMENT_SPREAD,
1416
                'name' => $this->buildFragmentName($lexer),
1417
                'directives' => $this->buildDirectives($lexer),
1418
                'loc' => $this->buildLocation($lexer, $start),
1419
            ];
1420
        }
1421
1422
        if ($tokenValue === 'on') {
1423
            $lexer->advance();
1424
1425
            $typeCondition = $this->buildNamedType($lexer);
1426
        }
1427
1428
        return [
1429
            'kind' => NodeKindEnum::INLINE_FRAGMENT,
1430
            'typeCondition' => $typeCondition ?? null,
1431
            'directives' => $this->buildDirectives($lexer),
1432
            'selectionSet' => $this->buildSelectionSet($lexer),
1433
            'loc' => $this->buildLocation($lexer, $start),
1434
        ];
1435
    }
1436
1437
    /**
1438
     * @param LexerInterface $lexer
1439
     *
1440
     * @return array|null
1441
     * @throws SyntaxErrorException
1442
     */
1443
    protected function buildField(LexerInterface $lexer): ?array
1444
    {
1445
        $start = $lexer->getToken();
1446
1447
        $nameOrAlias = $this->buildName($lexer);
1448
1449
        if ($this->skip($lexer, TokenKindEnum::COLON)) {
1450
            $alias = $nameOrAlias;
1451
            $name = $this->buildName($lexer);
1452
        } else {
1453
            $name = $nameOrAlias;
1454
        }
1455
1456
        return [
1457
            'kind' => NodeKindEnum::FIELD,
1458
            'alias' => $alias ?? null,
1459
            'name' => $name,
1460
            'arguments' => $this->buildArguments($lexer, false),
1461
            'directives' => $this->buildDirectives($lexer),
1462
            'selectionSet' => $this->peek($lexer, TokenKindEnum::BRACE_L)
1463
                ? $this->buildSelectionSet($lexer)
1464
                : null,
1465
            'loc' => $this->buildLocation($lexer, $start),
1466
        ];
1467
    }
1468
1469
    /**
1470
     * @param LexerInterface $lexer
1471
     *
1472
     * @return array
1473
     * @throws SyntaxErrorException
1474
     */
1475
    protected function buildConstValue(LexerInterface $lexer): array
1476
    {
1477
        return $this->buildValueLiteral($lexer, true);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildValueLiteral($lexer, true) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
1478
    }
1479
1480
    /**
1481
     * @param LexerInterface $lexer
1482
     *
1483
     * @return array
1484
     * @throws SyntaxErrorException
1485
     */
1486
    protected function buildValue(LexerInterface $lexer): array
1487
    {
1488
        return $this->buildValueLiteral($lexer, false);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildValueLiteral($lexer, false) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
1489
    }
1490
1491
    /**
1492
     * @param LexerInterface $lexer
1493
     *
1494
     * @return array
1495
     * @throws SyntaxErrorException
1496
     */
1497
    protected function buildVariableDefinition(LexerInterface $lexer): array
1498
    {
1499
        $start = $lexer->getToken();
1500
1501
        /**
1502
         * @param LexerInterface $lexer
1503
         *
1504
         * @return mixed
1505
         * @throws SyntaxErrorException
1506
         */
1507
        $buildType = function (LexerInterface $lexer) {
1508
            $this->expect($lexer, TokenKindEnum::COLON);
1509
1510
            return $this->buildTypeReference($lexer);
1511
        };
1512
1513
        return [
1514
            'kind' => NodeKindEnum::VARIABLE_DEFINITION,
1515
            'variable' => $this->buildVariable($lexer),
1516
            'type' => $buildType($lexer),
1517
            'defaultValue' => $this->skip($lexer, TokenKindEnum::EQUALS)
1518
                ? $this->buildValueLiteral($lexer, true)
1519
                : null,
1520
            'loc' => $this->buildLocation($lexer, $start),
1521
        ];
1522
    }
1523
}
1524