YodaStyleFixer::fixTokensComparePart()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 6
c 1
b 0
f 0
dl 0
loc 9
rs 10
cc 1
nc 1
nop 3
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\Fixer\ControlStructure;
16
17
use PhpCsFixer\AbstractFixer;
18
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
19
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
20
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
21
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
22
use PhpCsFixer\FixerDefinition\CodeSample;
23
use PhpCsFixer\FixerDefinition\FixerDefinition;
24
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
25
use PhpCsFixer\Tokenizer\CT;
26
use PhpCsFixer\Tokenizer\Token;
27
use PhpCsFixer\Tokenizer\Tokens;
28
use PhpCsFixer\Tokenizer\TokensAnalyzer;
29
30
/**
31
 * @author Bram Gotink <[email protected]>
32
 * @author Dariusz Rumiński <[email protected]>
33
 * @author SpacePossum
34
 */
35
final class YodaStyleFixer extends AbstractFixer implements ConfigurableFixerInterface
36
{
37
    /**
38
     * @var array<int|string, Token>
39
     */
40
    private $candidatesMap;
41
42
    /**
43
     * @var array<int|string, null|bool>
44
     */
45
    private $candidateTypesConfiguration;
46
47
    /**
48
     * @var array<int|string>
49
     */
50
    private $candidateTypes;
51
52
    /**
53
     * {@inheritdoc}
54
     */
55
    public function configure(array $configuration): void
56
    {
57
        parent::configure($configuration);
58
59
        $this->resolveConfiguration();
60
    }
61
62
    /**
63
     * {@inheritdoc}
64
     */
65
    public function getDefinition(): FixerDefinitionInterface
66
    {
67
        return new FixerDefinition(
68
            'Write conditions in Yoda style (`true`), non-Yoda style (`[\'equal\' => false, \'identical\' => false, \'less_and_greater\' => false]`) or ignore those conditions (`null`) based on configuration.',
69
            [
70
                new CodeSample(
71
                    '<?php
72
    if ($a === null) {
73
        echo "null";
74
    }
75
'
76
                ),
77
                new CodeSample(
78
                    '<?php
79
    $b = $c != 1;  // equal
80
    $a = 1 === $b; // identical
81
    $c = $c > 3;   // less than
82
',
83
                    [
84
                        'equal' => true,
85
                        'identical' => false,
86
                        'less_and_greater' => null,
87
                    ]
88
                ),
89
                new CodeSample(
90
                    '<?php
91
return $foo === count($bar);
92
',
93
                    [
94
                        'always_move_variable' => true,
95
                    ]
96
                ),
97
                new CodeSample(
98
                    '<?php
99
    // Enforce non-Yoda style.
100
    if (null === $a) {
101
        echo "null";
102
    }
103
',
104
                    [
105
                        'equal' => false,
106
                        'identical' => false,
107
                        'less_and_greater' => false,
108
                    ]
109
                ),
110
            ]
111
        );
112
    }
113
114
    /**
115
     * {@inheritdoc}
116
     *
117
     * Must run after IsNullFixer.
118
     */
119
    public function getPriority(): int
120
    {
121
        return 0;
122
    }
123
124
    /**
125
     * {@inheritdoc}
126
     */
127
    public function isCandidate(Tokens $tokens): bool
128
    {
129
        return $tokens->isAnyTokenKindsFound($this->candidateTypes);
130
    }
131
132
    /**
133
     * {@inheritdoc}
134
     */
135
    protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
136
    {
137
        $this->fixTokens($tokens);
138
    }
139
140
    /**
141
     * {@inheritdoc}
142
     */
143
    protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
144
    {
145
        return new FixerConfigurationResolver([
146
            (new FixerOptionBuilder('equal', 'Style for equal (`==`, `!=`) statements.'))
147
                ->setAllowedTypes(['bool', 'null'])
148
                ->setDefault(true)
149
                ->getOption(),
150
            (new FixerOptionBuilder('identical', 'Style for identical (`===`, `!==`) statements.'))
151
                ->setAllowedTypes(['bool', 'null'])
152
                ->setDefault(true)
153
                ->getOption(),
154
            (new FixerOptionBuilder('less_and_greater', 'Style for less and greater than (`<`, `<=`, `>`, `>=`) statements.'))
155
                ->setAllowedTypes(['bool', 'null'])
156
                ->setDefault(null)
157
                ->getOption(),
158
            (new FixerOptionBuilder('always_move_variable', 'Whether variables should always be on non assignable side when applying Yoda style.'))
159
                ->setAllowedTypes(['bool'])
160
                ->setDefault(false)
161
                ->getOption(),
162
        ]);
163
    }
164
165
    /**
166
     * Finds the end of the right-hand side of the comparison at the given
167
     * index.
168
     *
169
     * The right-hand side ends when an operator with a lower precedence is
170
     * encountered or when the block level for `()`, `{}` or `[]` goes below
171
     * zero.
172
     *
173
     * @param Tokens $tokens The token list
174
     * @param int    $index  The index of the comparison
175
     *
176
     * @return int The last index of the right-hand side of the comparison
177
     */
178
    private function findComparisonEnd(Tokens $tokens, int $index): int
179
    {
180
        ++$index;
181
        $count = \count($tokens);
182
        while ($index < $count) {
183
            $token = $tokens[$index];
184
            if ($token->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) {
185
                ++$index;
186
187
                continue;
188
            }
189
190
            if ($this->isOfLowerPrecedence($token)) {
191
                break;
192
            }
193
194
            $block = Tokens::detectBlockType($token);
195
            if (null === $block) {
196
                ++$index;
197
198
                continue;
199
            }
200
201
            if (!$block['isStart']) {
202
                break;
203
            }
204
205
            $index = $tokens->findBlockEnd($block['type'], $index) + 1;
206
        }
207
208
        $prev = $tokens->getPrevMeaningfulToken($index);
209
210
        return $tokens[$prev]->isGivenKind(T_CLOSE_TAG) ? $tokens->getPrevMeaningfulToken($prev) : $prev;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $tokens[$prev]->i...fulToken($prev) : $prev could return the type null which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
Bug introduced by
It seems like $prev 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

210
        return $tokens[$prev]->isGivenKind(T_CLOSE_TAG) ? $tokens->getPrevMeaningfulToken(/** @scrutinizer ignore-type */ $prev) : $prev;
Loading history...
211
    }
212
213
    /**
214
     * Finds the start of the left-hand side of the comparison at the given
215
     * index.
216
     *
217
     * The left-hand side ends when an operator with a lower precedence is
218
     * encountered or when the block level for `()`, `{}` or `[]` goes below
219
     * zero.
220
     *
221
     * @param Tokens $tokens The token list
222
     * @param int    $index  The index of the comparison
223
     *
224
     * @return int The first index of the left-hand side of the comparison
225
     */
226
    private function findComparisonStart(Tokens $tokens, int $index): int
227
    {
228
        --$index;
229
        $nonBlockFound = false;
230
231
        while (0 <= $index) {
232
            $token = $tokens[$index];
233
            if ($token->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) {
234
                --$index;
235
236
                continue;
237
            }
238
239
            if ($this->isOfLowerPrecedence($token)) {
240
                break;
241
            }
242
243
            $block = Tokens::detectBlockType($token);
244
            if (null === $block) {
245
                --$index;
246
                $nonBlockFound = true;
247
248
                continue;
249
            }
250
251
            if (
252
                $block['isStart']
253
                || ($nonBlockFound && Tokens::BLOCK_TYPE_CURLY_BRACE === $block['type']) // closing of structure not related to the comparison
254
            ) {
255
                break;
256
            }
257
258
            $index = $tokens->findBlockStart($block['type'], $index) - 1;
259
        }
260
261
        return $tokens->getNextMeaningfulToken($index);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $tokens->getNextMeaningfulToken($index) could return the type null which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
262
    }
263
264
    private function fixTokens(Tokens $tokens): Tokens
265
    {
266
        for ($i = \count($tokens) - 1; $i > 1; --$i) {
267
            if ($tokens[$i]->isGivenKind($this->candidateTypes)) {
268
                $yoda = $this->candidateTypesConfiguration[$tokens[$i]->getId()];
269
            } elseif (
270
                ($tokens[$i]->equals('<') && \in_array('<', $this->candidateTypes, true))
271
                || ($tokens[$i]->equals('>') && \in_array('>', $this->candidateTypes, true))
272
            ) {
273
                $yoda = $this->candidateTypesConfiguration[$tokens[$i]->getContent()];
274
            } else {
275
                continue;
276
            }
277
278
            $fixableCompareInfo = $this->getCompareFixableInfo($tokens, $i, $yoda);
0 ignored issues
show
Bug introduced by
It seems like $yoda can also be of type null; however, parameter $yoda of PhpCsFixer\Fixer\Control...getCompareFixableInfo() does only seem to accept boolean, 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

278
            $fixableCompareInfo = $this->getCompareFixableInfo($tokens, $i, /** @scrutinizer ignore-type */ $yoda);
Loading history...
279
            if (null === $fixableCompareInfo) {
280
                continue;
281
            }
282
283
            $i = $this->fixTokensCompare(
284
                $tokens,
285
                $fixableCompareInfo['left']['start'],
286
                $fixableCompareInfo['left']['end'],
0 ignored issues
show
Bug introduced by
It seems like $fixableCompareInfo['left']['end'] can also be of type null; however, parameter $endLeft of PhpCsFixer\Fixer\Control...xer::fixTokensCompare() 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

286
                /** @scrutinizer ignore-type */ $fixableCompareInfo['left']['end'],
Loading history...
287
                $i,
288
                $fixableCompareInfo['right']['start'],
0 ignored issues
show
Bug introduced by
It seems like $fixableCompareInfo['right']['start'] can also be of type null; however, parameter $startRight of PhpCsFixer\Fixer\Control...xer::fixTokensCompare() 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

288
                /** @scrutinizer ignore-type */ $fixableCompareInfo['right']['start'],
Loading history...
289
                $fixableCompareInfo['right']['end']
290
            );
291
        }
292
293
        return $tokens;
294
    }
295
296
    /**
297
     * Fixes the comparison at the given index.
298
     *
299
     * A comparison is considered fixed when
300
     * - both sides are a variable (e.g. $a === $b)
301
     * - neither side is a variable (e.g. self::CONST === 3)
302
     * - only the right-hand side is a variable (e.g. 3 === self::$var)
303
     *
304
     * If the left-hand side and right-hand side of the given comparison are
305
     * swapped, this function runs recursively on the previous left-hand-side.
306
     *
307
     * @return int a upper bound for all non-fixed comparisons
308
     */
309
    private function fixTokensCompare(
310
        Tokens $tokens,
311
        int $startLeft,
312
        int $endLeft,
313
        int $compareOperatorIndex,
314
        int $startRight,
315
        int $endRight
316
    ): int {
317
        $type = $tokens[$compareOperatorIndex]->getId();
318
        $content = $tokens[$compareOperatorIndex]->getContent();
319
        if (\array_key_exists($type, $this->candidatesMap)) {
320
            $tokens[$compareOperatorIndex] = clone $this->candidatesMap[$type];
321
        } elseif (\array_key_exists($content, $this->candidatesMap)) {
322
            $tokens[$compareOperatorIndex] = clone $this->candidatesMap[$content];
323
        }
324
325
        $right = $this->fixTokensComparePart($tokens, $startRight, $endRight);
326
        $left = $this->fixTokensComparePart($tokens, $startLeft, $endLeft);
327
328
        for ($i = $startRight; $i <= $endRight; ++$i) {
329
            $tokens->clearAt($i);
330
        }
331
332
        for ($i = $startLeft; $i <= $endLeft; ++$i) {
333
            $tokens->clearAt($i);
334
        }
335
336
        $tokens->insertAt($startRight, $left);
337
        $tokens->insertAt($startLeft, $right);
338
339
        return $startLeft;
340
    }
341
342
    private function fixTokensComparePart(Tokens $tokens, int $start, int $end): Tokens
343
    {
344
        $newTokens = $tokens->generatePartialCode($start, $end);
345
        $newTokens = $this->fixTokens(Tokens::fromCode(sprintf('<?php %s;', $newTokens)));
346
        $newTokens->clearAt(\count($newTokens) - 1);
347
        $newTokens->clearAt(0);
348
        $newTokens->clearEmptyTokens();
349
350
        return $newTokens;
351
    }
352
353
    private function getCompareFixableInfo(Tokens $tokens, int $index, bool $yoda): ?array
354
    {
355
        $left = $this->getLeftSideCompareFixableInfo($tokens, $index);
356
        $right = $this->getRightSideCompareFixableInfo($tokens, $index);
357
358
        if (!$yoda && $this->isOfLowerPrecedenceAssignment($tokens[$tokens->getNextMeaningfulToken($right['end'])])) {
359
            return null;
360
        }
361
362
        if ($this->isListStatement($tokens, $left['start'], $left['end']) || $this->isListStatement($tokens, $right['start'], $right['end'])) {
0 ignored issues
show
Bug introduced by
It seems like $left['end'] can also be of type null; however, parameter $end of PhpCsFixer\Fixer\Control...ixer::isListStatement() 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

362
        if ($this->isListStatement($tokens, $left['start'], /** @scrutinizer ignore-type */ $left['end']) || $this->isListStatement($tokens, $right['start'], $right['end'])) {
Loading history...
Bug introduced by
It seems like $right['start'] can also be of type null; however, parameter $index of PhpCsFixer\Fixer\Control...ixer::isListStatement() 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

362
        if ($this->isListStatement($tokens, $left['start'], $left['end']) || $this->isListStatement($tokens, /** @scrutinizer ignore-type */ $right['start'], $right['end'])) {
Loading history...
363
            return null; // do not fix lists assignment inside statements
364
        }
365
366
        $strict = $this->configuration['always_move_variable'];
367
368
        $leftSideIsVariable = $this->isVariable($tokens, $left['start'], $left['end'], $strict);
0 ignored issues
show
Bug introduced by
It seems like $left['end'] can also be of type null; however, parameter $end of PhpCsFixer\Fixer\Control...tyleFixer::isVariable() 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

368
        $leftSideIsVariable = $this->isVariable($tokens, $left['start'], /** @scrutinizer ignore-type */ $left['end'], $strict);
Loading history...
369
        $rightSideIsVariable = $this->isVariable($tokens, $right['start'], $right['end'], $strict);
0 ignored issues
show
Bug introduced by
It seems like $right['start'] can also be of type null; however, parameter $start of PhpCsFixer\Fixer\Control...tyleFixer::isVariable() 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

369
        $rightSideIsVariable = $this->isVariable($tokens, /** @scrutinizer ignore-type */ $right['start'], $right['end'], $strict);
Loading history...
370
371
        if (!($leftSideIsVariable ^ $rightSideIsVariable)) {
372
            return null; // both are (not) variables, do not touch
373
        }
374
375
        if (!$strict) { // special handling for braces with not "always_move_variable"
376
            $leftSideIsVariable = $leftSideIsVariable && !$tokens[$left['start']]->equals('(');
377
            $rightSideIsVariable = $rightSideIsVariable && !$tokens[$right['start']]->equals('(');
378
        }
379
380
        return ($yoda && !$leftSideIsVariable) || (!$yoda && !$rightSideIsVariable)
381
            ? null
382
            : ['left' => $left, 'right' => $right]
383
        ;
384
    }
385
386
    private function getLeftSideCompareFixableInfo(Tokens $tokens, int $index): array
387
    {
388
        return [
389
            'start' => $this->findComparisonStart($tokens, $index),
390
            'end' => $tokens->getPrevMeaningfulToken($index),
391
        ];
392
    }
393
394
    private function getRightSideCompareFixableInfo(Tokens $tokens, int $index): array
395
    {
396
        return [
397
            'start' => $tokens->getNextMeaningfulToken($index),
398
            'end' => $this->findComparisonEnd($tokens, $index),
399
        ];
400
    }
401
402
    private function isListStatement(Tokens $tokens, int $index, int $end): bool
403
    {
404
        for ($i = $index; $i <= $end; ++$i) {
405
            if ($tokens[$i]->isGivenKind([T_LIST, CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE])) {
406
                return true;
407
            }
408
        }
409
410
        return false;
411
    }
412
413
    /**
414
     * Checks whether the given token has a lower precedence than `T_IS_EQUAL`
415
     * or `T_IS_IDENTICAL`.
416
     *
417
     * @param Token $token The token to check
418
     *
419
     * @return bool Whether the token has a lower precedence
420
     */
421
    private function isOfLowerPrecedence(Token $token): bool
422
    {
423
        static $tokens;
424
425
        if (null === $tokens) {
426
            $tokens = [
427
                T_BOOLEAN_AND,  // &&
428
                T_BOOLEAN_OR,   // ||
429
                T_CASE,         // case
430
                T_DOUBLE_ARROW, // =>
431
                T_ECHO,         // echo
432
                T_GOTO,         // goto
433
                T_LOGICAL_AND,  // and
434
                T_LOGICAL_OR,   // or
435
                T_LOGICAL_XOR,  // xor
436
                T_OPEN_TAG,     // <?php
437
                T_OPEN_TAG_WITH_ECHO,
438
                T_PRINT,        // print
439
                T_RETURN,       // return
440
                T_THROW,        // throw
441
                T_COALESCE,
442
                T_YIELD,        // yield
443
            ];
444
        }
445
446
        static $otherTokens = [
447
            // bitwise and, or, xor
448
            '&', '|', '^',
449
            // ternary operators
450
            '?', ':',
451
            // end of PHP statement
452
            ',', ';',
453
        ];
454
455
        return $this->isOfLowerPrecedenceAssignment($token) || $token->isGivenKind($tokens) || $token->equalsAny($otherTokens);
456
    }
457
458
    /**
459
     * Checks whether the given assignment token has a lower precedence than `T_IS_EQUAL`
460
     * or `T_IS_IDENTICAL`.
461
     */
462
    private function isOfLowerPrecedenceAssignment(Token $token)
463
    {
464
        static $tokens;
465
466
        if (null === $tokens) {
467
            $tokens = [
468
                T_AND_EQUAL,    // &=
469
                T_CONCAT_EQUAL, // .=
470
                T_DIV_EQUAL,    // /=
471
                T_MINUS_EQUAL,  // -=
472
                T_MOD_EQUAL,    // %=
473
                T_MUL_EQUAL,    // *=
474
                T_OR_EQUAL,     // |=
475
                T_PLUS_EQUAL,   // +=
476
                T_POW_EQUAL,    // **=
477
                T_SL_EQUAL,     // <<=
478
                T_SR_EQUAL,     // >>=
479
                T_XOR_EQUAL,    // ^=
480
            ];
481
482
            // @TODO: drop condition when PHP 7.4+ is required
483
            if (\defined('T_COALESCE_EQUAL')) {
484
                $tokens[] = T_COALESCE_EQUAL; // ??=
485
            }
486
        }
487
488
        return $token->equals('=') || $token->isGivenKind($tokens);
489
    }
490
491
    /**
492
     * Checks whether the tokens between the given start and end describe a
493
     * variable.
494
     *
495
     * @param Tokens $tokens The token list
496
     * @param int    $start  The first index of the possible variable
497
     * @param int    $end    The last index of the possible variable
498
     * @param bool   $strict Enable strict variable detection
499
     *
500
     * @return bool Whether the tokens describe a variable
501
     */
502
    private function isVariable(Tokens $tokens, int $start, int $end, bool $strict): bool
503
    {
504
        $tokenAnalyzer = new TokensAnalyzer($tokens);
505
506
        if ($start === $end) {
507
            return $tokens[$start]->isGivenKind(T_VARIABLE);
508
        }
509
510
        if ($tokens[$start]->equals('(')) {
511
            return true;
512
        }
513
514
        if ($strict) {
515
            for ($index = $start; $index <= $end; ++$index) {
516
                if (
517
                    $tokens[$index]->isCast()
518
                    || $tokens[$index]->isGivenKind(T_INSTANCEOF)
519
                    || $tokens[$index]->equals('!')
520
                    || $tokenAnalyzer->isBinaryOperator($index)
521
                ) {
522
                    return false;
523
                }
524
            }
525
        }
526
527
        $index = $start;
528
529
        // handle multiple braces around statement ((($a === 1)))
530
        while (
531
            $tokens[$index]->equals('(')
532
            && $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index) === $end
533
        ) {
534
            $index = $tokens->getNextMeaningfulToken($index);
535
            $end = $tokens->getPrevMeaningfulToken($end);
536
        }
537
538
        $expectString = false;
539
        while ($index <= $end) {
540
            $current = $tokens[$index];
541
            if ($current->isComment() || $current->isWhitespace() || $tokens->isEmptyAt($index)) {
0 ignored issues
show
Bug introduced by
It seems like $index can also be of type null; however, parameter $index of PhpCsFixer\Tokenizer\Tokens::isEmptyAt() 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

541
            if ($current->isComment() || $current->isWhitespace() || $tokens->isEmptyAt(/** @scrutinizer ignore-type */ $index)) {
Loading history...
542
                ++$index;
543
544
                continue;
545
            }
546
547
            // check if this is the last token
548
            if ($index === $end) {
549
                return $current->isGivenKind($expectString ? T_STRING : T_VARIABLE);
550
            }
551
552
            if ($current->isGivenKind([T_LIST, CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE])) {
553
                return false;
554
            }
555
556
            $nextIndex = $tokens->getNextMeaningfulToken($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...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

556
            $nextIndex = $tokens->getNextMeaningfulToken(/** @scrutinizer ignore-type */ $index);
Loading history...
557
            $next = $tokens[$nextIndex];
558
559
            // self:: or ClassName::
560
            if ($current->isGivenKind(T_STRING) && $next->isGivenKind(T_DOUBLE_COLON)) {
561
                $index = $tokens->getNextMeaningfulToken($nextIndex);
562
563
                continue;
564
            }
565
566
            // \ClassName
567
            if ($current->isGivenKind(T_NS_SEPARATOR) && $next->isGivenKind(T_STRING)) {
568
                $index = $nextIndex;
569
570
                continue;
571
            }
572
573
            // ClassName\
574
            if ($current->isGivenKind(T_STRING) && $next->isGivenKind(T_NS_SEPARATOR)) {
575
                $index = $nextIndex;
576
577
                continue;
578
            }
579
580
            // $a-> or a-> (as in $b->a->c)
581
            if ($current->isGivenKind([T_STRING, T_VARIABLE]) && $next->isObjectOperator()) {
582
                $index = $tokens->getNextMeaningfulToken($nextIndex);
583
                $expectString = true;
584
585
                continue;
586
            }
587
588
            // $a[...], a[...] (as in $c->a[$b]), $a{...} or a{...} (as in $c->a{$b})
589
            if (
590
                $current->isGivenKind($expectString ? T_STRING : T_VARIABLE)
591
                && $next->equalsAny(['[', [CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN, '{']])
592
            ) {
593
                $index = $tokens->findBlockEnd(
594
                    $next->equals('[') ? Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE : Tokens::BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE,
595
                    $nextIndex
0 ignored issues
show
Bug introduced by
It seems like $nextIndex can also be of type null; however, parameter $searchIndex of PhpCsFixer\Tokenizer\Tokens::findBlockEnd() 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

595
                    /** @scrutinizer ignore-type */ $nextIndex
Loading history...
596
                );
597
598
                if ($index === $end) {
599
                    return true;
600
                }
601
602
                $index = $tokens->getNextMeaningfulToken($index);
603
604
                if (!$tokens[$index]->equalsAny(['[', [CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN, '{']]) && !$tokens[$index]->isObjectOperator()) {
605
                    return false;
606
                }
607
608
                $index = $tokens->getNextMeaningfulToken($index);
609
                $expectString = true;
610
611
                continue;
612
            }
613
614
            // $a(...) or $a->b(...)
615
            if ($strict && $current->isGivenKind([T_STRING, T_VARIABLE]) && $next->equals('(')) {
616
                return false;
617
            }
618
619
            // {...} (as in $a->{$b})
620
            if ($expectString && $current->isGivenKind(CT::T_DYNAMIC_PROP_BRACE_OPEN)) {
621
                $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_DYNAMIC_PROP_BRACE, $index);
622
                if ($index === $end) {
623
                    return true;
624
                }
625
626
                $index = $tokens->getNextMeaningfulToken($index);
627
628
                if (!$tokens[$index]->isObjectOperator()) {
629
                    return false;
630
                }
631
632
                $index = $tokens->getNextMeaningfulToken($index);
633
                $expectString = true;
634
635
                continue;
636
            }
637
638
            break;
639
        }
640
641
        return !$this->isConstant($tokens, $start, $end);
0 ignored issues
show
Bug introduced by
It seems like $end can also be of type null; however, parameter $end of PhpCsFixer\Fixer\Control...tyleFixer::isConstant() 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

641
        return !$this->isConstant($tokens, $start, /** @scrutinizer ignore-type */ $end);
Loading history...
642
    }
643
644
    private function isConstant(Tokens $tokens, int $index, int $end): bool
645
    {
646
        $expectArrayOnly = false;
647
        $expectNumberOnly = false;
648
        $expectNothing = false;
649
650
        for (; $index <= $end; ++$index) {
651
            $token = $tokens[$index];
652
653
            if ($token->isComment() || $token->isWhitespace()) {
654
                continue;
655
            }
656
657
            if ($expectNothing) {
658
                return false;
659
            }
660
661
            if ($expectArrayOnly) {
662
                if ($token->equalsAny(['(', ')', [CT::T_ARRAY_SQUARE_BRACE_CLOSE]])) {
663
                    continue;
664
                }
665
666
                return false;
667
            }
668
669
            if ($token->isGivenKind([T_ARRAY,  CT::T_ARRAY_SQUARE_BRACE_OPEN])) {
670
                $expectArrayOnly = true;
671
672
                continue;
673
            }
674
675
            if ($expectNumberOnly && !$token->isGivenKind([T_LNUMBER, T_DNUMBER])) {
676
                return false;
677
            }
678
679
            if ($token->equals('-')) {
680
                $expectNumberOnly = true;
681
682
                continue;
683
            }
684
685
            if (
686
                $token->isGivenKind([T_LNUMBER, T_DNUMBER, T_CONSTANT_ENCAPSED_STRING])
687
                || $token->equalsAny([[T_STRING, 'true'], [T_STRING, 'false'], [T_STRING, 'null']])
688
            ) {
689
                $expectNothing = true;
690
691
                continue;
692
            }
693
694
            return false;
695
        }
696
697
        return true;
698
    }
699
700
    private function resolveConfiguration(): void
701
    {
702
        $candidateTypes = [];
703
        $this->candidatesMap = [];
704
705
        if (null !== $this->configuration['equal']) {
706
            // `==`, `!=` and `<>`
707
            $candidateTypes[T_IS_EQUAL] = $this->configuration['equal'];
708
            $candidateTypes[T_IS_NOT_EQUAL] = $this->configuration['equal'];
709
        }
710
711
        if (null !== $this->configuration['identical']) {
712
            // `===` and `!==`
713
            $candidateTypes[T_IS_IDENTICAL] = $this->configuration['identical'];
714
            $candidateTypes[T_IS_NOT_IDENTICAL] = $this->configuration['identical'];
715
        }
716
717
        if (null !== $this->configuration['less_and_greater']) {
718
            // `<`, `<=`, `>` and `>=`
719
            $candidateTypes[T_IS_SMALLER_OR_EQUAL] = $this->configuration['less_and_greater'];
720
            $this->candidatesMap[T_IS_SMALLER_OR_EQUAL] = new Token([T_IS_GREATER_OR_EQUAL, '>=']);
721
722
            $candidateTypes[T_IS_GREATER_OR_EQUAL] = $this->configuration['less_and_greater'];
723
            $this->candidatesMap[T_IS_GREATER_OR_EQUAL] = new Token([T_IS_SMALLER_OR_EQUAL, '<=']);
724
725
            $candidateTypes['<'] = $this->configuration['less_and_greater'];
726
            $this->candidatesMap['<'] = new Token('>');
727
728
            $candidateTypes['>'] = $this->configuration['less_and_greater'];
729
            $this->candidatesMap['>'] = new Token('<');
730
        }
731
732
        $this->candidateTypesConfiguration = $candidateTypes;
733
        $this->candidateTypes = array_keys($candidateTypes);
734
    }
735
}
736