Passed
Push — master ( ea9949...4750e8 )
by Christoffer
02:23
created

Parser::parseInputFieldsDefinition()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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