ParseTreeCreator::create()   F
last analyzed

Complexity

Conditions 28
Paths 41

Size

Total Lines 105

Duplication

Lines 24
Ratio 22.86 %

Importance

Changes 0
Metric Value
cc 28
nc 41
nop 0
dl 24
loc 105
rs 3.3333
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
namespace Psalm\Internal\Type;
3
4
use function array_pop;
5
use function count;
6
use function in_array;
7
use function preg_match;
8
use Psalm\Exception\TypeParseTreeException;
9
use function strlen;
10
use function strtolower;
11
12
/**
13
 * @internal
14
 */
15
class ParseTreeCreator
16
{
17
    /** @var ParseTree */
18
    private $parse_tree;
19
20
    /** @var ParseTree */
21
    private $current_leaf;
22
23
    /** @var array<int, array{0: string, 1: int}> */
24
    private $type_tokens;
25
26
    /** @var int */
27
    private $type_token_count;
28
29
    /** @var int */
30
    private $t = 0;
31
32
    /**
33
     * @param array<int, array{0: string, 1: int}> $type_tokens
34
     */
35
    public function __construct(array $type_tokens)
36
    {
37
        $this->type_tokens = $type_tokens;
38
        $this->type_token_count = count($type_tokens);
39
        $this->parse_tree = new ParseTree\Root();
40
        $this->current_leaf = $this->parse_tree;
41
    }
42
43
    public function create() : ParseTree
44
    {
45
        while ($this->t < $this->type_token_count) {
46
            $type_token = $this->type_tokens[$this->t];
47
48
            switch ($type_token[0]) {
49
                case '<':
50
                case '{':
51
                case ']':
52
                    throw new TypeParseTreeException('Unexpected token ' . $type_token[0]);
53
54
                case '[':
55
                    $this->handleOpenSquareBracket();
56
                    break;
57
58
                case '(':
59
                    $this->handleOpenRoundBracket();
60
                    break;
61
62
                case ')':
63
                    $this->handleClosedRoundBracket();
64
                    break;
65
66 View Code Duplication
                case '>':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
67
                    do {
68
                        if ($this->current_leaf->parent === null) {
69
                            throw new TypeParseTreeException('Cannot parse generic type');
70
                        }
71
72
                        $this->current_leaf = $this->current_leaf->parent;
73
                    } while (!$this->current_leaf instanceof ParseTree\GenericTree);
74
75
                    $this->current_leaf->terminated = true;
76
77
                    break;
78
79 View Code Duplication
                case '}':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
80
                    do {
81
                        if ($this->current_leaf->parent === null) {
82
                            throw new TypeParseTreeException('Cannot parse array type');
83
                        }
84
85
                        $this->current_leaf = $this->current_leaf->parent;
86
                    } while (!$this->current_leaf instanceof ParseTree\ObjectLikeTree);
87
88
                    $this->current_leaf->terminated = true;
89
90
                    break;
91
92
                case ',':
93
                    $this->handleComma();
94
                    break;
95
96
                case '...':
97
                case '=':
98
                    $this->handleEllipsisOrEquals($type_token);
99
                    break;
100
101
                case ':':
102
                    $this->handleColon();
103
                    break;
104
105
                case ' ':
106
                    $this->handleSpace();
107
                    break;
108
109
                case '?':
110
                    $this->handleQuestionMark();
111
                    break;
112
113
                case '|':
114
                    $this->handleBar();
115
                    break;
116
117
                case '&':
118
                    $this->handleAmpersand();
119
                    break;
120
121
                case 'is':
122
                case 'as':
123
                    $this->handleIsOrAs($type_token);
124
                    break;
125
126
                default:
127
                    $this->handleValue($type_token);
128
                    break;
129
            }
130
131
            $this->t++;
132
        }
133
134
        $this->parse_tree->cleanParents();
135
136
        if ($this->current_leaf !== $this->parse_tree
137
            && ($this->parse_tree instanceof ParseTree\GenericTree
138
                || $this->parse_tree instanceof ParseTree\CallableTree
139
                || $this->parse_tree instanceof ParseTree\ObjectLikeTree)
140
        ) {
141
            throw new TypeParseTreeException(
142
                'Unterminated bracket'
143
            );
144
        }
145
146
        return $this->parse_tree;
147
    }
148
149
    /**
150
     * @param  array{0: string, 1: int} $current_token
151
     */
152
    private function createMethodParam($current_token, ParseTree $current_parent) : void
153
    {
154
        $byref = false;
155
        $variadic = false;
156
        $has_default = false;
157
        $default = '';
158
159
        if ($current_token[0] === '&') {
160
            throw new TypeParseTreeException('Magic args cannot be passed by reference');
161
        }
162
163
        if ($current_token[0] === '...') {
164
            $variadic = true;
165
166
            ++$this->t;
167
            $current_token = $this->t < $this->type_token_count ? $this->type_tokens[$this->t] : null;
168
        }
169
170
        if (!$current_token || $current_token[0][0] !== '$') {
171
            throw new TypeParseTreeException('Unexpected token after space');
172
        }
173
174
        $new_parent_leaf = new ParseTree\MethodParamTree(
175
            $current_token[0],
176
            $byref,
177
            $variadic,
178
            $current_parent
179
        );
180
181
        for ($j = $this->t + 1; $j < $this->type_token_count; ++$j) {
182
            $ahead_type_token = $this->type_tokens[$j];
183
184
            if ($ahead_type_token[0] === ','
185
                || ($ahead_type_token[0] === ')' && $this->type_tokens[$j - 1][0] !== '(')
186
            ) {
187
                $this->t = $j - 1;
188
                break;
189
            }
190
191
            if ($has_default) {
192
                $default .= $ahead_type_token[0];
193
            }
194
195
            if ($ahead_type_token[0] === '=') {
196
                $has_default = true;
197
                continue;
198
            }
199
200
            if ($j === $this->type_token_count - 1) {
201
                throw new TypeParseTreeException('Unterminated method');
202
            }
203
        }
204
205
        $new_parent_leaf->default = $default;
206
207
        if ($this->current_leaf !== $current_parent) {
208
            $new_parent_leaf->children = [$this->current_leaf];
209
            $this->current_leaf->parent = $new_parent_leaf;
210
            array_pop($current_parent->children);
211
        }
212
213
        $current_parent->children[] = $new_parent_leaf;
214
215
        $this->current_leaf = $new_parent_leaf;
216
    }
217
218
    private function handleOpenSquareBracket() : void
219
    {
220
        if ($this->current_leaf instanceof ParseTree\Root) {
221
            throw new TypeParseTreeException('Unexpected token [');
222
        }
223
224
        $indexed_access = false;
225
226
        $next_token = $this->t + 1 < $this->type_token_count ? $this->type_tokens[$this->t + 1] : null;
227
228
        if (!$next_token || $next_token[0] !== ']') {
229
            $next_next_token = $this->t + 2 < $this->type_token_count ? $this->type_tokens[$this->t + 2] : null;
230
231
            if ($next_next_token !== null && $next_next_token[0] === ']') {
232
                $indexed_access = true;
233
                ++$this->t;
234
            } else {
235
                throw new TypeParseTreeException('Unexpected token [');
236
            }
237
        }
238
239
        $current_parent = $this->current_leaf->parent;
240
241
        if ($indexed_access) {
242
            if ($next_token === null) {
243
                throw new TypeParseTreeException('Unexpected token [');
244
            }
245
246
            $new_parent_leaf = new ParseTree\IndexedAccessTree($next_token[0], $current_parent);
247
        } else {
248
            if ($this->current_leaf instanceof ParseTree\ObjectLikePropertyTree) {
249
                throw new TypeParseTreeException('Unexpected token [');
250
            }
251
252
            $new_parent_leaf = new ParseTree\GenericTree('array', $current_parent);
253
        }
254
255
        $this->current_leaf->parent = $new_parent_leaf;
256
        $new_parent_leaf->children = [$this->current_leaf];
257
258
        if ($current_parent) {
259
            array_pop($current_parent->children);
260
            $current_parent->children[] = $new_parent_leaf;
261
        } else {
262
            $this->parse_tree = $new_parent_leaf;
263
        }
264
265
        $this->current_leaf = $new_parent_leaf;
266
        ++$this->t;
267
    }
268
269
    private function handleOpenRoundBracket() : void
270
    {
271
        if ($this->current_leaf instanceof ParseTree\Value) {
272
            throw new TypeParseTreeException('Unrecognised token (');
273
        }
274
275
        $new_parent = !$this->current_leaf instanceof ParseTree\Root ? $this->current_leaf : null;
276
277
        $new_leaf = new ParseTree\EncapsulationTree(
278
            $new_parent
279
        );
280
281
        if ($this->current_leaf instanceof ParseTree\Root) {
282
            $this->current_leaf = $this->parse_tree = $new_leaf;
283
            return;
284
        }
285
286
        if ($new_leaf->parent) {
287
            $new_leaf->parent->children[] = $new_leaf;
288
        }
289
290
        $this->current_leaf = $new_leaf;
291
    }
292
293
    private function handleClosedRoundBracket() : void
294
    {
295
        $prev_token = $this->t > 0 ? $this->type_tokens[$this->t - 1] : null;
296
297
        if ($prev_token !== null
298
            && $prev_token[0] === '('
299
            && $this->current_leaf instanceof ParseTree\CallableTree
300
        ) {
301
            return;
302
        }
303
304
        do {
305
            if ($this->current_leaf->parent === null) {
306
                break;
307
            }
308
309
            $this->current_leaf = $this->current_leaf->parent;
310
        } while (!$this->current_leaf instanceof ParseTree\EncapsulationTree
311
            && !$this->current_leaf instanceof ParseTree\CallableTree
312
            && !$this->current_leaf instanceof ParseTree\MethodTree);
313
314
        if ($this->current_leaf instanceof ParseTree\EncapsulationTree
315
            || $this->current_leaf instanceof ParseTree\CallableTree
316
        ) {
317
            $this->current_leaf->terminated = true;
318
        }
319
    }
320
321
    private function handleComma() : void
322
    {
323
        if ($this->current_leaf instanceof ParseTree\Root) {
324
            throw new TypeParseTreeException('Unexpected token ,');
325
        }
326
327
        if (!$this->current_leaf->parent) {
328
            throw new TypeParseTreeException('Cannot parse comma without a parent node');
329
        }
330
331
        $context_node = $this->current_leaf;
332
333
        if ($context_node instanceof ParseTree\GenericTree
334
            || $context_node instanceof ParseTree\ObjectLikeTree
335
            || $context_node instanceof ParseTree\CallableTree
336
            || $context_node instanceof ParseTree\MethodTree
337
        ) {
338
            $context_node = $context_node->parent;
339
        }
340
341
        while ($context_node
342
            && !$context_node instanceof ParseTree\GenericTree
343
            && !$context_node instanceof ParseTree\ObjectLikeTree
344
            && !$context_node instanceof ParseTree\CallableTree
345
            && !$context_node instanceof ParseTree\MethodTree
346
        ) {
347
            $context_node = $context_node->parent;
348
        }
349
350
        if (!$context_node) {
351
            throw new TypeParseTreeException('Cannot parse comma in non-generic/array type');
352
        }
353
354
        $this->current_leaf = $context_node;
355
    }
356
357
    /** @param array{0: string, 1: int} $type_token */
358
    private function handleEllipsisOrEquals($type_token) : void
359
    {
360
        $prev_token = $this->t > 0 ? $this->type_tokens[$this->t - 1] : null;
361
362
        if ($prev_token && ($prev_token[0] === '...' || $prev_token[0] === '=')) {
363
            throw new TypeParseTreeException('Cannot have duplicate tokens');
364
        }
365
366
        $current_parent = $this->current_leaf->parent;
367
368 View Code Duplication
        if ($this->current_leaf instanceof ParseTree\MethodTree && $type_token[0] === '...') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
369
            $this->createMethodParam($type_token, $this->current_leaf);
370
            return;
371
        }
372
373
        while ($current_parent
374
            && !$current_parent instanceof ParseTree\CallableTree
375
            && !$current_parent instanceof ParseTree\CallableParamTree
376
        ) {
377
            $this->current_leaf = $current_parent;
378
            $current_parent = $current_parent->parent;
379
        }
380
381
        if (!$current_parent || !$this->current_leaf) {
382
            if ($this->current_leaf instanceof ParseTree\CallableTree
383
                && $type_token[0] === '...'
384
            ) {
385
                $current_parent = $this->current_leaf;
386
            } else {
387
                throw new TypeParseTreeException('Unexpected token ' . $type_token[0]);
388
            }
389
        }
390
391
        if ($current_parent instanceof ParseTree\CallableParamTree) {
392
            throw new TypeParseTreeException('Cannot have variadic param with a default');
393
        }
394
395
        $new_leaf = new ParseTree\CallableParamTree($current_parent);
396
        $new_leaf->has_default = $type_token[0] === '=';
397
        $new_leaf->variadic = $type_token[0] === '...';
398
399
        if ($current_parent !== $this->current_leaf) {
400
            $new_leaf->children = [$this->current_leaf];
401
            $this->current_leaf->parent = $new_leaf;
402
403
            array_pop($current_parent->children);
404
            $current_parent->children[] = $new_leaf;
405
        } else {
406
            $current_parent->children[] = $new_leaf;
407
        }
408
409
        $this->current_leaf = $new_leaf;
410
    }
