Completed
Push — master ( b72ba3...24c473 )
by Vladimir
17s queued 14s
created

Parser::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 2
crap 2
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\Node;
36
use GraphQL\Language\AST\NodeList;
37
use GraphQL\Language\AST\NonNullTypeNode;
38
use GraphQL\Language\AST\NullValueNode;
39
use GraphQL\Language\AST\ObjectFieldNode;
40
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
41
use GraphQL\Language\AST\ObjectTypeExtensionNode;
42
use GraphQL\Language\AST\ObjectValueNode;
43
use GraphQL\Language\AST\OperationDefinitionNode;
44
use GraphQL\Language\AST\OperationTypeDefinitionNode;
45
use GraphQL\Language\AST\ScalarTypeDefinitionNode;
46
use GraphQL\Language\AST\ScalarTypeExtensionNode;
47
use GraphQL\Language\AST\SchemaDefinitionNode;
48
use GraphQL\Language\AST\SchemaTypeExtensionNode;
49
use GraphQL\Language\AST\SelectionSetNode;
50
use GraphQL\Language\AST\StringValueNode;
51
use GraphQL\Language\AST\TypeExtensionNode;
52
use GraphQL\Language\AST\TypeSystemDefinitionNode;
53
use GraphQL\Language\AST\UnionTypeDefinitionNode;
54
use GraphQL\Language\AST\UnionTypeExtensionNode;
55
use GraphQL\Language\AST\VariableDefinitionNode;
56
use GraphQL\Language\AST\VariableNode;
57
use function count;
58
use function sprintf;
59
60
/**
61
 * Parses string containing GraphQL query or [type definition](type-system/type-language.md) to Abstract Syntax Tree.
62
 *
63
 * @method static NameNode name(Source|string $source, bool[] $options = [])
64
 * @method static DocumentNode document(Source|string $source, bool[] $options = [])
65
 * @method static ExecutableDefinitionNode|TypeSystemDefinitionNode definition(Source|string $source, bool[] $options = [])
66
 * @method static ExecutableDefinitionNode executableDefinition(Source|string $source, bool[] $options = [])
67
 * @method static OperationDefinitionNode operationDefinition(Source|string $source, bool[] $options = [])
68
 * @method static string operationType(Source|string $source, bool[] $options = [])
69
 * @method static NodeList|VariableDefinitionNode[] variableDefinitions(Source|string $source, bool[] $options = [])
70
 * @method static VariableDefinitionNode variableDefinition(Source|string $source, bool[] $options = [])
71
 * @method static VariableNode variable(Source|string $source, bool[] $options = [])
72
 * @method static SelectionSetNode selectionSet(Source|string $source, bool[] $options = [])
73
 * @method static mixed selection(Source|string $source, bool[] $options = [])
74
 * @method static FieldNode field(Source|string $source, bool[] $options = [])
75
 * @method static NodeList|ArgumentNode[] arguments(Source|string $source, bool[] $options = [])
76
 * @method static ArgumentNode argument(Source|string $source, bool[] $options = [])
77
 * @method static ArgumentNode constArgument(Source|string $source, bool[] $options = [])
78
 * @method static FragmentSpreadNode|InlineFragmentNode fragment(Source|string $source, bool[] $options = [])
79
 * @method static FragmentDefinitionNode fragmentDefinition(Source|string $source, bool[] $options = [])
80
 * @method static NameNode fragmentName(Source|string $source, bool[] $options = [])
81
 * @method static BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|ListValueNode|NullValueNode|ObjectValueNode|StringValueNode|VariableNode valueLiteral(Source|string $source, bool[] $options = [])
82
 * @method static StringValueNode stringLiteral(Source|string $source, bool[] $options = [])
83
 * @method static BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|StringValueNode|VariableNode constValue(Source|string $source, bool[] $options = [])
84
 * @method static BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|ListValueNode|ObjectValueNode|StringValueNode|VariableNode variableValue(Source|string $source, bool[] $options = [])
85
 * @method static ListValueNode array(Source|string $source, bool[] $options = [])
86
 * @method static ObjectValueNode object(Source|string $source, bool[] $options = [])
87
 * @method static ObjectFieldNode objectField(Source|string $source, bool[] $options = [])
88
 * @method static NodeList|DirectiveNode[] directives(Source|string $source, bool[] $options = [])
89
 * @method static DirectiveNode directive(Source|string $source, bool[] $options = [])
90
 * @method static ListTypeNode|NameNode|NonNullTypeNode typeReference(Source|string $source, bool[] $options = [])
91
 * @method static NamedTypeNode namedType(Source|string $source, bool[] $options = [])
92
 * @method static TypeSystemDefinitionNode typeSystemDefinition(Source|string $source, bool[] $options = [])
93
 * @method static StringValueNode|null description(Source|string $source, bool[] $options = [])
94
 * @method static SchemaDefinitionNode schemaDefinition(Source|string $source, bool[] $options = [])
95
 * @method static OperationTypeDefinitionNode operationTypeDefinition(Source|string $source, bool[] $options = [])
96
 * @method static ScalarTypeDefinitionNode scalarTypeDefinition(Source|string $source, bool[] $options = [])
97
 * @method static ObjectTypeDefinitionNode objectTypeDefinition(Source|string $source, bool[] $options = [])
98
 * @method static NamedTypeNode[] implementsInterfaces(Source|string $source, bool[] $options = [])
99
 * @method static FieldDefinitionNode[] fieldsDefinition(Source|string $source, bool[] $options = [])
100
 * @method static FieldDefinitionNode fieldDefinition(Source|string $source, bool[] $options = [])
101
 * @method static InputValueDefinitionNode[] argumentsDefinition(Source|string $source, bool[] $options = [])
102
 * @method static InputValueDefinitionNode inputValueDefinition(Source|string $source, bool[] $options = [])
103
 * @method static InterfaceTypeDefinitionNode interfaceTypeDefinition(Source|string $source, bool[] $options = [])
104
 * @method static UnionTypeDefinitionNode unionTypeDefinition(Source|string $source, bool[] $options = [])
105
 * @method static NamedTypeNode[] unionMemberTypes(Source|string $source, bool[] $options = [])
106
 * @method static EnumTypeDefinitionNode enumTypeDefinition(Source|string $source, bool[] $options = [])
107
 * @method static EnumValueDefinitionNode[] enumValuesDefinition(Source|string $source, bool[] $options = [])
108
 * @method static EnumValueDefinitionNode enumValueDefinition(Source|string $source, bool[] $options = [])
109
 * @method static InputObjectTypeDefinitionNode inputObjectTypeDefinition(Source|string $source, bool[] $options = [])
110
 * @method static InputValueDefinitionNode[] inputFieldsDefinition(Source|string $source, bool[] $options = [])
111
 * @method static TypeExtensionNode typeExtension(Source|string $source, bool[] $options = [])
112
 * @method static SchemaTypeExtensionNode schemaTypeExtension(Source|string $source, bool[] $options = [])
113
 * @method static ScalarTypeExtensionNode scalarTypeExtension(Source|string $source, bool[] $options = [])
114
 * @method static ObjectTypeExtensionNode objectTypeExtension(Source|string $source, bool[] $options = [])
115
 * @method static InterfaceTypeExtensionNode interfaceTypeExtension(Source|string $source, bool[] $options = [])
116
 * @method static UnionTypeExtensionNode unionTypeExtension(Source|string $source, bool[] $options = [])
117
 * @method static EnumTypeExtensionNode enumTypeExtension(Source|string $source, bool[] $options = [])
118
 * @method static InputObjectTypeExtensionNode inputObjectTypeExtension(Source|string $source, bool[] $options = [])
119
 * @method static DirectiveDefinitionNode directiveDefinition(Source|string $source, bool[] $options = [])
120
 * @method static DirectiveLocation[] directiveLocations(Source|string $source, bool[] $options = [])
121
 * @method static DirectiveLocation directiveLocation(Source|string $source, bool[] $options = [])
122
 */
