TokensAnalyzer::isBlockMultiline()   B
last analyzed

Complexity

Conditions 9
Paths 5

Size

Total Lines 30
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 16
c 1
b 0
f 0
dl 0
loc 30
rs 8.0555
cc 9
nc 5
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of PHP CS Fixer.
7
 *
8
 * (c) Fabien Potencier <[email protected]>
9
 *     Dariusz Rumiński <[email protected]>
10
 *
11
 * This source file is subject to the MIT license that is bundled
12
 * with this source code in the file LICENSE.
13
 */
14
15
namespace PhpCsFixer\Tokenizer;
16
17
use PhpCsFixer\Tokenizer\Analyzer\AttributeAnalyzer;
18
use PhpCsFixer\Tokenizer\Analyzer\GotoLabelAnalyzer;
19
20
/**
21
 * Analyzer of Tokens collection.
22
 *
23
 * Its role is to provide the ability to analyze collection.
24
 *
25
 * @author Dariusz Rumiński <[email protected]>
26
 * @author Gregor Harlan <[email protected]>
27
 * @author SpacePossum
28
 *
29
 * @internal
30
 */
31
final class TokensAnalyzer
32
{
33
    /**
34
     * Tokens collection instance.
35
     *
36
     * @var Tokens
37
     */
38
    private $tokens;
39
40
    /**
41
     * @var ?GotoLabelAnalyzer
42
     */
43
    private $gotoLabelAnalyzer;
44
45
    public function __construct(Tokens $tokens)
46
    {
47
        $this->tokens = $tokens;
48
    }
49
50
    /**
51
     * Get indexes of methods and properties in classy code (classes, interfaces and traits).
52
     *
53
     * @return array[]
54
     */
55
    public function getClassyElements(): array
56
    {
57
        $elements = [];
58
59
        for ($index = 1, $count = \count($this->tokens) - 2; $index < $count; ++$index) {
60
            if ($this->tokens[$index]->isClassy()) {
61
                [$index, $newElements] = $this->findClassyElements($index, $index);
62
                $elements += $newElements;
63
            }
64
        }
65
66
        ksort($elements);
67
68
        return $elements;
69
    }
70
71
    /**
72
     * Get indexes of namespace uses.
73
     *
74
     * @param bool $perNamespace Return namespace uses per namespace
75
     *
76
     * @return int[]|int[][]
77
     */
78
    public function getImportUseIndexes(bool $perNamespace = false): array
79
    {
80
        $tokens = $this->tokens;
81
82
        $uses = [];
83
        $namespaceIndex = 0;
84
85
        for ($index = 0, $limit = $tokens->count(); $index < $limit; ++$index) {
86
            $token = $tokens[$index];
87
88
            if ($token->isGivenKind(T_NAMESPACE)) {
89
                $nextTokenIndex = $tokens->getNextTokenOfKind($index, [';', '{']);
90
                $nextToken = $tokens[$nextTokenIndex];
91
92
                if ($nextToken->equals('{')) {
93
                    $index = $nextTokenIndex;
94
                }
95
96
                if ($perNamespace) {
97
                    ++$namespaceIndex;
98
                }
99
100
                continue;
101
            }
102
103
            if ($token->isGivenKind(T_USE)) {
104
                $uses[$namespaceIndex][] = $index;
105
            }
106
        }
107
108
        if (!$perNamespace && isset($uses[$namespaceIndex])) {
109
            return $uses[$namespaceIndex];
110
        }
111
112
        return $uses;
113
    }
114
115
    /**
116
     * Check if there is an array at given index.
117
     */
118
    public function isArray(int $index): bool
119
    {
120
        return $this->tokens[$index]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]);
121
    }
122
123
    /**
124
     * Check if the array at index is multiline.
125
     *
126
     * This only checks the root-level of the array.
127
     */
128
    public function isArrayMultiLine(int $index): bool
129
    {
130
        if (!$this->isArray($index)) {
131
            throw new \InvalidArgumentException(sprintf('Not an array at given index %d.', $index));
132
        }
133
134
        $tokens = $this->tokens;
135
136
        // Skip only when its an array, for short arrays we need the brace for correct
137
        // level counting
138
        if ($tokens[$index]->isGivenKind(T_ARRAY)) {
139
            $index = $tokens->getNextMeaningfulToken($index);
140
        }
141
142
        return $this->isBlockMultiline($tokens, $index);
0 ignored issues
show
Bug introduced by
It seems like $index can also be of type null; however, parameter $index of PhpCsFixer\Tokenizer\Tok...zer::isBlockMultiline() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

142
        return $this->isBlockMultiline($tokens, /** @scrutinizer ignore-type */ $index);
Loading history...
143
    }