411
412
    private function handleColon() : void
413
    {
414
        if ($this->current_leaf instanceof ParseTree\Root) {
415
            throw new TypeParseTreeException('Unexpected token :');
416
        }
417
418
        $current_parent = $this->current_leaf->parent;
419
420 View Code Duplication
        if ($this->current_leaf instanceof ParseTree\CallableTree) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
421
            $new_parent_leaf = new ParseTree\CallableWithReturnTypeTree($current_parent);
422
            $this->current_leaf->parent = $new_parent_leaf;
423
            $new_parent_leaf->children = [$this->current_leaf];
424
425
            if ($current_parent) {
426
                array_pop($current_parent->children);
427
                $current_parent->children[] = $new_parent_leaf;
428
            } else {
429
                $this->parse_tree = $new_parent_leaf;
430
            }
431
432
            $this->current_leaf = $new_parent_leaf;
433
            return;
434
        }
435
436 View Code Duplication
        if ($this->current_leaf instanceof ParseTree\MethodTree) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
437
            $new_parent_leaf = new ParseTree\MethodWithReturnTypeTree($current_parent);
438
            $this->current_leaf->parent = $new_parent_leaf;
439
            $new_parent_leaf->children = [$this->current_leaf];
440
441
            if ($current_parent) {
442
                array_pop($current_parent->children);
443
                $current_parent->children[] = $new_parent_leaf;
444
            } else {
445
                $this->parse_tree = $new_parent_leaf;
446
            }
