Parser   F
last analyzed

Complexity

Total Complexity 190

Size/Duplication

Total Lines 1597
Duplicated Lines 0 %

Test Coverage

Coverage 98.81%

Importance

Changes 6
Bugs 0 Features 0
Metric Value
wmc 190
eloc 691
c 6
b 0
f 0
dl 0
loc 1597
ccs 745
cts 754
cp 0.9881
rs 1.909

73 Methods

Rating   Name   Duplication   Size   Complexity  
A parseDirectiveLocation() 0 9 2
A parseUnionTypeExtension() 0 17 3
A parseInputObjectTypeExtension() 0 19 3
A parseDirectiveDefinition() 0 17 1
B parseExecutableDefinition() 0 16 7
A parseEnumValuesDefinition() 0 14 2
A parseObjectTypeExtension() 0 23 4
A parseObjectTypeDefinition() 0 17 1
A __construct() 0 4 2
A parseConstArgument() 0 12 1
A parseType() 0 8 1
A parse() 0 5 1
A parseSchemaTypeExtension() 0 21 4
A loc() 0 7 2
A parseScalarTypeExtension() 0 15 2
A parseValue() 0 8 1
A __callStatic() 0 8 1
A parseEnumTypeExtension() 0 19 3
A parseInputFieldsDefinition() 0 14 2
A parseSelection() 0 5 2
A parseOperationTypeDefinition() 0 11 1
A parseArray() 0 13 2
A parseVariable() 0 8 1
A parseEnumTypeDefinition() 0 15 1
A parseArgument() 0 12 1
A many() 0 10 2
A parseVariableDefinitions() 0 11 2
A parseDescription() 0 7 2
A parseUnionMemberTypes() 0 12 3
A skip() 0 9 2
A parseName() 0 7 1
A expectKeyword() 0 13 3
A parseOperationType() 0 13 4
A parseConstValue() 0 3 1
A parseStringLiteral() 0 9 1
A parseUnionTypeDefinition() 0 15 1
A parseSchemaDefinition() 0 18 1
A parseDocument() 0 13 1
A parseArgumentsDefinition() 0 14 2
A parseDirectives() 0 8 2
A parseFragment() 0 24 4
A parseInputValueDefinition() 0 20 2
B parseTypeExtension() 0 24 9
A parseFieldDefinition() 0 17 1
A parseDirective() 0 9 1
A parseObjectField() 0 11 1
A parseDirectiveLocations() 0 10 2
A expect() 0 14 2
C parseDefinition() 0 31 17
A parseTypeReference() 0 22 3
A unexpected() 0 5 2
A parseFieldsDefinition() 0 26 5
A parseEnumValueDefinition() 0 12 1
A parseArguments() 0 13 3
A any() 0 10 2
A parseInterfaceTypeDefinition() 0 15 1
A parseScalarTypeDefinition() 0 13 1
A parseFragmentDefinition() 0 24 2
A parseInterfaceTypeExtension() 0 19 3
A parseSelectionSet() 0 14 1
A parseVariableValue() 0 3 1
A parseVariableDefinition() 0 15 2
A parseNamedType() 0 7 1
A peek() 0 3 1
C parseValueLiteral() 0 58 13
A parseOperationDefinition() 0 28 3
A parseImplementsInterfaces() 0 16 5
A parseObject() 0 12 2
A parseFragmentName() 0 7 2
A peekDescription() 0 3 2
C parseTypeSystemDefinition() 0 31 12
A parseInputObjectTypeDefinition() 0 15 1
A parseField() 0 20 3

How to fix   Complexity   

Complex Class

Complex classes like Parser often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Parser, and based on these observations, apply Extract Interface, too.

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