Failed Conditions
Push — master ( cc39b3...a3ef1b )
by Šimon
10s
created

Parser::parseEnumTypeDefinition()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 12
dl 0
loc 15
c 0
b 0
f 0
rs 9.8666
ccs 13
cts 13
cp 1
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Language;
6
7
use GraphQL\Error\SyntaxError;
8
use GraphQL\Language\AST\ArgumentNode;
9
use GraphQL\Language\AST\BooleanValueNode;
10
use GraphQL\Language\AST\DirectiveDefinitionNode;
11
use GraphQL\Language\AST\DirectiveNode;
12
use GraphQL\Language\AST\DocumentNode;
13
use GraphQL\Language\AST\EnumTypeDefinitionNode;
14
use GraphQL\Language\AST\EnumTypeExtensionNode;
15
use GraphQL\Language\AST\EnumValueDefinitionNode;
16
use GraphQL\Language\AST\EnumValueNode;
17
use GraphQL\Language\AST\ExecutableDefinitionNode;
18
use GraphQL\Language\AST\FieldDefinitionNode;
19
use GraphQL\Language\AST\FieldNode;
20
use GraphQL\Language\AST\FloatValueNode;
21
use GraphQL\Language\AST\FragmentDefinitionNode;
22
use GraphQL\Language\AST\FragmentSpreadNode;
23
use GraphQL\Language\AST\InlineFragmentNode;
24
use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
25
use GraphQL\Language\AST\InputObjectTypeExtensionNode;
26
use GraphQL\Language\AST\InputValueDefinitionNode;
27
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
28
use GraphQL\Language\AST\InterfaceTypeExtensionNode;
29
use GraphQL\Language\AST\IntValueNode;
30
use GraphQL\Language\AST\ListTypeNode;
31
use GraphQL\Language\AST\ListValueNode;
32
use GraphQL\Language\AST\Location;
33
use GraphQL\Language\AST\NamedTypeNode;
34
use GraphQL\Language\AST\NameNode;
35
use GraphQL\Language\AST\NodeList;
36
use GraphQL\Language\AST\NonNullTypeNode;
37
use GraphQL\Language\AST\NullValueNode;
38
use GraphQL\Language\AST\ObjectFieldNode;
39
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
40
use GraphQL\Language\AST\ObjectTypeExtensionNode;
41
use GraphQL\Language\AST\ObjectValueNode;
42
use GraphQL\Language\AST\OperationDefinitionNode;
43
use GraphQL\Language\AST\OperationTypeDefinitionNode;
44
use GraphQL\Language\AST\ScalarTypeDefinitionNode;
45
use GraphQL\Language\AST\ScalarTypeExtensionNode;
46
use GraphQL\Language\AST\SchemaDefinitionNode;
47
use GraphQL\Language\AST\SelectionSetNode;
48
use GraphQL\Language\AST\StringValueNode;
49
use GraphQL\Language\AST\TypeExtensionNode;
50
use GraphQL\Language\AST\TypeSystemDefinitionNode;
51
use GraphQL\Language\AST\UnionTypeDefinitionNode;
52
use GraphQL\Language\AST\UnionTypeExtensionNode;
53
use GraphQL\Language\AST\VariableDefinitionNode;
54
use GraphQL\Language\AST\VariableNode;
55
use function count;
56
use function sprintf;
57
58
/**
59
 * Parses string containing GraphQL query or [type definition](type-system/type-language.md) to Abstract Syntax Tree.
60
 */