144
145
    public function isBlockMultiline(Tokens $tokens, int $index): bool
146
    {
147
        $blockType = Tokens::detectBlockType($tokens[$index]);
148
149
        if (null === $blockType || !$blockType['isStart']) {
150
            throw new \InvalidArgumentException(sprintf('Not an block start at given index %d.', $index));
151
        }
152
153
        $endIndex = $tokens->findBlockEnd($blockType['type'], $index);
154
155
        for (++$index; $index < $endIndex; ++$index) {
156
            $token = $tokens[$index];
157
            $blockType = Tokens::detectBlockType($token);
158
159
            if ($blockType && $blockType['isStart']) {
160
                $index = $tokens->findBlockEnd($blockType['type'], $index);
161
162
                continue;
163
            }
164
165
            if (
166
                $token->isWhitespace()
167
                && !$tokens[$index - 1]->isGivenKind(T_END_HEREDOC)
168
                && false !== strpos($token->getContent(), "\n")
169
            ) {
170
                return true;
171
            }
172
        }
173
174
        return false;
175
    }
176
177
    /**
178
     * Returns the attributes of the method under the given index.
179
     *
180
     * The array has the following items:
181
     * 'visibility' int|null  T_PRIVATE, T_PROTECTED or T_PUBLIC
182
     * 'static'     bool
183
     * 'abstract'   bool
184
     * 'final'      bool
185
     *
186
     * @param int $index Token index of the method (T_FUNCTION)
187
     */
188
    public function getMethodAttributes(int $index): array
189
    {
190
        $tokens = $this->tokens;
191
        $token = $tokens[$index];
192
193
        if (!$token->isGivenKind(T_FUNCTION)) {
194
            throw new \LogicException(sprintf('No T_FUNCTION at given index %d, got "%s".', $index, $token->getName()));
195
        }
196
197
        $attributes = [
198
            'visibility' => null,
199
            'static' => false,
200
            'abstract' => false,
201
            'final' => false,
202
        ];
203
204
        for ($i = $index; $i >= 0; --$i) {
205
            $tokenIndex = $tokens->getPrevMeaningfulToken($i);
206
207
            $i = $tokenIndex;
208
            $token = $tokens[$tokenIndex];
209
210
            if ($token->isGivenKind(T_STATIC)) {
211
                $attributes['static'] = true;
212
213
                continue;
214
            }
215
216
            if ($token->isGivenKind(T_FINAL)) {
217
                $attributes['final'] = true;
218
219
                continue;
220
            }
221
222
            if ($token->isGivenKind(T_ABSTRACT)) {
223
                $attributes['abstract'] = true;
224
225
                continue;
226
            }
227
228
            // visibility
229
230
            if ($token->isGivenKind(T_PRIVATE)) {
231
                $attributes['visibility'] = T_PRIVATE;
232
233
                continue;
234
            }
235
236
            if ($token->isGivenKind(T_PROTECTED)) {
237
                $attributes['visibility'] = T_PROTECTED;
238
239
                continue;
240
            }
241
242
            if ($token->isGivenKind(T_PUBLIC)) {
243
                $attributes['visibility'] = T_PUBLIC;
244
245
                continue;
246
            }
247
248
            // found a meaningful token that is not part of
249
            // the function signature; stop looking
250
            break;
251
        }
252
253
        return $attributes;
254
    }
255
256
    /**
257
     * Check if there is an anonymous class under given index.
258
     */
259
    public function isAnonymousClass(int $index): bool
260
    {
261
        if (!$this->tokens[$index]->isClassy()) {
262
            throw new \LogicException(sprintf('No classy token at given index %d.', $index));
263
        }
264
265
        if (!$this->tokens[$index]->isGivenKind(T_CLASS)) {
266
            return false;
267
        }
268
269
        return $this->tokens[$this->tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_NEW);
270
    }
271
272
    /**
273
     * Check if the function under given index is a lambda.
274
     */
275
    public function isLambda(int $index): bool