123
class Parser
124
{
125
    /**
126
     * Given a GraphQL source, parses it into a `GraphQL\Language\AST\DocumentNode`.
127
     * Throws `GraphQL\Error\SyntaxError` if a syntax error is encountered.
128
     *
129
     * Available options:
130
     *
131
     * noLocation: boolean,
132
     *   (By default, the parser creates AST nodes that know the location
133
     *   in the source that they correspond to. This configuration flag
134
     *   disables that behavior for performance or testing.)
135
     *
136
     * allowLegacySDLEmptyFields: boolean
137
     *   If enabled, the parser will parse empty fields sets in the Schema
138
     *   Definition Language. Otherwise, the parser will follow the current
139
     *   specification.
140
     *
141
     *   This option is provided to ease adoption of the final SDL specification
142
     *   and will be removed in a future major release.
143
     *
144
     * allowLegacySDLImplementsInterfaces: boolean
145
     *   If enabled, the parser will parse implemented interfaces with no `&`
146
     *   character between each interface. Otherwise, the parser will follow the
147
     *   current specification.
148
     *
149
     *   This option is provided to ease adoption of the final SDL specification
150
     *   and will be removed in a future major release.
151
     *
152
     * experimentalFragmentVariables: boolean,
153
     *   (If enabled, the parser will understand and parse variable definitions
154
     *   contained in a fragment definition. They'll be represented in the
155
     *   `variableDefinitions` field of the FragmentDefinitionNode.
156
     *
157
     *   The syntax is identical to normal, query-defined variables. For example:
158
     *
159
     *     fragment A($var: Boolean = false) on T  {
160
     *       ...
161
     *     }
162
     *
163
     *   Note: this feature is experimental and may change or be removed in the
164
     *   future.)
165
     *
166
     * @param Source|string $source
167
     * @param bool[]        $options
168
     *
169
     * @return DocumentNode
170
     *
171
     * @throws SyntaxError
172
     *
173
     * @api
174
     */
175 921
    public static function parse($source, array $options = [])
176
    {
177 921
        $parser = new self($source, $options);
178
179 918
        return $parser->parseDocument();
180
    }
181
182
    /**
183
     * Given a string containing a GraphQL value (ex. `[42]`), parse the AST for
184
     * that value.
185
     * Throws `GraphQL\Error\SyntaxError` if a syntax error is encountered.
186
     *
187
     * This is useful within tools that operate upon GraphQL Values directly and
188
     * in isolation of complete GraphQL documents.
189
     *
190
     * Consider providing the results to the utility function: `GraphQL\Utils\AST::valueFromAST()`.
191
     *
192
     * @param Source|string $source
193
     * @param bool[]        $options
194
     *
195
     * @return BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|ListValueNode|ObjectValueNode|StringValueNode|VariableNode
196
     *
197
     * @api
198
     */
199 21
    public static function parseValue($source, array $options = [])
200
    {
201 21
        $parser = new Parser($source, $options);
202 21
        $parser->expect(Token::SOF);
203 21
        $value = $parser->parseValueLiteral(false);
204 21
        $parser->expect(Token::EOF);
205
206 21
        return $value;
207
    }
208
209
    /**
210
     * Given a string containing a GraphQL Type (ex. `[Int!]`), parse the AST for
211
     * that type.
212
     * Throws `GraphQL\Error\SyntaxError` if a syntax error is encountered.
213
     *
214
     * This is useful within tools that operate upon GraphQL Types directly and
215
     * in isolation of complete GraphQL documents.
216
     *
217
     * Consider providing the results to the utility function: `GraphQL\Utils\AST::typeFromAST()`.
218
     *
219
     * @param Source|string $source
220
     * @param bool[]        $options
221
     *
222
     * @return ListTypeNode|NameNode|NonNullTypeNode
223
     *
224
     * @api
225
     */
226 5
    public static function parseType($source, array $options = [])
227
    {
228 5
        $parser = new Parser($source, $options);
229 5
        $parser->expect(Token::SOF);
230 5
        $type = $parser->parseTypeReference();
231 5
        $parser->expect(Token::EOF);
232
233 5
        return $type;
234
    }
235
236
    /**
237
     * Parse partial source by delegating calls to the internal parseX methods.
238
     *
239
     * @param Source|string $name
240
     * @param bool[]        $arguments
241
     *
242
     * @throws SyntaxError
243
     */
244 1
    public static function __callStatic(string $name, array $arguments) : Node
245
    {
246 1
        $parser = new Parser(...$arguments);
0 ignored issues
show
Bug introduced by
$arguments is expanded, but the parameter $source of GraphQL\Language\Parser::__construct() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

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