447
448
            $this->current_leaf = $new_parent_leaf;
449
            return;
450
        }
451
452
        if ($current_parent && $current_parent instanceof ParseTree\ObjectLikePropertyTree) {
453
            return;
454
        }
455
456
        while (($current_parent instanceof ParseTree\UnionTree
457
                || $current_parent instanceof ParseTree\CallableWithReturnTypeTree)
458
            && $this->current_leaf->parent
459
        ) {
460
            $this->current_leaf = $this->current_leaf->parent;
461
            $current_parent = $this->current_leaf->parent;
462
        }
463
464
        if ($current_parent && $current_parent instanceof ParseTree\ConditionalTree) {
465
            if (count($current_parent->children) > 1) {
466
                throw new TypeParseTreeException('Cannot process colon in conditional twice');
467
            }
468
469
            $this->current_leaf = $current_parent;
470
            return;
471
        }
472
473
        if (!$current_parent) {
474
            throw new TypeParseTreeException('Cannot process colon without parent');
475
        }
476
477
        if (!$this->current_leaf instanceof ParseTree\Value) {
478
            throw new TypeParseTreeException('Unexpected LHS of property');
479
        }
480
481
        if (!$current_parent instanceof ParseTree\ObjectLikeTree) {
482
            throw new TypeParseTreeException('Saw : outside of object-like array');
483
        }