276
    {
277
        if (
278
            !$this->tokens[$index]->isGivenKind(T_FUNCTION)
279
            && (\PHP_VERSION_ID < 70400 || !$this->tokens[$index]->isGivenKind(T_FN))
280
        ) {
281
            throw new \LogicException(sprintf('No T_FUNCTION or T_FN at given index %d, got "%s".', $index, $this->tokens[$index]->getName()));
282
        }
283
284
        $startParenthesisIndex = $this->tokens->getNextMeaningfulToken($index);
285
        $startParenthesisToken = $this->tokens[$startParenthesisIndex];
286
287
        // skip & for `function & () {}` syntax
288
        if ($startParenthesisToken->isGivenKind(CT::T_RETURN_REF)) {
289
            $startParenthesisIndex = $this->tokens->getNextMeaningfulToken($startParenthesisIndex);
0 ignored issues
show
Bug introduced by
It seems like $startParenthesisIndex can also be of type null; however, parameter $index of PhpCsFixer\Tokenizer\Tok...etNextMeaningfulToken() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

289
            $startParenthesisIndex = $this->tokens->getNextMeaningfulToken(/** @scrutinizer ignore-type */ $startParenthesisIndex);
Loading history...
290
            $startParenthesisToken = $this->tokens[$startParenthesisIndex];
291
        }
292
293
        return $startParenthesisToken->equals('(');
294
    }
295
296
    /**
297
     * Check if the T_STRING under given index is a constant invocation.
298
     */
299
    public function isConstantInvocation(int $index): bool
