Passed
Pull Request — master (#184)
by Christoffer
02:32
created

ASTBuilder::isOperation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 1
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 (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 LexerInterface $lexer
999
     * @return array|null
1000
     * @throws SyntaxErrorException
1001
     */
1002
    protected function buildSelectionSet(LexerInterface $lexer): ?array
1003
    {
1004
        $start = $lexer->getToken();
1005
1006
        return [
1007
            'kind'       => NodeKindEnum::SELECTION_SET,
1008
            'selections' => $this->many(
1009
                $lexer,
1010
                TokenKindEnum::BRACE_L,
1011
                [$this, 'buildSelection'],
1012
                TokenKindEnum::BRACE_R
1013
            ),
1014
            'loc'        => $this->buildLocation($lexer, $start),
1015
        ];
1016
    }
1017
1018
    /**
1019
     * @param LexerInterface $lexer
1020
     * @return array
1021
     * @throws SyntaxErrorException
1022
     */
1023
    protected function buildSelection(LexerInterface $lexer): array
1024
    {
1025
        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...
1026
            ? $this->buildFragment($lexer)
1027
            : $this->buildField($lexer);
1028
    }
1029
1030
    /**
1031
     * @param LexerInterface $lexer
1032
     * @return array|null
1033
     * @throws SyntaxErrorException
1034
     */
1035
    protected function buildScalarTypeDefinition(LexerInterface $lexer): ?array
1036
    {
1037
        $start = $lexer->getToken();
1038
1039
        $description = $this->buildDescription($lexer);
1040
1041
        $this->expectKeyword($lexer, KeywordEnum::SCALAR);
1042
1043
        return [
1044
            'kind'        => NodeKindEnum::SCALAR_TYPE_DEFINITION,
1045
            'description' => $description,
1046
            'name'        => $this->buildName($lexer),
1047
            'directives'  => $this->buildDirectives($lexer),
1048
            'loc'         => $this->buildLocation($lexer, $start),
1049
        ];
1050
    }
1051
1052
    /**
1053
     * @param LexerInterface $lexer
1054
     * @param bool           $isConst
1055
     * @return array|null
1056
     * @throws SyntaxErrorException
1057
     */
1058
    protected function buildScalarTypeExtension(LexerInterface $lexer, bool $isConst = false): ?array
1059
    {
1060
        $start = $lexer->getToken();
1061
1062
        $this->expectKeyword($lexer, KeywordEnum::EXTEND);
1063
        $this->expectKeyword($lexer, KeywordEnum::SCALAR);
1064
1065
        $name       = $this->buildName($lexer);
1066
        $directives = $this->buildDirectives($lexer, $isConst);
1067
1068
        if (empty($directives)) {
1069
            throw $this->unexpected($lexer);
1070
        }
1071
1072
        return [
1073
            'kind'       => NodeKindEnum::SCALAR_TYPE_EXTENSION,
1074
            'name'       => $name,
1075
            'directives' => $directives,
1076
            'loc'        => $this->buildLocation($lexer, $start),
1077
        ];
1078
    }
1079
1080
    protected function buildStringLiteral(LexerInterface $lexer): ?array
1081
    {
1082
        $token = $lexer->getToken();
1083
1084
        $lexer->advance();
1085
1086
        return [
1087
            'kind'  => NodeKindEnum::STRING,
1088
            'value' => $token->getValue(),
1089
            'block' => $token->getKind() === TokenKindEnum::BLOCK_STRING,
1090
            'loc'   => $this->buildLocation($lexer, $token),
1091
        ];
1092
    }
1093
1094
    /**
1095
     * @param LexerInterface $lexer
1096
     * @return array
1097
     * @throws SyntaxErrorException
1098
     */
1099
    protected function parseTypeReference(LexerInterface $lexer): array
1100
    {
1101
        $start = $lexer->getToken();
1102
1103
        if ($this->skip($lexer, TokenKindEnum::BRACKET_L)) {
1104
            $type = $this->buildTypeReference($lexer);
1105
1106
            $this->expect($lexer, TokenKindEnum::BRACKET_R);
1107
1108
            $type = [
1109
                'kind' => NodeKindEnum::LIST_TYPE,
1110
                'type' => $type,
1111
                'loc'  => $this->buildLocation($lexer, $start),
1112
            ];
1113
        } else {
1114
            $type = $this->buildNamedType($lexer);
1115
        }
1116
1117
        if ($this->skip($lexer, TokenKindEnum::BANG)) {
1118
            return [
1119
                'kind' => NodeKindEnum::NON_NULL_TYPE,
1120
                'type' => $type,
1121
                'loc'  => $this->buildLocation($lexer, $start),
1122
            ];
1123
        }
1124
1125
        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...
1126
    }
1127
1128
    /**
1129
     * @param LexerInterface $lexer
1130
     * @return array|null
1131
     * @throws SyntaxErrorException
1132
     */
1133
    protected function buildUnionTypeDefinition(LexerInterface $lexer): ?array
1134
    {
1135
        $start = $lexer->getToken();
1136
1137
        $description = $this->buildDescription($lexer);
1138
1139
        $this->expectKeyword($lexer, KeywordEnum::UNION);
1140
1141
        return [
1142
            'kind'        => NodeKindEnum::UNION_TYPE_DEFINITION,
1143
            'description' => $description,
1144
            'name'        => $this->buildName($lexer),
1145
            'directives'  => $this->buildDirectives($lexer),
1146
            'types'       => $this->buildUnionMemberTypes($lexer),
1147
            'loc'         => $this->buildLocation($lexer, $start),
1148
        ];
1149
    }
1150
1151
    /**
1152
     * @param LexerInterface $lexer
1153
     * @return array|null
1154
     * @throws SyntaxErrorException
1155
     */
1156
    protected function buildUnionTypeExtension(LexerInterface $lexer): ?array
1157
    {
1158
        $start = $lexer->getToken();
1159
1160
        $this->expectKeyword($lexer, KeywordEnum::EXTEND);
1161
        $this->expectKeyword($lexer, KeywordEnum::UNION);
1162
1163
        $name       = $this->buildName($lexer);
1164
        $directives = $this->buildDirectives($lexer);
1165
        $types      = $this->buildUnionMemberTypes($lexer);
1166
1167
        if (empty($directives) && empty($types)) {
1168
            throw $this->unexpected($lexer);
1169
        }
1170
1171
        return [
1172
            'kind'       => NodeKindEnum::UNION_TYPE_EXTENSION,
1173
            'name'       => $name,
1174
            'directives' => $directives,
1175
            'types'      => $types,
1176
            'loc'        => $this->buildLocation($lexer, $start),
1177
        ];
1178
    }
1179
1180
    /**
1181
     * @param LexerInterface $lexer
1182
     * @return array|null
1183
     * @throws SyntaxErrorException
1184
     */
1185
    protected function buildUnionMemberTypes(LexerInterface $lexer): ?array
1186
    {
1187
        $types = [];
1188
1189
        if ($this->skip($lexer, TokenKindEnum::EQUALS)) {
1190
            // Optional leading pipe
1191
            $this->skip($lexer, TokenKindEnum::PIPE);
1192
1193
            do {
1194
                $types[] = $this->buildNamedType($lexer);
1195
            } while ($this->skip($lexer, TokenKindEnum::PIPE));
1196
        }
1197
1198
        return $types;
1199
    }
1200
1201
    /**
1202
     * @param LexerInterface $lexer
1203
     * @param bool           $isConst
1204
     * @return array
1205
     * @throws SyntaxErrorException
1206
     */
1207
    protected function parseValueLiteral(LexerInterface $lexer, bool $isConst): array
1208
    {
1209
        $token = $lexer->getToken();
1210
1211
        switch ($token->getKind()) {
1212
            case TokenKindEnum::BRACKET_L:
1213
                return $this->buildList($lexer, $isConst);
1214
            case TokenKindEnum::BRACE_L:
1215
                return $this->buildObject($lexer, $isConst);
1216
            case TokenKindEnum::INT:
1217
                $lexer->advance();
1218
1219
                return [
1220
                    'kind'  => NodeKindEnum::INT,
1221
                    'value' => $token->getValue(),
1222
                    'loc'   => $this->buildLocation($lexer, $token),
1223
                ];
1224
            case TokenKindEnum::FLOAT:
1225
                $lexer->advance();
1226
1227
                return [
1228
                    'kind'  => NodeKindEnum::FLOAT,
1229
                    'value' => $token->getValue(),
1230
                    'loc'   => $this->buildLocation($lexer, $token),
1231
                ];
1232
            case TokenKindEnum::STRING:
1233
            case TokenKindEnum::BLOCK_STRING:
1234
                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...
1235
            case TokenKindEnum::NAME:
1236
                $value = $token->getValue();
1237
1238
                if ($value === 'true' || $value === 'false') {
1239
                    $lexer->advance();
1240
1241
                    return [
1242
                        'kind'  => NodeKindEnum::BOOLEAN,
1243
                        'value' => $value === 'true',
1244
                        'loc'   => $this->buildLocation($lexer, $token),
1245
                    ];
1246
                }
1247
1248
                if ($value === 'null') {
1249
                    $lexer->advance();
1250
1251
                    return [
1252
                        'kind' => NodeKindEnum::NULL,
1253
                        'loc'  => $this->buildLocation($lexer, $token),
1254
                    ];
1255
                }
1256
1257
                $lexer->advance();
1258
1259
                return [
1260
                    'kind'  => NodeKindEnum::ENUM,
1261
                    'value' => $token->getValue(),
1262
                    'loc'   => $this->buildLocation($lexer, $token),
1263
                ];
1264
            case TokenKindEnum::DOLLAR:
1265
                if (!$isConst) {
1266
                    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...
1267
                }
1268
                break;
1269
        }
1270
1271
        throw $this->unexpected($lexer);
1272
    }
1273
1274
    /**
1275
     * @param LexerInterface $lexer
1276
     * @return array
1277
     * @throws SyntaxErrorException
1278
     */
1279
    protected function buildConstValue(LexerInterface $lexer): array
1280
    {
1281
        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...
1282
    }
1283
1284
    /**
1285
     * @param LexerInterface $lexer
1286
     * @return array
1287
     * @throws SyntaxErrorException
1288
     */
1289
    protected function buildValue(LexerInterface $lexer): array
1290
    {
1291
        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...
1292
    }
1293
1294
    /**
1295
     * @param LexerInterface $lexer
1296
     * @param bool           $isConst
1297
     * @return array
1298
     * @throws SyntaxErrorException
1299
     */
1300
    protected function buildList(LexerInterface $lexer, bool $isConst): array
1301
    {
1302
        $start = $lexer->getToken();
1303
1304
        return [
1305
            'kind'   => NodeKindEnum::LIST,
1306
            'values' => $this->any(
1307
                $lexer,
1308
                TokenKindEnum::BRACKET_L,
1309
                [$this, $isConst ? 'buildConstValue' : 'buildValue'],
1310
                TokenKindEnum::BRACKET_R
1311
            ),
1312
            'loc'    => $this->buildLocation($lexer, $start),
1313
        ];
1314
    }
1315
1316
    /**
1317
     * @param LexerInterface $lexer
1318
     * @param bool           $isConst
1319
     * @return array
1320
     * @throws SyntaxErrorException
1321
     */
1322
    protected function buildObject(LexerInterface $lexer, bool $isConst): array
1323
    {
1324
        $start = $lexer->getToken();
1325
1326
        $this->expect($lexer, TokenKindEnum::BRACE_L);
1327
1328
        $fields = [];
1329
1330
        while (!$this->skip($lexer, TokenKindEnum::BRACE_R)) {
1331
            $fields[] = $this->buildObjectField($lexer, $isConst);
1332
        }
1333
1334
        return [
1335
            'kind'   => NodeKindEnum::OBJECT,
1336
            'fields' => $fields,
1337
            'loc'    => $this->buildLocation($lexer, $start),
1338
        ];
1339
    }
1340
1341
    /**
1342
     * @param LexerInterface $lexer
1343
     * @param bool           $isConst
1344
     * @return array
1345
     * @throws SyntaxErrorException
1346
     */
1347
    protected function buildObjectField(LexerInterface $lexer, bool $isConst): array
1348
    {
1349
        $start = $lexer->getToken();
1350
1351
        $buildValue = function (LexerInterface $lexer, bool $isConst) {
1352
            $this->expect($lexer, TokenKindEnum::COLON);
1353
1354
            return $this->buildValueLiteral($lexer, $isConst);
1355
        };
1356
1357
        return [
1358
            'kind'  => NodeKindEnum::OBJECT_FIELD,
1359
            'name'  => $this->buildName($lexer),
1360
            'value' => $buildValue($lexer, $isConst),
1361
            'loc'   => $this->buildLocation($lexer, $start),
1362
        ];
1363
    }
1364
1365
    /**
1366
     * @param LexerInterface $lexer
1367
     * @return array|null
1368
     * @throws SyntaxErrorException
1369
     */
1370
    protected function buildVariable(LexerInterface $lexer): ?array
1371
    {
1372
        $start = $lexer->getToken();
1373
1374
        $this->expect($lexer, TokenKindEnum::DOLLAR);
1375
1376
        return [
1377
            'kind' => NodeKindEnum::VARIABLE,
1378
            'name' => $this->buildName($lexer),
1379
            'loc'  => $this->buildLocation($lexer, $start),
1380
        ];
1381
    }
1382
1383
    /**
1384
     * @param LexerInterface $lexer
1385
     * @return array|null
1386
     * @throws SyntaxErrorException
1387
     */
1388
    protected function buildVariableDefinitions(LexerInterface $lexer): ?array
1389
    {
1390
        return $this->peek($lexer, TokenKindEnum::PAREN_L)
1391
            ? $this->many($lexer, TokenKindEnum::PAREN_L, [$this, 'buildVariableDefinition'], TokenKindEnum::PAREN_R)
1392
            : [];
1393
    }
1394
1395
    /**
1396
     * @param LexerInterface $lexer
1397
     * @return array
1398
     * @throws SyntaxErrorException
1399
     */
1400
    protected function buildVariableDefinition(LexerInterface $lexer): array
1401
    {
1402
        $start = $lexer->getToken();
1403
1404
        /**
1405
         * @param LexerInterface $lexer
1406
         * @return mixed
1407
         * @throws SyntaxErrorException
1408
         */
1409
        $buildType = function (LexerInterface $lexer) {
1410
            $this->expect($lexer, TokenKindEnum::COLON);
1411
1412
            return $this->buildTypeReference($lexer);
1413
        };
1414
1415
        return [
1416
            'kind'         => NodeKindEnum::VARIABLE_DEFINITION,
1417
            'variable'     => $this->buildVariable($lexer),
1418
            'type'         => $buildType($lexer),
1419
            'defaultValue' => $this->skip($lexer, TokenKindEnum::EQUALS)
1420
                ? $this->buildValueLiteral($lexer, true)
1421
                : null,
1422
            'loc'          => $this->buildLocation($lexer, $start),
1423
        ];
1424
    }
1425
}
1426