484
485
        $prev_token = $this->t > 0 ? $this->type_tokens[$this->t - 1] : null;
486
487
        $new_parent_leaf = new ParseTree\ObjectLikePropertyTree($this->current_leaf->value, $current_parent);
488
        $new_parent_leaf->possibly_undefined = $prev_token !== null && $prev_token[0] === '?';
489
        $this->current_leaf->parent = $new_parent_leaf;
490
491
        array_pop($current_parent->children);
492
        $current_parent->children[] = $new_parent_leaf;
493
494
        $this->current_leaf = $new_parent_leaf;
495
    }
496
497
    private function handleSpace() : void
498
    {
499
        if ($this->current_leaf instanceof ParseTree\Root) {
500
            throw new TypeParseTreeException('Unexpected space');
501
        }
502
503
        if ($this->current_leaf instanceof ParseTree\ObjectLikeTree) {
504
            return;
505
        }
506
507
        $current_parent = $this->current_leaf->parent;
508
509
        if ($current_parent instanceof ParseTree\CallableTree) {
510
            return;
511
        }
512
513
        while ($current_parent && !$current_parent instanceof ParseTree\MethodTree) {
514
            $this->current_leaf = $current_parent;
515
            $current_parent = $current_parent->parent;
516
        }
517
518
        $next_token = $this->t + 1 < $this->type_token_count ? $this->type_tokens[$this->t + 1] : null;
519
520
        if (!$current_parent instanceof ParseTree\MethodTree || !$next_token) {
521
            throw new TypeParseTreeException('Unexpected space');
522
        }
523
524
        ++$this->t;
525
526
        $this->createMethodParam($next_token, $current_parent);
527
    }