300
    {
301
        if (!$this->tokens[$index]->isGivenKind(T_STRING)) {
302
            throw new \LogicException(sprintf('No T_STRING at given index %d, got "%s".', $index, $this->tokens[$index]->getName()));
303
        }
304
305
        $nextIndex = $this->tokens->getNextMeaningfulToken($index);
306
307
        if (
308
            $this->tokens[$nextIndex]->equalsAny(['(', '{'])
309
            || $this->tokens[$nextIndex]->isGivenKind([T_AS, T_DOUBLE_COLON, T_ELLIPSIS, T_NS_SEPARATOR, CT::T_RETURN_REF, CT::T_TYPE_ALTERNATION, T_VARIABLE])
310
        ) {
311
            return false;
312
        }
313
314
        $prevIndex = $this->tokens->getPrevMeaningfulToken($index);
315
316
        if ($this->tokens[$prevIndex]->isGivenKind([T_AS, T_CLASS, T_CONST, T_DOUBLE_COLON, T_FUNCTION, T_GOTO, CT::T_GROUP_IMPORT_BRACE_OPEN, T_INTERFACE, T_TRAIT, CT::T_TYPE_COLON]) || $this->tokens[$prevIndex]->isObjectOperator()) {
317
            return false;
318
        }
319
320
        while ($this->tokens[$prevIndex]->isGivenKind([CT::T_NAMESPACE_OPERATOR, T_NS_SEPARATOR, T_STRING])) {
321
            $prevIndex = $this->tokens->getPrevMeaningfulToken($prevIndex);
0 ignored issues
show
Bug introduced by
It seems like $prevIndex can also be of type null; however, parameter $index of PhpCsFixer\Tokenizer\Tok...etPrevMeaningfulToken() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

321
            $prevIndex = $this->tokens->getPrevMeaningfulToken(/** @scrutinizer ignore-type */ $prevIndex);
Loading history...
322
        }
323
324
        if ($this->tokens[$prevIndex]->isGivenKind([CT::T_CONST_IMPORT, T_EXTENDS, CT::T_FUNCTION_IMPORT, T_IMPLEMENTS, T_INSTANCEOF, T_INSTEADOF, T_NAMESPACE, T_NEW, CT::T_NULLABLE_TYPE, CT::T_TYPE_COLON, T_USE, CT::T_USE_TRAIT])) {
325
            return false;
326
        }
327
328
        // `FOO & $bar` could be:
329
        //   - function reference parameter: function baz(Foo & $bar) {}
330
        //   - bit operator: $x = FOO & $bar;
331
        if ($this->tokens[$nextIndex]->equals('&') && $this->tokens[$this->tokens->getNextMeaningfulToken($nextIndex)]->isGivenKind(T_VARIABLE)) {
0 ignored issues
show
Bug introduced by
It seems like $nextIndex can also be of type null; however, parameter $index of PhpCsFixer\Tokenizer\Tok...etNextMeaningfulToken() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

331
        if ($this->tokens[$nextIndex]->equals('&') && $this->tokens[$this->tokens->getNextMeaningfulToken(/** @scrutinizer ignore-type */ $nextIndex)]->isGivenKind(T_VARIABLE)) {
Loading history...
332
            $checkIndex = $this->tokens->getPrevTokenOfKind($prevIndex, [';', '{', '}', [T_FUNCTION], [T_OPEN_TAG], [T_OPEN_TAG_WITH_ECHO]]);
0 ignored issues
show
Bug introduced by
It seems like $prevIndex can also be of type null; however, parameter $index of PhpCsFixer\Tokenizer\Tokens::getPrevTokenOfKind() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

332
            $checkIndex = $this->tokens->getPrevTokenOfKind(/** @scrutinizer ignore-type */ $prevIndex, [';', '{', '}', [T_FUNCTION], [T_OPEN_TAG], [T_OPEN_TAG_WITH_ECHO]]);
Loading history...
333
334
            if ($this->tokens[$checkIndex]->isGivenKind(T_FUNCTION)) {
335
                return false;
336
            }
337
        }
338
339
        // check for `extends`/`implements`/`use` list
340
        if ($this->tokens[$prevIndex]->equals(',')) {
341
            $checkIndex = $prevIndex;
342
            while ($this->tokens[$checkIndex]->equalsAny([',', [T_AS], [CT::T_NAMESPACE_OPERATOR], [T_NS_SEPARATOR], [T_STRING]])) {
343
                $checkIndex = $this->tokens->getPrevMeaningfulToken($checkIndex);
344
            }
345
346
            if ($this->tokens[$checkIndex]->isGivenKind([T_EXTENDS, CT::T_GROUP_IMPORT_BRACE_OPEN, T_IMPLEMENTS, T_USE, CT::T_USE_TRAIT])) {
347
                return false;
348
            }
349
        }
350
351
        // check for array in double quoted string: `"..$foo[bar].."`
352
        if ($this->tokens[$prevIndex]->equals('[') && $this->tokens[$nextIndex]->equals(']')) {
353
            $checkToken = $this->tokens[$this->tokens->getNextMeaningfulToken($nextIndex)];
354
355
            if ($checkToken->equals('"') || $checkToken->isGivenKind([T_CURLY_OPEN, T_DOLLAR_OPEN_CURLY_BRACES, T_ENCAPSED_AND_WHITESPACE, T_VARIABLE])) {
356
                return false;
357
            }
358
        }
359
360
        // check for attribute: `#[Foo]`
361
        if (AttributeAnalyzer::isAttribute($this->tokens, $index)) {
362
            return false;
363
        }
364
365
        // check for goto label
366
        if ($this->tokens[$nextIndex]->equals(':')) {
367
            if (null === $this->gotoLabelAnalyzer) {
368
                $this->gotoLabelAnalyzer = new GotoLabelAnalyzer();
369
            }
370
371
            if ($this->gotoLabelAnalyzer->belongsToGoToLabel($this->tokens, $nextIndex)) {
0 ignored issues
show
Bug introduced by
It seems like $nextIndex can also be of type null; however, parameter $index of PhpCsFixer\Tokenizer\Ana...r::belongsToGoToLabel() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

371
            if ($this->gotoLabelAnalyzer->belongsToGoToLabel($this->tokens, /** @scrutinizer ignore-type */ $nextIndex)) {
Loading history...
372
                return false;
373
            }
374
        }
375
376
        // check for non-capturing catches
377
        while ($this->tokens[$prevIndex]->isGivenKind([CT::T_TYPE_ALTERNATION, T_STRING])) {
378
            $prevIndex = $this->tokens->getPrevMeaningfulToken($prevIndex);
379
        }
380
381
        if ($this->tokens[$prevIndex]->equals('(')) {
382
            $prevPrevIndex = $this->tokens->getPrevMeaningfulToken($prevIndex);
383
            if ($this->tokens[$prevPrevIndex]->isGivenKind(T_CATCH)) {
384
                return false;
385
            }
386
        }
387
388
        return true;
389
    }
390
391
    /**
392
     * Checks if there is an unary successor operator under given index.
393
     */
394
    public function isUnarySuccessorOperator(int $index): bool
395
    {
396
        static $allowedPrevToken = [
397
            ']',
398
            [T_STRING],
399
            [T_VARIABLE],
400
            [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE],
401
            [CT::T_DYNAMIC_PROP_BRACE_CLOSE],
402
            [CT::T_DYNAMIC_VAR_BRACE_CLOSE],
403
        ];
404
405
        $tokens = $this->tokens;
406
        $token = $tokens[$index];
407
408
        if (!$token->isGivenKind([T_INC, T_DEC])) {
409
            return false;
410
        }
411
412
        $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)];
413
414
        return $prevToken->equalsAny($allowedPrevToken);
415
    }
