Failed Conditions
Push — master ( 4d4282...aed406 )
by Vladimir
14s queued 12s
created

Parser::parseInputObjectTypeExtension()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 19
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 3.0026

Importance

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