528
529
    private function handleQuestionMark() : void
530
    {
531
        $next_token = $this->t + 1 < $this->type_token_count ? $this->type_tokens[$this->t + 1] : null;
532
533
        if ($next_token === null || $next_token[0] !== ':') {
534
            while (($this->current_leaf instanceof ParseTree\Value
535
                    || $this->current_leaf instanceof ParseTree\UnionTree
536
                    || ($this->current_leaf instanceof ParseTree\ObjectLikeTree
537
                        && $this->current_leaf->terminated)
538
                    || ($this->current_leaf instanceof ParseTree\GenericTree
539
                        && $this->current_leaf->terminated)
540
                    || ($this->current_leaf instanceof ParseTree\EncapsulationTree
541
                        && $this->current_leaf->terminated)
542
                    || ($this->current_leaf instanceof ParseTree\CallableTree
543
                        && $this->current_leaf->terminated)
544
                    || $this->current_leaf instanceof ParseTree\IntersectionTree)
545
                && $this->current_leaf->parent
546
            ) {
547
                $this->current_leaf = $this->current_leaf->parent;
548
            }
549
550
            if ($this->current_leaf instanceof ParseTree\TemplateIsTree && $this->current_leaf->parent) {
551
                $current_parent = $this->current_leaf->parent;
552
553
                $new_leaf = new ParseTree\ConditionalTree(
554
                    $this->current_leaf,
555
                    $this->current_leaf->parent
556
                );
557
558
                $this->current_leaf->parent = $new_leaf;
559
560
                array_pop($current_parent->children);
561
                $current_parent->children[] = $new_leaf;
562
                $this->current_leaf = $new_leaf;
563
            } else {
564
                $new_parent = !$this->current_leaf instanceof ParseTree\Root ? $this->current_leaf : null;
565
566
                if (!$next_token) {
567
                    throw new TypeParseTreeException('Unexpected token ?');
568
                }
569
570
                $new_leaf = new ParseTree\NullableTree(
571
                    $new_parent
572
                );
573
574
                if ($this->current_leaf instanceof ParseTree\Root) {
575
                    $this->current_leaf = $this->parse_tree = $new_leaf;
576
                    return;
577
                }
578
579
                if ($new_leaf->parent) {
580
                    $new_leaf->parent->children[] = $new_leaf;
581
                }
582
583
                $this->current_leaf = $new_leaf;
584
            }
585
        }
586
    }
587
588
    private function handleBar() : void