416
417
    /**
418
     * Checks if there is an unary predecessor operator under given index.
419
     */
420
    public function isUnaryPredecessorOperator(int $index): bool
421
    {
422
        static $potentialSuccessorOperator = [T_INC, T_DEC];
423
424
        static $potentialBinaryOperator = ['+', '-', '&', [CT::T_RETURN_REF]];
425
426
        static $otherOperators;
427
        if (null === $otherOperators) {
428
            $otherOperators = ['!', '~', '@', [T_ELLIPSIS]];
429
        }
430
431
        static $disallowedPrevTokens;
432
        if (null === $disallowedPrevTokens) {
433
            $disallowedPrevTokens = [
434
                ']',
435
                '}',
436
                ')',
437
                '"',
438
                '`',
439
                [CT::T_ARRAY_SQUARE_BRACE_CLOSE],
440
                [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE],
441
                [CT::T_DYNAMIC_PROP_BRACE_CLOSE],
442
                [CT::T_DYNAMIC_VAR_BRACE_CLOSE],
443
                [T_CLASS_C],
444
                [T_CONSTANT_ENCAPSED_STRING],
445
                [T_DEC],
446
                [T_DIR],
447
                [T_DNUMBER],
448
                [T_FILE],
449
                [T_FUNC_C],
450
                [T_INC],
451
                [T_LINE],
452
                [T_LNUMBER],
453
                [T_METHOD_C],
454
                [T_NS_C],
455
                [T_STRING],
456
                [T_TRAIT_C],
457
                [T_VARIABLE],
458
            ];
459
        }
460
461
        $tokens = $this->tokens;
462
        $token = $tokens[$index];
463
464
        if ($token->isGivenKind($potentialSuccessorOperator)) {
465
            return !$this->isUnarySuccessorOperator($index);
466
        }
467
468
        if ($token->equalsAny($otherOperators)) {
469
            return true;
470
        }
471
472
        if (!$token->equalsAny($potentialBinaryOperator)) {
473
            return false;
474
        }
475
476
        $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)];
477
478
        if (!$prevToken->equalsAny($disallowedPrevTokens)) {
479
            return true;
480
        }
481
482
        if (!$token->equals('&') || !$prevToken->isGivenKind(T_STRING)) {
483
            return false;
484
        }
485
486
        static $searchTokens = [
487
            ';',
488
            '{',
489
            '}',
490
            [T_FUNCTION],
491
            [T_OPEN_TAG],
492
            [T_OPEN_TAG_WITH_ECHO],
493
        ];
494
        $prevToken = $tokens[$tokens->getPrevTokenOfKind($index, $searchTokens)];
495
496
        return $prevToken->isGivenKind(T_FUNCTION);
497
    }
498
499
    /**
500
     * Checks if there is a binary operator under given index.
501
     */
502
    public function isBinaryOperator(int $index): bool
