GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Parser::parseType()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

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

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