589
    {
590
        if ($this->current_leaf instanceof ParseTree\Root) {
591
            throw new TypeParseTreeException('Unexpected token |');
592
        }
593
594
        $current_parent = $this->current_leaf->parent;
595
596
        if ($current_parent instanceof ParseTree\CallableWithReturnTypeTree) {
597
            $this->current_leaf = $current_parent;
598
            $current_parent = $current_parent->parent;
599
        }
600
601
        if ($current_parent instanceof ParseTree\NullableTree) {
602
            $this->current_leaf = $current_parent;
603
            $current_parent = $current_parent->parent;
604
        }
605
606
        if ($this->current_leaf instanceof ParseTree\UnionTree) {
607
            throw new TypeParseTreeException('Unexpected token |');
608
        }
609
610
        if ($current_parent && $current_parent instanceof ParseTree\UnionTree) {
611
            $this->current_leaf = $current_parent;
612
            return;
613
        }
614
615
        if ($current_parent && $current_parent instanceof ParseTree\IntersectionTree) {
616
            $this->current_leaf = $current_parent;
617
            $current_parent = $this->current_leaf->parent;
618
        }
619
620
        if ($current_parent instanceof ParseTree\TemplateIsTree) {
621
            $new_parent_leaf = new ParseTree\UnionTree($this->current_leaf);
622
            $new_parent_leaf->children = [$this->current_leaf];
623
            $new_parent_leaf->parent = $current_parent;
624
            $this->current_leaf->parent = $new_parent_leaf;
625
        } else {
626
            $new_parent_leaf = new ParseTree\UnionTree($current_parent);
627
            $new_parent_leaf->children = [$this->current_leaf];
628
            $this->current_leaf->parent = $new_parent_leaf;
629
        }
630
631
        if ($current_parent) {
632
            array_pop($current_parent->children);
633
            $current_parent->children[] = $new_parent_leaf;
634
        } else {
635
            $this->parse_tree = $new_parent_leaf;
636
        }
637
638
        $this->current_leaf = $new_parent_leaf;
639
    }
640
641
    private function handleAmpersand() : void
642
    {
643
        if ($this->current_leaf instanceof ParseTree\Root) {
644
            throw new TypeParseTreeException(
645
                'Unexpected &'
646
            );
647
        }
648
649
        $current_parent = $this->current_leaf->parent;
650
651
        if ($this->current_leaf instanceof ParseTree\MethodTree && $current_parent) {
652
            $this->createMethodParam($this->type_tokens[$this->t], $current_parent);
653
            return;
654
        }
655
656
        if ($current_parent && $current_parent instanceof ParseTree\IntersectionTree) {
657
            $this->current_leaf = $current_parent;
658
            return;
659
        }
660
661
        $new_parent_leaf = new ParseTree\IntersectionTree($current_parent);
662
        $new_parent_leaf->children = [$this->current_leaf];
663
        $this->current_leaf->parent = $new_parent_leaf;
664
665
        if ($current_parent) {
666
            array_pop($current_parent->children);
667
            $current_parent->children[] = $new_parent_leaf;
668
        } else {
669
            $this->parse_tree = $new_parent_leaf;
670
        }
671
672
        $this->current_leaf = $new_parent_leaf;
673
    }
674
675
    /** @param array{0: string, 1: int} $type_token */
676
    private function handleIsOrAs($type_token) : void
677
    {
678
        if ($this->t === 0) {
679
            $this->handleValue($type_token);
680
        } else {
681
            $current_parent = $this->current_leaf->parent;
682
683
            if ($current_parent) {
684
                array_pop($current_parent->children);
685
            }
686
687
            if ($type_token[0] === 'as') {
688
                $next_token = $this->t + 1 < $this->type_token_count ? $this->type_tokens[$this->t + 1] : null;
689
690
                if (!$this->current_leaf instanceof ParseTree\Value
691
                    || !$current_parent instanceof ParseTree\GenericTree
692
                    || !$next_token
693
                ) {
694
                    throw new TypeParseTreeException('Unexpected token ' . $type_token[0]);
695
                }
696
697
                $this->current_leaf = new ParseTree\TemplateAsTree(
698
                    $this->current_leaf->value,
699
                    $next_token[0],
700
                    $current_parent
701
                );
702
703
                $current_parent->children[] = $this->current_leaf;
704
                ++$this->t;
705
            } elseif ($this->current_leaf instanceof ParseTree\Value) {
706
                $this->current_leaf = new ParseTree\TemplateIsTree(
707
                    $this->current_leaf->value,
708
                    $current_parent
709
                );
710
711
                if ($current_parent) {
712
                    $current_parent->children[] = $this->current_leaf;
713
                }
714
            }
715
        }
716
    }
