Failed Conditions
Push — master ( 7ff3e9...e7de06 )
by Vladimir
04:33 queued 01:54
created

Parser::parseDirective()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1

Importance

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