Parser::lexArgument()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 8
nc 1
nop 1
dl 0
loc 16
rs 10
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\Language\Node\ArgumentNode;
7
use Digia\GraphQL\Language\Node\BooleanValueNode;
8
use Digia\GraphQL\Language\Node\DirectiveDefinitionNode;
9
use Digia\GraphQL\Language\Node\DirectiveNode;
10
use Digia\GraphQL\Language\Node\DocumentNode;
11
use Digia\GraphQL\Language\Node\EnumTypeDefinitionNode;
12
use Digia\GraphQL\Language\Node\EnumTypeExtensionNode;
13
use Digia\GraphQL\Language\Node\EnumValueDefinitionNode;
14
use Digia\GraphQL\Language\Node\EnumValueNode;
15
use Digia\GraphQL\Language\Node\ExecutableDefinitionNodeInterface;
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\SchemaExtensionNode;
46
use Digia\GraphQL\Language\Node\SelectionSetNode;
47
use Digia\GraphQL\Language\Node\StringValueNode;
48
use Digia\GraphQL\Language\Node\TypeNodeInterface;
49
use Digia\GraphQL\Language\Node\TypeSystemDefinitionNodeInterface;
50
use Digia\GraphQL\Language\Node\TypeSystemExtensionNodeInterface;
51
use Digia\GraphQL\Language\Node\UnionTypeDefinitionNode;
52
use Digia\GraphQL\Language\Node\UnionTypeExtensionNode;
53
use Digia\GraphQL\Language\Node\ValueNodeInterface;
54
use Digia\GraphQL\Language\Node\VariableDefinitionNode;
55
use Digia\GraphQL\Language\Node\VariableNode;
56
use function Digia\GraphQL\Util\arraySome;
57
58
/** @noinspection PhpHierarchyChecksInspection */
59
60
/**
61
 * Class Parser
62
 * @package Digia\GraphQL\Language
63
 */