717
718
    /** @param array{0: string, 1: int} $type_token */
719
    private function handleValue($type_token) : void
720
    {
721
        $new_parent = !$this->current_leaf instanceof ParseTree\Root ? $this->current_leaf : null;
722
723 View Code Duplication
        if ($this->current_leaf instanceof ParseTree\MethodTree && $type_token[0][0] === '$') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
724
            $this->createMethodParam($type_token, $this->current_leaf);
725
            return;
726
        }
727
728
        $next_token = $this->t + 1 < $this->type_token_count ? $this->type_tokens[$this->t + 1] : null;
729
730
        switch ($next_token[0] ?? null) {
731
            case '<':
732
                $new_leaf = new ParseTree\GenericTree(
733
                    $type_token[0],
734
                    $new_parent
735
                );
736
                ++$this->t;
737
                break;
738
739
            case '{':
740
                $new_leaf = new ParseTree\ObjectLikeTree(
741
                    $type_token[0],
742
                    $new_parent
743
                );
744
                ++$this->t;
745
                break;
746
747
            case '(':
748
                if (in_array(strtolower($type_token[0]), ['closure', 'callable', '\closure'], true)) {
749
                    $new_leaf = new ParseTree\CallableTree(
750
                        $type_token[0],
751
                        $new_parent
752
                    );
753
                } elseif ($type_token[0] !== 'array'
754
                    && $type_token[0][0] !== '\\'
755
                    && $this->current_leaf instanceof ParseTree\Root
756
                ) {
757
                    $new_leaf = new ParseTree\MethodTree(
758
                        $type_token[0],
759
                        $new_parent
760
                    );
761
                } else {
762
                    throw new TypeParseTreeException(
763
                        'Bracket must be preceded by “Closure”, “callable” or a valid @method name'
764
                    );
765
                }
766
767
                ++$this->t;
768
                break;
769
770
            case '::':
771
                $nexter_token = $this->t + 2 < $this->type_token_count ? $this->type_tokens[$this->t + 2] : null;
772
773
                if ($this->current_leaf instanceof ParseTree\ObjectLikeTree) {
774
                    throw new TypeParseTreeException(
775
                        'Unexpected :: in array key'
776
                    );
777
                }
778
779
                if (!$nexter_token
780
                    || (!preg_match('/^([a-zA-Z_][a-zA-Z_0-9]*\*?|\*)$/', $nexter_token[0])
781
                        && strtolower($nexter_token[0]) !== 'class')
782
                ) {
783
                    throw new TypeParseTreeException(
784
                        'Invalid class constant ' . ($nexter_token[0] ?? '<empty>')
785
                    );
786
                }
787
788
                $new_leaf = new ParseTree\Value(
789
                    $type_token[0] . '::' . $nexter_token[0],
790
                    $type_token[1],
791
                    $type_token[1] + 2 + strlen($nexter_token[0]),
792
                    $new_parent
793
                );
794
795
                $this->t += 2;
796
797
                break;
798
799
            default:
800
                if ($type_token[0] === '$this') {
801
                    $type_token[0] = 'static';
802
                }
803
804
                $new_leaf = new ParseTree\Value(
805
                    $type_token[0],
806
                    $type_token[1],
807
                    $type_token[1] + strlen($type_token[0]),
808
                    $new_parent
809
                );
810
                break;
811
        }
812
813
        if ($this->current_leaf instanceof ParseTree\Root) {
814
            $this->current_leaf = $this->parse_tree = $new_leaf;
815
            return;
816
        }
817
818
        if ($new_leaf->parent) {
819
            $new_leaf->parent->children[] = $new_leaf;
820
        }
821
822
        $this->current_leaf = $new_leaf;
823
    }
824
}
825