61
class Parser
62
{
63
    /**
64
     * Given a GraphQL source, parses it into a `GraphQL\Language\AST\DocumentNode`.
65
     * Throws `GraphQL\Error\SyntaxError` if a syntax error is encountered.
66
     *
67
     * Available options:
68
     *
69
     * noLocation: boolean,
70
     *   (By default, the parser creates AST nodes that know the location
71
     *   in the source that they correspond to. This configuration flag
72
     *   disables that behavior for performance or testing.)
73
     *
74
     * allowLegacySDLEmptyFields: boolean
75
     *   If enabled, the parser will parse empty fields sets in the Schema
76
     *   Definition Language. Otherwise, the parser will follow the current
77
     *   specification.
78
     *
79
     *   This option is provided to ease adoption of the final SDL specification
80
     *   and will be removed in a future major release.
81
     *
82
     * allowLegacySDLImplementsInterfaces: boolean
83
     *   If enabled, the parser will parse implemented interfaces with no `&`
84
     *   character between each interface. Otherwise, the parser will follow the
85
     *   current specification.
86
     *
87
     *   This option is provided to ease adoption of the final SDL specification
88
     *   and will be removed in a future major release.
89
     *
90
     * experimentalFragmentVariables: boolean,
91
     *   (If enabled, the parser will understand and parse variable definitions
92
     *   contained in a fragment definition. They'll be represented in the
93
     *   `variableDefinitions` field of the FragmentDefinitionNode.
94
     *
95
     *   The syntax is identical to normal, query-defined variables. For example:
96
     *
97
     *     fragment A($var: Boolean = false) on T  {
98
     *       ...
99
     *     }
100
     *
101
     *   Note: this feature is experimental and may change or be removed in the
102
     *   future.)
103
     *
104
     * @api
105
     * @param Source|string $source
106
     * @param bool[]        $options
107
     * @return DocumentNode
108
     * @throws SyntaxError
109
     */
110 815
    public static function parse($source, array $options = [])
111
    {
112 815
        $sourceObj = $source instanceof Source ? $source : new Source($source);
113 812
        $parser    = new self($sourceObj, $options);
114
115 812
        return $parser->parseDocument();
116
    }
117
118
    /**
119
     * Given a string containing a GraphQL value (ex. `[42]`), parse the AST for
120
     * that value.
121
     * Throws `GraphQL\Error\SyntaxError` if a syntax error is encountered.
122
     *
123
     * This is useful within tools that operate upon GraphQL Values directly and
124
     * in isolation of complete GraphQL documents.
125
     *
126
     * Consider providing the results to the utility function: `GraphQL\Utils\AST::valueFromAST()`.
127
     *
128
     * @api
129
     * @param Source|string $source
130
     * @param bool[]        $options
131
     * @return BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|ListValueNode|ObjectValueNode|StringValueNode|VariableNode
132
     */
133 21
    public static function parseValue($source, array $options = [])
134
    {
135 21
        $sourceObj = $source instanceof Source ? $source : new Source($source);
136 21
        $parser    = new Parser($sourceObj, $options);
137 21
        $parser->expect(Token::SOF);
138 21
        $value = $parser->parseValueLiteral(false);
139 21
        $parser->expect(Token::EOF);
140
141 21
        return $value;
142
    }
143
144
    /**
145
     * Given a string containing a GraphQL Type (ex. `[Int!]`), parse the AST for
146
     * that type.
147
     * Throws `GraphQL\Error\SyntaxError` if a syntax error is encountered.
148
     *
149
     * This is useful within tools that operate upon GraphQL Types directly and
150
     * in isolation of complete GraphQL documents.
151
     *
152
     * Consider providing the results to the utility function: `GraphQL\Utils\AST::typeFromAST()`.
153
     *
154
     * @api
155
     * @param Source|string $source
156
     * @param bool[]        $options
157
     * @return ListTypeNode|NameNode|NonNullTypeNode
158
     */
159 5
    public static function parseType($source, array $options = [])
160
    {
161 5
        $sourceObj = $source instanceof Source ? $source : new Source($source);
162 5
        $parser    = new Parser($sourceObj, $options);
163 5
        $parser->expect(Token::SOF);
164 5
        $type = $parser->parseTypeReference();
165 5
        $parser->expect(Token::EOF);
166
167 5
        return $type;
168
    }
169
170
    /** @var Lexer */
171
    private $lexer;
172
173
    /**
174
     *
175
     * @param bool[] $options
176
     */
177 838
    public function __construct(Source $source, array $options = [])
178
    {
179 838
        $this->lexer = new Lexer($source, $options);
180 838
    }
181
182
    /**
183
     * Returns a location object, used to identify the place in
184
     * the source that created a given parsed object.
185
     *
186
     * @return Location|null
187
     */
188 830
    private function loc(Token $startToken)
189
    {
190 830
        if (empty($this->lexer->options['noLocation'])) {
191 813
            return new Location($startToken, $this->lexer->lastToken, $this->lexer->source);
192
        }
193
194 17
        return null;
195
    }
196
197
    /**
198
     * Determines if the next token is of a given kind
199
     *
200
     * @param string $kind
201
     * @return bool
202
     */
203 812
    private function peek($kind)
204
    {
205 812
        return $this->lexer->token->kind === $kind;
206
    }
207
208
    /**
209
     * If the next token is of the given kind, return true after advancing
210
     * the parser. Otherwise, do not change the parser state and return false.
211
     *
212
     * @param string $kind
213
     * @return bool
214
     */
215 818
    private function skip($kind)
216
    {
217 818
        $match = $this->lexer->token->kind === $kind;
218
219 818
        if ($match) {
220 814
            $this->lexer->advance();
221
        }
222
223 818
        return $match;
224
    }
225
226
    /**
227
     * If the next token is of the given kind, return that token after advancing
228
     * the parser. Otherwise, do not change the parser state and return false.
229
     * @param string $kind
230
     * @return Token
231
     * @throws SyntaxError
232
     */
233 838
    private function expect($kind)
234
    {
235 838
        $token = $this->lexer->token;
236
237 838
        if ($token->kind === $kind) {
238 838
            $this->lexer->advance();
239
240 838
            return $token;
241
        }
242
243 11
        throw new SyntaxError(
244 11
            $this->lexer->source,
245 11
            $token->start,
246 11
            sprintf('Expected %s, found %s', $kind, $token->getDescription())
247
        );
248
    }
249
250
    /**
251
     * If the next token is a keyword with the given value, return that token after
252
     * advancing the parser. Otherwise, do not change the parser state and return
253
     * false.
254
     *
255
     * @param string $value
256
     * @return Token
257
     * @throws SyntaxError
258
     */
259 330
    private function expectKeyword($value)
260
    {
261 330
        $token = $this->lexer->token;
262
263 330
        if ($token->kind === Token::NAME && $token->value === $value) {
264 330
            $this->lexer->advance();
265
266 330
            return $token;
267
        }
268 2
        throw new SyntaxError(
269 2
            $this->lexer->source,
270 2
            $token->start,
271 2
            'Expected "' . $value . '", found ' . $token->getDescription()
272
        );
273
    }
274
275
    /**
276
     * @return SyntaxError
277
     */
278 9
    private function unexpected(?Token $atToken = null)
279
    {
280 9
        $token = $atToken ?: $this->lexer->token;
281
282 9
        return new SyntaxError($this->lexer->source, $token->start, 'Unexpected ' . $token->getDescription());
283
    }
284
285
    /**
286
     * Returns a possibly empty list of parse nodes, determined by
287
     * the parseFn. This list begins with a lex token of openKind
288
     * and ends with a lex token of closeKind. Advances the parser
289
     * to the next lex token after the closing token.
290
     *
291
     * @param string   $openKind
292
     * @param callable $parseFn
293
     * @param string   $closeKind
294
     * @return NodeList
295
     * @throws SyntaxError
296
     */
297 34
    private function any($openKind, $parseFn, $closeKind)
298
    {
299 34
        $this->expect($openKind);
300
301 34
        $nodes = [];
302 34
        while (! $this->skip($closeKind)) {
303 32
            $nodes[] = $parseFn($this);
304
        }
305
306 33
        return new NodeList($nodes);
307
    }
308
309
    /**
310
     * Returns a non-empty list of parse nodes, determined by
311
     * the parseFn. This list begins with a lex token of openKind
312
     * and ends with a lex token of closeKind. Advances the parser
313
     * to the next lex token after the closing token.
314
     *
315
     * @param string   $openKind
316
     * @param callable $parseFn
317
     * @param string   $closeKind
318
     * @return NodeList
319
     * @throws SyntaxError
320
     */
321 793
    private function many($openKind, $parseFn, $closeKind)
322
    {
323 793
        $this->expect($openKind);
324
325 792
        $nodes = [$parseFn($this)];
326 786
        while (! $this->skip($closeKind)) {
327 314
            $nodes[] = $parseFn($this);
328
        }
329
330 785
        return new NodeList($nodes);
331
    }
332
333
    /**
334
     * Converts a name lex token into a name parse node.
335
     *
336
     * @return NameNode
337
     * @throws SyntaxError
338
     */
339 818
    private function parseName()
340
    {
341 818
        $token = $this->expect(Token::NAME);
342
343 816
        return new NameNode([
344 816
            'value' => $token->value,
345 816
            'loc'   => $this->loc($token),
346
        ]);
347
    }
348
349
    /**
350
     * Implements the parsing rules in the Document section.
351
     *
352
     * @return DocumentNode
353
     * @throws SyntaxError
354
     */
355 812
    private function parseDocument()
356
    {
357 812
        $start = $this->lexer->token;
358 812
        $this->expect(Token::SOF);
359
360 812
        $definitions = [];
361
        do {
362 812
            $definitions[] = $this->parseDefinition();
363 793
        } while (! $this->skip(Token::EOF));
364
365 791
        return new DocumentNode([
366 791
            'definitions' => new NodeList($definitions),
367 791
            'loc'         => $this->loc($start),
368
        ]);
369
    }
370
371
    /**
372
     * @return ExecutableDefinitionNode|TypeSystemDefinitionNode
373
     * @throws SyntaxError
374
     */
375 812
    private function parseDefinition()
376
    {
377 812
        if ($this->peek(Token::NAME)) {
378 528
            switch ($this->lexer->token->value) {
379 528
                case 'query':
380 347
                case 'mutation':
381 335
                case 'subscription':
382 330
                case 'fragment':
383 402
                    return $this->parseExecutableDefinition();
384
385
                // Note: The schema definition language is an experimental addition.
386 130
                case 'schema':
387 127
                case 'scalar':
388 126
                case 'type':
389 78
                case 'interface':
390 57
                case 'union':
391 40
                case 'enum':
392 31
                case 'input':
393 16
                case 'extend':
394 9
                case 'directive':
395
                    // Note: The schema definition language is an experimental addition.
396 130
                    return $this->parseTypeSystemDefinition();
397
            }
398 323
        } elseif ($this->peek(Token::BRACE_L)) {
399 316
            return $this->parseExecutableDefinition();
400 7
        } elseif ($this->peekDescription()) {
401
            // Note: The schema definition language is an experimental addition.
402 6
            return $this->parseTypeSystemDefinition();
403
        }
404
405 3
        throw $this->unexpected();
406
    }
407
408
    /**
409
     * @return ExecutableDefinitionNode
410
     * @throws SyntaxError
411
     */
412 687
    private function parseExecutableDefinition()
413
    {
414 687
        if ($this->peek(Token::NAME)) {
415 402
            switch ($this->lexer->token->value) {
416 402
                case 'query':
417 218
                case 'mutation':
418 206
                case 'subscription':
419 265
                    return $this->parseOperationDefinition();
420
421 201
                case 'fragment':
422 201
                    return $this->parseFragmentDefinition();
423
            }
424 316
        } elseif ($this->peek(Token::BRACE_L)) {
425 316
            return $this->parseOperationDefinition();
426
        }
427
428
        throw $this->unexpected();
429
    }
430
431
    // Implements the parsing rules in the Operations section.
432
433
    /**
434
     * @return OperationDefinitionNode
435
     * @throws SyntaxError
436
     */
437 570
    private function parseOperationDefinition()
438
    {
439 570
        $start = $this->lexer->token;
440 570
        if ($this->peek(Token::BRACE_L)) {
441 316
            return new OperationDefinitionNode([
442 316
                'operation'           => 'query',
443
                'name'                => null,
444 316
                'variableDefinitions' => new NodeList([]),
445 316
                'directives'          => new NodeList([]),
446 316
                'selectionSet'        => $this->parseSelectionSet(),
447 312
                'loc'                 => $this->loc($start),
448
            ]);
449
        }
450
451 265
        $operation = $this->parseOperationType();
452
453 265
        $name = null;
454 265
        if ($this->peek(Token::NAME)) {
455 249
            $name = $this->parseName();
456
        }
457
458 265
        return new OperationDefinitionNode([
459 265
            'operation'           => $operation,
460 265
            'name'                => $name,
461 265
            'variableDefinitions' => $this->parseVariableDefinitions(),
462 264
            'directives'          => $this->parseDirectives(false),
463 264
            'selectionSet'        => $this->parseSelectionSet(),
464 263
            'loc'                 => $this->loc($start),
465
        ]);
466
    }
467
468
    /**
469
     * @return string
470
     * @throws SyntaxError
471
     */
472 317
    private function parseOperationType()
473
    {
474 317
        $operationToken = $this->expect(Token::NAME);
475 317
        switch ($operationToken->value) {
476 317
            case 'query':
477 308
                return 'query';
478 38
            case 'mutation':
479 30
                return 'mutation';
480 18
            case 'subscription':
481 18
                return 'subscription';
482
        }
483
484
        throw $this->unexpected($operationToken);
485
    }
486
487
    /**
488
     * @return VariableDefinitionNode[]|NodeList
489
     */
490 268
    private function parseVariableDefinitions()
491
    {
492 268
        return $this->peek(Token::PAREN_L) ?
493 123
            $this->many(
494 123
                Token::PAREN_L,
495
                function () {
496 123
                    return $this->parseVariableDefinition();
497 123
                },
498 123
                Token::PAREN_R
499
            ) :
500 267
            new NodeList([]);
501
    }
502
503
    /**
504
     * @return VariableDefinitionNode
505
     * @throws SyntaxError
506
     */
507 123
    private function parseVariableDefinition()
508
    {
509 123
        $start = $this->lexer->token;
510 123
        $var   = $this->parseVariable();
511
512 123
        $this->expect(Token::COLON);
513 123
        $type = $this->parseTypeReference();
514
515 123
        return new VariableDefinitionNode([
516 123
            'variable'     => $var,
517 123
            'type'         => $type,
518
            'defaultValue' =>
519 123
                ($this->skip(Token::EQUALS) ? $this->parseValueLiteral(true) : null),
520 122
            'loc'          => $this->loc($start),
521
        ]);
522
    }
523
524
    /**
525
     * @return VariableNode
526
     * @throws SyntaxError
527
     */
528 132
    private function parseVariable()
529
    {
530 132
        $start = $this->lexer->token;
531 132
        $this->expect(Token::DOLLAR);
532
533 132
        return new VariableNode([
534 132
            'name' => $this->parseName(),
535 132
            'loc'  => $this->loc($start),
536
        ]);
537
    }
538
539
    /**
540
     * @return SelectionSetNode
541
     */
542 685
    private function parseSelectionSet()
543
    {
544 685
        $start = $this->lexer->token;
545
546 685
        return new SelectionSetNode(
547
            [
548 685
                'selections' => $this->many(
549 685
                    Token::BRACE_L,
550
                    function () {
551 684
                        return $this->parseSelection();
552 685
                    },
553 685
                    Token::BRACE_R
554
                ),
555 680
                'loc'        => $this->loc($start),
556
            ]
557
        );
558
    }
559
560
    /**
561
     *  Selection :
562
     *   - Field
563
     *   - FragmentSpread
564
     *   - InlineFragment
565
     *
566
     * @return mixed
567
     */
568 684
    private function parseSelection()
569
    {
570 684
        return $this->peek(Token::SPREAD) ?
571 189
            $this->parseFragment() :
572 683
            $this->parseField();
573
    }
574
575
    /**
576
     * @return FieldNode
577
     * @throws SyntaxError
578
     */
579 672
    private function parseField()
580
    {
581 672
        $start       = $this->lexer->token;
582 672
        $nameOrAlias = $this->parseName();
583
584 671
        if ($this->skip(Token::COLON)) {
585 60
            $alias = $nameOrAlias;
586 60
            $name  = $this->parseName();
587
        } else {
588 653
            $alias = null;
589 653
            $name  = $nameOrAlias;
590
        }
591
592 670
        return new FieldNode([
593 670
            'alias'        => $alias,
594 670
            'name'         => $name,
595 670
            'arguments'    => $this->parseArguments(false),
596 670
            'directives'   => $this->parseDirectives(false),
597 670
            'selectionSet' => $this->peek(Token::BRACE_L) ? $this->parseSelectionSet() : null,
598 670
            'loc'          => $this->loc($start),
599
        ]);
600
    }
601
602
    /**
603
     * @param bool $isConst
604
     * @return ArgumentNode[]|NodeList
605
     * @throws SyntaxError
606
     */
607 675
    private function parseArguments($isConst)
608
    {
609 675
        $parseFn = $isConst ?
610
            function () {
611 3
                return $this->parseConstArgument();
612 5
            } :
613
            function () {
614 311
                return $this->parseArgument();
615 675
            };
616
617 675
        return $this->peek(Token::PAREN_L) ?
618 314
            $this->many(Token::PAREN_L, $parseFn, Token::PAREN_R) :
619 675
            new NodeList([]);
620
    }
621
622
    /**
623
     * @return ArgumentNode
624
     * @throws SyntaxError
625
     */
626 311
    private function parseArgument()
627
    {
628 311
        $start = $this->lexer->token;
629 311
        $name  = $this->parseName();
630
631 311
        $this->expect(Token::COLON);
632 311
        $value = $this->parseValueLiteral(false);
633
634 311
        return new ArgumentNode([
635 311
            'name'  => $name,
636 311
            'value' => $value,
637 311
            'loc'   => $this->loc($start),
638
        ]);
639
    }
640
641
    /**
642
     * @return ArgumentNode
643
     * @throws SyntaxError
644
     */
645 3
    private function parseConstArgument()
646
    {
647 3
        $start = $this->lexer->token;
648 3
        $name  = $this->parseName();
649
650 3
        $this->expect(Token::COLON);
651 3
        $value = $this->parseConstValue();
652
653 3
        return new ArgumentNode([
654 3
            'name'  => $name,
655 3
            'value' => $value,
656 3
            'loc'   => $this->loc($start),
657
        ]);
658
    }
659
660
    // Implements the parsing rules in the Fragments section.
661
662
    /**
663
     * @return FragmentSpreadNode|InlineFragmentNode
664
     * @throws SyntaxError
665
     */
666 189
    private function parseFragment()
667
    {
668 189
        $start = $this->lexer->token;
669 189
        $this->expect(Token::SPREAD);
670
671 189
        if ($this->peek(Token::NAME) && $this->lexer->token->value !== 'on') {
672 124
            return new FragmentSpreadNode([
673 124
                'name'       => $this->parseFragmentName(),
674 124
                'directives' => $this->parseDirectives(false),
675 124
                'loc'        => $this->loc($start),
676
            ]);
677
        }
678
679 87
        $typeCondition = null;
680 87
        if ($this->lexer->token->value === 'on') {
681 83
            $this->lexer->advance();
682 83
            $typeCondition = $this->parseNamedType();
683
        }
684
685 86
        return new InlineFragmentNode([
686 86
            'typeCondition' => $typeCondition,
687 86
            'directives'    => $this->parseDirectives(false),
688 86
            'selectionSet'  => $this->parseSelectionSet(),
689 86
            'loc'           => $this->loc($start),
690
        ]);
691
    }
692
693
    /**
694
     * @return FragmentDefinitionNode
695
     * @throws SyntaxError
696
     */
697 201
    private function parseFragmentDefinition()
698
    {
699 201
        $start = $this->lexer->token;
700 201
        $this->expectKeyword('fragment');
701
702 201
        $name = $this->parseFragmentName();
703
704
        // Experimental support for defining variables within fragments changes
705
        // the grammar of FragmentDefinition:
706
        //   - fragment FragmentName VariableDefinitions? on TypeCondition Directives? SelectionSet
707 200
        $variableDefinitions = null;
708 200
        if (isset($this->lexer->options['experimentalFragmentVariables'])) {
709 3
            $variableDefinitions = $this->parseVariableDefinitions();
710
        }
711 200
        $this->expectKeyword('on');
712 199
        $typeCondition = $this->parseNamedType();
713
714 199
        return new FragmentDefinitionNode([
715 199
            'name'                => $name,
716 199
            'variableDefinitions' => $variableDefinitions,
717 199
            'typeCondition'       => $typeCondition,
718 199
            'directives'          => $this->parseDirectives(false),
719 199
            'selectionSet'        => $this->parseSelectionSet(),
720 199
            'loc'                 => $this->loc($start),
721
        ]);
722
    }
723
724
    /**
725
     * @return NameNode
726
     * @throws SyntaxError
727
     */
728 203
    private function parseFragmentName()
729
    {
730 203
        if ($this->lexer->token->value === 'on') {
731 1
            throw $this->unexpected();
732
        }
733
734 202
        return $this->parseName();
735
    }
736
737
    // Implements the parsing rules in the Values section.
738
739
    /**
740
     * Value[Const] :
741
     *   - [~Const] Variable
742
     *   - IntValue
743
     *   - FloatValue
744
     *   - StringValue
745
     *   - BooleanValue
746
     *   - NullValue
747
     *   - EnumValue
748
     *   - ListValue[?Const]
749
     *   - ObjectValue[?Const]
750
     *
751
     * BooleanValue : one of `true` `false`
752
     *
753
     * NullValue : `null`
754
     *
755
     * EnumValue : Name but not `true`, `false` or `null`
756
     *
757
     * @param bool $isConst
758
     * @return BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|StringValueNode|VariableNode|ListValueNode|ObjectValueNode|NullValueNode
759
     * @throws SyntaxError
760
     */
761 352
    private function parseValueLiteral($isConst)
762
    {
763 352
        $token = $this->lexer->token;
764 352
        switch ($token->kind) {
765 352
            case Token::BRACKET_L:
766 34
                return $this->parseArray($isConst);
767 351
            case Token::BRACE_L:
768 42
                return $this->parseObject($isConst);
769 351
            case Token::INT:
770 91
                $this->lexer->advance();
771
772 91
                return new IntValueNode([
773 91
                    'value' => $token->value,
774 91
                    'loc'   => $this->loc($token),
775
                ]);
776 299
            case Token::FLOAT:
777 14
                $this->lexer->advance();
778
779 14
                return new FloatValueNode([
780 14
                    'value' => $token->value,
781 14
                    'loc'   => $this->loc($token),
782
                ]);
783 291
            case Token::STRING:
784 223
            case Token::BLOCK_STRING:
785 106
                return $this->parseStringLiteral();
786 220
            case Token::NAME:
787 122
                if ($token->value === 'true' || $token->value === 'false') {
788 81
                    $this->lexer->advance();
789
790 81
                    return new BooleanValueNode([
791 81
                        'value' => $token->value === 'true',
792 81
                        'loc'   => $this->loc($token),
793
                    ]);
794 63
                } elseif ($token->value === 'null') {
795 29
                    $this->lexer->advance();
796
797 29
                    return new NullValueNode([
798 29
                        'loc' => $this->loc($token),
799
                    ]);
800
                } else {
801 44
                    $this->lexer->advance();
802
803 44
                    return new EnumValueNode([
804 44
                        'value' => $token->value,
805 44
                        'loc'   => $this->loc($token),
806
                    ]);
807
                }
808
                break;
809
810 111
            case Token::DOLLAR:
811 111
                if (! $isConst) {
812 110
                    return $this->parseVariable();
813
                }
814 1
                break;
815
        }
816 1
        throw $this->unexpected();
817
    }
818
819
    /**
820
     * @return StringValueNode
821
     */
822 112
    private function parseStringLiteral()
823
    {
824 112
        $token = $this->lexer->token;
825 112
        $this->lexer->advance();
826
827 112
        return new StringValueNode([
828 112
            'value' => $token->value,
829 112
            'block' => $token->kind === Token::BLOCK_STRING,
830 112
            'loc'   => $this->loc($token),
831
        ]);
832
    }
833
834
    /**
835
     * @return BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|StringValueNode|VariableNode
836
     * @throws SyntaxError
837
     */
838 9
    private function parseConstValue()
839
    {
840 9
        return $this->parseValueLiteral(true);
841
    }
842
843
    /**
844
     * @return BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|ListValueNode|ObjectValueNode|StringValueNode|VariableNode
845
     */
846 27
    private function parseVariableValue()
847
    {
848 27
        return $this->parseValueLiteral(false);
849
    }
850
851
    /**
852
     * @param bool $isConst
853
     * @return ListValueNode
854
     */
855 34
    private function parseArray($isConst)
856
    {
857 34
        $start   = $this->lexer->token;
858
        $parseFn = $isConst ? function () {
859 5
            return $this->parseConstValue();
860
        } : function () {
861 27
            return $this->parseVariableValue();
862 34
        };
863
864 34
        return new ListValueNode(
865
            [
866 34
                'values' => $this->any(Token::BRACKET_L, $parseFn, Token::BRACKET_R),
867 33
                'loc'    => $this->loc($start),
868
            ]
869
        );
870
    }
871
872
    /**
873
     * @param bool $isConst
874
     * @return ObjectValueNode
875
     */
876 42
    private function parseObject($isConst)
877
    {
878 42
        $start = $this->lexer->token;
879 42
        $this->expect(Token::BRACE_L);
880 42
        $fields = [];
881 42
        while (! $this->skip(Token::BRACE_R)) {
882 42
            $fields[] = $this->parseObjectField($isConst);
883
        }
884
885 41
        return new ObjectValueNode([
886 41
            'fields' => new NodeList($fields),
887 41
            'loc'    => $this->loc($start),
888
        ]);
889
    }
890
891
    /**
892
     * @param bool $isConst
893
     * @return ObjectFieldNode
894
     */
895 42
    private function parseObjectField($isConst)
896
    {
897 42
        $start = $this->lexer->token;
898 42
        $name  = $this->parseName();
899
900 42
        $this->expect(Token::COLON);
901
902 42
        return new ObjectFieldNode([
903 42
            'name'  => $name,
904 42
            'value' => $this->parseValueLiteral($isConst),
905 41
            'loc'   => $this->loc($start),
906
        ]);
907
    }
908
909
    // Implements the parsing rules in the Directives section.
910
911
    /**
912
     * @param bool $isConst
913
     * @return DirectiveNode[]|NodeList
914
     * @throws SyntaxError
915
     */
916 802
    private function parseDirectives($isConst)
917
    {
918 802
        $directives = [];
919 802
        while ($this->peek(Token::AT)) {
920 62
            $directives[] = $this->parseDirective($isConst);
921
        }
922
923 802
        return new NodeList($directives);
924
    }
925
926
    /**
927
     * @param bool $isConst
928
     * @return DirectiveNode
929
     * @throws SyntaxError
930
     */
931 62
    private function parseDirective($isConst)
932
    {
933 62
        $start = $this->lexer->token;
934 62
        $this->expect(Token::AT);
935
936 62
        return new DirectiveNode([
937 62
            'name'      => $this->parseName(),
938 62
            'arguments' => $this->parseArguments($isConst),
939 62
            'loc'       => $this->loc($start),
940
        ]);
941
    }
942
943
    // Implements the parsing rules in the Types section.
944
945
    /**
946
     * Handles the Type: TypeName, ListType, and NonNullType parsing rules.
947
     *
948
     * @return ListTypeNode|NameNode|NonNullTypeNode
949
     * @throws SyntaxError
950
     */
951 237
    private function parseTypeReference()
952
    {
953 237
        $start = $this->lexer->token;
954
955 237
        if ($this->skip(Token::BRACKET_L)) {
956 30
            $type = $this->parseTypeReference();
957 30
            $this->expect(Token::BRACKET_R);
958 30
            $type = new ListTypeNode([
959 30
                'type' => $type,
960 30
                'loc'  => $this->loc($start),
961
            ]);
962
        } else {
963 237
            $type = $this->parseNamedType();
964
        }
965 237
        if ($this->skip(Token::BANG)) {
966 55
            return new NonNullTypeNode([
967 55
                'type' => $type,
968 55
                'loc'  => $this->loc($start),
969
            ]);
970
        }
971
972 202
        return $type;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $type also could return the type GraphQL\Language\AST\NamedTypeNode which is incompatible with the documented return type GraphQL\Language\AST\Lis...age\AST\NonNullTypeNode.
Loading history...
973
    }
974
975 458
    private function parseNamedType()
976
    {
977 458
        $start = $this->lexer->token;
978
979 458
        return new NamedTypeNode([
980 458
            'name' => $this->parseName(),
981 455
            'loc'  => $this->loc($start),
982
        ]);
983
    }
984
985
    // Implements the parsing rules in the Type Definition section.
986
987
    /**
988
     * TypeSystemDefinition :
989
     *   - SchemaDefinition
990
     *   - TypeDefinition
991
     *   - TypeExtension
992
     *   - DirectiveDefinition
993
     *
994
     * TypeDefinition :
995
     *   - ScalarTypeDefinition
996
     *   - ObjectTypeDefinition
997
     *   - InterfaceTypeDefinition
998
     *   - UnionTypeDefinition
999
     *   - EnumTypeDefinition
1000
     *   - InputObjectTypeDefinition
1001
     *
1002
     * @return TypeSystemDefinitionNode
1003
     * @throws SyntaxError
1004
     */
1005 132
    private function parseTypeSystemDefinition()
1006
    {
1007
        // Many definitions begin with a description and require a lookahead.
1008 132
        $keywordToken = $this->peekDescription()
1009 6
            ? $this->lexer->lookahead()
1010 132
            : $this->lexer->token;
1011
1012 132
        if ($keywordToken->kind === Token::NAME) {
1013 132
            switch ($keywordToken->value) {
1014 132
                case 'schema':
1015 53
                    return $this->parseSchemaDefinition();
1016 130
                case 'scalar':
1017 7
                    return $this->parseScalarTypeDefinition();
1018 129
                case 'type':
1019 109
                    return $this->parseObjectTypeDefinition();
1020 78
                case 'interface':
1021 31
                    return $this->parseInterfaceTypeDefinition();
1022 57
                case 'union':
1023 24
                    return $this->parseUnionTypeDefinition();
1024 40
                case 'enum':
1025 16
                    return $this->parseEnumTypeDefinition();
1026 31
                case 'input':
1027 19
                    return $this->parseInputObjectTypeDefinition();
1028 16
                case 'extend':
1029 10
                    return $this->parseTypeExtension();
1030 8
                case 'directive':
1031 8
                    return $this->parseDirectiveDefinition();
1032
            }
1033
        }
1034
1035
        throw $this->unexpected($keywordToken);
1036
    }
1037
1038
    /**
1039
     * @return bool
1040
     */
1041 133
    private function peekDescription()
1042
    {
1043 133
        return $this->peek(Token::STRING) || $this->peek(Token::BLOCK_STRING);
1044
    }
1045
1046
    /**
1047
     * @return StringValueNode|null
1048
     */
1049 125
    private function parseDescription()
1050
    {
1051 125
        if ($this->peekDescription()) {
1052 8
            return $this->parseStringLiteral();
1053
        }
1054 125
    }
1055
1056
    /**
1057
     * @return SchemaDefinitionNode
1058
     * @throws SyntaxError
1059
     */
1060 53
    private function parseSchemaDefinition()
1061
    {
1062 53
        $start = $this->lexer->token;
1063 53
        $this->expectKeyword('schema');
1064 53
        $directives = $this->parseDirectives(true);
1065
1066 53
        $operationTypes = $this->many(
1067 53
            Token::BRACE_L,
1068
            function () {
1069 53
                return $this->parseOperationTypeDefinition();
1070 53
            },
1071 53
            Token::BRACE_R
1072
        );
1073
1074 53
        return new SchemaDefinitionNode([
1075 53
            'directives'     => $directives,
1076 53
            'operationTypes' => $operationTypes,
1077 53
            'loc'            => $this->loc($start),
1078
        ]);
1079
    }
1080
1081
    /**
1082
     * @return OperationTypeDefinitionNode
1083
     * @throws SyntaxError
1084
     */
1085 53
    private function parseOperationTypeDefinition()
1086
    {
1087 53
        $start     = $this->lexer->token;
1088 53
        $operation = $this->parseOperationType();
1089 53
        $this->expect(Token::COLON);
1090 53
        $type = $this->parseNamedType();
1091
1092 53
        return new OperationTypeDefinitionNode([
1093 53
            'operation' => $operation,
1094 53
            'type'      => $type,
1095 53
            'loc'       => $this->loc($start),
1096
        ]);
1097
    }
1098
1099
    /**
1100
     * @return ScalarTypeDefinitionNode
1101
     * @throws SyntaxError
1102
     */
1103 7
    private function parseScalarTypeDefinition()
1104
    {
1105 7
        $start       = $this->lexer->token;
1106 7
        $description = $this->parseDescription();
1107 7
        $this->expectKeyword('scalar');
1108 7
        $name       = $this->parseName();
1109 7
        $directives = $this->parseDirectives(true);
1110
1111 7
        return new ScalarTypeDefinitionNode([
1112 7
            'name'        => $name,
1113 7
            'directives'  => $directives,
1114 7
            'loc'         => $this->loc($start),
1115 7
            'description' => $description,
1116
        ]);
1117
    }
1118
1119
    /**
1120
     * @return ObjectTypeDefinitionNode
1121
     * @throws SyntaxError
1122
     */
1123 109
    private function parseObjectTypeDefinition()
1124
    {
1125 109
        $start       = $this->lexer->token;
1126 109
        $description = $this->parseDescription();
1127 109
        $this->expectKeyword('type');
1128 109
        $name       = $this->parseName();
1129 109
        $interfaces = $this->parseImplementsInterfaces();
1130 109
        $directives = $this->parseDirectives(true);
1131 109
        $fields     = $this->parseFieldsDefinition();
1132
1133 108
        return new ObjectTypeDefinitionNode([
1134 108
            'name'        => $name,
1135 108
            'interfaces'  => $interfaces,
1136 108
            'directives'  => $directives,
1137 108
            'fields'      => $fields,
1138 108
            'loc'         => $this->loc($start),
1139 108
            'description' => $description,
1140
        ]);
1141
    }
1142
1143
    /**
1144
     * ImplementsInterfaces :
1145
     *   - implements `&`? NamedType
1146
     *   - ImplementsInterfaces & NamedType
1147
     *
1148
     * @return NamedTypeNode[]
1149
     */
1150 113
    private function parseImplementsInterfaces()
1151
    {
1152 113
        $types = [];
1153 113
        if ($this->lexer->token->value === 'implements') {
1154 36
            $this->lexer->advance();
1155
            // Optional leading ampersand
1156 36
            $this->skip(Token::AMP);
1157
            do {
1158 36
                $types[] = $this->parseNamedType();
1159 36
            } while ($this->skip(Token::AMP) ||
1160
            // Legacy support for the SDL?
1161 36
            (! empty($this->lexer->options['allowLegacySDLImplementsInterfaces']) && $this->peek(Token::NAME))
1162
            );
1163
        }
1164
1165 113
        return $types;
1166
    }
1167
1168
    /**
1169
     * @return FieldDefinitionNode[]|NodeList
1170
     * @throws SyntaxError
1171
     */
1172 114
    private function parseFieldsDefinition()
1173
    {
1174
        // Legacy support for the SDL?
1175 114
        if (! empty($this->lexer->options['allowLegacySDLEmptyFields']) &&
1176 114
            $this->peek(Token::BRACE_L) &&
1177 114
            $this->lexer->lookahead()->kind === Token::BRACE_R
1178
        ) {
1179 1
            $this->lexer->advance();
1180 1
            $this->lexer->advance();
1181
1182 1
            return [];
1183
        }
1184
1185 113
        return $this->peek(Token::BRACE_L)
1186 109
            ? $this->many(
1187 109
                Token::BRACE_L,
1188
                function () {
1189 109
                    return $this->parseFieldDefinition();
1190 109
                },
1191 109
                Token::BRACE_R
1192
            )
1193 112
            : new NodeList([]);
1194
    }
1195
1196
    /**
1197
     * @return FieldDefinitionNode
1198
     * @throws SyntaxError
1199
     */
1200 109
    private function parseFieldDefinition()
1201
    {
1202 109
        $start       = $this->lexer->token;
1203 109
        $description = $this->parseDescription();
1204 109
        $name        = $this->parseName();
1205 108
        $args        = $this->parseArgumentDefs();
1206 108
        $this->expect(Token::COLON);
1207 108
        $type       = $this->parseTypeReference();
1208 108
        $directives = $this->parseDirectives(true);
1209
1210 108
        return new FieldDefinitionNode([
1211 108
            'name'        => $name,
1212 108
            'arguments'   => $args,
1213 108
            'type'        => $type,
1214 108
            'directives'  => $directives,
1215 108
            'loc'         => $this->loc($start),
1216 108
            'description' => $description,
1217
        ]);
1218
    }
1219
1220
    /**
1221
     * @return InputValueDefinitionNode[]|NodeList
1222
     * @throws SyntaxError
1223
     */
1224 109
    private function parseArgumentDefs()
1225
    {
1226 109
        if (! $this->peek(Token::PAREN_L)) {
1227 95
            return new NodeList([]);
1228
        }
1229
1230 34
        return $this->many(
1231 34
            Token::PAREN_L,
1232
            function () {
1233 34
                return $this->parseInputValueDef();
1234 34
            },
1235 34
            Token::PAREN_R
1236
        );
1237
    }
1238
1239
    /**
1240
     * @return InputValueDefinitionNode
1241
     * @throws SyntaxError
1242
     */
1243 43
    private function parseInputValueDef()
1244
    {
1245 43
        $start       = $this->lexer->token;
1246 43
        $description = $this->parseDescription();
1247 43
        $name        = $this->parseName();
1248 43
        $this->expect(Token::COLON);
1249 42
        $type         = $this->parseTypeReference();
1250 42
        $defaultValue = null;
1251 42
        if ($this->skip(Token::EQUALS)) {
1252 5
            $defaultValue = $this->parseConstValue();
1253
        }
1254 42
        $directives = $this->parseDirectives(true);
1255
1256 42
        return new InputValueDefinitionNode([
1257 42
            'name'         => $name,
1258 42
            'type'         => $type,
1259 42
            'defaultValue' => $defaultValue,
1260 42
            'directives'   => $directives,
1261 42
            'loc'          => $this->loc($start),
1262 42
            'description'  => $description,
1263
        ]);
1264
    }
1265
1266
    /**
1267
     * @return InterfaceTypeDefinitionNode
1268
     * @throws SyntaxError
1269
     */
1270 31
    private function parseInterfaceTypeDefinition()
1271
    {
1272 31
        $start       = $this->lexer->token;
1273 31
        $description = $this->parseDescription();
1274 31
        $this->expectKeyword('interface');
1275 31
        $name       = $this->parseName();
1276 31
        $directives = $this->parseDirectives(true);
1277 31
        $fields     = $this->parseFieldsDefinition();
1278
1279 31
        return new InterfaceTypeDefinitionNode([
1280 31
            'name'        => $name,
1281 31
            'directives'  => $directives,
1282 31
            'fields'      => $fields,
1283 31
            'loc'         => $this->loc($start),
1284 31
            'description' => $description,
1285
        ]);
1286
    }
1287
1288
    /**
1289
     * UnionTypeDefinition :
1290
     *   - Description? union Name Directives[Const]? UnionMemberTypes?
1291
     *
1292
     * @return UnionTypeDefinitionNode
1293
     * @throws SyntaxError
1294
     */
1295 24
    private function parseUnionTypeDefinition()
1296
    {
1297 24
        $start       = $this->lexer->token;
1298 24
        $description = $this->parseDescription();
1299 24
        $this->expectKeyword('union');
1300 24
        $name       = $this->parseName();
1301 24
        $directives = $this->parseDirectives(true);
1302 24
        $types      = $this->parseUnionMemberTypes();
1303
1304 20
        return new UnionTypeDefinitionNode([
1305 20
            'name'        => $name,
1306 20
            'directives'  => $directives,
1307 20
            'types'       => $types,
1308 20
            'loc'         => $this->loc($start),
1309 20
            'description' => $description,
1310
        ]);
1311
    }
1312
1313
    /**
1314
     * UnionMemberTypes :
1315
     *   - = `|`? NamedType
1316
     *   - UnionMemberTypes | NamedType
1317
     *
1318
     * @return NamedTypeNode[]
1319
     */
1320 24
    private function parseUnionMemberTypes()
1321
    {
1322 24
        $types = [];
1323 24
        if ($this->skip(Token::EQUALS)) {
1324
            // Optional leading pipe
1325 23
            $this->skip(Token::PIPE);
1326
            do {
1327 23
                $types[] = $this->parseNamedType();
1328 21
            } while ($this->skip(Token::PIPE));
1329
        }
1330
1331 20
        return $types;
1332
    }
1333
1334
    /**
1335
     * @return EnumTypeDefinitionNode
1336
     * @throws SyntaxError
1337
     */
1338 16
    private function parseEnumTypeDefinition()
1339
    {
1340 16
        $start       = $this->lexer->token;
1341 16
        $description = $this->parseDescription();
1342 16
        $this->expectKeyword('enum');
1343 16
        $name       = $this->parseName();
1344 16
        $directives = $this->parseDirectives(true);
1345 16
        $values     = $this->parseEnumValuesDefinition();
1346
1347 16
        return new EnumTypeDefinitionNode([
1348 16
            'name'        => $name,
1349 16
            'directives'  => $directives,
1350 16
            'values'      => $values,
1351 16
            'loc'         => $this->loc($start),
1352 16
            'description' => $description,
1353
        ]);
1354
    }
1355
1356
    /**
1357
     * @return EnumValueDefinitionNode[]|NodeList
1358
     * @throws SyntaxError
1359
     */
1360 16
    private function parseEnumValuesDefinition()
1361
    {
1362 16
        return $this->peek(Token::BRACE_L)
1363 15
            ? $this->many(
1364 15
                Token::BRACE_L,
1365
                function () {
1366 15
                    return $this->parseEnumValueDefinition();
1367 15
                },
1368 15
                Token::BRACE_R
1369
            )
1370 16
            : new NodeList([]);
1371
    }
1372
1373
    /**
1374
     * @return EnumValueDefinitionNode
1375
     * @throws SyntaxError
1376
     */
1377 15
    private function parseEnumValueDefinition()
1378
    {
1379 15
        $start       = $this->lexer->token;
1380 15
        $description = $this->parseDescription();
1381 15
        $name        = $this->parseName();
1382 15
        $directives  = $this->parseDirectives(true);
1383
1384 15
        return new EnumValueDefinitionNode([
1385 15
            'name'        => $name,
1386 15
            'directives'  => $directives,
1387 15
            'loc'         => $this->loc($start),
1388 15
            'description' => $description,
1389
        ]);
1390
    }
1391
1392
    /**
1393
     * @return InputObjectTypeDefinitionNode
1394
     * @throws SyntaxError
1395
     */
1396 19
    private function parseInputObjectTypeDefinition()
1397
    {
1398 19
        $start       = $this->lexer->token;
1399 19
        $description = $this->parseDescription();
1400 19
        $this->expectKeyword('input');
1401 19
        $name       = $this->parseName();
1402 19
        $directives = $this->parseDirectives(true);
1403 19
        $fields     = $this->parseInputFieldsDefinition();
1404
1405 18
        return new InputObjectTypeDefinitionNode([
1406 18
            'name'        => $name,
1407 18
            'directives'  => $directives,
1408 18
            'fields'      => $fields,
1409 18
            'loc'         => $this->loc($start),
1410 18
            'description' => $description,
1411
        ]);
1412
    }
1413
1414
    /**
1415
     * @return InputValueDefinitionNode[]|NodeList
1416
     * @throws SyntaxError
1417
     */
1418 19
    private function parseInputFieldsDefinition()
1419
    {
1420 19
        return $this->peek(Token::BRACE_L)
1421 18
            ? $this->many(
1422 18
                Token::BRACE_L,
1423
                function () {
1424 18
                    return $this->parseInputValueDef();
1425 18
                },
1426 18
                Token::BRACE_R
1427
            )
1428 18
            : new NodeList([]);
1429
    }
1430
1431
    /**
1432
     * TypeExtension :
1433
     *   - ScalarTypeExtension
1434
     *   - ObjectTypeExtension
1435
     *   - InterfaceTypeExtension
1436
     *   - UnionTypeExtension
1437
     *   - EnumTypeExtension
1438
     *   - InputObjectTypeDefinition
1439
     *
1440
     * @return TypeExtensionNode
1441
     * @throws SyntaxError
1442
     */
1443 10
    private function parseTypeExtension()
1444
    {
1445 10
        $keywordToken = $this->lexer->lookahead();
1446
1447 10
        if ($keywordToken->kind === Token::NAME) {
1448 9
            switch ($keywordToken->value) {
1449 9
                case 'scalar':
1450 3
                    return $this->parseScalarTypeExtension();
1451 9
                case 'type':
1452 8
                    return $this->parseObjectTypeExtension();
1453 4
                case 'interface':
1454 3
                    return $this->parseInterfaceTypeExtension();
1455 4
                case 'union':
1456 3
                    return $this->parseUnionTypeExtension();
1457 4
                case 'enum':
1458 3
                    return $this->parseEnumTypeExtension();
1459 4
                case 'input':
1460 3
                    return $this->parseInputObjectTypeExtension();
1461
            }
1462
        }
1463
1464 2
        throw $this->unexpected($keywordToken);
1465
    }
1466
1467
    /**
1468
     * @return ScalarTypeExtensionNode
1469
     * @throws SyntaxError
1470
     */
1471 3
    private function parseScalarTypeExtension()
1472
    {
1473 3
        $start = $this->lexer->token;
1474 3
        $this->expectKeyword('extend');
1475 3
        $this->expectKeyword('scalar');
1476 3
        $name       = $this->parseName();
1477 3
        $directives = $this->parseDirectives(true);
1478 3
        if (count($directives) === 0) {
1479
            throw $this->unexpected();
1480
        }
1481
1482 3
        return new ScalarTypeExtensionNode([
1483 3
            'name'       => $name,
1484 3
            'directives' => $directives,
1485 3
            'loc'        => $this->loc($start),
1486
        ]);
1487
    }
1488
1489
    /**
1490
     * @return ObjectTypeExtensionNode
1491
     * @throws SyntaxError
1492
     */
1493 8
    private function parseObjectTypeExtension()
1494
    {
1495 8
        $start = $this->lexer->token;
1496 8
        $this->expectKeyword('extend');
1497 8
        $this->expectKeyword('type');
1498 8
        $name       = $this->parseName();
1499 8
        $interfaces = $this->parseImplementsInterfaces();
1500 8
        $directives = $this->parseDirectives(true);
1501 8
        $fields     = $this->parseFieldsDefinition();
1502
1503 8
        if (count($interfaces) === 0 &&
1504 8
            count($directives) === 0 &&
1505 8
            count($fields) === 0
1506
        ) {
1507 1
            throw $this->unexpected();
1508
        }
1509
1510 7
        return new ObjectTypeExtensionNode([
1511 7
            'name'       => $name,
1512 7
            'interfaces' => $interfaces,
1513 7
            'directives' => $directives,
1514 7
            'fields'     => $fields,
1515 7
            'loc'        => $this->loc($start),
1516
        ]);
1517
    }
1518
1519
    /**
1520
     * @return InterfaceTypeExtensionNode
1521
     * @throws SyntaxError
1522
     */
1523 3
    private function parseInterfaceTypeExtension()
1524
    {
1525 3
        $start = $this->lexer->token;
1526 3
        $this->expectKeyword('extend');
1527 3
        $this->expectKeyword('interface');
1528 3
        $name       = $this->parseName();
1529 3
        $directives = $this->parseDirectives(true);
1530 3
        $fields     = $this->parseFieldsDefinition();
1531 3
        if (count($directives) === 0 &&
1532 3
            count($fields) === 0
1533
        ) {
1534
            throw $this->unexpected();
1535
        }
1536
1537 3
        return new InterfaceTypeExtensionNode([
1538 3
            'name'       => $name,
1539 3
            'directives' => $directives,
1540 3
            'fields'     => $fields,
1541 3
            'loc'        => $this->loc($start),
1542
        ]);
1543
    }
1544
1545
    /**
1546
     * UnionTypeExtension :
1547
     *   - extend union Name Directives[Const]? UnionMemberTypes
1548
     *   - extend union Name Directives[Const]
1549
     *
1550
     * @return UnionTypeExtensionNode
1551
     * @throws SyntaxError
1552
     */
1553 3
    private function parseUnionTypeExtension()
1554
    {
1555 3
        $start = $this->lexer->token;
1556 3
        $this->expectKeyword('extend');
1557 3
        $this->expectKeyword('union');
1558 3
        $name       = $this->parseName();
1559 3
        $directives = $this->parseDirectives(true);
1560 3
        $types      = $this->parseUnionMemberTypes();
1561 3
        if (count($directives) === 0 &&
1562 3
            ! $types
0 ignored issues
show
Bug Best Practice introduced by
The expression $types of type GraphQL\Language\AST\NamedTypeNode[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
introduced by
$types is a non-empty array, thus ! $types is always false.
Loading history...
1563
        ) {
1564
            throw $this->unexpected();
1565
        }
1566
1567 3
        return new UnionTypeExtensionNode([
1568 3
            'name'       => $name,
1569 3
            'directives' => $directives,
1570 3
            'types'      => $types,
1571 3
            'loc'        => $this->loc($start),
1572
        ]);
1573
    }
1574
1575
    /**
1576
     * @return EnumTypeExtensionNode
1577
     * @throws SyntaxError
1578
     */
1579 3
    private function parseEnumTypeExtension()
1580
    {
1581 3
        $start = $this->lexer->token;
1582 3
        $this->expectKeyword('extend');
1583 3
        $this->expectKeyword('enum');
1584 3
        $name       = $this->parseName();
1585 3
        $directives = $this->parseDirectives(true);
1586 3
        $values     = $this->parseEnumValuesDefinition();
1587 3
        if (count($directives) === 0 &&
1588 3
            count($values) === 0
1589
        ) {
1590
            throw $this->unexpected();
1591
        }
1592
1593 3
        return new EnumTypeExtensionNode([
1594 3
            'name'       => $name,
1595 3
            'directives' => $directives,
1596 3
            'values'     => $values,
1597 3
            'loc'        => $this->loc($start),
1598
        ]);
1599
    }
1600
1601
    /**
1602
     * @return InputObjectTypeExtensionNode
1603
     * @throws SyntaxError
1604
     */
1605 3
    private function parseInputObjectTypeExtension()
1606
    {
1607 3
        $start = $this->lexer->token;
1608 3
        $this->expectKeyword('extend');
1609 3
        $this->expectKeyword('input');
1610 3
        $name       = $this->parseName();
1611 3
        $directives = $this->parseDirectives(true);
1612 3
        $fields     = $this->parseInputFieldsDefinition();
1613 3
        if (count($directives) === 0 &&
1614 3
            count($fields) === 0
1615
        ) {
1616
            throw $this->unexpected();
1617
        }
1618
1619 3
        return new InputObjectTypeExtensionNode([
1620 3
            'name'       => $name,
1621 3
            'directives' => $directives,
1622 3
            'fields'     => $fields,
1623 3
            'loc'        => $this->loc($start),
1624
        ]);
1625
    }
1626
1627
    /**
1628
     * DirectiveDefinition :
1629
     *   - directive @ Name ArgumentsDefinition? on DirectiveLocations
1630
     *
1631
     * @return DirectiveDefinitionNode
1632
     * @throws SyntaxError
1633
     */
1634 8
    private function parseDirectiveDefinition()
1635
    {
1636 8
        $start       = $this->lexer->token;
1637 8
        $description = $this->parseDescription();
1638 8
        $this->expectKeyword('directive');
1639 8
        $this->expect(Token::AT);
1640 8
        $name = $this->parseName();
1641 8
        $args = $this->parseArgumentDefs();
1642 8
        $this->expectKeyword('on');
1643 8
        $locations = $this->parseDirectiveLocations();
1644
1645 7
        return new DirectiveDefinitionNode([
1646 7
            'name'        => $name,
1647 7
            'arguments'   => $args,
1648 7
            'locations'   => $locations,
1649 7
            'loc'         => $this->loc($start),
1650 7
            'description' => $description,
1651
        ]);
1652
    }
1653
1654
    /**
1655
     * @return NameNode[]
1656
     * @throws SyntaxError
1657
     */
1658 8
    private function parseDirectiveLocations()
1659
    {
1660
        // Optional leading pipe
1661 8
        $this->skip(Token::PIPE);
1662 8
        $locations = [];
1663
        do {
1664 8
            $locations[] = $this->parseDirectiveLocation();
1665 8
        } while ($this->skip(Token::PIPE));
1666
1667 7
        return $locations;
1668
    }
1669
1670
    /**
1671
     * @return NameNode
1672
     * @throws SyntaxError
1673
     */
1674 8
    private function parseDirectiveLocation()
1675
    {
1676 8
        $start = $this->lexer->token;
1677 8
        $name  = $this->parseName();
1678 8
        if (DirectiveLocation::has($name->value)) {
1679 8
            return $name;
1680
        }
1681
1682 1
        throw $this->unexpected($start);
1683
    }
1684
}
1685