64
class Parser implements ParserInterface
65
{
66
    /**
67
     * @var LexerInterface
68
     */
69
    protected $lexer;
70
71
    /**
72
     * @param string $name
73
     * @param array  $arguments
74
     * @return mixed
75
     *
76
     * @throws InvariantException
77
     * @throws SyntaxErrorException
78
     */
79
    public function __call(string $name, array $arguments)
80
    {
81
        $lexCallback = \str_replace('parse', 'lex', $name);
82
83
        if (\method_exists($this, $lexCallback)) {
84
            return $this->parsePartial([$this, $lexCallback], $arguments[0], $arguments[1] ?? []);
85
        }
86
87
        return $this;
88
    }
89
90
    /**
91
     * Given a GraphQL source, parses it into a Document.
92
     * Throws GraphQLError if a syntax error is encountered.
93
     *
94
     * @inheritdoc
95
     * @throws SyntaxErrorException
96
     * @throws \ReflectionException
97
     * @throws InvariantException
98
     */
99
    public function parse(Source $source, array $options = []): DocumentNode
100
    {
101
        $this->lexer = $this->createLexer($source, $options);
102
103
        return $this->lexDocument();
104
    }
105
106
    /**
107
     * @param callable $lexCallback
108
     * @param Source   $source
109
     * @param array    $options
110
     * @return NodeInterface
111
     * @throws SyntaxErrorException
112
     */
113
    protected function parsePartial(callable $lexCallback, Source $source, array $options = []): NodeInterface
114
    {
115
        $this->lexer = $this->createLexer($source, $options);
116
117
        $this->expect(TokenKindEnum::SOF);
118
        $node = $lexCallback();
119
        $this->expect(TokenKindEnum::EOF);
120
121
        return $node;
122
    }
123
124
    /**
125
     * Converts a name lex token into a name parse node.
126
     *
127
     * @return NameNode
128
     * @throws SyntaxErrorException
129
     */
130
    protected function lexName(): NameNode
131
    {
132
        $token = $this->expect(TokenKindEnum::NAME);
133
134
        return new NameNode($token->getValue(), $this->createLocation($token));
135
    }
136
137
    // Implements the parsing rules in the Document section.
138
139
    /**
140
     * Document : Definition+
141
     *
142
     * @return DocumentNode
143
     * @throws SyntaxErrorException
144
     * @throws \ReflectionException
145
     */
146
    protected function lexDocument(): DocumentNode
147
    {
148
        $start = $this->lexer->getToken();
149
150
        $this->expect(TokenKindEnum::SOF);
151
152
        $definitions = [];
153
154
        do {
155
            $definitions[] = $this->lexDefinition();
156
        } while (!$this->skip(TokenKindEnum::EOF));
157
158
        return new DocumentNode($definitions, $this->createLocation($start));
159
    }
160
161
    /**
162
     * Definition :
163
     *   - ExecutableDefinition
164
     *   - TypeSystemDefinition
165
     *
166
     * @return NodeInterface
167
     * @throws SyntaxErrorException
168
     * @throws \ReflectionException
169
     */
170
    protected function lexDefinition(): NodeInterface
171
    {
172
        if ($this->peek(TokenKindEnum::NAME)) {
173
            $token = $this->lexer->getToken();
174
175
            switch ($token->getValue()) {
176
                case KeywordEnum::QUERY:
177
                case KeywordEnum::MUTATION:
178
                case KeywordEnum::SUBSCRIPTION:
179
                case KeywordEnum::FRAGMENT:
180
                    return $this->lexExecutableDefinition();
181
                case KeywordEnum::SCHEMA:
182
                case KeywordEnum::SCALAR:
183
                case KeywordEnum::TYPE:
184
                case KeywordEnum::INTERFACE:
185
                case KeywordEnum::UNION:
186
                case KeywordEnum::ENUM:
187
                case KeywordEnum::INPUT:
188
                case KeywordEnum::DIRECTIVE:
189
                    return $this->lexTypeSystemDefinition();
190
                case KeywordEnum::EXTEND:
191
                    return $this->lexTypeSystemExtension();
192
193
            }
194
        } elseif ($this->peek(TokenKindEnum::BRACE_L)) {
195
            return $this->lexExecutableDefinition();
196
        } elseif ($this->peekDescription()) {
197
            return $this->lexTypeSystemDefinition();
198
        }
199
200
        throw $this->unexpected();
201
    }
202
203
    /**
204
     * ExecutableDefinition :
205
     *   - OperationDefinition
206
     *   - FragmentDefinition
207
     *
208
     * @return ExecutableDefinitionNodeInterface
209
     * @throws SyntaxErrorException
210
     */
211
    protected function lexExecutableDefinition(): ExecutableDefinitionNodeInterface
212
    {
213
        if ($this->peek(TokenKindEnum::NAME)) {
214
            // Valid names are: query, mutation, subscription and fragment
215
            $token = $this->lexer->getToken();
216
217
            switch ($token->getValue()) {
218
                case KeywordEnum::QUERY:
219
                case KeywordEnum::MUTATION:
220
                case KeywordEnum::SUBSCRIPTION:
221
                    return $this->lexOperationDefinition();
222
                case KeywordEnum::FRAGMENT:
223
                    return $this->lexFragmentDefinition();
224
            }
225
        } elseif ($this->peek(TokenKindEnum::BRACE_L)) {
226
            // Anonymous query
227
            return $this->lexOperationDefinition();
228
        }
229
230
        throw $this->unexpected();
231
    }
232
233
    // Implements the parsing rules in the Operations section.
234
235
    /**
236
     * OperationDefinition :
237
     *  - SelectionSet
238
     *  - OperationType Name? VariableDefinitions? Directives? SelectionSet
239
     *
240
     * @return OperationDefinitionNode
241
     * @throws SyntaxErrorException
242
     */
243
    protected function lexOperationDefinition(): OperationDefinitionNode
244
    {
245
        $start = $this->lexer->getToken();
246
247
        if ($this->peek(TokenKindEnum::BRACE_L)) {
248
            // Anonymous query
249
            return new OperationDefinitionNode(
250
                KeywordEnum::QUERY,
251
                null,
252
                [],
253
                [],
254
                $this->lexSelectionSet(),
255
                $this->createLocation($start)
256
            );
257
        }
258
259
        $operation = $this->lexOperationType();
260
261
        if ($this->peek(TokenKindEnum::NAME)) {
262
            $name = $this->lexName();
263
        }
264
265
        return new OperationDefinitionNode(
266
            $operation,
0 ignored issues
show
Bug introduced by
It seems like $operation can also be of type null; however, parameter $operation of Digia\GraphQL\Language\N...tionNode::__construct() 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

266
            /** @scrutinizer ignore-type */ $operation,
Loading history...
267
            $name ?? null,
268
            $this->lexVariableDefinitions(),
269
            $this->lexDirectives(),
270
            $this->lexSelectionSet(),
271
            $this->createLocation($start)
272
        );
273
    }
274
275
    /**
276
     * OperationType : one of query mutation subscription
277
     *
278
     * @return null|string
279
     * @throws SyntaxErrorException
280
     */
281
    protected function lexOperationType(): ?string
282
    {
283
        $token = $this->expect(TokenKindEnum::NAME);
284
        $value = $token->getValue();
285
286
        if (isOperation($value)) {
0 ignored issues
show
Bug introduced by
It seems like $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

286
        if (isOperation(/** @scrutinizer ignore-type */ $value)) {
Loading history...
287
            return $value;
288
        }
289
290
        throw $this->unexpected($token);
291
    }
292
293
    /**
294
     * VariableDefinitions : ( VariableDefinition+ )
295
     *
296
     * @return array
297
     * @throws SyntaxErrorException
298
     */
299
    protected function lexVariableDefinitions(): array
300
    {
301
        return $this->peek(TokenKindEnum::PAREN_L)
302
            ? $this->many(
303
                TokenKindEnum::PAREN_L,
304
                [$this, 'lexVariableDefinition'],
305
                TokenKindEnum::PAREN_R
306
            )
307
            : [];
308
    }
309
310
    /**
311
     * VariableDefinition : Variable : Type DefaultValue?
312
     *
313
     * @return VariableDefinitionNode
314
     * @throws SyntaxErrorException
315
     */
316
    protected function lexVariableDefinition(): VariableDefinitionNode
317
    {
318
        $start = $this->lexer->getToken();
319
320
        /**
321
         * @return TypeNodeInterface
322
         */
323
        $parseType = function (): TypeNodeInterface {
324
            $this->expect(TokenKindEnum::COLON);
325
            return $this->lexType();
326
        };
327
328
        return new VariableDefinitionNode(
329
            $this->lexVariable(),
330
            $parseType(),
331
            $this->skip(TokenKindEnum::EQUALS)
332
                ? $this->lexValue(true)
333
                : null,
334
            $this->createLocation($start)
335
        );
336
    }
337
338
    /**
339
     * Variable : $ Name
340
     *
341
     * @return VariableNode
342
     * @throws SyntaxErrorException
343
     */
344
    protected function lexVariable(): VariableNode
345
    {
346
        $start = $this->lexer->getToken();
347
348
        $this->expect(TokenKindEnum::DOLLAR);
349
350
        return new VariableNode($this->lexName(), $this->createLocation($start));
351
    }
352
353
    /**
354
     * SelectionSet : { Selection+ }
355
     *
356
     * @return SelectionSetNode
357
     * @throws SyntaxErrorException
358
     */
359
    protected function lexSelectionSet(): SelectionSetNode
360
    {
361
        $start = $this->lexer->getToken();
362
363
        return new SelectionSetNode(
364
            $this->many(
365
                TokenKindEnum::BRACE_L,
366
                [$this, 'lexSelection'],
367
                TokenKindEnum::BRACE_R
368
            ),
369
            $this->createLocation($start)
370
        );
371
    }
372
373
    /**
374
     * Selection :
375
     *   - Field
376
     *   - FragmentSpread
377
     *   - InlineFragment
378
     *
379
     * @return NodeInterface|FragmentNodeInterface|FieldNode
380
     * @throws SyntaxErrorException
381
     */
382
    protected function lexSelection(): NodeInterface
383
    {
384
        return $this->peek(TokenKindEnum::SPREAD)
385
            ? $this->lexFragment()
386
            : $this->lexField();
387
    }
388
389
    /**
390
     * Field : Alias? Name Arguments? Directives? SelectionSet?
391
     *
392
     * Alias : Name :
393
     *
394
     * @return FieldNode
395
     * @throws SyntaxErrorException
396
     */
397
    protected function lexField(): FieldNode
398
    {
399
        $start = $this->lexer->getToken();
400
401
        $nameOrAlias = $this->lexName();
402
403
        if ($this->skip(TokenKindEnum::COLON)) {
404
            $alias = $nameOrAlias;
405
            $name  = $this->lexName();
406
        } else {
407
            $name = $nameOrAlias;
408
        }
409
410
        return new FieldNode(
411
            $alias ?? null,
412
            $name,
413
            $this->lexArguments(false),
414
            $this->lexDirectives(),
415
            $this->peek(TokenKindEnum::BRACE_L)
416
                ? $this->lexSelectionSet()
417
                : null,
418
            $this->createLocation($start)
419
        );
420
    }
421
422
    /**
423
     * Arguments[Const] : ( Argument[?Const]+ )
424
     *
425
     * @param bool $isConst
426
     * @return array
427
     * @throws SyntaxErrorException
428
     */
429
    protected function lexArguments(bool $isConst = false): ?array
430
    {
431
        /**
432
         * @return ArgumentNode
433
         */
434
        $parseFunction = function () use ($isConst): ArgumentNode {
435
            return $this->lexArgument($isConst);
436
        };
437
438
        return $this->peek(TokenKindEnum::PAREN_L)
439
            ? $this->many(
440
                TokenKindEnum::PAREN_L,
441
                $parseFunction,
442
                TokenKindEnum::PAREN_R
443
            )
444
            : [];
445
    }
446
447
    /**
448
     * Argument[Const] : Name : Value[?Const]
449
     *
450
     * @param bool $isConst
451
     * @return ArgumentNode
452
     * @throws SyntaxErrorException
453
     */
454
    protected function lexArgument(bool $isConst = false): ArgumentNode
455
    {
456
        $start = $this->lexer->getToken();
457
458
        /**
459
         * @return NodeInterface|TypeNodeInterface|ValueNodeInterface
460
         */
461
        $parseValue = function () use ($isConst): NodeInterface {
462
            $this->expect(TokenKindEnum::COLON);
463
            return $this->lexValue($isConst);
464
        };
465
466
        return new ArgumentNode(
467
            $this->lexName(),
468
            $parseValue(),
469
            $this->createLocation($start)
470
        );
471
    }
472
473
    // Implements the parsing rules in the Fragments section.
474
475
    /**
476
     * Corresponds to both FragmentSpread and InlineFragment in the spec.
477
     *
478
     * FragmentSpread : ... FragmentName Directives?
479
     *
480
     * InlineFragment : ... TypeCondition? Directives? SelectionSet
481
     *
482
     * @return FragmentNodeInterface
483
     * @throws SyntaxErrorException
484
     */
485
    protected function lexFragment(): FragmentNodeInterface
486
    {
487
        $start = $this->lexer->getToken();
488
489
        $this->expect(TokenKindEnum::SPREAD);
490
491
        $token = $this->lexer->getToken();
492
493
        if (KeywordEnum::ON !== $token->getValue() && $this->peek(TokenKindEnum::NAME)) {
494
            return new FragmentSpreadNode(
495
                $this->lexFragmentName($token),
496
                $this->lexDirectives(),
497
                null,
498
                $this->createLocation($start)
499
            );
500
        }
501
502
        if (KeywordEnum::ON === $token->getValue()) {
503
            $this->lexer->advance();
504
            $typeCondition = $this->lexNamedType();
505
        }
506
507
        return new InlineFragmentNode(
508
            $typeCondition ?? null,
509
            $this->lexDirectives(),
510
            $this->lexSelectionSet(),
511
            $this->createLocation($start)
512
        );
513
    }
514
515
    /**
516
     * FragmentDefinition :
517
     *   - fragment FragmentName on TypeCondition Directives? SelectionSet
518
     *
519
     * TypeCondition : NamedType
520
     *
521
     * @return FragmentDefinitionNode
522
     * @throws SyntaxErrorException
523
     */
524
    protected function lexFragmentDefinition(): FragmentDefinitionNode
525
    {
526
        $start = $this->lexer->getToken();
527
528
        $this->expectKeyword(KeywordEnum::FRAGMENT);
529
530
        $parseTypeCondition = function () {
531
            $this->expectKeyword(KeywordEnum::ON);
532
            return $this->lexNamedType();
533
        };
534
535
        return new FragmentDefinitionNode(
536
            $this->lexFragmentName(),
537
            $this->lexVariableDefinitions(),
538
            $parseTypeCondition(),
539
            $this->lexDirectives(),
540
            $this->lexSelectionSet(),
541
            $this->createLocation($start)
542
        );
543
    }
544
545
    /**
546
     * FragmentName : Name but not `on`
547
     *
548
     * @param Token|null $token
549
     * @return NameNode
550
     * @throws SyntaxErrorException
551
     */
552
    protected function lexFragmentName(?Token $token = null): NameNode
553
    {
554
        if (null === $token) {
555
            $token = $this->lexer->getToken();
556
        }
557
558
        if (KeywordEnum::ON === $token->getValue()) {
559
            throw $this->unexpected();
560
        }
561
562
        return $this->lexName();
563
    }
564
565
    // Implements the parsing rules in the Values section.
566
567
    /**
568
     * Value[Const] :
569
     *   - [~Const] Variable
570
     *   - IntValue
571
     *   - FloatValue
572
     *   - StringValue
573
     *   - BooleanValue
574
     *   - NullValue
575
     *   - EnumValue
576
     *   - ListValue[?Const]
577
     *   - ObjectValue[?Const]
578
     *
579
     * BooleanValue : one of `true` `false`
580
     *
581
     * NullValue : `null`
582
     *
583
     * EnumValue : Name but not `true`, `false` or `null`
584
     *
585
     * @param bool $isConst
586
     * @return NodeInterface|ValueNodeInterface|TypeNodeInterface
587
     * @throws SyntaxErrorException
588
     */
589
    protected function lexValue(bool $isConst = false): NodeInterface
590
    {
591
        $token = $this->lexer->getToken();
592
        $value = $token->getValue();
593
594
        switch ($token->getKind()) {
595
            case TokenKindEnum::BRACKET_L:
596
                return $this->lexList($isConst);
597
            case TokenKindEnum::BRACE_L:
598
                return $this->lexObject($isConst);
599
            case TokenKindEnum::INT:
600
                $this->lexer->advance();
601
                return new IntValueNode($value, $this->createLocation($token));
602
            case TokenKindEnum::FLOAT:
603
                $this->lexer->advance();
604
                return new FloatValueNode($value, $this->createLocation($token));
605
            case TokenKindEnum::STRING:
606
            case TokenKindEnum::BLOCK_STRING:
607
                return $this->lexStringLiteral();
608
            case TokenKindEnum::NAME:
609
                if ($value === 'true' || $value === 'false') {
610
                    $this->lexer->advance();
611
                    return new BooleanValueNode($value === 'true', $this->createLocation($token));
612
                }
613
614
                if ($value === 'null') {
615
                    $this->lexer->advance();
616
                    return new NullValueNode($this->createLocation($token));
617
                }
618
619
                $this->lexer->advance();
620
                return new EnumValueNode($value, $this->createLocation($token));
621
            case TokenKindEnum::DOLLAR:
622
                if (!$isConst) {
623
                    return $this->lexVariable();
624
                }
625
                break;
626
        }
627
628
        throw $this->unexpected();
629
    }
630
631
    /**
632
     * @return StringValueNode
633
     */
634
    protected function lexStringLiteral(): StringValueNode
635
    {
636
        $token = $this->lexer->getToken();
637
638
        $this->lexer->advance();
639
640
        return new StringValueNode(
641
            $token->getValue(),
642
            TokenKindEnum::BLOCK_STRING === $token->getKind(),
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 lexList(bool $isConst): ListValueNode
657
    {
658
        $start = $this->lexer->getToken();
659
660
        $parseFunction = function () use ($isConst) {
661
            return $this->lexValue($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 lexObject(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->lexObjectField($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 lexObjectField(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->lexValue($isConst);
716
        };
717
718
        return new ObjectFieldNode(
719
            $this->lexName(),
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 lexDirectives(bool $isConst = false): array
735
    {
736
        $directives = [];
737
738
        while ($this->peek(TokenKindEnum::AT)) {
739
            $directives[] = $this->lexDirective($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 lexDirective(bool $isConst = false): DirectiveNode
753
    {
754
        $start = $this->lexer->getToken();
755
756
        $this->expect(TokenKindEnum::AT);
757
758
        return new DirectiveNode(
759
            $this->lexName(),
760
            $this->lexArguments($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 lexType(): TypeNodeInterface
777
    {
778
        $start = $this->lexer->getToken();
779
780
        if ($this->skip(TokenKindEnum::BRACKET_L)) {
781
            $type = $this->lexType();
782
783
            $this->expect(TokenKindEnum::BRACKET_R);
784
785
            $type = new ListTypeNode($type, $this->createLocation($start));
786
        } else {
787
            $type = $this->lexNamedType();
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 lexNamedType(): NamedTypeNode
804
    {
805
        $start = $this->lexer->getToken();
806
807
        return new NamedTypeNode($this->lexName(), $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 TypeSystemDefinitionNodeInterface
828
     * @throws SyntaxErrorException
829
     * @throws \ReflectionException
830
     */
831
    protected function lexTypeSystemDefinition(): TypeSystemDefinitionNodeInterface
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 (TokenKindEnum::NAME === $token->getKind()) {
839
            switch ($token->getValue()) {
840
                case KeywordEnum::SCHEMA:
841
                    return $this->lexSchemaDefinition();
842
                case KeywordEnum::SCALAR:
843
                    return $this->lexScalarTypeDefinition();
844
                case KeywordEnum::TYPE:
845
                    return $this->lexObjectTypeDefinition();
846
                case KeywordEnum::INTERFACE:
847
                    return $this->lexInterfaceTypeDefinition();
848
                case KeywordEnum::UNION:
849
                    return $this->lexUnionTypeDefinition();
850
                case KeywordEnum::ENUM:
851
                    return $this->lexEnumTypeDefinition();
852
                case KeywordEnum::INPUT:
853
                    return $this->lexInputObjectTypeDefinition();
854
                case KeywordEnum::DIRECTIVE:
855
                    return $this->lexDirectiveDefinition();
856
            }
857
        }
858
859
        throw $this->unexpected($token);
860
    }
861
862
    /**
863
     * @return bool
864
     */
865
    protected function peekDescription(): bool
866
    {
867
        return $this->peek(TokenKindEnum::STRING) || $this->peek(TokenKindEnum::BLOCK_STRING);
868
    }
869
870
    /**
871
     * Description : StringValue
872
     *
873
     * @return StringValueNode|null
874
     */
875
    public function lexDescription(): ?StringValueNode
876
    {
877
        return $this->peekDescription()
878
            ? $this->lexStringLiteral()
879
            : null;
880
    }
881
882
    /**
883
     * SchemaDefinition : schema Directives[Const]? { OperationTypeDefinition+ }
884
     *
885
     * @return SchemaDefinitionNode
886
     * @throws SyntaxErrorException
887
     */
888
    protected function lexSchemaDefinition(): SchemaDefinitionNode
889
    {
890
        $start = $this->lexer->getToken();
891
892
        $this->expectKeyword(KeywordEnum::SCHEMA);
893
894
        return new SchemaDefinitionNode(
895
            $this->lexDirectives(),
896
            $this->many(
897
                TokenKindEnum::BRACE_L,
898
                [$this, 'lexOperationTypeDefinition'],
899
                TokenKindEnum::BRACE_R
900
            ),
901
            $this->createLocation($start)
902
        );
903
    }
904
905
    /**
906
     * OperationTypeDefinition : OperationType : NamedType
907
     *
908
     * @return OperationTypeDefinitionNode
909
     * @throws SyntaxErrorException
910
     */
911
    protected function lexOperationTypeDefinition(): OperationTypeDefinitionNode
912
    {
913
        $start = $this->lexer->getToken();
914
915
        $operation = $this->lexOperationType();
916
917
        $this->expect(TokenKindEnum::COLON);
918
919
        return new OperationTypeDefinitionNode(
920
            $operation,
0 ignored issues
show
Bug introduced by
It seems like $operation can also be of type null; however, parameter $operation of Digia\GraphQL\Language\N...tionNode::__construct() 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

920
            /** @scrutinizer ignore-type */ $operation,
Loading history...
921
            $this->lexNamedType(),
922
            $this->createLocation($start)
923
        );
924
    }
925
926
    /**
927
     * ScalarTypeDefinition : Description? scalar Name Directives[Const]?
928
     *
929
     * @return ScalarTypeDefinitionNode
930
     * @throws SyntaxErrorException
931
     */
932
    protected function lexScalarTypeDefinition(): ScalarTypeDefinitionNode
933
    {
934
        $start = $this->lexer->getToken();
935
936
        $description = $this->lexDescription();
937
938
        $this->expectKeyword(KeywordEnum::SCALAR);
939
940
        return new ScalarTypeDefinitionNode(
941
            $description,
942
            $this->lexName(),
943
            $this->lexDirectives(),
944
            $this->createLocation($start)
945
        );
946
    }
947
948
    /**
949
     * ObjectTypeDefinition :
950
     *   Description?
951
     *   type Name ImplementsInterfaces? Directives[Const]? FieldsDefinition?
952
     *
953
     * @return ObjectTypeDefinitionNode
954
     * @throws SyntaxErrorException
955
     */
956
    protected function lexObjectTypeDefinition(): ObjectTypeDefinitionNode
957
    {
958
        $start = $this->lexer->getToken();
959
960
        $description = $this->lexDescription();
961
962
        $this->expectKeyword(KeywordEnum::TYPE);
963
964
        return new ObjectTypeDefinitionNode(
965
            $description,
966
            $this->lexName(),
967
            $this->lexImplementsInterfaces(),
968
            $this->lexDirectives(),
969
            $this->lexFieldsDefinition(),
970
            $this->createLocation($start)
971
        );
972
    }
973
974
    /**
975
     * ImplementsInterfaces :
976
     *   - implements `&`? NamedType
977
     *   - ImplementsInterfaces & NamedType
978
     *
979
     * @return array
980
     * @throws SyntaxErrorException
981
     */
982
    protected function lexImplementsInterfaces(): array
983
    {
984
        $types = [];
985
986
        $token = $this->lexer->getToken();
987
988
        if ('implements' === $token->getValue()) {
989
            $this->lexer->advance();
990
991
            // Optional leading ampersand
992
            $this->skip(TokenKindEnum::AMP);
993
994
            do {
995
                $types[] = $this->lexNamedType();
996
            } while ($this->skip(TokenKindEnum::AMP));
997
        }
998
999
        return $types;
1000
    }
1001
1002
    /**
1003
     * FieldsDefinition : { FieldDefinition+ }
1004
     *
1005
     * @return array
1006
     * @throws SyntaxErrorException
1007
     */
1008
    protected function lexFieldsDefinition(): array
1009
    {
1010
        return $this->peek(TokenKindEnum::BRACE_L)
1011
            ? $this->many(
1012
                TokenKindEnum::BRACE_L,
1013
                [$this, 'lexFieldDefinition'],
1014
                TokenKindEnum::BRACE_R
1015
            )
1016
            : [];
1017
    }
1018
1019
    /**
1020
     * FieldDefinition :
1021
     *   - Description? Name ArgumentsDefinition? : Type Directives[Const]?
1022
     *
1023
     * @return FieldDefinitionNode
1024
     * @throws SyntaxErrorException
1025
     */
1026
    protected function lexFieldDefinition(): FieldDefinitionNode
1027
    {
1028
        $start = $this->lexer->getToken();
1029
1030
        $description = $this->lexDescription();
1031
        $name        = $this->lexName();
1032
        $arguments   = $this->lexArgumentsDefinition();
1033
1034
        $this->expect(TokenKindEnum::COLON);
1035
1036
        return new FieldDefinitionNode(
1037
            $description,
1038
            $name,
1039
            $arguments,
1040
            $this->lexType(),
1041
            $this->lexDirectives(),
1042
            $this->createLocation($start)
1043
        );
1044
    }
1045
1046
    /**
1047
     * ArgumentsDefinition : ( InputValueDefinition+ )
1048
     *
1049
     * @return InputValueDefinitionNode[]
1050
     * @throws SyntaxErrorException
1051
     */
1052
    protected function lexArgumentsDefinition(): array
1053
    {
1054
        $parseFunction = function (): InputValueDefinitionNode {
1055
            return $this->lexInputValueDefinition();
1056
        };
1057
1058
        return $this->peek(TokenKindEnum::PAREN_L)
1059
            ? $this->many(
1060
                TokenKindEnum::PAREN_L,
1061
                $parseFunction,
1062
                TokenKindEnum::PAREN_R
1063
            )
1064
            : [];
1065
    }
1066
1067
    /**
1068
     * InputValueDefinition :
1069
     *   - Description? Name : Type DefaultValue? Directives[Const]?
1070
     *
1071
     * @return InputValueDefinitionNode
1072
     * @throws SyntaxErrorException
1073
     */
1074
    protected function lexInputValueDefinition(): InputValueDefinitionNode
1075
    {
1076
        $start = $this->lexer->getToken();
1077
1078
        $description = $this->lexDescription();
1079
        $name        = $this->lexName();
1080
1081
        $this->expect(TokenKindEnum::COLON);
1082
1083
        return new InputValueDefinitionNode(
1084
            $description,
1085
            $name,
1086
            $this->lexType(),
1087
            $this->skip(TokenKindEnum::EQUALS)
1088
                ? $this->lexValue(true)
1089
                : null,
1090
            $this->lexDirectives(true),
1091
            $this->createLocation($start)
1092
        );
1093
    }
1094
1095
    /**
1096
     * InterfaceTypeDefinition :
1097
     *   - Description? interface Name Directives[Const]? FieldsDefinition?
1098
     *
1099
     * @return InterfaceTypeDefinitionNode
1100
     * @throws SyntaxErrorException
1101
     */
1102
    protected function lexInterfaceTypeDefinition(): InterfaceTypeDefinitionNode
1103
    {
1104
        $start = $this->lexer->getToken();
1105
1106
        $description = $this->lexDescription();
1107
1108
        $this->expectKeyword(KeywordEnum::INTERFACE);
1109
1110
        return new InterfaceTypeDefinitionNode(
1111
            $description,
1112
            $this->lexName(),
1113
            $this->lexDirectives(),
1114
            $this->lexFieldsDefinition(),
1115
            $this->createLocation($start)
1116
        );
1117
    }
1118
1119
    /**
1120
     * UnionTypeDefinition :
1121
     *   - Description? union Name Directives[Const]? UnionMemberTypes?
1122
     *
1123
     * @return UnionTypeDefinitionNode
1124
     * @throws SyntaxErrorException
1125
     */
1126
    protected function lexUnionTypeDefinition(): UnionTypeDefinitionNode
1127
    {
1128
        $start = $this->lexer->getToken();
1129
1130
        $description = $this->lexDescription();
1131
1132
        $this->expectKeyword(KeywordEnum::UNION);
1133
1134
        return new UnionTypeDefinitionNode(
1135
            $description,
1136
            $this->lexName(),
1137
            $this->lexDirectives(),
1138
            $this->lexUnionMemberTypes(),
1139
            $this->createLocation($start)
1140
        );
1141
    }
1142
1143
    /**
1144
     * UnionMemberTypes :
1145
     *   - = `|`? NamedType
1146
     *   - UnionMemberTypes | NamedType
1147
     *
1148
     * @return array
1149
     * @throws SyntaxErrorException
1150
     */
1151
    protected function lexUnionMemberTypes(): array
1152
    {
1153
        $types = [];
1154
1155
        if ($this->skip(TokenKindEnum::EQUALS)) {
1156
            // Optional leading pipe
1157
            $this->skip(TokenKindEnum::PIPE);
1158
1159
            do {
1160
                $types[] = $this->lexNamedType();
1161
            } while ($this->skip(TokenKindEnum::PIPE));
1162
        }
1163
1164
        return $types;
1165
    }
1166
1167
    /**
1168
     * EnumTypeDefinition :
1169
     *   - Description? enum Name Directives[Const]? EnumValuesDefinition?
1170
     *
1171
     * @return EnumTypeDefinitionNode
1172
     * @throws SyntaxErrorException
1173
     */
1174
    protected function lexEnumTypeDefinition(): EnumTypeDefinitionNode
1175
    {
1176
        $start = $this->lexer->getToken();
1177
1178
        $description = $this->lexDescription();
1179
1180
        $this->expectKeyword(KeywordEnum::ENUM);
1181
1182
        return new EnumTypeDefinitionNode(
1183
            $description,
1184
            $this->lexName(),
1185
            $this->lexDirectives(),
1186
            $this->lexEnumValuesDefinition(),
1187
            $this->createLocation($start)
1188
        );
1189
    }
1190
1191
    /**
1192
     * EnumValuesDefinition : { EnumValueDefinition+ }
1193
     *
1194
     * @return array
1195
     * @throws SyntaxErrorException
1196
     */
1197
    protected function lexEnumValuesDefinition(): array
1198
    {
1199
        return $this->peek(TokenKindEnum::BRACE_L)
1200
            ? $this->many(
1201
                TokenKindEnum::BRACE_L,
1202
                [$this, 'lexEnumValueDefinition'],
1203
                TokenKindEnum::BRACE_R
1204
            )
1205
            : [];
1206
    }
1207
1208
    /**
1209
     * EnumValueDefinition : Description? EnumValue Directives[Const]?
1210
     *
1211
     * EnumValue : Name
1212
     *
1213
     * @return EnumValueDefinitionNode
1214
     * @throws SyntaxErrorException
1215
     */
1216
    protected function lexEnumValueDefinition(): EnumValueDefinitionNode
1217
    {
1218
        $start = $this->lexer->getToken();
1219
1220
        return new EnumValueDefinitionNode(
1221
            $this->lexDescription(),
1222
            $this->lexName(),
1223
            $this->lexDirectives(),
1224
            $this->createLocation($start)
1225
        );
1226
    }
1227
1228
    /**
1229
     * InputObjectTypeDefinition :
1230
     *   - Description? input Name Directives[Const]? InputFieldsDefinition?
1231
     *
1232
     * @return InputObjectTypeDefinitionNode
1233
     * @throws SyntaxErrorException
1234
     */
1235
    protected function lexInputObjectTypeDefinition(): InputObjectTypeDefinitionNode
1236
    {
1237
        $start = $this->lexer->getToken();
1238
1239
        $description = $this->lexDescription();
1240
1241
        $this->expectKeyword(KeywordEnum::INPUT);
1242
1243
        return new InputObjectTypeDefinitionNode(
1244
            $description,
1245
            $this->lexName(),
1246
            $this->lexDirectives(true),
1247
            $this->lexInputFieldsDefinition(),
1248
            $this->createLocation($start)
1249
        );
1250
    }
1251
1252
    /**
1253
     * InputFieldsDefinition : { InputValueDefinition+ }
1254
     *
1255
     * @return array
1256
     * @throws SyntaxErrorException
1257
     */
1258
    protected function lexInputFieldsDefinition(): array
1259
    {
1260
        $parseFunction = function (): InputValueDefinitionNode {
1261
            return $this->lexInputValueDefinition();
1262
        };
1263
1264
        return $this->peek(TokenKindEnum::BRACE_L)
1265
            ? $this->many(
1266
                TokenKindEnum::BRACE_L,
1267
                $parseFunction,
1268
                TokenKindEnum::BRACE_R
1269
            )
1270
            : [];
1271
    }
1272
1273
    /**
1274
     * TypeExtension :
1275
     *   - ScalarTypeExtension
1276
     *   - ObjectTypeExtension
1277
     *   - InterfaceTypeExtension
1278
     *   - UnionTypeExtension
1279
     *   - EnumTypeExtension
1280
     *   - InputObjectTypeDefinition
1281
     *
1282
     * @return TypeSystemExtensionNodeInterface
1283
     * @throws SyntaxErrorException
1284
     */
1285
    protected function lexTypeSystemExtension(): TypeSystemExtensionNodeInterface
1286
    {
1287
        $token = $this->lexer->lookahead();
1288
1289
        if (TokenKindEnum::NAME === $token->getKind()) {
1290
            switch ($token->getValue()) {
1291
                case KeywordEnum::SCHEMA:
1292
                    return $this->lexSchemaExtension();
1293
                case KeywordEnum::SCALAR:
1294
                    return $this->lexScalarTypeExtension(false);
1295
                case KeywordEnum::TYPE:
1296
                    return $this->lexObjectTypeExtension();
1297
                case KeywordEnum::INTERFACE:
1298
                    return $this->lexInterfaceTypeExtension();
1299
                case KeywordEnum::UNION:
1300
                    return $this->lexUnionTypeExtension();
1301
                case KeywordEnum::ENUM:
1302
                    return $this->lexEnumTypeExtension();
1303
                case KeywordEnum::INPUT:
1304
                    return $this->lexInputObjectTypeExtension();
1305
            }
1306
        }
1307
1308
        throw $this->unexpected($token);
1309
    }
1310
1311
    /**
1312
     * SchemaExtension :
1313
     *  - extend schema Directives[Const]? { OperationTypeDefinition+ }
1314
     *  - extend schema Directives[Const]
1315
     *
1316
     * @return SchemaExtensionNode
1317
     * @throws SyntaxErrorException
1318
     */
1319
    protected function lexSchemaExtension(): SchemaExtensionNode
1320
    {
1321
        $start = $this->lexer->getToken();
1322
1323
        $this->expectKeyword(KeywordEnum::EXTEND);
1324
        $this->expectKeyword(KeywordEnum::SCHEMA);
1325
1326
        $directives = $this->lexDirectives(true);
1327
1328
        $parseFunction = function (): OperationTypeDefinitionNode {
1329
            return $this->lexOperationTypeDefinition();
1330
        };
1331
1332
        $operationTypes = $this->peek(TokenKindEnum::BRACE_L)
1333
            ? $this->many(
1334
                TokenKindEnum::BRACE_L,
1335
                $parseFunction,
1336
                TokenKindEnum::BRACE_R
1337
            )
1338
            : [];
1339
1340
        if (empty($directives) && empty($operationTypes)) {
1341
            $this->unexpected();
1342
        }
1343
1344
        return new SchemaExtensionNode($directives, $operationTypes, $this->createLocation($start));
1345
    }
1346
1347
    /**
1348
     * ScalarTypeExtension :
1349
     *   - extend scalar Name Directives[Const]
1350
     *
1351
     * @param bool $isConst
1352
     * @return ScalarTypeExtensionNode
1353
     * @throws SyntaxErrorException
1354
     */
1355
    protected function lexScalarTypeExtension(bool $isConst = false): ScalarTypeExtensionNode
1356
    {
1357
        $start = $this->lexer->getToken();
1358
1359
        $this->expectKeyword(KeywordEnum::EXTEND);
1360
        $this->expectKeyword(KeywordEnum::SCALAR);
1361
1362
        $name       = $this->lexName();
1363
        $directives = $this->lexDirectives($isConst);
1364
1365
        if (empty($directives)) {
1366
            throw $this->unexpected();
1367
        }
1368
1369
        return new ScalarTypeExtensionNode($name, $directives, $this->createLocation($start));
1370
    }
1371
1372
    /**
1373
     * ObjectTypeExtension :
1374
     *  - extend type Name ImplementsInterfaces? Directives[Const]? FieldsDefinition
1375
     *  - extend type Name ImplementsInterfaces? Directives[Const]
1376
     *  - extend type Name ImplementsInterfaces
1377
     *
1378
     * @return ObjectTypeExtensionNode
1379
     * @throws SyntaxErrorException
1380
     */
1381
    protected function lexObjectTypeExtension(): ObjectTypeExtensionNode
1382
    {
1383
        $start = $this->lexer->getToken();
1384
1385
        $this->expectKeyword(KeywordEnum::EXTEND);
1386
        $this->expectKeyword(KeywordEnum::TYPE);
1387
1388
        $name       = $this->lexName();
1389
        $interfaces = $this->lexImplementsInterfaces();
1390
        $directives = $this->lexDirectives();
1391
        $fields     = $this->lexFieldsDefinition();
1392
1393
        if (empty($interfaces) && empty($directives) && empty($fields)) {
1394
            throw $this->unexpected();
1395
        }
1396
1397
        return new ObjectTypeExtensionNode(
1398
            $name,
1399
            $interfaces,
1400
            $directives,
1401
            $fields,
1402
            $this->createLocation($start)
1403
        );
1404
    }
1405
1406
    /**
1407
     * InterfaceTypeExtension :
1408
     *   - extend interface Name Directives[Const]? FieldsDefinition
1409
     *   - extend interface Name Directives[Const]
1410
     *
1411
     * @return InterfaceTypeExtensionNode
1412
     * @throws SyntaxErrorException
1413
     */
1414
    protected function lexInterfaceTypeExtension(): InterfaceTypeExtensionNode
1415
    {
1416
        $start = $this->lexer->getToken();
1417
1418
        $this->expectKeyword(KeywordEnum::EXTEND);
1419
        $this->expectKeyword(KeywordEnum::INTERFACE);
1420
1421
        $name       = $this->lexName();
1422
        $directives = $this->lexDirectives();
1423
        $fields     = $this->lexFieldsDefinition();
1424
1425
        if (empty($directives) && empty($fields)) {
1426
            throw $this->unexpected();
1427
        }
1428
1429
        return new InterfaceTypeExtensionNode($name, $directives, $fields, $this->createLocation($start));
1430
    }
1431
1432
    /**
1433
     * UnionTypeExtension :
1434
     *   - extend union Name Directives[Const]? UnionMemberTypes
1435
     *   - extend union Name Directives[Const]
1436
     *
1437
     * @return UnionTypeExtensionNode
1438
     * @throws SyntaxErrorException
1439
     */
1440
    protected function lexUnionTypeExtension(): UnionTypeExtensionNode
1441
    {
1442
        $start = $this->lexer->getToken();
1443
1444
        $this->expectKeyword(KeywordEnum::EXTEND);
1445
        $this->expectKeyword(KeywordEnum::UNION);
1446
1447
        $name       = $this->lexName();
1448
        $directives = $this->lexDirectives();
1449
        $types      = $this->lexUnionMemberTypes();
1450
1451
        if (empty($directives) && empty($types)) {
1452
            throw $this->unexpected();
1453
        }
1454
1455
        return new UnionTypeExtensionNode($name, $directives, $types, $this->createLocation($start));
1456
    }
1457
1458
    /**
1459
     * EnumTypeExtension :
1460
     *   - extend enum Name Directives[Const]? EnumValuesDefinition
1461
     *   - extend enum Name Directives[Const]
1462
     *
1463
     * @return EnumTypeExtensionNode
1464
     * @throws SyntaxErrorException
1465
     */
1466
    protected function lexEnumTypeExtension(): EnumTypeExtensionNode
1467
    {
1468
        $start = $this->lexer->getToken();
1469
1470
        $this->expectKeyword(KeywordEnum::EXTEND);
1471
        $this->expectKeyword(KeywordEnum::ENUM);
1472
1473
        $name       = $this->lexName();
1474
        $directives = $this->lexDirectives();
1475
        $values     = $this->lexEnumValuesDefinition();
1476
1477
        if (empty($directives) && empty($values)) {
1478
            throw $this->unexpected();
1479
        }
1480
1481
        return new EnumTypeExtensionNode($name, $directives, $values, $this->createLocation($start));
1482
    }
1483
1484
    /**
1485
     * InputObjectTypeExtension :
1486
     *   - extend input Name Directives[Const]? InputFieldsDefinition
1487
     *   - extend input Name Directives[Const]
1488
     *
1489
     * @return InputObjectTypeExtensionNode
1490
     * @throws SyntaxErrorException
1491
     */
1492
    protected function lexInputObjectTypeExtension(): InputObjectTypeExtensionNode
1493
    {
1494
        $start = $this->lexer->getToken();
1495
1496
        $this->expectKeyword(KeywordEnum::EXTEND);
1497
        $this->expectKeyword(KeywordEnum::INPUT);
1498
1499
        $name       = $this->lexName();
1500
        $directives = $this->lexDirectives(true);
1501
        $fields     = $this->lexInputFieldsDefinition();
1502
1503
        if (empty($directives) && empty($fields)) {
1504
            throw $this->unexpected();
1505
        }
1506
1507
        return new InputObjectTypeExtensionNode($name, $directives, $fields, $this->createLocation($start));
1508
    }
1509
1510
    /**
1511
     * DirectiveDefinition :
1512
     *   - Description? directive @ Name ArgumentsDefinition? on DirectiveLocations
1513
     *
1514
     * @return DirectiveDefinitionNode
1515
     * @throws SyntaxErrorException
1516
     * @throws \ReflectionException
1517
     */
1518
    protected function lexDirectiveDefinition(): DirectiveDefinitionNode
1519
    {
1520
        $start = $this->lexer->getToken();
1521
1522
        $description = $this->lexDescription();
1523
1524
        $this->expectKeyword(KeywordEnum::DIRECTIVE);
1525
        $this->expect(TokenKindEnum::AT);
1526
1527
        $name      = $this->lexName();
1528
        $arguments = $this->lexArgumentsDefinition();
1529
1530
        $this->expectKeyword(KeywordEnum::ON);
1531
1532
        $locations = $this->lexDirectiveLocations();
1533
1534
        return new DirectiveDefinitionNode(
1535
            $description,
1536
            $name,
1537
            $arguments,
1538
            $locations,
1539
            $this->createLocation($start)
1540
        );
1541
    }
1542
1543
    /**
1544
     * DirectiveLocations :
1545
     *   - `|`? DirectiveLocation
1546
     *   - DirectiveLocations | DirectiveLocation
1547
     *
1548
     * @return array
1549
     * @throws SyntaxErrorException
1550
     * @throws \ReflectionException
1551
     */
1552
    protected function lexDirectiveLocations(): array
1553
    {
1554
        $this->skip(TokenKindEnum::PIPE);
1555
1556
        $locations = [];
1557
1558
        do {
1559
            $locations[] = $this->lexDirectiveLocation();
1560
        } while ($this->skip(TokenKindEnum::PIPE));
1561
1562
        return $locations;
1563
    }
1564
1565
    /**
1566
     * DirectiveLocation :
1567
     *   - ExecutableDirectiveLocation
1568
     *   - TypeSystemDirectiveLocation
1569
     *
1570
     * ExecutableDirectiveLocation : one of
1571
     *   `QUERY`
1572
     *   `MUTATION`
1573
     *   `SUBSCRIPTION`
1574
     *   `FIELD`
1575
     *   `FRAGMENT_DEFINITION`
1576
     *   `FRAGMENT_SPREAD`
1577
     *   `INLINE_FRAGMENT`
1578
     *
1579
     * TypeSystemDirectiveLocation : one of
1580
     *   `SCHEMA`
1581
     *   `SCALAR`
1582
     *   `OBJECT`
1583
     *   `FIELD_DEFINITION`
1584
     *   `ARGUMENT_DEFINITION`
1585
     *   `INTERFACE`
1586
     *   `UNION`
1587
     *   `ENUM`
1588
     *   `ENUM_VALUE`
1589
     *   `INPUT_OBJECT`
1590
     *   `INPUT_FIELD_DEFINITION`
1591
     *
1592
     * @return NameNode
1593
     * @throws SyntaxErrorException
1594
     * @throws \ReflectionException
1595
     */
1596
    protected function lexDirectiveLocation(): NameNode
1597
    {
1598
        $start = $this->lexer->getToken();
1599
1600
        $name = $this->lexName();
1601
1602
        if (arraySome(DirectiveLocationEnum::values(), function ($value) use ($name) {
1603
            return $name->getValue() === $value;
1604
        })) {
1605
            return $name;
1606
        }
1607
1608
        throw $this->unexpected($start);
1609
    }
1610
1611
    /**
1612
     * Returns a location object, used to identify the place in
1613
     * the source that created a given parsed object.
1614
     *
1615
     * @param Token $start
1616
     * @return Location|null
1617
     */
1618
    protected function createLocation(Token $start): ?Location
1619
    {
1620
        return !$this->lexer->getOption('noLocation', false)
1621
            ? new Location(
1622
                $start->getStart(),
1623
                $this->lexer->getLastToken()->getEnd(),
1624
                $this->lexer->getSource()
1625
            )
1626
            : null;
1627
    }
1628
1629
    /**
1630
     * @param Source $source
1631
     * @param array  $options
1632
     * @return LexerInterface
1633
     */
1634
    protected function createLexer($source, array $options): LexerInterface
1635
    {
1636
        return new Lexer($source, $options);
1637
    }
1638
1639
    /**
1640
     * Determines if the next token is of a given kind.
1641
     *
1642
     * @param string $kind
1643
     * @return bool
1644
     */
1645
    protected function peek(string $kind): bool
1646
    {
1647
        return $kind === $this->lexer->getToken()->getKind();
1648
    }
1649
1650
    /**
1651
     * If the next token is of the given kind, return true after advancing
1652
     * the lexer. Otherwise, do not change the parser state and return false.
1653
     *
1654
     * @param string $kind
1655
     * @return bool
1656
     */
1657
    protected function skip(string $kind): bool
1658
    {
1659
        if ($match = $this->peek($kind)) {
1660
            $this->lexer->advance();
1661
        }
1662
1663
        return $match;
1664
    }
1665
1666
    /**
1667
     * If the next token is of the given kind, return that token after advancing
1668
     * the lexer. Otherwise, do not change the parser state and throw an error.
1669
     *
1670
     * @param string $kind
1671
     * @return Token
1672
     * @throws SyntaxErrorException
1673
     */
1674
    protected function expect(string $kind): Token
1675
    {
1676
        $token = $this->lexer->getToken();
1677
1678
        if ($kind === $token->getKind()) {
1679
            $this->lexer->advance();
1680
            return $token;
1681
        }
1682
1683
        throw $this->lexer->createSyntaxErrorException(\sprintf('Expected %s, found %s.', $kind, $token));
1684
    }
1685
1686
    /**
1687
     * @param string $value
1688
     * @return Token
1689
     * @throws SyntaxErrorException
1690
     */
1691
    protected function expectKeyword(string $value): Token
1692
    {
1693
        $token = $this->lexer->getToken();
1694
1695
        if (TokenKindEnum::NAME === $token->getKind() && $value === $token->getValue()) {
1696
            $this->lexer->advance();
1697
            return $token;
1698
        }
1699
1700
        throw $this->lexer->createSyntaxErrorException(\sprintf('Expected %s, found %s', $value, $token));
1701
    }
1702
1703
    /**
1704
     * Helper function for creating an error when an unexpected lexed token
1705
     * is encountered.
1706
     *
1707
     * @param Token|null $atToken
1708
     * @return SyntaxErrorException
1709
     */
1710
    protected function unexpected(?Token $atToken = null): SyntaxErrorException
1711
    {
1712
        $token = $atToken ?? $this->lexer->getToken();
1713
1714
        return $this->lexer->createSyntaxErrorException(\sprintf('Unexpected %s', $token));
1715
    }
1716
1717
    /**
1718
     * Returns a possibly empty list of parse nodes, determined by
1719
     * the parseFn. This list begins with a lex token of openKind
1720
     * and ends with a lex token of closeKind. Advances the parser
1721
     * to the next lex token after the closing token.
1722
     *
1723
     * @param string   $openKind
1724
     * @param callable $parseFunction
1725
     * @param string   $closeKind
1726
     * @return array
1727
     * @throws SyntaxErrorException
1728
     */
1729
    protected function any(string $openKind, callable $parseFunction, string $closeKind): array
1730
    {
1731
        $this->expect($openKind);
1732
1733
        $nodes = [];
1734
1735
        while (!$this->skip($closeKind)) {
1736
            $nodes[] = $parseFunction();
1737
        }
1738
1739
        return $nodes;
1740
    }
1741
1742
    /**
1743
     * Returns a non-empty list of parse nodes, determined by
1744
     * the parseFn. This list begins with a lex token of openKind
1745
     * and ends with a lex token of closeKind. Advances the parser
1746
     * to the next lex token after the closing token.
1747
     *
1748
     * @param string   $openKind
1749
     * @param callable $parseFunction
1750
     * @param string   $closeKind
1751
     * @return array
1752
     * @throws SyntaxErrorException
1753
     */
1754
    protected function many(string $openKind, callable $parseFunction, string $closeKind): array
1755
    {
1756
        $this->expect($openKind);
1757
1758
        $nodes = [$parseFunction()];
1759
1760
        while (!$this->skip($closeKind)) {
1761
            $nodes[] = $parseFunction();
1762
        }
1763
1764
        return $nodes;
1765
    }
1766
}
1767