503
    {
504
        static $nonArrayOperators = [
505
            '=' => true,
506
            '*' => true,
507
            '/' => true,
508
            '%' => true,
509
            '<' => true,
510
            '>' => true,
511
            '|' => true,
512
            '^' => true,
513
            '.' => true,
514
        ];
515
516
        static $potentialUnaryNonArrayOperators = [
517
            '+' => true,
518
            '-' => true,
519
            '&' => true,
520
        ];
521
522
        static $arrayOperators;
523
        if (null === $arrayOperators) {
524
            $arrayOperators = [
525
                T_AND_EQUAL => true,            // &=
526
                T_BOOLEAN_AND => true,          // &&
527
                T_BOOLEAN_OR => true,           // ||
528
                T_CONCAT_EQUAL => true,         // .=
529
                T_DIV_EQUAL => true,            // /=
530
                T_DOUBLE_ARROW => true,         // =>
531
                T_IS_EQUAL => true,             // ==
532
                T_IS_GREATER_OR_EQUAL => true,  // >=
533
                T_IS_IDENTICAL => true,         // ===
534
                T_IS_NOT_EQUAL => true,         // !=, <>
535
                T_IS_NOT_IDENTICAL => true,     // !==
536
                T_IS_SMALLER_OR_EQUAL => true,  // <=
537
                T_LOGICAL_AND => true,          // and
538
                T_LOGICAL_OR => true,           // or
539
                T_LOGICAL_XOR => true,          // xor
540
                T_MINUS_EQUAL => true,          // -=
541
                T_MOD_EQUAL => true,            // %=
542
                T_MUL_EQUAL => true,            // *=
543
                T_OR_EQUAL => true,             // |=
544
                T_PLUS_EQUAL => true,           // +=
545
                T_POW => true,                  // **
546
                T_POW_EQUAL => true,            // **=
547
                T_SL => true,                   // <<
548
                T_SL_EQUAL => true,             // <<=
549
                T_SR => true,                   // >>
550
                T_SR_EQUAL => true,             // >>=
551
                T_XOR_EQUAL => true,            // ^=
552
                T_SPACESHIP => true,            // <=>
553
                T_COALESCE => true,             // ??
554
            ];
555
556
            // @TODO: drop condition when PHP 7.4+ is required
557
            if (\defined('T_COALESCE_EQUAL')) {
558
                $arrayOperators[T_COALESCE_EQUAL] = true;  // ??=
559
            }
560
        }
561
562
        $tokens = $this->tokens;
563
        $token = $tokens[$index];
564
565
        if ($token->isArray()) {
566
            return isset($arrayOperators[$token->getId()]);
567
        }
568
569
        if (isset($nonArrayOperators[$token->getContent()])) {
570
            return true;
571
        }
572
573
        if (isset($potentialUnaryNonArrayOperators[$token->getContent()])) {
574
            return !$this->isUnaryPredecessorOperator($index);
575
        }
576
577
        return false;
578
    }
579
580
    /**
581
     * Check if `T_WHILE` token at given index is `do { ... } while ();` syntax
582
     * and not `while () { ...}`.
583
     */
584
    public function isWhilePartOfDoWhile(int $index): bool
585
    {
586
        $tokens = $this->tokens;
587
        $token = $tokens[$index];
588
589
        if (!$token->isGivenKind(T_WHILE)) {
590
            throw new \LogicException(sprintf('No T_WHILE at given index %d, got "%s".', $index, $token->getName()));
591
        }
592
593
        $endIndex = $tokens->getPrevMeaningfulToken($index);
594
        if (!$tokens[$endIndex]->equals('}')) {
595
            return false;
596
        }
597
598
        $startIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $endIndex);
0 ignored issues
show
Bug introduced by
It seems like $endIndex can also be of type null; however, parameter $searchIndex of PhpCsFixer\Tokenizer\Tokens::findBlockStart() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

598
        $startIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, /** @scrutinizer ignore-type */ $endIndex);
Loading history...
599
        $beforeStartIndex = $tokens->getPrevMeaningfulToken($startIndex);
600
601
        return $tokens[$beforeStartIndex]->isGivenKind(T_DO);
602
    }
603
604
    public function isSuperGlobal(int $index): bool
605
    {
606
        static $superNames = [
607
            '$_COOKIE' => true,
608
            '$_ENV' => true,
609
            '$_FILES' => true,
610
            '$_GET' => true,
611
            '$_POST' => true,
612
            '$_REQUEST' => true,
613
            '$_SERVER' => true,
614
            '$_SESSION' => true,
615
            '$GLOBALS' => true,
616
        ];
617
618
        $token = $this->tokens[$index];
619
620
        if (!$token->isGivenKind(T_VARIABLE)) {
621
            return false;
622
        }
623
624
        return isset($superNames[strtoupper($token->getContent())]);
625
    }
626
627
    /**
628
     * Find classy elements.
629
     *
630
     * Searches in tokens from the classy (start) index till the end (index) of the classy.
631
     * Returns an array; first value is the index until the method has analysed (int), second the found classy elements (array).
632
     *
633
     * @param int $classIndex classy index
634
     */
635
    private function findClassyElements(int $classIndex, int $index): array
