Passed
Pull Request — master (#188)
by Sebastian
02:51
created

ASTBuilder::buildTypeSystemDefinition()   C

Complexity

Conditions 12
Paths 22

Size

Total Lines 29
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 29
rs 5.1612
c 0
b 0
f 0
cc 12
eloc 22
nc 22
nop 1

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Digia\GraphQL\Language;
4
5
use Digia\GraphQL\Error\SyntaxErrorException;
6
use Digia\GraphQL\Language\Node\NodeKindEnum;
7
8
class ASTBuilder implements ASTBuilderInterface
9
{
10
    use LexerUtilsTrait;
11
12
    /**
13
     * @param LexerInterface $lexer
14
     * @return array|null
15
     * @throws SyntaxErrorException
16
     * @throws \ReflectionException
17
     */
18
    public function buildDocument(LexerInterface $lexer): ?array
19
    {
20
        $start = $lexer->getToken();
21
22
        $this->expect($lexer, TokenKindEnum::SOF);
23
24
        $definitions = [];
25
26
        do {
27
            $definitions[] = $this->buildDefinition($lexer);
28
        } while (!$this->skip($lexer, TokenKindEnum::EOF));
29
30
        return [
31
            'kind'        => NodeKindEnum::DOCUMENT,
32
            'definitions' => $definitions,
33
            'loc'         => $this->buildLocation($lexer, $start),
34
        ];
35
    }
36
37
    /**
38
     * @param LexerInterface $lexer
39
     * @param bool           $isConst
40
     * @return array|null
41
     * @throws SyntaxErrorException
42
     */
43
    public function buildValueLiteral(LexerInterface $lexer, bool $isConst = false): ?array
44
    {
45
        return $this->parseValueLiteral($lexer, $isConst);
46
    }
47
48
    /**
49
     * @param LexerInterface $lexer
50
     * @return array|null
51
     * @throws SyntaxErrorException
52
     */
53
    public function buildTypeReference(LexerInterface $lexer): ?array
54
    {
55
        return $this->parseTypeReference($lexer);
56
    }
57
58
    /**
59
     * @param LexerInterface $lexer
60
     * @return array|null
61
     * @throws SyntaxErrorException
62
     */
63
    protected function buildOperationDefinition(LexerInterface $lexer): ?array
64
    {
65
        $start = $lexer->getToken();
66
67
        if ($this->peek($lexer, TokenKindEnum::BRACE_L)) {
68
            return [
69
                'kind'                => NodeKindEnum::OPERATION_DEFINITION,
70
                'operation'           => 'query',
71
                'name'                => null,
72
                'variableDefinitions' => [],
73
                'directives'          => [],
74
                'selectionSet'        => $this->buildSelectionSet($lexer),
75
                'loc'                 => $this->buildLocation($lexer, $start),
76
            ];
77
        }
78
79
        $operation = $this->parseOperationType($lexer);
80
81
        if ($this->peek($lexer, TokenKindEnum::NAME)) {
82
            $name = $this->buildName($lexer);
83
        }
84
85
        return [
86
            'kind'                => NodeKindEnum::OPERATION_DEFINITION,
87
            'operation'           => $operation,
88
            'name'                => $name ?? null,
89
            'variableDefinitions' => $this->buildVariableDefinitions($lexer),
90
            'directives'          => $this->buildDirectives($lexer),
91
            'selectionSet'        => $this->buildSelectionSet($lexer),
92
            'loc'                 => $this->buildLocation($lexer, $start),
93
        ];
94
    }
95
96
    /**
97
     * @param LexerInterface $lexer
98
     * @param bool           $isConst
99
     * @return array
100
     * @throws SyntaxErrorException
101
     */
102
    protected function buildArguments(LexerInterface $lexer, bool $isConst = false): ?array
103
    {
104
        return $this->peek($lexer, TokenKindEnum::PAREN_L)
105
            ? $this->many(
106
                $lexer,
107
                TokenKindEnum::PAREN_L,
108
                [$this, $isConst ? 'buildConstArgument' : 'buildArgument'],
109
                TokenKindEnum::PAREN_R
110
            )
111
            : [];
112
    }
113
114
    /**
115
     * @param LexerInterface $lexer
116
     * @return array
117
     * @throws SyntaxErrorException
118
     */
119
    protected function buildArgument(LexerInterface $lexer): array
120
    {
121
        $start = $lexer->getToken();
122
123
        /**
124
         * @param LexerInterface $lexer
125
         * @return mixed
126
         * @throws SyntaxErrorException
127
         */
128
        $buildValue = function (LexerInterface $lexer) {
129
            $this->expect($lexer, TokenKindEnum::COLON);
130
            return $this->buildValueLiteral($lexer);
131
        };
132
133
        return [
134
            'kind'  => NodeKindEnum::ARGUMENT,
135
            'name'  => $this->buildName($lexer),
136
            'value' => $buildValue($lexer),
137
            'loc'   => $this->buildLocation($lexer, $start),
138
        ];
139
    }
140
141
    /**
142
     * @param LexerInterface $lexer
143
     * @return array
144
     * @throws SyntaxErrorException
145
     */
146
    protected function buildConstArgument(LexerInterface $lexer): array
147
    {
148
        $start = $lexer->getToken();
149
150
        /**
151
         * @param LexerInterface $lexer
152
         * @return mixed
153
         * @throws SyntaxErrorException
154
         */
155
        $buildValue = function (LexerInterface $lexer) {
156
            $this->expect($lexer, TokenKindEnum::COLON);
157
            return $this->buildValueLiteral($lexer);
158
        };
159
160
        return [
161
            'kind'  => NodeKindEnum::ARGUMENT,
162
            'name'  => $this->buildName($lexer),
163
            'value' => $buildValue($lexer),
164
            'loc'   => $this->buildLocation($lexer, $start),
165
        ];
166
    }
167
168
    /**
169
     * @param LexerInterface $lexer
170
     * @return array|null
171
     * @throws SyntaxErrorException
172
     */
173
    protected function buildArgumentsDefinition(LexerInterface $lexer): ?array
174
    {
175
        $buildFunction = function (LexerInterface $lexer): array {
176
            return $this->buildInputValueDefinition($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildInputValueDefinition($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
177
        };
178
179
        return $this->peek($lexer, TokenKindEnum::PAREN_L)
180
            ? $this->many(
181
                $lexer,
182
                TokenKindEnum::PAREN_L,
183
                $buildFunction,
184
                TokenKindEnum::PAREN_R
185
            )
186
            : [];
187
    }
188
189
    /**
190
     * @inheritdoc
191
     */
192
    public function buildDescription(LexerInterface $lexer): ?array
193
    {
194
        return $this->peekDescription($lexer)
195
            ? $this->buildStringLiteral($lexer)
196
            : null;
197
    }
198
199
    /**
200
     * @param LexerInterface $lexer
201
     * @return array|null
202
     * @throws SyntaxErrorException
203
     * @throws \ReflectionException
204
     */
205
    protected function buildDirectiveDefinition(LexerInterface $lexer): ?array
206
    {
207
        $start = $lexer->getToken();
208
209
        $description = $this->buildDescription($lexer);
210
211
        $this->expectKeyword($lexer, KeywordEnum::DIRECTIVE);
212
        $this->expect($lexer, TokenKindEnum::AT);
213
214
        $name      = $this->buildName($lexer);
215
        $arguments = $this->buildArgumentsDefinition($lexer);
216
217
        $this->expectKeyword($lexer, KeywordEnum::ON);
218
219
        $locations = $this->buildDirectiveLocations($lexer);
220
221
        return [
222
            'kind'        => NodeKindEnum::DIRECTIVE_DEFINITION,
223
            'description' => $description,
224
            'name'        => $name,
225
            'arguments'   => $arguments,
226
            'locations'   => $locations,
227
            'loc'         => $this->buildLocation($lexer, $start),
228
        ];
229
    }
230
231
    /**
232
     * @param LexerInterface $lexer
233
     * @return array
234
     * @throws SyntaxErrorException
235
     * @throws \ReflectionException
236
     */
237
    protected function buildDirectiveLocations(LexerInterface $lexer): array
238
    {
239
        $this->skip($lexer, TokenKindEnum::PIPE);
240
241
        $locations = [];
242
243
        do {
244
            $locations[] = $this->buildDirectiveLocation($lexer);
245
        } while ($this->skip($lexer, TokenKindEnum::PIPE));
246
247
        return $locations;
248
    }
249
250
    /**
251
     * @param LexerInterface $lexer
252
     * @return array
253
     * @throws SyntaxErrorException
254
     * @throws \ReflectionException
255
     */
256
    protected function buildDirectiveLocation(LexerInterface $lexer): array
257
    {
258
        $start = $lexer->getToken();
259
260
        $name = $this->buildName($lexer);
261
262
        if (\in_array($name['value'], DirectiveLocationEnum::values(), true)) {
263
            return $name;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $name could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
264
        }
265
266
        throw $this->unexpected($lexer, $start);
267
    }
268
269
    /**
270
     * @param LexerInterface $lexer
271
     * @param bool           $isConst
272
     * @return array|null
273
     * @throws SyntaxErrorException
274
     */
275
    protected function buildDirectives(LexerInterface $lexer, bool $isConst = false): ?array
276
    {
277
        $directives = [];
278
279
        while ($this->peek($lexer, TokenKindEnum::AT)) {
280
            $directives[] = $this->buildDirective($lexer, $isConst);
281
        }
282
283
        return $directives;
284
    }
285
286
    /**
287
     * @param LexerInterface $lexer
288
     * @param bool           $isConst
289
     * @return array
290
     * @throws SyntaxErrorException
291
     */
292
    protected function buildDirective(LexerInterface $lexer, bool $isConst): array
293
    {
294
        $start = $lexer->getToken();
295
296
        $this->expect($lexer, TokenKindEnum::AT);
297
298
        return [
299
            'kind'      => NodeKindEnum::DIRECTIVE,
300
            'name'      => $this->buildName($lexer),
301
            'arguments' => $this->buildArguments($lexer, $isConst),
302
            'loc'       => $this->buildLocation($lexer, $start),
303
        ];
304
    }
305
306
    /**
307
     * @param LexerInterface $lexer
308
     * @return array
309
     * @throws SyntaxErrorException
310
     * @throws \ReflectionException
311
     */
312
    protected function buildDefinition(LexerInterface $lexer): array
313
    {
314
        if ($this->peek($lexer, TokenKindEnum::NAME)) {
315
            switch ($lexer->getTokenValue()) {
316
                case KeywordEnum::QUERY:
317
                case KeywordEnum::MUTATION:
318
                case KeywordEnum::SUBSCRIPTION:
319
                case KeywordEnum::FRAGMENT:
320
                    return $this->buildExecutableDefinition($lexer);
321
                case KeywordEnum::SCHEMA:
322
                case KeywordEnum::SCALAR:
323
                case KeywordEnum::TYPE:
324
                case KeywordEnum::INTERFACE:
325
                case KeywordEnum::UNION:
326
                case KeywordEnum::ENUM:
327
                case KeywordEnum::INPUT:
328
                case KeywordEnum::EXTEND:
329
                case KeywordEnum::DIRECTIVE:
330
                    // Note: The schema definition language is an experimental addition.
331
                    return $this->buildTypeSystemDefinition($lexer);
332
            }
333
        } elseif ($this->peek($lexer, TokenKindEnum::BRACE_L)) {
334
            return $this->buildExecutableDefinition($lexer);
335
        } elseif ($this->peekDescription($lexer)) {
336
            // Note: The schema definition language is an experimental addition.
337
            return $this->buildTypeSystemDefinition($lexer);
338
        }
339
340
        throw $this->unexpected($lexer);
341
    }
342
343
    /**
344
     * @param LexerInterface $lexer
345
     * @return array
346
     * @throws SyntaxErrorException
347
     */
348
    protected function buildExecutableDefinition(LexerInterface $lexer): array
349
    {
350
        if ($this->peek($lexer, TokenKindEnum::NAME)) {
351
            // valid names are: query, mutation, subscription and fragment
352
            switch ($lexer->getToken()->getValue()) {
353
                case KeywordEnum::QUERY:
354
                case KeywordEnum::MUTATION:
355
                case KeywordEnum::SUBSCRIPTION:
356
                    return $this->buildOperationDefinition($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildOperationDefinition($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
357
                case KeywordEnum::FRAGMENT:
358
                    return $this->buildFragmentDefinition($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildFragmentDefinition($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
359
            }
360
        } elseif ($this->peek($lexer, TokenKindEnum::BRACE_L)) {
361
            // Anonymous query
362
            return $this->buildOperationDefinition($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildOperationDefinition($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
363
        }
364
365
        throw $this->unexpected($lexer);
366
    }
367
368
    /**
369
     * @param LexerInterface $lexer
370
     * @return array
371
     * @throws SyntaxErrorException
372
     * @throws \ReflectionException
373
     */
374
    protected function buildTypeSystemDefinition(LexerInterface $lexer): array
375
    {
376
        // Many definitions begin with a description and require a lookahead.
377
        $keywordToken = $this->peekDescription($lexer) ? $lexer->lookahead() : $lexer->getToken();
378
379
        if ($keywordToken->getKind() === TokenKindEnum::NAME) {
380
            switch ($keywordToken->getValue()) {
381
                case KeywordEnum::SCHEMA:
382
                    return $this->buildSchemaDefinition($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildSchemaDefinition($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
383
                case KeywordEnum::SCALAR:
384
                    return $this->buildScalarTypeDefinition($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildScalarTypeDefinition($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
385
                case KeywordEnum::TYPE:
386
                    return $this->buildObjectTypeDefinition($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildObjectTypeDefinition($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
387
                case KeywordEnum::INTERFACE:
388
                    return $this->buildInterfaceTypeDefinition($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildInter...eTypeDefinition($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
389
                case KeywordEnum::UNION:
390
                    return $this->buildUnionTypeDefinition($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildUnionTypeDefinition($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
391
                case KeywordEnum::ENUM:
392
                    return $this->buildEnumTypeDefinition($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildEnumTypeDefinition($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
393
                case KeywordEnum::INPUT:
394
                    return $this->buildInputObjectTypeDefinition($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildInput...tTypeDefinition($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
395
                case KeywordEnum::DIRECTIVE:
396
                    return $this->buildDirectiveDefinition($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildDirectiveDefinition($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
397
                case KeywordEnum::EXTEND:
398
                    return $this->buildTypeSystemExtension($lexer);
399
            }
400
        }
401
402
        throw $this->unexpected($lexer, $keywordToken);
403
    }
404
405
    /**
406
     * @param LexerInterface $lexer
407
     * @return array
408
     * @throws SyntaxErrorException
409
     */
410
    protected function buildTypeSystemExtension(LexerInterface $lexer): array
411
    {
412
        $keywordToken = $lexer->lookahead();
413
414
        if ($keywordToken->getKind() === TokenKindEnum::NAME) {
415
            switch ($keywordToken->getValue()) {
416
                case KeywordEnum::SCALAR:
417
                    return $this->buildScalarTypeExtension($lexer, false);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildScala...xtension($lexer, false) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
418
                case KeywordEnum::TYPE:
419
                    return $this->buildObjectTypeExtension($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildObjectTypeExtension($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
420
                case KeywordEnum::INTERFACE:
421
                    return $this->buildInterfaceTypeExtension($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildInterfaceTypeExtension($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
422
                case KeywordEnum::UNION:
423
                    return $this->buildUnionTypeExtension($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildUnionTypeExtension($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
424
                case KeywordEnum::ENUM:
425
                    return $this->buildEnumTypeExtension($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildEnumTypeExtension($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
426
                case KeywordEnum::INPUT:
427
                    return $this->buildInputObjectTypeExtension($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildInput...ctTypeExtension($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
428
            }
429
        }
430
431
        throw $this->unexpected($lexer, $keywordToken);
432
    }
433
434
    /**
435
     * @param LexerInterface $lexer
436
     * @return array|null
437
     * @throws SyntaxErrorException
438
     */
439
    protected function buildEnumTypeDefinition(LexerInterface $lexer): ?array
440
    {
441
        $start = $lexer->getToken();
442
443
        $description = $this->buildDescription($lexer);
444
445
        $this->expectKeyword($lexer, KeywordEnum::ENUM);
446
447
        return [
448
            'kind'        => NodeKindEnum::ENUM_TYPE_DEFINITION,
449
            'description' => $description,
450
            'name'        => $this->buildName($lexer),
451
            'directives'  => $this->buildDirectives($lexer),
452
            'values'      => $this->buildEnumValuesDefinition($lexer),
453
            'loc'         => $this->buildLocation($lexer, $start),
454
        ];
455
    }
456
457
    /**
458
     * @param LexerInterface $lexer
459
     * @return array|null
460
     * @throws SyntaxErrorException
461
     */
462
    protected function buildEnumTypeExtension(LexerInterface $lexer): ?array
463
    {
464
        $start = $lexer->getToken();
465
466
        $this->expectKeyword($lexer, KeywordEnum::EXTEND);
467
        $this->expectKeyword($lexer, KeywordEnum::ENUM);
468
469
        $name       = $this->buildName($lexer);
470
        $directives = $this->buildDirectives($lexer);
471
        $values     = $this->buildEnumValuesDefinition($lexer);
472
473
        if (empty($directives) && empty($values)) {
474
            throw $this->unexpected($lexer);
475
        }
476
477
        return [
478
            'kind'       => NodeKindEnum::ENUM_TYPE_EXTENSION,
479
            'name'       => $name,
480
            'directives' => $directives,
481
            'values'     => $values,
482
            'loc'        => $this->buildLocation($lexer, $start),
483
        ];
484
    }
485
486
    /**
487
     * @param LexerInterface $lexer
488
     * @return array|null
489
     * @throws SyntaxErrorException
490
     */
491
    protected function buildEnumValuesDefinition(LexerInterface $lexer): ?array
492
    {
493
        return $this->peek($lexer, TokenKindEnum::BRACE_L)
494
            ? $this->many(
495
                $lexer,
496
                TokenKindEnum::BRACE_L,
497
                [$this, 'buildEnumValueDefinition'],
498
                TokenKindEnum::BRACE_R
499
            )
500
            : [];
501
    }
502
503
    /**
504
     * @param LexerInterface $lexer
505
     * @return array
506
     * @throws SyntaxErrorException
507
     */
508
    protected function buildEnumValueDefinition(LexerInterface $lexer): array
509
    {
510
        $start = $lexer->getToken();
511
512
        return [
513
            'kind'        => NodeKindEnum::ENUM_VALUE_DEFINITION,
514
            'description' => $this->buildDescription($lexer),
515
            'name'        => $this->buildName($lexer),
516
            'directives'  => $this->buildDirectives($lexer),
517
            'loc'         => $this->buildLocation($lexer, $start),
518
        ];
519
    }
520
521
    /**
522
     * @param LexerInterface $lexer
523
     * @return array|null
524
     * @throws SyntaxErrorException
525
     */
526
    protected function buildField(LexerInterface $lexer): ?array
527
    {
528
        $start = $lexer->getToken();
529
530
        $nameOrAlias = $this->buildName($lexer);
531
532
        if ($this->skip($lexer, TokenKindEnum::COLON)) {
533
            $alias = $nameOrAlias;
534
            $name  = $this->buildName($lexer);
535
        } else {
536
            $name = $nameOrAlias;
537
        }
538
539
        return [
540
            'kind'         => NodeKindEnum::FIELD,
541
            'alias'        => $alias ?? null,
542
            'name'         => $name,
543
            'arguments'    => $this->buildArguments($lexer, false),
544
            'directives'   => $this->buildDirectives($lexer),
545
            'selectionSet' => $this->peek($lexer, TokenKindEnum::BRACE_L)
546
                ? $this->buildSelectionSet($lexer)
547
                : null,
548
            'loc'          => $this->buildLocation($lexer, $start),
549
        ];
550
    }
551
552
    /**
553
     * @param LexerInterface $lexer
554
     * @return array|null
555
     * @throws SyntaxErrorException
556
     */
557
    protected function buildFieldsDefinition(LexerInterface $lexer): ?array
558
    {
559
        return $this->peek($lexer, TokenKindEnum::BRACE_L)
560
            ? $this->many(
561
                $lexer,
562
                TokenKindEnum::BRACE_L,
563
                [$this, 'buildFieldDefinition'],
564
                TokenKindEnum::BRACE_R
565
            )
566
            : [];
567
    }
568
569
    /**
570
     * @param LexerInterface $lexer
571
     * @return array
572
     * @throws SyntaxErrorException
573
     */
574
    protected function buildFieldDefinition(LexerInterface $lexer): array
575
    {
576
        $start = $lexer->getToken();
577
578
        $description = $this->buildDescription($lexer);
579
        $name        = $this->buildName($lexer);
580
        $arguments   = $this->buildArgumentsDefinition($lexer);
581
582
        $this->expect($lexer, TokenKindEnum::COLON);
583
584
        return [
585
            'kind'        => NodeKindEnum::FIELD_DEFINITION,
586
            'description' => $description,
587
            'name'        => $name,
588
            'arguments'   => $arguments,
589
            'type'        => $this->buildTypeReference($lexer),
590
            'directives'  => $this->buildDirectives($lexer),
591
            'loc'         => $this->buildLocation($lexer, $start),
592
        ];
593
    }
594
595
    /**
596
     * @param LexerInterface $lexer
597
     * @return array|null
598
     * @throws SyntaxErrorException
599
     */
600
    protected function buildFragment(LexerInterface $lexer): ?array
601
    {
602
        $start = $lexer->getToken();
603
604
        $this->expect($lexer, TokenKindEnum::SPREAD);
605
606
        $tokenValue = $lexer->getTokenValue();
607
608
        if ($tokenValue !== 'on' && $this->peek($lexer, TokenKindEnum::NAME)) {
609
            return [
610
                'kind'       => NodeKindEnum::FRAGMENT_SPREAD,
611
                'name'       => $this->buildFragmentName($lexer),
612
                'directives' => $this->buildDirectives($lexer),
613
                'loc'        => $this->buildLocation($lexer, $start),
614
            ];
615
        }
616
617
        if ($tokenValue === 'on') {
618
            $lexer->advance();
619
620
            $typeCondition = $this->buildNamedType($lexer);
621
        }
622
623
        return [
624
            'kind'          => NodeKindEnum::INLINE_FRAGMENT,
625
            'typeCondition' => $typeCondition ?? null,
626
            'directives'    => $this->buildDirectives($lexer),
627
            'selectionSet'  => $this->buildSelectionSet($lexer),
628
            'loc'           => $this->buildLocation($lexer, $start),
629
        ];
630
    }
631
632
    /**
633
     * @param LexerInterface $lexer
634
     * @return array|null
635
     * @throws SyntaxErrorException
636
     */
637
    protected function buildFragmentDefinition(LexerInterface $lexer): ?array
638
    {
639
        $start = $lexer->getToken();
640
641
        $this->expectKeyword($lexer, KeywordEnum::FRAGMENT);
642
643
        $buildTypeCondition = function (LexerInterface $lexer) {
644
            $this->expectKeyword($lexer, 'on');
645
            return $this->buildNamedType($lexer);
646
        };
647
648
        return [
649
            'kind'                => NodeKindEnum::FRAGMENT_DEFINITION,
650
            'name'                => $this->buildFragmentName($lexer),
651
            'variableDefinitions' => $this->buildVariableDefinitions($lexer),
652
            'typeCondition'       => $buildTypeCondition($lexer),
653
            'directives'          => $this->buildDirectives($lexer),
654
            'selectionSet'        => $this->buildSelectionSet($lexer),
655
            'loc'                 => $this->buildLocation($lexer, $start),
656
        ];
657
    }
658
659
    /**
660
     * @param LexerInterface $lexer
661
     * @return array
662
     * @throws SyntaxErrorException
663
     */
664
    protected function buildFragmentName(LexerInterface $lexer): array
665
    {
666
        if ($lexer->getTokenValue() === 'on') {
667
            throw $this->unexpected($lexer);
668
        }
669
670
        return $this->buildName($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildName($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
671
    }
672
673
    /**
674
     * @param LexerInterface $lexer
675
     * @return array|null
676
     * @throws SyntaxErrorException
677
     */
678
    protected function buildImplementsInterfaces(LexerInterface $lexer): ?array
679
    {
680
        $types = [];
681
682
        if ($lexer->getTokenValue() === 'implements') {
683
            $lexer->advance();
684
685
            // Optional leading ampersand
686
            $this->skip($lexer, TokenKindEnum::AMP);
687
688
            do {
689
                $types[] = $this->buildNamedType($lexer);
690
            } while ($this->skip($lexer, TokenKindEnum::AMP));
691
        }
692
693
        return $types;
694
    }
695
696
    /**
697
     * @param LexerInterface $lexer
698
     * @return array|null
699
     * @throws SyntaxErrorException
700
     */
701
    protected function buildInputFieldsDefinition(LexerInterface $lexer): ?array
702
    {
703
        $buildFunction = function (LexerInterface $lexer): array {
704
            return $this->buildInputValueDefinition($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildInputValueDefinition($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
705
        };
706
707
        return $this->peek($lexer, TokenKindEnum::BRACE_L)
708
            ? $this->many(
709
                $lexer,
710
                TokenKindEnum::BRACE_L,
711
                $buildFunction,
712
                TokenKindEnum::BRACE_R
713
            )
714
            : [];
715
    }
716
717
    /**
718
     * @param LexerInterface $lexer
719
     * @return array|null
720
     * @throws SyntaxErrorException
721
     */
722
    protected function buildInputObjectTypeDefinition(LexerInterface $lexer): ?array
723
    {
724
        $start = $lexer->getToken();
725
726
        $description = $this->buildDescription($lexer);
727
728
        $this->expectKeyword($lexer, KeywordEnum::INPUT);
729
730
        return [
731
            'kind'        => NodeKindEnum::INPUT_OBJECT_TYPE_DEFINITION,
732
            'description' => $description,
733
            'name'        => $this->buildName($lexer),
734
            'directives'  => $this->buildDirectives($lexer, true),
735
            'fields'      => $this->buildInputFieldsDefinition($lexer),
736
            'loc'         => $this->buildLocation($lexer, $start),
737
        ];
738
    }
739
740
    /**
741
     * @param LexerInterface $lexer
742
     * @return array|null
743
     * @throws SyntaxErrorException
744
     */
745
    protected function buildInputObjectTypeExtension(LexerInterface $lexer): ?array
746
    {
747
        $start = $lexer->getToken();
748
749
        $this->expectKeyword($lexer, KeywordEnum::EXTEND);
750
        $this->expectKeyword($lexer, KeywordEnum::INPUT);
751
752
        $name       = $this->buildName($lexer);
753
        $directives = $this->buildDirectives($lexer, true);
754
        $fields     = $this->buildInputFieldsDefinition($lexer);
755
756
        if (empty($directives) && empty($fields)) {
757
            throw $this->unexpected($lexer);
758
        }
759
760
        return [
761
            'kind'       => NodeKindEnum::INPUT_OBJECT_TYPE_EXTENSION,
762
            'name'       => $name,
763
            'directives' => $directives,
764
            'fields'     => $fields,
765
            'loc'        => $this->buildLocation($lexer, $start),
766
        ];
767
    }
768
769
    /**
770
     * @param LexerInterface $lexer
771
     * @return array|null
772
     * @throws SyntaxErrorException
773
     */
774
    protected function buildInputValueDefinition(LexerInterface $lexer): ?array
775
    {
776
        $start = $lexer->getToken();
777
778
        $description = $this->buildDescription($lexer);
779
        $name        = $this->buildName($lexer);
780
781
        $this->expect($lexer, TokenKindEnum::COLON);
782
783
        return [
784
            'kind'         => NodeKindEnum::INPUT_VALUE_DEFINITION,
785
            'description'  => $description,
786
            'name'         => $name,
787
            'type'         => $this->buildTypeReference($lexer),
788
            'defaultValue' => $this->skip($lexer, TokenKindEnum::EQUALS)
789
                ? $this->buildValueLiteral($lexer, true)
790
                : null,
791
            'directives'   => $this->buildDirectives($lexer, true),
792
            'loc'          => $this->buildLocation($lexer, $start),
793
        ];
794
    }
795
796
    /**
797
     * @param LexerInterface $lexer
798
     * @return array|null
799
     * @throws SyntaxErrorException
800
     */
801
    protected function buildInterfaceTypeDefinition(LexerInterface $lexer): ?array
802
    {
803
        $start = $lexer->getToken();
804
805
        $description = $this->buildDescription($lexer);
806
807
        $this->expectKeyword($lexer, KeywordEnum::INTERFACE);
808
809
        return [
810
            'kind'        => NodeKindEnum::INTERFACE_TYPE_DEFINITION,
811
            'description' => $description,
812
            'name'        => $this->buildName($lexer),
813
            'directives'  => $this->buildDirectives($lexer),
814
            'fields'      => $this->buildFieldsDefinition($lexer),
815
            'loc'         => $this->buildLocation($lexer, $start),
816
        ];
817
    }
818
819
    /**
820
     * @param LexerInterface $lexer
821
     * @return array|null
822
     * @throws SyntaxErrorException
823
     */
824
    protected function buildInterfaceTypeExtension(LexerInterface $lexer): ?array
825
    {
826
        $start = $lexer->getToken();
827
828
        $this->expectKeyword($lexer, KeywordEnum::EXTEND);
829
        $this->expectKeyword($lexer, KeywordEnum::INTERFACE);
830
831
        $name       = $this->buildName($lexer);
832
        $directives = $this->buildDirectives($lexer);
833
        $fields     = $this->buildFieldsDefinition($lexer);
834
835
        if (empty($directives) && empty($fields)) {
836
            throw $this->unexpected($lexer);
837
        }
838
839
        return [
840
            'kind'       => NodeKindEnum::INTERFACE_TYPE_EXTENSION,
841
            'name'       => $name,
842
            'directives' => $directives,
843
            'fields'     => $fields,
844
            'loc'        => $this->buildLocation($lexer, $start),
845
        ];
846
    }
847
848
    /**
849
     * @param LexerInterface $lexer
850
     * @return array|null
851
     * @throws SyntaxErrorException
852
     */
853
    protected function buildName(LexerInterface $lexer): ?array
854
    {
855
        $token = $this->expect($lexer, TokenKindEnum::NAME);
856
857
        return [
858
            'kind'  => NodeKindEnum::NAME,
859
            'value' => $token->getValue(),
860
            'loc'   => $this->buildLocation($lexer, $token),
861
        ];
862
    }
863
864
    /**
865
     * @param LexerInterface $lexer
866
     * @return array|null
867
     * @throws SyntaxErrorException
868
     */
869
    protected function buildNamedType(LexerInterface $lexer): ?array
870
    {
871
        $start = $lexer->getToken();
872
873
        return [
874
            'kind' => NodeKindEnum::NAMED_TYPE,
875
            'name' => $this->buildName($lexer),
876
            'loc'  => $this->buildLocation($lexer, $start),
877
        ];
878
    }
879
880
    /**
881
     * @param LexerInterface $lexer
882
     * @return array|null
883
     * @throws SyntaxErrorException
884
     */
885
    protected function buildObjectTypeDefinition(LexerInterface $lexer): ?array
886
    {
887
        $start = $lexer->getToken();
888
889
        $description = $this->buildDescription($lexer);
890
891
        $this->expectKeyword($lexer, KeywordEnum::TYPE);
892
893
        return [
894
            'kind'        => NodeKindEnum::OBJECT_TYPE_DEFINITION,
895
            'description' => $description,
896
            'name'        => $this->buildName($lexer),
897
            'interfaces'  => $this->buildImplementsInterfaces($lexer),
898
            'directives'  => $this->buildDirectives($lexer),
899
            'fields'      => $this->buildFieldsDefinition($lexer),
900
            'loc'         => $this->buildLocation($lexer, $start),
901
        ];
902
    }
903
904
    /**
905
     * @param LexerInterface $lexer
906
     * @return array|null
907
     * @throws SyntaxErrorException
908
     */
909
    protected function buildObjectTypeExtension(LexerInterface $lexer): ?array
910
    {
911
        $start = $lexer->getToken();
912
913
        $this->expectKeyword($lexer, KeywordEnum::EXTEND);
914
        $this->expectKeyword($lexer, KeywordEnum::TYPE);
915
916
        $name       = $this->buildName($lexer);
917
        $interfaces = $this->buildImplementsInterfaces($lexer);
918
        $directives = $this->buildDirectives($lexer);
919
        $fields     = $this->buildFieldsDefinition($lexer);
920
921
        if (empty($interfaces) && empty($directives) && empty($fields)) {
922
            throw $this->unexpected($lexer);
923
        }
924
925
        return [
926
            'kind'       => NodeKindEnum::OBJECT_TYPE_EXTENSION,
927
            'name'       => $name,
928
            'interfaces' => $interfaces,
929
            'directives' => $directives,
930
            'fields'     => $fields,
931
            'loc'        => $this->buildLocation($lexer, $start),
932
        ];
933
    }
934
935
    /**
936
     * @param LexerInterface $lexer
937
     * @return array|null
938
     * @throws SyntaxErrorException
939
     */
940
    protected function buildSchemaDefinition(LexerInterface $lexer): ?array
941
    {
942
        $start = $lexer->getToken();
943
944
        $this->expectKeyword($lexer, KeywordEnum::SCHEMA);
945
946
        return [
947
            'kind'           => NodeKindEnum::SCHEMA_DEFINITION,
948
            'directives'     => $this->buildDirectives($lexer),
949
            'operationTypes' => $this->many(
950
                $lexer,
951
                TokenKindEnum::BRACE_L,
952
                [$this, 'buildOperationTypeDefinition'],
953
                TokenKindEnum::BRACE_R
954
            ),
955
            'loc'            => $this->buildLocation($lexer, $start),
956
        ];
957
    }
958
959
    /**
960
     * @param LexerInterface $lexer
961
     * @return array
962
     * @throws SyntaxErrorException
963
     */
964
    protected function buildOperationTypeDefinition(LexerInterface $lexer): array
965
    {
966
        $start = $lexer->getToken();
967
968
        $operation = $this->parseOperationType($lexer);
969
970
        $this->expect($lexer, TokenKindEnum::COLON);
971
972
        return [
973
            'kind'      => NodeKindEnum::OPERATION_TYPE_DEFINITION,
974
            'operation' => $operation,
975
            'type'      => $this->buildNamedType($lexer),
976
            'loc'       => $this->buildLocation($lexer, $start),
977
        ];
978
    }
979
980
    /**
981
     * @param LexerInterface $lexer
982
     * @return string
983
     * @throws SyntaxErrorException
984
     */
985
    protected function parseOperationType(LexerInterface $lexer): string
986
    {
987
        $token = $this->expect($lexer, TokenKindEnum::NAME);
988
        $value = $token->getValue();
989
990
        if ($this->isOperation($value)) {
991
            return $value;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $value could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
992
        }
993
994
        throw $this->unexpected($lexer, $token);
995
    }
996
997
    /**
998
     * @param string $value
999
     * @return bool
1000
     */
1001
    protected function isOperation(string $value): bool
1002
    {
1003
        return \in_array($value, ['query', 'mutation', 'subscription'], true);
1004
    }
1005
1006
    /**
1007
     * @param LexerInterface $lexer
1008
     * @return array|null
1009
     * @throws SyntaxErrorException
1010
     */
1011
    protected function buildSelectionSet(LexerInterface $lexer): ?array
1012
    {
1013
        $start = $lexer->getToken();
1014
1015
        return [
1016
            'kind'       => NodeKindEnum::SELECTION_SET,
1017
            'selections' => $this->many(
1018
                $lexer,
1019
                TokenKindEnum::BRACE_L,
1020
                [$this, 'buildSelection'],
1021
                TokenKindEnum::BRACE_R
1022
            ),
1023
            'loc'        => $this->buildLocation($lexer, $start),
1024
        ];
1025
    }
1026
1027
    /**
1028
     * @param LexerInterface $lexer
1029
     * @return array
1030
     * @throws SyntaxErrorException
1031
     */
1032
    protected function buildSelection(LexerInterface $lexer): array
1033
    {
1034
        return $this->peek($lexer, TokenKindEnum::SPREAD)
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->peek($lexe...his->buildField($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
1035
            ? $this->buildFragment($lexer)
1036
            : $this->buildField($lexer);
1037
    }
1038
1039
    /**
1040
     * @param LexerInterface $lexer
1041
     * @return array|null
1042
     * @throws SyntaxErrorException
1043
     */
1044
    protected function buildScalarTypeDefinition(LexerInterface $lexer): ?array
1045
    {
1046
        $start = $lexer->getToken();
1047
1048
        $description = $this->buildDescription($lexer);
1049
1050
        $this->expectKeyword($lexer, KeywordEnum::SCALAR);
1051
1052
        return [
1053
            'kind'        => NodeKindEnum::SCALAR_TYPE_DEFINITION,
1054
            'description' => $description,
1055
            'name'        => $this->buildName($lexer),
1056
            'directives'  => $this->buildDirectives($lexer),
1057
            'loc'         => $this->buildLocation($lexer, $start),
1058
        ];
1059
    }
1060
1061
    /**
1062
     * @param LexerInterface $lexer
1063
     * @param bool           $isConst
1064
     * @return array|null
1065
     * @throws SyntaxErrorException
1066
     */
1067
    protected function buildScalarTypeExtension(LexerInterface $lexer, bool $isConst = false): ?array
1068
    {
1069
        $start = $lexer->getToken();
1070
1071
        $this->expectKeyword($lexer, KeywordEnum::EXTEND);
1072
        $this->expectKeyword($lexer, KeywordEnum::SCALAR);
1073
1074
        $name       = $this->buildName($lexer);
1075
        $directives = $this->buildDirectives($lexer, $isConst);
1076
1077
        if (empty($directives)) {
1078
            throw $this->unexpected($lexer);
1079
        }
1080
1081
        return [
1082
            'kind'       => NodeKindEnum::SCALAR_TYPE_EXTENSION,
1083
            'name'       => $name,
1084
            'directives' => $directives,
1085
            'loc'        => $this->buildLocation($lexer, $start),
1086
        ];
1087
    }
1088
1089
    protected function buildStringLiteral(LexerInterface $lexer): ?array
1090
    {
1091
        $token = $lexer->getToken();
1092
1093
        $lexer->advance();
1094
1095
        return [
1096
            'kind'  => NodeKindEnum::STRING,
1097
            'value' => $token->getValue(),
1098
            'block' => $token->getKind() === TokenKindEnum::BLOCK_STRING,
1099
            'loc'   => $this->buildLocation($lexer, $token),
1100
        ];
1101
    }
1102
1103
    /**
1104
     * @param LexerInterface $lexer
1105
     * @return array
1106
     * @throws SyntaxErrorException
1107
     */
1108
    protected function parseTypeReference(LexerInterface $lexer): array
1109
    {
1110
        $start = $lexer->getToken();
1111
1112
        if ($this->skip($lexer, TokenKindEnum::BRACKET_L)) {
1113
            $type = $this->buildTypeReference($lexer);
1114
1115
            $this->expect($lexer, TokenKindEnum::BRACKET_R);
1116
1117
            $type = [
1118
                'kind' => NodeKindEnum::LIST_TYPE,
1119
                'type' => $type,
1120
                'loc'  => $this->buildLocation($lexer, $start),
1121
            ];
1122
        } else {
1123
            $type = $this->buildNamedType($lexer);
1124
        }
1125
1126
        if ($this->skip($lexer, TokenKindEnum::BANG)) {
1127
            return [
1128
                'kind' => NodeKindEnum::NON_NULL_TYPE,
1129
                'type' => $type,
1130
                'loc'  => $this->buildLocation($lexer, $start),
1131
            ];
1132
        }
1133
1134
        return $type;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $type could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
1135
    }
1136
1137
    /**
1138
     * @param LexerInterface $lexer
1139
     * @return array|null
1140
     * @throws SyntaxErrorException
1141
     */
1142
    protected function buildUnionTypeDefinition(LexerInterface $lexer): ?array
1143
    {
1144
        $start = $lexer->getToken();
1145
1146
        $description = $this->buildDescription($lexer);
1147
1148
        $this->expectKeyword($lexer, KeywordEnum::UNION);
1149
1150
        return [
1151
            'kind'        => NodeKindEnum::UNION_TYPE_DEFINITION,
1152
            'description' => $description,
1153
            'name'        => $this->buildName($lexer),
1154
            'directives'  => $this->buildDirectives($lexer),
1155
            'types'       => $this->buildUnionMemberTypes($lexer),
1156
            'loc'         => $this->buildLocation($lexer, $start),
1157
        ];
1158
    }
1159
1160
    /**
1161
     * @param LexerInterface $lexer
1162
     * @return array|null
1163
     * @throws SyntaxErrorException
1164
     */
1165
    protected function buildUnionTypeExtension(LexerInterface $lexer): ?array
1166
    {
1167
        $start = $lexer->getToken();
1168
1169
        $this->expectKeyword($lexer, KeywordEnum::EXTEND);
1170
        $this->expectKeyword($lexer, KeywordEnum::UNION);
1171
1172
        $name       = $this->buildName($lexer);
1173
        $directives = $this->buildDirectives($lexer);
1174
        $types      = $this->buildUnionMemberTypes($lexer);
1175
1176
        if (empty($directives) && empty($types)) {
1177
            throw $this->unexpected($lexer);
1178
        }
1179
1180
        return [
1181
            'kind'       => NodeKindEnum::UNION_TYPE_EXTENSION,
1182
            'name'       => $name,
1183
            'directives' => $directives,
1184
            'types'      => $types,
1185
            'loc'        => $this->buildLocation($lexer, $start),
1186
        ];
1187
    }
1188
1189
    /**
1190
     * @param LexerInterface $lexer
1191
     * @return array|null
1192
     * @throws SyntaxErrorException
1193
     */
1194
    protected function buildUnionMemberTypes(LexerInterface $lexer): ?array
1195
    {
1196
        $types = [];
1197
1198
        if ($this->skip($lexer, TokenKindEnum::EQUALS)) {
1199
            // Optional leading pipe
1200
            $this->skip($lexer, TokenKindEnum::PIPE);
1201
1202
            do {
1203
                $types[] = $this->buildNamedType($lexer);
1204
            } while ($this->skip($lexer, TokenKindEnum::PIPE));
1205
        }
1206
1207
        return $types;
1208
    }
1209
1210
    /**
1211
     * @param LexerInterface $lexer
1212
     * @param bool           $isConst
1213
     * @return array
1214
     * @throws SyntaxErrorException
1215
     */
1216
    protected function parseValueLiteral(LexerInterface $lexer, bool $isConst): array
1217
    {
1218
        $token = $lexer->getToken();
1219
1220
        switch ($token->getKind()) {
1221
            case TokenKindEnum::BRACKET_L:
1222
                return $this->buildList($lexer, $isConst);
1223
            case TokenKindEnum::BRACE_L:
1224
                return $this->buildObject($lexer, $isConst);
1225
            case TokenKindEnum::INT:
1226
                $lexer->advance();
1227
1228
                return [
1229
                    'kind'  => NodeKindEnum::INT,
1230
                    'value' => $token->getValue(),
1231
                    'loc'   => $this->buildLocation($lexer, $token),
1232
                ];
1233
            case TokenKindEnum::FLOAT:
1234
                $lexer->advance();
1235
1236
                return [
1237
                    'kind'  => NodeKindEnum::FLOAT,
1238
                    'value' => $token->getValue(),
1239
                    'loc'   => $this->buildLocation($lexer, $token),
1240
                ];
1241
            case TokenKindEnum::STRING:
1242
            case TokenKindEnum::BLOCK_STRING:
1243
                return $this->buildStringLiteral($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildStringLiteral($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
1244
            case TokenKindEnum::NAME:
1245
                $value = $token->getValue();
1246
1247
                if ($value === 'true' || $value === 'false') {
1248
                    $lexer->advance();
1249
1250
                    return [
1251
                        'kind'  => NodeKindEnum::BOOLEAN,
1252
                        'value' => $value === 'true',
1253
                        'loc'   => $this->buildLocation($lexer, $token),
1254
                    ];
1255
                }
1256
1257
                if ($value === 'null') {
1258
                    $lexer->advance();
1259
1260
                    return [
1261
                        'kind' => NodeKindEnum::NULL,
1262
                        'loc'  => $this->buildLocation($lexer, $token),
1263
                    ];
1264
                }
1265
1266
                $lexer->advance();
1267
1268
                return [
1269
                    'kind'  => NodeKindEnum::ENUM,
1270
                    'value' => $token->getValue(),
1271
                    'loc'   => $this->buildLocation($lexer, $token),
1272
                ];
1273
            case TokenKindEnum::DOLLAR:
1274
                if (!$isConst) {
1275
                    return $this->buildVariable($lexer);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildVariable($lexer) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
1276
                }
1277
                break;
1278
        }
1279
1280
        throw $this->unexpected($lexer);
1281
    }
1282
1283
    /**
1284
     * @param LexerInterface $lexer
1285
     * @return array
1286
     * @throws SyntaxErrorException
1287
     */
1288
    protected function buildConstValue(LexerInterface $lexer): array
1289
    {
1290
        return $this->buildValueLiteral($lexer, true);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildValueLiteral($lexer, true) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
1291
    }
1292
1293
    /**
1294
     * @param LexerInterface $lexer
1295
     * @return array
1296
     * @throws SyntaxErrorException
1297
     */
1298
    protected function buildValue(LexerInterface $lexer): array
1299
    {
1300
        return $this->buildValueLiteral($lexer, false);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildValueLiteral($lexer, false) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
1301
    }
1302
1303
    /**
1304
     * @param LexerInterface $lexer
1305
     * @param bool           $isConst
1306
     * @return array
1307
     * @throws SyntaxErrorException
1308
     */
1309
    protected function buildList(LexerInterface $lexer, bool $isConst): array
1310
    {
1311
        $start = $lexer->getToken();
1312
1313
        return [
1314
            'kind'   => NodeKindEnum::LIST,
1315
            'values' => $this->any(
1316
                $lexer,
1317
                TokenKindEnum::BRACKET_L,
1318
                [$this, $isConst ? 'buildConstValue' : 'buildValue'],
1319
                TokenKindEnum::BRACKET_R
1320
            ),
1321
            'loc'    => $this->buildLocation($lexer, $start),
1322
        ];
1323
    }
1324
1325
    /**
1326
     * @param LexerInterface $lexer
1327
     * @param bool           $isConst
1328
     * @return array
1329
     * @throws SyntaxErrorException
1330
     */
1331
    protected function buildObject(LexerInterface $lexer, bool $isConst): array
1332
    {
1333
        $start = $lexer->getToken();
1334
1335
        $this->expect($lexer, TokenKindEnum::BRACE_L);
1336
1337
        $fields = [];
1338
1339
        while (!$this->skip($lexer, TokenKindEnum::BRACE_R)) {
1340
            $fields[] = $this->buildObjectField($lexer, $isConst);
1341
        }
1342
1343
        return [
1344
            'kind'   => NodeKindEnum::OBJECT,
1345
            'fields' => $fields,
1346
            'loc'    => $this->buildLocation($lexer, $start),
1347
        ];
1348
    }
1349
1350
    /**
1351
     * @param LexerInterface $lexer
1352
     * @param bool           $isConst
1353
     * @return array
1354
     * @throws SyntaxErrorException
1355
     */
1356
    protected function buildObjectField(LexerInterface $lexer, bool $isConst): array
1357
    {
1358
        $start = $lexer->getToken();
1359
1360
        $buildValue = function (LexerInterface $lexer, bool $isConst) {
1361
            $this->expect($lexer, TokenKindEnum::COLON);
1362
1363
            return $this->buildValueLiteral($lexer, $isConst);
1364
        };
1365
1366
        return [
1367
            'kind'  => NodeKindEnum::OBJECT_FIELD,
1368
            'name'  => $this->buildName($lexer),
1369
            'value' => $buildValue($lexer, $isConst),
1370
            'loc'   => $this->buildLocation($lexer, $start),
1371
        ];
1372
    }
1373
1374
    /**
1375
     * @param LexerInterface $lexer
1376
     * @return array|null
1377
     * @throws SyntaxErrorException
1378
     */
1379
    protected function buildVariable(LexerInterface $lexer): ?array
1380
    {
1381
        $start = $lexer->getToken();
1382
1383
        $this->expect($lexer, TokenKindEnum::DOLLAR);
1384
1385
        return [
1386
            'kind' => NodeKindEnum::VARIABLE,
1387
            'name' => $this->buildName($lexer),
1388
            'loc'  => $this->buildLocation($lexer, $start),
1389
        ];
1390
    }
1391
1392
    /**
1393
     * @param LexerInterface $lexer
1394
     * @return array|null
1395
     * @throws SyntaxErrorException
1396
     */
1397
    protected function buildVariableDefinitions(LexerInterface $lexer): ?array
1398
    {
1399
        return $this->peek($lexer, TokenKindEnum::PAREN_L)
1400
            ? $this->many($lexer, TokenKindEnum::PAREN_L, [$this, 'buildVariableDefinition'], TokenKindEnum::PAREN_R)
1401
            : [];
1402
    }
1403
1404
    /**
1405
     * @param LexerInterface $lexer
1406
     * @return array
1407
     * @throws SyntaxErrorException
1408
     */
1409
    protected function buildVariableDefinition(LexerInterface $lexer): array
1410
    {
1411
        $start = $lexer->getToken();
1412
1413
        /**
1414
         * @param LexerInterface $lexer
1415
         * @return mixed
1416
         * @throws SyntaxErrorException
1417
         */
1418
        $buildType = function (LexerInterface $lexer) {
1419
            $this->expect($lexer, TokenKindEnum::COLON);
1420
1421
            return $this->buildTypeReference($lexer);
1422
        };
1423
1424
        return [
1425
            'kind'         => NodeKindEnum::VARIABLE_DEFINITION,
1426
            'variable'     => $this->buildVariable($lexer),
1427
            'type'         => $buildType($lexer),
1428
            'defaultValue' => $this->skip($lexer, TokenKindEnum::EQUALS)
1429
                ? $this->buildValueLiteral($lexer, true)
1430
                : null,
1431
            'loc'          => $this->buildLocation($lexer, $start),
1432
        ];
1433
    }
1434
}
1435