Passed
Pull Request — master (#196)
by Christoffer
02:50
created

Parser::parseNamedType()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace Digia\GraphQL\Language;
4
5
use Digia\GraphQL\Error\InvariantException;
6
use Digia\GraphQL\Error\SyntaxErrorException;
7
use Digia\GraphQL\Language\Node\ArgumentNode;
8
use Digia\GraphQL\Language\Node\BooleanValueNode;
9
use Digia\GraphQL\Language\Node\DirectiveDefinitionNode;
10
use Digia\GraphQL\Language\Node\DirectiveNode;
11
use Digia\GraphQL\Language\Node\DocumentNode;
12
use Digia\GraphQL\Language\Node\EnumTypeDefinitionNode;
13
use Digia\GraphQL\Language\Node\EnumTypeExtensionNode;
14
use Digia\GraphQL\Language\Node\EnumValueDefinitionNode;
15
use Digia\GraphQL\Language\Node\EnumValueNode;
16
use Digia\GraphQL\Language\Node\FieldDefinitionNode;
17
use Digia\GraphQL\Language\Node\FieldNode;
18
use Digia\GraphQL\Language\Node\FloatValueNode;
19
use Digia\GraphQL\Language\Node\FragmentDefinitionNode;
20
use Digia\GraphQL\Language\Node\FragmentNodeInterface;
21
use Digia\GraphQL\Language\Node\FragmentSpreadNode;
22
use Digia\GraphQL\Language\Node\InlineFragmentNode;
23
use Digia\GraphQL\Language\Node\InputObjectTypeDefinitionNode;
24
use Digia\GraphQL\Language\Node\InputObjectTypeExtensionNode;
25
use Digia\GraphQL\Language\Node\InputValueDefinitionNode;
26
use Digia\GraphQL\Language\Node\InterfaceTypeDefinitionNode;
27
use Digia\GraphQL\Language\Node\InterfaceTypeExtensionNode;
28
use Digia\GraphQL\Language\Node\IntValueNode;
29
use Digia\GraphQL\Language\Node\ListTypeNode;
30
use Digia\GraphQL\Language\Node\ListValueNode;
31
use Digia\GraphQL\Language\Node\NamedTypeNode;
32
use Digia\GraphQL\Language\Node\NameNode;
33
use Digia\GraphQL\Language\Node\NodeInterface;
34
use Digia\GraphQL\Language\Node\NonNullTypeNode;
35
use Digia\GraphQL\Language\Node\NullValueNode;
36
use Digia\GraphQL\Language\Node\ObjectFieldNode;
37
use Digia\GraphQL\Language\Node\ObjectTypeDefinitionNode;
38
use Digia\GraphQL\Language\Node\ObjectTypeExtensionNode;
39
use Digia\GraphQL\Language\Node\ObjectValueNode;
40
use Digia\GraphQL\Language\Node\OperationDefinitionNode;
41
use Digia\GraphQL\Language\Node\OperationTypeDefinitionNode;
42
use Digia\GraphQL\Language\Node\ScalarTypeDefinitionNode;
43
use Digia\GraphQL\Language\Node\ScalarTypeExtensionNode;
44
use Digia\GraphQL\Language\Node\SchemaDefinitionNode;
45
use Digia\GraphQL\Language\Node\SelectionSetNode;
46
use Digia\GraphQL\Language\Node\StringValueNode;
47
use Digia\GraphQL\Language\Node\TypeDefinitionNodeInterface;
48
use Digia\GraphQL\Language\Node\TypeExtensionNodeInterface;
49
use Digia\GraphQL\Language\Node\TypeNodeInterface;
50
use Digia\GraphQL\Language\Node\UnionTypeDefinitionNode;
51
use Digia\GraphQL\Language\Node\UnionTypeExtensionNode;
52
use Digia\GraphQL\Language\Node\ValueNodeInterface;
53
use Digia\GraphQL\Language\Node\VariableDefinitionNode;
54
use Digia\GraphQL\Language\Node\VariableNode;
55
use function Digia\GraphQL\Util\arraySome;
56
57
class Parser implements ParserInterface
58
{
59
    /**
60
     * @var LexerInterface
61
     */
62
    protected $lexer;
63
64
    /**
65
     * Given a GraphQL source, parses it into a Document.
66
     * Throws GraphQLError if a syntax error is encountered.
67
     *
68
     * @inheritdoc
69
     * @throws SyntaxErrorException
70
     * @throws \ReflectionException
71
     * @throws InvariantException
72
     */
73
    public function parse($source, array $options = []): DocumentNode
74
    {
75
        $this->lexer = $this->createLexer($source, $options);
76
77
        return $this->parseDocument();
78
    }
79
80
    /**
81
     * Given a string containing a GraphQL value (ex. `[42]`), parse the AST for
82
     * that value.
83
     * Throws GraphQLError if a syntax error is encountered.
84
     *
85
     * This is useful within tools that operate upon GraphQL Values directly and
86
     * in isolation of complete GraphQL documents.
87
     *
88
     * @inheritdoc
89
     * @throws SyntaxErrorException
90
     * @throws InvariantException
91
     */
92
    public function parseValue($source, array $options = []): NodeInterface
93
    {
94
        $this->lexer = $this->createLexer($source, $options);
95
96
        $this->expect(TokenKindEnum::SOF);
97
        $value = $this->parseValueLiteral();
98
        $this->expect(TokenKindEnum::EOF);
99
100
        return $value;
101
    }
102
103
    /**
104
     * Given a string containing a GraphQL Type (ex. `[Int!]`), parse the AST for
105
     * that type.
106
     * Throws GraphQLError if a syntax error is encountered.
107
     *
108
     * This is useful within tools that operate upon GraphQL Types directly and
109
     * in isolation of complete GraphQL documents.
110
     *
111
     * @inheritdoc
112
     * @throws SyntaxErrorException
113
     * @throws InvariantException
114
     */
115
    public function parseType($source, array $options = []): TypeNodeInterface
116
    {
117
        $this->lexer = $this->createLexer($source, $options);
118
119
        $this->expect(TokenKindEnum::SOF);
120
        $type = $this->parseTypeReference();
121
        $this->expect(TokenKindEnum::EOF);
122
123
        return $type;
124
    }
125
126
    /**
127
     * Converts a name lex token into a name parse node.
128
     *
129
     * @return NameNode
130
     * @throws SyntaxErrorException
131
     */
132
    protected function parseName(): NameNode
133
    {
134
        $token = $this->expect(TokenKindEnum::NAME);
135
136
        return new NameNode($token->value, $this->createLocation($token));
137
    }
138
139
    // Implements the parsing rules in the Document section.
140
141
    /**
142
     * Document : Definition+
143
     *
144
     * @return DocumentNode
145
     * @throws SyntaxErrorException
146
     * @throws \ReflectionException
147
     */
148
    public function parseDocument(): DocumentNode
149
    {
150
        $start = $this->lexer->getToken();
151
152
        $this->expect(TokenKindEnum::SOF);
153
154
        $definitions = [];
155
156
        do {
157
            $definitions[] = $this->parseDefinition();
158
        } while (!$this->skip(TokenKindEnum::EOF));
159
160
        return new DocumentNode($definitions, $this->createLocation($start));
161
    }
162
163
    /**
164
     * Definition :
165
     *   - ExecutableDefinition
166
     *   - TypeSystemDefinition
167
     *
168
     * @return NodeInterface
169
     * @throws SyntaxErrorException
170
     * @throws \ReflectionException
171
     */
172
    protected function parseDefinition(): NodeInterface
173
    {
174
        if ($this->peek(TokenKindEnum::NAME)) {
175
            $token = $this->lexer->getToken();
176
177
            switch ($token->value) {
178
                case KeywordEnum::QUERY:
179
                case KeywordEnum::MUTATION:
180
                case KeywordEnum::SUBSCRIPTION:
181
                case KeywordEnum::FRAGMENT:
182
                    return $this->parseExecutableDefinition();
183
                case KeywordEnum::SCHEMA:
184
                case KeywordEnum::SCALAR:
185
                case KeywordEnum::TYPE:
186
                case KeywordEnum::INTERFACE:
187
                case KeywordEnum::UNION:
188
                case KeywordEnum::ENUM:
189
                case KeywordEnum::INPUT:
190
                case KeywordEnum::EXTEND:
191
                case KeywordEnum::DIRECTIVE:
192
                    // Note: The schema definition language is an experimental addition.
193
                    return $this->parseTypeSystemDefinition();
194
            }
195
        } elseif ($this->peek(TokenKindEnum::BRACE_L)) {
196
            return $this->parseExecutableDefinition();
197
        } elseif ($this->peekDescription()) {
198
            // Note: The schema definition language is an experimental addition.
199
            return $this->parseTypeSystemDefinition();
200
        }
201
202
        throw $this->unexpected();
203
    }
204
205
    /**
206
     * ExecutableDefinition :
207
     *   - OperationDefinition
208
     *   - FragmentDefinition
209
     *
210
     * @return NodeInterface
211
     * @throws SyntaxErrorException
212
     */
213
    protected function parseExecutableDefinition(): NodeInterface
214
    {
215
        if ($this->peek(TokenKindEnum::NAME)) {
216
            // Valid names are: query, mutation, subscription and fragment
217
            $token = $this->lexer->getToken();
218
219
            switch ($token->value) {
220
                case KeywordEnum::QUERY:
221
                case KeywordEnum::MUTATION:
222
                case KeywordEnum::SUBSCRIPTION:
223
                    return $this->parseOperationDefinition();
224
                case KeywordEnum::FRAGMENT:
225
                    return $this->parseFragmentDefinition();
226
            }
227
        } elseif ($this->peek(TokenKindEnum::BRACE_L)) {
228
            // Anonymous query
229
            return $this->parseOperationDefinition();
230
        }
231
232
        throw $this->unexpected();
233
    }
234
235
    // Implements the parsing rules in the Operations section.
236
237
    /**
238
     * OperationDefinition :
239
     *  - SelectionSet
240
     *  - OperationType Name? VariableDefinitions? Directives? SelectionSet
241
     *
242
     * @return OperationDefinitionNode
243
     * @throws SyntaxErrorException
244
     */
245
    protected function parseOperationDefinition(): OperationDefinitionNode
246
    {
247
        $start = $this->lexer->getToken();
248
249
        if ($this->peek(TokenKindEnum::BRACE_L)) {
250
            // Anonymous query
251
            return new OperationDefinitionNode(
252
                KeywordEnum::QUERY,
253
                null,
254
                [],
255
                [],
256
                $this->parseSelectionSet(),
257
                $this->createLocation($start)
258
            );
259
        }
260
261
        $operation = $this->parseOperationType();
262
263
        if ($this->peek(TokenKindEnum::NAME)) {
264
            $name = $this->parseName();
265
        }
266
267
        return new OperationDefinitionNode(
268
            $operation,
269
            $name ?? null,
270
            $this->parseVariableDefinitions(),
271
            $this->parseDirectives(),
272
            $this->parseSelectionSet(),
273
            $this->createLocation($start)
274
        );
275
    }
276
277
    /**
278
     * OperationType : one of query mutation subscription
279
     *
280
     * @return string
281
     * @throws SyntaxErrorException
282
     */
283
    protected function parseOperationType(): string
284
    {
285
        $token = $this->expect(TokenKindEnum::NAME);
286
287
        if (isOperation($token->value)) {
0 ignored issues
show
Bug introduced by
It seems like $token->value can also be of type null; however, parameter $value of Digia\GraphQL\Language\isOperation() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

287
        if (isOperation(/** @scrutinizer ignore-type */ $token->value)) {
Loading history...
288
            return $token->value;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $token->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...
289
        }
290
291
        throw $this->unexpected($token);
292
    }
293
294
    /**
295
     * VariableDefinitions : ( VariableDefinition+ )
296
     *
297
     * @return array
298
     * @throws SyntaxErrorException
299
     */
300
    protected function parseVariableDefinitions(): array
301
    {
302
        return $this->peek(TokenKindEnum::PAREN_L)
303
            ? $this->many(
304
                TokenKindEnum::PAREN_L,
305
                [$this, 'parseVariableDefinition'],
306
                TokenKindEnum::PAREN_R
307
            )
308
            : [];
309
    }
310
311
    /**
312
     * VariableDefinition : Variable : Type DefaultValue?
313
     *
314
     * @return VariableDefinitionNode
315
     * @throws SyntaxErrorException
316
     */
317
    protected function parseVariableDefinition(): VariableDefinitionNode
318
    {
319
        $start = $this->lexer->getToken();
320
321
        /**
322
         * @return TypeNodeInterface
323
         */
324
        $parseType = function (): TypeNodeInterface {
325
            $this->expect(TokenKindEnum::COLON);
326
            return $this->parseTypeReference();
327
        };
328
329
        return new VariableDefinitionNode(
330
            $this->parseVariable(),
331
            $parseType(),
332
            $this->skip(TokenKindEnum::EQUALS)
333
                ? $this->parseValueLiteral(true)
334
                : null,
335
            $this->createLocation($start)
336
        );
337
    }
338
339
    /**
340
     * Variable : $ Name
341
     *
342
     * @return VariableNode
343
     * @throws SyntaxErrorException
344
     */
345
    protected function parseVariable(): VariableNode
346
    {
347
        $start = $this->lexer->getToken();
348
349
        $this->expect(TokenKindEnum::DOLLAR);
350
351
        return new VariableNode($this->parseName(), $this->createLocation($start));
352
    }
353
354
    /**
355
     * SelectionSet : { Selection+ }
356
     *
357
     * @return SelectionSetNode
358
     * @throws SyntaxErrorException
359
     */
360
    protected function parseSelectionSet(): SelectionSetNode
361
    {
362
        $start = $this->lexer->getToken();
363
364
        return new SelectionSetNode(
365
            $this->many(
366
                TokenKindEnum::BRACE_L,
367
                [$this, 'parseSelection'],
368
                TokenKindEnum::BRACE_R
369
            ),
370
            $this->createLocation($start)
371
        );
372
    }
373
374
    /**
375
     * Selection :
376
     *   - Field
377
     *   - FragmentSpread
378
     *   - InlineFragment
379
     *
380
     * @return NodeInterface|FragmentNodeInterface|FieldNode
381
     * @throws SyntaxErrorException
382
     */
383
    protected function parseSelection(): NodeInterface
384
    {
385
        return $this->peek(TokenKindEnum::SPREAD)
386
            ? $this->parseFragment()
387
            : $this->parseField();
388
    }
389
390
    /**
391
     * Field : Alias? Name Arguments? Directives? SelectionSet?
392
     *
393
     * Alias : Name :
394
     *
395
     * @return FieldNode
396
     * @throws SyntaxErrorException
397
     */
398
    protected function parseField(): FieldNode
399
    {
400
        $start = $this->lexer->getToken();
401
402
        $nameOrAlias = $this->parseName();
403
404
        if ($this->skip(TokenKindEnum::COLON)) {
405
            $alias = $nameOrAlias;
406
            $name  = $this->parseName();
407
        } else {
408
            $name = $nameOrAlias;
409
        }
410
411
        return new FieldNode(
412
            $alias ?? null,
413
            $name,
414
            $this->parseArguments(false),
415
            $this->parseDirectives(),
416
            $this->peek(TokenKindEnum::BRACE_L)
417
                ? $this->parseSelectionSet()
418
                : null,
419
            $this->createLocation($start)
420
        );
421
    }
422
423
    /**
424
     * Arguments[Const] : ( Argument[?Const]+ )
425
     *
426
     * @param bool           $isConst
427
     * @return array
428
     * @throws SyntaxErrorException
429
     */
430
    protected function parseArguments(bool $isConst = false): ?array
431
    {
432
        /**
433
         * @return ArgumentNode
434
         */
435
        $parseFunction = function () use ($isConst): ArgumentNode {
436
            return $this->parseArgument($isConst);
437
        };
438
439
        return $this->peek(TokenKindEnum::PAREN_L)
440
            ? $this->many(
441
                TokenKindEnum::PAREN_L,
442
                $parseFunction,
443
                TokenKindEnum::PAREN_R
444
            )
445
            : [];
446
    }
447
448
    /**
449
     * Argument[Const] : Name : Value[?Const]
450
     *
451
     * @param bool           $isConst
452
     * @return ArgumentNode
453
     * @throws SyntaxErrorException
454
     */
455
    protected function parseArgument(bool $isConst = false): ArgumentNode
456
    {
457
        $start = $this->lexer->getToken();
458
459
        /**
460
         * @return NodeInterface|TypeNodeInterface|ValueNodeInterface
461
         */
462
        $parseValue = function () use ($isConst): NodeInterface {
463
            $this->expect(TokenKindEnum::COLON);
464
            return $this->parseValueLiteral($isConst);
465
        };
466
467
        return new ArgumentNode(
468
            $this->parseName(),
469
            $parseValue(),
470
            $this->createLocation($start)
471
        );
472
    }
473
474
    // Implements the parsing rules in the Fragments section.
475
476
    /**
477
     * Corresponds to both FragmentSpread and InlineFragment in the spec.
478
     *
479
     * FragmentSpread : ... FragmentName Directives?
480
     *
481
     * InlineFragment : ... TypeCondition? Directives? SelectionSet
482
     *
483
     * @return FragmentNodeInterface
484
     * @throws SyntaxErrorException
485
     */
486
    protected function parseFragment(): FragmentNodeInterface
487
    {
488
        $start = $this->lexer->getToken();
489
490
        $this->expect(TokenKindEnum::SPREAD);
491
492
        $token = $this->lexer->getToken();
493
494
        if (KeywordEnum::ON !== $token->value && $this->peek(TokenKindEnum::NAME)) {
495
            return new FragmentSpreadNode(
496
                $this->parseFragmentName($token),
497
                $this->parseDirectives(),
498
                null,
499
                $this->createLocation($start)
500
            );
501
        }
502
503
        if (KeywordEnum::ON === $token->value) {
504
            $this->lexer->advance();
505
            $typeCondition = $this->parseNamedType();
506
        }
507
508
        return new InlineFragmentNode(
509
            $typeCondition ?? null,
510
            $this->parseDirectives(),
511
            $this->parseSelectionSet(),
512
            $this->createLocation($start)
513
        );
514
    }
515
516
    /**
517
     * FragmentDefinition :
518
     *   - fragment FragmentName on TypeCondition Directives? SelectionSet
519
     *
520
     * TypeCondition : NamedType
521
     *
522
     * @return FragmentDefinitionNode
523
     * @throws SyntaxErrorException
524
     */
525
    protected function parseFragmentDefinition(): FragmentDefinitionNode
526
    {
527
        $start = $this->lexer->getToken();
528
529
        $this->expectKeyword(KeywordEnum::FRAGMENT);
530
531
        $parseTypeCondition = function () {
532
            $this->expectKeyword(KeywordEnum::ON);
533
            return $this->parseNamedType();
534
        };
535
536
        return new FragmentDefinitionNode(
537
            $this->parseFragmentName(),
538
            $this->parseVariableDefinitions(),
539
            $parseTypeCondition(),
540
            $this->parseDirectives(),
541
            $this->parseSelectionSet(),
542
            $this->createLocation($start)
543
        );
544
    }
545
546
    /**
547
     * FragmentName : Name but not `on`
548
     *
549
     * @param Token|null $token
550
     * @return NameNode
551
     * @throws SyntaxErrorException
552
     */
553
    protected function parseFragmentName(?Token $token = null): NameNode
554
    {
555
        if (null === $token) {
556
            $token = $this->lexer->getToken();
557
        }
558
559
        if (KeywordEnum::ON === $token->value) {
560
            throw $this->unexpected();
561
        }
562
563
        return $this->parseName();
564
    }
565
566
    // Implements the parsing rules in the Values section.
567
568
    /**
569
     * Value[Const] :
570
     *   - [~Const] Variable
571
     *   - IntValue
572
     *   - FloatValue
573
     *   - StringValue
574
     *   - BooleanValue
575
     *   - NullValue
576
     *   - EnumValue
577
     *   - ListValue[?Const]
578
     *   - ObjectValue[?Const]
579
     *
580
     * BooleanValue : one of `true` `false`
581
     *
582
     * NullValue : `null`
583
     *
584
     * EnumValue : Name but not `true`, `false` or `null`
585
     *
586
     * @param bool $isConst
587
     * @return NodeInterface|ValueNodeInterface|TypeNodeInterface
588
     * @throws SyntaxErrorException
589
     */
590
    protected function parseValueLiteral(bool $isConst = false): NodeInterface
591
    {
592
        $token = $this->lexer->getToken();
593
594
        switch ($token->kind) {
595
            case TokenKindEnum::BRACKET_L:
596
                return $this->parseList($isConst);
597
            case TokenKindEnum::BRACE_L:
598
                return $this->parseObject($isConst);
599
            case TokenKindEnum::INT:
600
                $this->lexer->advance();
601
                return new IntValueNode($token->value, $this->createLocation($token));
602
            case TokenKindEnum::FLOAT:
603
                $this->lexer->advance();
604
                return new FloatValueNode($token->value, $this->createLocation($token));
605
            case TokenKindEnum::STRING:
606
            case TokenKindEnum::BLOCK_STRING:
607
                return $this->parseStringLiteral();
608
            case TokenKindEnum::NAME:
609
                if ($token->value === 'true' || $token->value === 'false') {
610
                    $this->lexer->advance();
611
                    return new BooleanValueNode($token->value === 'true', $this->createLocation($token));
612
                }
613
614
                if ($token->value === 'null') {
615
                    $this->lexer->advance();
616
                    return new NullValueNode($this->createLocation($token));
617
                }
618
619
                $this->lexer->advance();
620
                return new EnumValueNode($token->value, $this->createLocation($token));
621
            case TokenKindEnum::DOLLAR:
622
                if (!$isConst) {
623
                    return $this->parseVariable();
624
                }
625
                break;
626
        }
627
628
        throw $this->unexpected();
629
    }
630
631
    /**
632
     * @return StringValueNode
633
     */
634
    protected function parseStringLiteral(): StringValueNode
635
    {
636
        $token = $this->lexer->getToken();
637
638
        $this->lexer->advance();
639
640
        return new StringValueNode(
641
            $token->value,
642
            $token->kind === TokenKindEnum::BLOCK_STRING,
643
            $this->createLocation($token)
644
        );
645
    }
646
647
    /**
648
     * ListValue[Const] :
649
     *   - [ ]
650
     *   - [ Value[?Const]+ ]
651
     *
652
     * @param bool $isConst
653
     * @return ListValueNode
654
     * @throws SyntaxErrorException
655
     */
656
    protected function parseList(bool $isConst): ListValueNode
657
    {
658
        $start = $this->lexer->getToken();
659
660
        $parseFunction = function () use ($isConst) {
661
            return $this->parseValueLiteral($isConst);
662
        };
663
664
        return new ListValueNode(
665
            $this->any(
666
                TokenKindEnum::BRACKET_L,
667
                $parseFunction,
668
                TokenKindEnum::BRACKET_R
669
            ),
670
            $this->createLocation($start)
671
        );
672
    }
673
674
    /**
675
     * ObjectValue[Const] :
676
     *   - { }
677
     *   - { ObjectField[?Const]+ }
678
     *
679
     * @param bool $isConst
680
     * @return ObjectValueNode
681
     * @throws SyntaxErrorException
682
     */
683
    protected function parseObject(bool $isConst): ObjectValueNode
684
    {
685
        $start = $this->lexer->getToken();
686
687
        $this->expect(TokenKindEnum::BRACE_L);
688
689
        $fields = [];
690
691
        while (!$this->skip(TokenKindEnum::BRACE_R)) {
692
            $fields[] = $this->parseObjectField($isConst);
693
        }
694
695
        return new ObjectValueNode($fields, $this->createLocation($start));
696
    }
697
698
    /**
699
     * ObjectField[Const] : Name : Value[?Const]
700
     *
701
     * @param bool $isConst
702
     * @return ObjectFieldNode
703
     * @throws SyntaxErrorException
704
     */
705
    protected function parseObjectField(bool $isConst): ObjectFieldNode
706
    {
707
        $start = $this->lexer->getToken();
708
709
        /**
710
         * @param bool $isConst
711
         * @return NodeInterface|TypeNodeInterface|ValueNodeInterface
712
         */
713
        $parseValue = function (bool $isConst): NodeInterface {
714
            $this->expect(TokenKindEnum::COLON);
715
            return $this->parseValueLiteral($isConst);
716
        };
717
718
        return new ObjectFieldNode(
719
            $this->parseName(),
720
            $parseValue($isConst),
721
            $this->createLocation($start)
722
        );
723
    }
724
725
    // Implements the parsing rules in the Directives section.
726
727
    /**
728
     * Directives[Const] : Directive[?Const]+
729
     *
730
     * @param bool $isConst
731
     * @return array
732
     * @throws SyntaxErrorException
733
     */
734
    protected function parseDirectives(bool $isConst = false): array
735
    {
736
        $directives = [];
737
738
        while ($this->peek(TokenKindEnum::AT)) {
739
            $directives[] = $this->parseDirective($isConst);
740
        }
741
742
        return $directives;
743
    }
744
745
    /**
746
     * Directive[Const] : @ Name Arguments[?Const]?
747
     *
748
     * @param bool $isConst
749
     * @return DirectiveNode
750
     * @throws SyntaxErrorException
751
     */
752
    protected function parseDirective(bool $isConst): DirectiveNode
753
    {
754
        $start = $this->lexer->getToken();
755
756
        $this->expect(TokenKindEnum::AT);
757
758
        return new DirectiveNode(
759
            $this->parseName(),
760
            $this->parseArguments($isConst),
761
            $this->createLocation($start)
762
        );
763
    }
764
765
    // Implements the parsing rules in the Types section.
766
767
    /**
768
     * Type :
769
     *   - NamedType
770
     *   - ListType
771
     *   - NonNullType
772
     *
773
     * @return TypeNodeInterface
774
     * @throws SyntaxErrorException
775
     */
776
    protected function parseTypeReference(): TypeNodeInterface
777
    {
778
        $start = $this->lexer->getToken();
779
780
        if ($this->skip(TokenKindEnum::BRACKET_L)) {
781
            $type = $this->parseTypeReference();
782
783
            $this->expect(TokenKindEnum::BRACKET_R);
784
785
            $type = new ListTypeNode($type, $this->createLocation($start));
786
        } else {
787
            $type = $this->parseNamedType();
788
        }
789
790
        if ($this->skip(TokenKindEnum::BANG)) {
791
            return new NonNullTypeNode($type, $this->createLocation($start));
792
        }
793
794
        return $type;
795
    }
796
797
    /**
798
     * NamedType : Name
799
     *
800
     * @return NamedTypeNode
801
     * @throws SyntaxErrorException
802
     */
803
    protected function parseNamedType(): NamedTypeNode
804
    {
805
        $start = $this->lexer->getToken();
806
807
        return new NamedTypeNode($this->parseName(), $this->createLocation($start));
808
    }
809
810
    // Implements the parsing rules in the Type Definition section.
811
812
    /**
813
     * TypeSystemDefinition :
814
     *   - SchemaDefinition
815
     *   - TypeDefinition
816
     *   - TypeExtension
817
     *   - DirectiveDefinition
818
     *
819
     * TypeDefinition :
820
     *   - ScalarTypeDefinition
821
     *   - ObjectTypeDefinition
822
     *   - InterfaceTypeDefinition
823
     *   - UnionTypeDefinition
824
     *   - EnumTypeDefinition
825
     *   - InputObjectTypeDefinition
826
     *
827
     * @return NodeInterface|TypeDefinitionNodeInterface|TypeExtensionNodeInterface
828
     * @throws SyntaxErrorException
829
     * @throws \ReflectionException
830
     */
831
    protected function parseTypeSystemDefinition(): NodeInterface
832
    {
833
        // Many definitions begin with a description and require a lookahead.
834
        $token = $this->peekDescription()
835
            ? $this->lexer->lookahead()
836
            : $this->lexer->getToken();
837
838
        if ($token->kind === TokenKindEnum::NAME) {
839
            switch ($token->value) {
840
                case KeywordEnum::SCHEMA:
841
                    return $this->parseSchemaDefinition();
842
                case KeywordEnum::SCALAR:
843
                    return $this->parseScalarTypeDefinition();
844
                case KeywordEnum::TYPE:
845
                    return $this->parseObjectTypeDefinition();
846
                case KeywordEnum::INTERFACE:
847
                    return $this->parseInterfaceTypeDefinition();
848
                case KeywordEnum::UNION:
849
                    return $this->parseUnionTypeDefinition();
850
                case KeywordEnum::ENUM:
851
                    return $this->parseEnumTypeDefinition();
852
                case KeywordEnum::INPUT:
853
                    return $this->parseInputObjectTypeDefinition();
854
                case KeywordEnum::DIRECTIVE:
855
                    return $this->parseDirectiveDefinition();
856
                case KeywordEnum::EXTEND:
857
                    return $this->parseTypeSystemExtension();
858
            }
859
        }
860
861
        throw $this->unexpected($token);
862
    }
863
864
    /**
865
     * @return bool
866
     */
867
    protected function peekDescription(): bool
868
    {
869
        return $this->peek(TokenKindEnum::STRING) || $this->peek(TokenKindEnum::BLOCK_STRING);
870
    }
871
872
    /**
873
     * Description : StringValue
874
     *
875
     * @return NodeInterface|null
876
     */
877
    public function parseDescription(): ?StringValueNode
878
    {
879
        return $this->peekDescription()
880
            ? $this->parseStringLiteral()
881
            : null;
882
    }
883
884
    /**
885
     * SchemaDefinition : schema Directives[Const]? { OperationTypeDefinition+ }
886
     *
887
     * @return SchemaDefinitionNode
888
     * @throws SyntaxErrorException
889
     */
890
    protected function parseSchemaDefinition(): SchemaDefinitionNode
891
    {
892
        $start = $this->lexer->getToken();
893
894
        $this->expectKeyword(KeywordEnum::SCHEMA);
895
896
        return new SchemaDefinitionNode(
897
            $this->parseDirectives(),
898
            $this->many(
899
                TokenKindEnum::BRACE_L,
900
                [$this, 'parseOperationTypeDefinition'],
901
                TokenKindEnum::BRACE_R
902
            ),
903
            $this->createLocation($start)
904
        );
905
    }
906
907
    /**
908
     * OperationTypeDefinition : OperationType : NamedType
909
     *
910
     * @return OperationTypeDefinitionNode
911
     * @throws SyntaxErrorException
912
     */
913
    protected function parseOperationTypeDefinition(): OperationTypeDefinitionNode
914
    {
915
        $start = $this->lexer->getToken();
916
917
        $operation = $this->parseOperationType();
918
919
        $this->expect(TokenKindEnum::COLON);
920
921
        return new OperationTypeDefinitionNode(
922
            $operation,
923
            $this->parseNamedType(),
924
            $this->createLocation($start)
925
        );
926
    }
927
928
    /**
929
     * ScalarTypeDefinition : Description? scalar Name Directives[Const]?
930
     *
931
     * @return ScalarTypeDefinitionNode
932
     * @throws SyntaxErrorException
933
     */
934
    protected function parseScalarTypeDefinition(): ScalarTypeDefinitionNode
935
    {
936
        $start = $this->lexer->getToken();
937
938
        $description = $this->parseDescription();
939
940
        $this->expectKeyword(KeywordEnum::SCALAR);
941
942
        return new ScalarTypeDefinitionNode(
943
            $description,
944
            $this->parseName(),
945
            $this->parseDirectives(),
946
            $this->createLocation($start)
947
        );
948
    }
949
950
    /**
951
     * ObjectTypeDefinition :
952
     *   Description?
953
     *   type Name ImplementsInterfaces? Directives[Const]? FieldsDefinition?
954
     *
955
     * @return ObjectTypeDefinitionNode
956
     * @throws SyntaxErrorException
957
     */
958
    protected function parseObjectTypeDefinition(): ObjectTypeDefinitionNode
959
    {
960
        $start = $this->lexer->getToken();
961
962
        $description = $this->parseDescription();
963
964
        $this->expectKeyword(KeywordEnum::TYPE);
965
966
        return new ObjectTypeDefinitionNode(
967
            $description,
968
            $this->parseName(),
969
            $this->parseImplementsInterfaces(),
970
            $this->parseDirectives(),
971
            $this->parseFieldsDefinition(),
972
            $this->createLocation($start)
973
        );
974
    }
975
976
    /**
977
     * ImplementsInterfaces :
978
     *   - implements `&`? NamedType
979
     *   - ImplementsInterfaces & NamedType
980
     *
981
     * @return array
982
     * @throws SyntaxErrorException
983
     */
984
    protected function parseImplementsInterfaces(): array
985
    {
986
        $types = [];
987
988
        $token = $this->lexer->getToken();
989
990
        if ('implements' === $token->value) {
991
            $this->lexer->advance();
992
993
            // Optional leading ampersand
994
            $this->skip(TokenKindEnum::AMP);
995
996
            do {
997
                $types[] = $this->parseNamedType();
998
            } while ($this->skip(TokenKindEnum::AMP));
999
        }
1000
1001
        return $types;
1002
    }
1003
1004
    /**
1005
     * FieldsDefinition : { FieldDefinition+ }
1006
     *
1007
     * @return array
1008
     * @throws SyntaxErrorException
1009
     */
1010
    protected function parseFieldsDefinition(): array
1011
    {
1012
        return $this->peek(TokenKindEnum::BRACE_L)
1013
            ? $this->many(
1014
                TokenKindEnum::BRACE_L,
1015
                [$this, 'parseFieldDefinition'],
1016
                TokenKindEnum::BRACE_R
1017
            )
1018
            : [];
1019
    }
1020
1021
    /**
1022
     * FieldDefinition :
1023
     *   - Description? Name ArgumentsDefinition? : Type Directives[Const]?
1024
     *
1025
     * @return FieldDefinitionNode
1026
     * @throws SyntaxErrorException
1027
     */
1028
    protected function parseFieldDefinition(): FieldDefinitionNode
1029
    {
1030
        $start = $this->lexer->getToken();
1031
1032
        $description = $this->parseDescription();
1033
        $name        = $this->parseName();
1034
        $arguments   = $this->parseArgumentsDefinition();
1035
1036
        $this->expect(TokenKindEnum::COLON);
1037
1038
        return new FieldDefinitionNode(
1039
            $description,
1040
            $name,
1041
            $arguments,
1042
            $this->parseTypeReference(),
1043
            $this->parseDirectives(),
1044
            $this->createLocation($start)
1045
        );
1046
    }
1047
1048
    /**
1049
     * ArgumentsDefinition : ( InputValueDefinition+ )
1050
     *
1051
     * @return InputValueDefinitionNode[]
1052
     * @throws SyntaxErrorException
1053
     */
1054
    protected function parseArgumentsDefinition(): array
1055
    {
1056
        $parseFunction = function (): InputValueDefinitionNode {
1057
            return $this->parseInputValueDefinition();
1058
        };
1059
1060
        return $this->peek(TokenKindEnum::PAREN_L)
1061
            ? $this->many(
1062
                TokenKindEnum::PAREN_L,
1063
                $parseFunction,
1064
                TokenKindEnum::PAREN_R
1065
            )
1066
            : [];
1067
    }
1068
1069
    /**
1070
     * InputValueDefinition :
1071
     *   - Description? Name : Type DefaultValue? Directives[Const]?
1072
     *
1073
     * @return InputValueDefinitionNode
1074
     * @throws SyntaxErrorException
1075
     */
1076
    protected function parseInputValueDefinition(): InputValueDefinitionNode
1077
    {
1078
        $start = $this->lexer->getToken();
1079
1080
        $description = $this->parseDescription();
1081
        $name        = $this->parseName();
1082
1083
        $this->expect(TokenKindEnum::COLON);
1084
1085
        return new InputValueDefinitionNode(
1086
            $description,
1087
            $name,
1088
            $this->parseTypeReference(),
1089
            $this->skip(TokenKindEnum::EQUALS)
1090
                ? $this->parseValueLiteral(true)
1091
                : null,
1092
            $this->parseDirectives(true),
1093
            $this->createLocation($start)
1094
        );
1095
    }
1096
1097
    /**
1098
     * InterfaceTypeDefinition :
1099
     *   - Description? interface Name Directives[Const]? FieldsDefinition?
1100
     *
1101
     * @return InterfaceTypeDefinitionNode
1102
     * @throws SyntaxErrorException
1103
     */
1104
    protected function parseInterfaceTypeDefinition(): InterfaceTypeDefinitionNode
1105
    {
1106
        $start = $this->lexer->getToken();
1107
1108
        $description = $this->parseDescription();
1109
1110
        $this->expectKeyword(KeywordEnum::INTERFACE);
1111
1112
        return new InterfaceTypeDefinitionNode(
1113
            $description,
1114
            $this->parseName(),
1115
            $this->parseDirectives(),
1116
            $this->parseFieldsDefinition(),
1117
            $this->createLocation($start)
1118
        );
1119
    }
1120
1121
    /**
1122
     * UnionTypeDefinition :
1123
     *   - Description? union Name Directives[Const]? UnionMemberTypes?
1124
     *
1125
     * @return UnionTypeDefinitionNode
1126
     * @throws SyntaxErrorException
1127
     */
1128
    protected function parseUnionTypeDefinition(): UnionTypeDefinitionNode
1129
    {
1130
        $start = $this->lexer->getToken();
1131
1132
        $description = $this->parseDescription();
1133
1134
        $this->expectKeyword(KeywordEnum::UNION);
1135
1136
        return new UnionTypeDefinitionNode(
1137
            $description,
1138
            $this->parseName(),
1139
            $this->parseDirectives(),
1140
            $this->parseUnionMemberTypes(),
1141
            $this->createLocation($start)
1142
        );
1143
    }
1144
1145
    /**
1146
     * UnionMemberTypes :
1147
     *   - = `|`? NamedType
1148
     *   - UnionMemberTypes | NamedType
1149
     *
1150
     * @return array
1151
     * @throws SyntaxErrorException
1152
     */
1153
    protected function parseUnionMemberTypes(): array
1154
    {
1155
        $types = [];
1156
1157
        if ($this->skip(TokenKindEnum::EQUALS)) {
1158
            // Optional leading pipe
1159
            $this->skip(TokenKindEnum::PIPE);
1160
1161
            do {
1162
                $types[] = $this->parseNamedType();
1163
            } while ($this->skip(TokenKindEnum::PIPE));
1164
        }
1165
1166
        return $types;
1167
    }
1168
1169
    /**
1170
     * EnumTypeDefinition :
1171
     *   - Description? enum Name Directives[Const]? EnumValuesDefinition?
1172
     *
1173
     * @return EnumTypeDefinitionNode
1174
     * @throws SyntaxErrorException
1175
     */
1176
    protected function parseEnumTypeDefinition(): EnumTypeDefinitionNode
1177
    {
1178
        $start = $this->lexer->getToken();
1179
1180
        $description = $this->parseDescription();
1181
1182
        $this->expectKeyword(KeywordEnum::ENUM);
1183
1184
        return new EnumTypeDefinitionNode(
1185
            $description,
1186
            $this->parseName(),
1187
            $this->parseDirectives(),
1188
            $this->parseEnumValuesDefinition(),
1189
            $this->createLocation($start)
1190
        );
1191
    }
1192
1193
    /**
1194
     * EnumValuesDefinition : { EnumValueDefinition+ }
1195
     *
1196
     * @return array
1197
     * @throws SyntaxErrorException
1198
     */
1199
    protected function parseEnumValuesDefinition(): array
1200
    {
1201
        return $this->peek(TokenKindEnum::BRACE_L)
1202
            ? $this->many(
1203
                TokenKindEnum::BRACE_L,
1204
                [$this, 'parseEnumValueDefinition'],
1205
                TokenKindEnum::BRACE_R
1206
            )
1207
            : [];
1208
    }
1209
1210
    /**
1211
     * EnumValueDefinition : Description? EnumValue Directives[Const]?
1212
     *
1213
     * EnumValue : Name
1214
     *
1215
     * @return EnumValueDefinitionNode
1216
     * @throws SyntaxErrorException
1217
     */
1218
    protected function parseEnumValueDefinition(): EnumValueDefinitionNode
1219
    {
1220
        $start = $this->lexer->getToken();
1221
1222
        return new EnumValueDefinitionNode(
1223
            $this->parseDescription(),
1224
            $this->parseName(),
1225
            $this->parseDirectives(),
1226
            $this->createLocation($start)
1227
        );
1228
    }
1229
1230
    /**
1231
     * InputObjectTypeDefinition :
1232
     *   - Description? input Name Directives[Const]? InputFieldsDefinition?
1233
     *
1234
     * @return InputObjectTypeDefinitionNode
1235
     * @throws SyntaxErrorException
1236
     */
1237
    protected function parseInputObjectTypeDefinition(): InputObjectTypeDefinitionNode
1238
    {
1239
        $start = $this->lexer->getToken();
1240
1241
        $description = $this->parseDescription();
1242
1243
        $this->expectKeyword(KeywordEnum::INPUT);
1244
1245
        return new InputObjectTypeDefinitionNode(
1246
            $description,
1247
            $this->parseName(),
1248
            $this->parseDirectives(true),
1249
            $this->parseInputFieldsDefinition(),
1250
            $this->createLocation($start)
1251
        );
1252
    }
1253
1254
    /**
1255
     * InputFieldsDefinition : { InputValueDefinition+ }
1256
     *
1257
     * @return array
1258
     * @throws SyntaxErrorException
1259
     */
1260
    protected function parseInputFieldsDefinition(): array
1261
    {
1262
        $parseFunction = function (): InputValueDefinitionNode {
1263
            return $this->parseInputValueDefinition();
1264
        };
1265
1266
        return $this->peek(TokenKindEnum::BRACE_L)
1267
            ? $this->many(
1268
                TokenKindEnum::BRACE_L,
1269
                $parseFunction,
1270
                TokenKindEnum::BRACE_R
1271
            )
1272
            : [];
1273
    }
1274
1275
    /**
1276
     * TypeExtension :
1277
     *   - ScalarTypeExtension
1278
     *   - ObjectTypeExtension
1279
     *   - InterfaceTypeExtension
1280
     *   - UnionTypeExtension
1281
     *   - EnumTypeExtension
1282
     *   - InputObjectTypeDefinition
1283
     *
1284
     * @return TypeExtensionNodeInterface
1285
     * @throws SyntaxErrorException
1286
     */
1287
    protected function parseTypeSystemExtension(): TypeExtensionNodeInterface
1288
    {
1289
        $token = $this->lexer->lookahead();
1290
1291
        if ($token->kind === TokenKindEnum::NAME) {
1292
            switch ($token->value) {
1293
                case KeywordEnum::SCALAR:
1294
                    return $this->parseScalarTypeExtension(false);
1295
                case KeywordEnum::TYPE:
1296
                    return $this->parseObjectTypeExtension();
1297
                case KeywordEnum::INTERFACE:
1298
                    return $this->parseInterfaceTypeExtension();
1299
                case KeywordEnum::UNION:
1300
                    return $this->parseUnionTypeExtension();
1301
                case KeywordEnum::ENUM:
1302
                    return $this->parseEnumTypeExtension();
1303
                case KeywordEnum::INPUT:
1304
                    return $this->parseInputObjectTypeExtension();
1305
            }
1306
        }
1307
1308
        throw $this->unexpected($token);
1309
    }
1310
1311
    /**
1312
     * ScalarTypeExtension :
1313
     *   - extend scalar Name Directives[Const]
1314
     *
1315
     * @param bool $isConst
1316
     * @return ScalarTypeExtensionNode
1317
     * @throws SyntaxErrorException
1318
     */
1319
    protected function parseScalarTypeExtension(bool $isConst = false): ScalarTypeExtensionNode
1320
    {
1321
        $start = $this->lexer->getToken();
1322
1323
        $this->expectKeyword(KeywordEnum::EXTEND);
1324
        $this->expectKeyword(KeywordEnum::SCALAR);
1325
1326
        $name       = $this->parseName();
1327
        $directives = $this->parseDirectives($isConst);
1328
1329
        if (empty($directives)) {
1330
            throw $this->unexpected();
1331
        }
1332
1333
        return new ScalarTypeExtensionNode($name, $directives, $this->createLocation($start));
1334
    }
1335
1336
    /**
1337
     * ObjectTypeExtension :
1338
     *  - extend type Name ImplementsInterfaces? Directives[Const]? FieldsDefinition
1339
     *  - extend type Name ImplementsInterfaces? Directives[Const]
1340
     *  - extend type Name ImplementsInterfaces
1341
     *
1342
     * @return ObjectTypeExtensionNode
1343
     * @throws SyntaxErrorException
1344
     */
1345
    protected function parseObjectTypeExtension(): ObjectTypeExtensionNode
1346
    {
1347
        $start = $this->lexer->getToken();
1348
1349
        $this->expectKeyword(KeywordEnum::EXTEND);
1350
        $this->expectKeyword(KeywordEnum::TYPE);
1351
1352
        $name       = $this->parseName();
1353
        $interfaces = $this->parseImplementsInterfaces();
1354
        $directives = $this->parseDirectives();
1355
        $fields     = $this->parseFieldsDefinition();
1356
1357
        if (empty($interfaces) && empty($directives) && empty($fields)) {
1358
            throw $this->unexpected();
1359
        }
1360
1361
        return new ObjectTypeExtensionNode(
1362
            $name,
1363
            $interfaces,
1364
            $directives,
1365
            $fields,
1366
            $this->createLocation($start)
1367
        );
1368
    }
1369
1370
    /**
1371
     * InterfaceTypeExtension :
1372
     *   - extend interface Name Directives[Const]? FieldsDefinition
1373
     *   - extend interface Name Directives[Const]
1374
     *
1375
     * @return InterfaceTypeExtensionNode
1376
     * @throws SyntaxErrorException
1377
     */
1378
    protected function parseInterfaceTypeExtension(): InterfaceTypeExtensionNode
1379
    {
1380
        $start = $this->lexer->getToken();
1381
1382
        $this->expectKeyword(KeywordEnum::EXTEND);
1383
        $this->expectKeyword(KeywordEnum::INTERFACE);
1384
1385
        $name       = $this->parseName();
1386
        $directives = $this->parseDirectives();
1387
        $fields     = $this->parseFieldsDefinition();
1388
1389
        if (empty($directives) && empty($fields)) {
1390
            throw $this->unexpected();
1391
        }
1392
1393
        return new InterfaceTypeExtensionNode($name, $directives, $fields, $this->createLocation($start));
1394
    }
1395
1396
    /**
1397
     * UnionTypeExtension :
1398
     *   - extend union Name Directives[Const]? UnionMemberTypes
1399
     *   - extend union Name Directives[Const]
1400
     *
1401
     * @return UnionTypeExtensionNode
1402
     * @throws SyntaxErrorException
1403
     */
1404
    protected function parseUnionTypeExtension(): UnionTypeExtensionNode
1405
    {
1406
        $start = $this->lexer->getToken();
1407
1408
        $this->expectKeyword(KeywordEnum::EXTEND);
1409
        $this->expectKeyword(KeywordEnum::UNION);
1410
1411
        $name       = $this->parseName();
1412
        $directives = $this->parseDirectives();
1413
        $types      = $this->parseUnionMemberTypes();
1414
1415
        if (empty($directives) && empty($types)) {
1416
            throw $this->unexpected();
1417
        }
1418
1419
        return new UnionTypeExtensionNode($name, $directives, $types, $this->createLocation($start));
1420
    }
1421
1422
    /**
1423
     * EnumTypeExtension :
1424
     *   - extend enum Name Directives[Const]? EnumValuesDefinition
1425
     *   - extend enum Name Directives[Const]
1426
     *
1427
     * @return EnumTypeExtensionNode
1428
     * @throws SyntaxErrorException
1429
     */
1430
    protected function parseEnumTypeExtension(): EnumTypeExtensionNode
1431
    {
1432
        $start = $this->lexer->getToken();
1433
1434
        $this->expectKeyword(KeywordEnum::EXTEND);
1435
        $this->expectKeyword(KeywordEnum::ENUM);
1436
1437
        $name       = $this->parseName();
1438
        $directives = $this->parseDirectives();
1439
        $values     = $this->parseEnumValuesDefinition();
1440
1441
        if (empty($directives) && empty($values)) {
1442
            throw $this->unexpected();
1443
        }
1444
1445
        return new EnumTypeExtensionNode($name, $directives, $values, $this->createLocation($start));
1446
    }
1447
1448
    /**
1449
     * InputObjectTypeExtension :
1450
     *   - extend input Name Directives[Const]? InputFieldsDefinition
1451
     *   - extend input Name Directives[Const]
1452
     *
1453
     * @return InputObjectTypeExtensionNode
1454
     * @throws SyntaxErrorException
1455
     */
1456
    protected function parseInputObjectTypeExtension(): InputObjectTypeExtensionNode
1457
    {
1458
        $start = $this->lexer->getToken();
1459
1460
        $this->expectKeyword(KeywordEnum::EXTEND);
1461
        $this->expectKeyword(KeywordEnum::INPUT);
1462
1463
        $name       = $this->parseName();
1464
        $directives = $this->parseDirectives(true);
1465
        $fields     = $this->parseInputFieldsDefinition();
1466
1467
        if (empty($directives) && empty($fields)) {
1468
            throw $this->unexpected();
1469
        }
1470
1471
        return new InputObjectTypeExtensionNode($name, $directives, $fields, $this->createLocation($start));
1472
    }
1473
1474
    /**
1475
     * DirectiveDefinition :
1476
     *   - Description? directive @ Name ArgumentsDefinition? on DirectiveLocations
1477
     *
1478
     * @return DirectiveDefinitionNode
1479
     * @throws SyntaxErrorException
1480
     * @throws \ReflectionException
1481
     */
1482
    protected function parseDirectiveDefinition(): DirectiveDefinitionNode
1483
    {
1484
        $start = $this->lexer->getToken();
1485
1486
        $description = $this->parseDescription();
1487
1488
        $this->expectKeyword(KeywordEnum::DIRECTIVE);
1489
        $this->expect(TokenKindEnum::AT);
1490
1491
        $name      = $this->parseName();
1492
        $arguments = $this->parseArgumentsDefinition();
1493
1494
        $this->expectKeyword(KeywordEnum::ON);
1495
1496
        $locations = $this->parseDirectiveLocations();
1497
1498
        return new DirectiveDefinitionNode(
1499
            $description,
1500
            $name,
1501
            $arguments,
1502
            $locations,
1503
            $this->createLocation($start)
1504
        );
1505
    }
1506
1507
    /**
1508
     * DirectiveLocations :
1509
     *   - `|`? DirectiveLocation
1510
     *   - DirectiveLocations | DirectiveLocation
1511
     *
1512
     * @return array
1513
     * @throws SyntaxErrorException
1514
     * @throws \ReflectionException
1515
     */
1516
    protected function parseDirectiveLocations(): array
1517
    {
1518
        $this->skip(TokenKindEnum::PIPE);
1519
1520
        $locations = [];
1521
1522
        do {
1523
            $locations[] = $this->parseDirectiveLocation();
1524
        } while ($this->skip(TokenKindEnum::PIPE));
1525
1526
        return $locations;
1527
    }
1528
1529
    /**
1530
     * DirectiveLocation :
1531
     *   - ExecutableDirectiveLocation
1532
     *   - TypeSystemDirectiveLocation
1533
     *
1534
     * ExecutableDirectiveLocation : one of
1535
     *   `QUERY`
1536
     *   `MUTATION`
1537
     *   `SUBSCRIPTION`
1538
     *   `FIELD`
1539
     *   `FRAGMENT_DEFINITION`
1540
     *   `FRAGMENT_SPREAD`
1541
     *   `INLINE_FRAGMENT`
1542
     *
1543
     * TypeSystemDirectiveLocation : one of
1544
     *   `SCHEMA`
1545
     *   `SCALAR`
1546
     *   `OBJECT`
1547
     *   `FIELD_DEFINITION`
1548
     *   `ARGUMENT_DEFINITION`
1549
     *   `INTERFACE`
1550
     *   `UNION`
1551
     *   `ENUM`
1552
     *   `ENUM_VALUE`
1553
     *   `INPUT_OBJECT`
1554
     *   `INPUT_FIELD_DEFINITION`
1555
     *
1556
     * @return NameNode
1557
     * @throws SyntaxErrorException
1558
     * @throws \ReflectionException
1559
     */
1560
    protected function parseDirectiveLocation(): NameNode
1561
    {
1562
        $start = $this->lexer->getToken();
1563
1564
        $name = $this->parseName();
1565
1566
        if (arraySome(DirectiveLocationEnum::values(), function ($value) use ($name) {
1567
            return $name->getValue() === $value;
1568
        })) {
1569
            return $name;
1570
        }
1571
1572
        throw $this->unexpected($start);
1573
    }
1574
1575
    /**
1576
     * Returns a location object, used to identify the place in
1577
     * the source that created a given parsed object.
1578
     *
1579
     * @param Token $start
1580
     * @return Location|null
1581
     */
1582
    protected function createLocation(Token $start): ?Location
1583
    {
1584
        return !$this->lexer->getOption('noLocation', false)
1585
            ? new Location(
1586
                $start->start,
1587
                $this->lexer->getLastToken()->end,
1588
                $this->lexer->getSource()
1589
            )
1590
            : null;
1591
    }
1592
1593
    /**
1594
     * @param string|Source $source
1595
     * @param array         $options
1596
     * @return LexerInterface
1597
     * @throws InvariantException
1598
     */
1599
    protected function createLexer($source, array $options): LexerInterface
1600
    {
1601
        return new Lexer($source instanceof Source ? $source : new Source($source), $options);
1602
    }
1603
1604
    /**
1605
     * Determines if the next token is of a given kind.
1606
     *
1607
     * @param string $kind
1608
     * @return bool
1609
     */
1610
    protected function peek(string $kind): bool
1611
    {
1612
        return $this->lexer->getToken()->kind === $kind;
1613
    }
1614
1615
    /**
1616
     * If the next token is of the given kind, return true after advancing
1617
     * the lexer. Otherwise, do not change the parser state and return false.
1618
     *
1619
     * @param string $kind
1620
     * @return bool
1621
     */
1622
    protected function skip(string $kind): bool
1623
    {
1624
        if ($match = $this->peek($kind)) {
1625
            $this->lexer->advance();
1626
        }
1627
1628
        return $match;
1629
    }
1630
1631
    /**
1632
     * If the next token is of the given kind, return that token after advancing
1633
     * the lexer. Otherwise, do not change the parser state and throw an error.
1634
     *
1635
     * @param string $kind
1636
     * @return Token
1637
     * @throws SyntaxErrorException
1638
     */
1639
    protected function expect(string $kind): Token
1640
    {
1641
        $token = $this->lexer->getToken();
1642
1643
        if ($token->kind === $kind) {
1644
            $this->lexer->advance();
1645
            return $token;
1646
        }
1647
1648
        throw $this->lexer->createSyntaxErrorException(\sprintf('Expected %s, found %s.', $kind, $token));
1649
    }
1650
1651
    /**
1652
     * @param string $value
1653
     * @return Token
1654
     * @throws SyntaxErrorException
1655
     */
1656
    protected function expectKeyword(string $value): Token
1657
    {
1658
        $token = $this->lexer->getToken();
1659
1660
        if ($token->kind === TokenKindEnum::NAME && $token->value === $value) {
1661
            $this->lexer->advance();
1662
            return $token;
1663
        }
1664
1665
        throw $this->lexer->createSyntaxErrorException(\sprintf('Expected %s, found %s', $value, $token));
1666
    }
1667
1668
    /**
1669
     * Helper function for creating an error when an unexpected lexed token
1670
     * is encountered.
1671
     *
1672
     * @param Token|null $atToken
1673
     * @return SyntaxErrorException
1674
     */
1675
    protected function unexpected(?Token $atToken = null): SyntaxErrorException
1676
    {
1677
        $token = $atToken ?? $this->lexer->getToken();
1678
1679
        return $this->lexer->createSyntaxErrorException(\sprintf('Unexpected %s', $token));
1680
    }
1681
1682
    /**
1683
     * Returns a possibly empty list of parse nodes, determined by
1684
     * the parseFn. This list begins with a lex token of openKind
1685
     * and ends with a lex token of closeKind. Advances the parser
1686
     * to the next lex token after the closing token.
1687
     *
1688
     * @param string   $openKind
1689
     * @param callable $parseFunction
1690
     * @param string   $closeKind
1691
     * @return array
1692
     * @throws SyntaxErrorException
1693
     */
1694
    protected function any(string $openKind, callable $parseFunction, string $closeKind): array
1695
    {
1696
        $this->expect($openKind);
1697
1698
        $nodes = [];
1699
1700
        while (!$this->skip($closeKind)) {
1701
            $nodes[] = $parseFunction();
1702
        }
1703
1704
        return $nodes;
1705
    }
1706
1707
    /**
1708
     * Returns a non-empty list of parse nodes, determined by
1709
     * the parseFn. This list begins with a lex token of openKind
1710
     * and ends with a lex token of closeKind. Advances the parser
1711
     * to the next lex token after the closing token.
1712
     *
1713
     * @param string   $openKind
1714
     * @param callable $parseFunction
1715
     * @param string   $closeKind
1716
     * @return array
1717
     * @throws SyntaxErrorException
1718
     */
1719
    protected function many(string $openKind, callable $parseFunction, string $closeKind): array
1720
    {
1721
        $this->expect($openKind);
1722
1723
        $nodes = [$parseFunction()];
1724
1725
        while (!$this->skip($closeKind)) {
1726
            $nodes[] = $parseFunction();
1727
        }
1728
1729
        return $nodes;
1730
    }
1731
}
1732