Passed
Pull Request — master (#190)
by Sebastian
03:03
created

ASTBuilder::buildVariableDefinition()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 24
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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