636
    {
637
        $elements = [];
638
        $curlyBracesLevel = 0;
639
        $bracesLevel = 0;
640
        ++$index; // skip the classy index itself
641
642
        for ($count = \count($this->tokens); $index < $count; ++$index) {
643
            $token = $this->tokens[$index];
644
645
            if ($token->isGivenKind(T_ENCAPSED_AND_WHITESPACE)) {
646
                continue;
647
            }
648
649
            if ($token->isClassy()) { // anonymous class in class
650
                // check for nested anonymous classes inside the new call of an anonymous class,
651
                // for example `new class(function (){new class(function (){new class(function (){}){};}){};}){};` etc.
652
                // if class(XYZ) {} skip till `(` as XYZ might contain functions etc.
653
654
                $nestedClassIndex = $index;
655
                $index = $this->tokens->getNextMeaningfulToken($index);
656
657
                if ($this->tokens[$index]->equals('(')) {
658
                    ++$index; // move after `(`
659
660
                    for ($nestedBracesLevel = 1; $index < $count; ++$index) {
661
                        $token = $this->tokens[$index];
662
663
                        if ($token->equals('(')) {
664
                            ++$nestedBracesLevel;
665
666
                            continue;
667
                        }
668
669
                        if ($token->equals(')')) {
670
                            --$nestedBracesLevel;
671
672
                            if (0 === $nestedBracesLevel) {
673
                                [$index, $newElements] = $this->findClassyElements($nestedClassIndex, $index);
0 ignored issues
show
Bug introduced by
It seems like $index can also be of type null; however, parameter $index of PhpCsFixer\Tokenizer\Tok...r::findClassyElements() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

673
                                [$index, $newElements] = $this->findClassyElements($nestedClassIndex, /** @scrutinizer ignore-type */ $index);
Loading history...
674
                                $elements += $newElements;
675
676
                                break;
677
                            }
678
679
                            continue;
680
                        }
681
682
                        if ($token->isClassy()) { // anonymous class in class
683
                            [$index, $newElements] = $this->findClassyElements($index, $index);
0 ignored issues
show
Bug introduced by
It seems like $index can also be of type null; however, parameter $classIndex of PhpCsFixer\Tokenizer\Tok...r::findClassyElements() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

683
                            [$index, $newElements] = $this->findClassyElements(/** @scrutinizer ignore-type */ $index, $index);
Loading history...
684
                            $elements += $newElements;
685
                        }
686
                    }
687
                } else {
688
                    [$index, $newElements] = $this->findClassyElements($nestedClassIndex, $nestedClassIndex);
689
                    $elements += $newElements;
690
                }
691
692
                continue;
693
            }
694
695
            if ($token->equals('(')) {
696
                ++$bracesLevel;
697
698
                continue;
699
            }
700
701
            if ($token->equals(')')) {
702
                --$bracesLevel;
703
704
                continue;
705
            }
706
707
            if ($token->equals('{')) {
708
                ++$curlyBracesLevel;
709
710
                continue;
711
            }
712
713
            if ($token->equals('}')) {
714
                --$curlyBracesLevel;
715
716
                if (0 === $curlyBracesLevel) {
717
                    break;
718
                }
719
720
                continue;
721
            }
722
723
            if (1 !== $curlyBracesLevel || !$token->isArray()) {
724
                continue;
725
            }
726
727
            if (0 === $bracesLevel && $token->isGivenKind(T_VARIABLE)) {
728
                $elements[$index] = [
729
                    'token' => $token,
730
                    'type' => 'property',
731
                    'classIndex' => $classIndex,
732
                ];
733
734
                continue;
735
            }
736
737
            if ($token->isGivenKind(T_FUNCTION)) {
738
                $elements[$index] = [
739
                    'token' => $token,
740
                    'type' => 'method',
741
                    'classIndex' => $classIndex,
742
                ];
743
            } elseif ($token->isGivenKind(T_CONST)) {
744
                $elements[$index] = [
745
                    'token' => $token,
746
                    'type' => 'const',
747
                    'classIndex' => $classIndex,
748
                ];
749
            } elseif ($token->isGivenKind(CT::T_USE_TRAIT)) {
750
                $elements[$index] = [
751
                    'token' => $token,
752
                    'type' => 'trait_import',
753
                    'classIndex' => $classIndex,
754
                ];
755
            }
756
        }
757
758
        return [$index, $elements];
759
    }
760
}
761