BracesFixer::findStatementEnd()   C
last analyzed

Complexity

Conditions 14
Paths 12

Size

Total Lines 59
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 32
c 1
b 0
f 0
dl 0
loc 59
rs 6.2666
cc 14
nc 12
nop 2

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
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\Basic;
16
17
use PhpCsFixer\AbstractProxyFixer;
18
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
19
use PhpCsFixer\Fixer\LanguageConstruct\DeclareParenthesesFixer;
20
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
21
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
22
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
23
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
24
use PhpCsFixer\FixerDefinition\CodeSample;
25
use PhpCsFixer\FixerDefinition\FixerDefinition;
26
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
27
use PhpCsFixer\Preg;
28
use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer;
29
use PhpCsFixer\Tokenizer\CT;
30
use PhpCsFixer\Tokenizer\Token;
31
use PhpCsFixer\Tokenizer\Tokens;
32
use PhpCsFixer\Tokenizer\TokensAnalyzer;
33
34
/**
35
 * Fixer for rules defined in PSR2 ¶4.1, ¶4.4, ¶5.
36
 *
37
 * @author Dariusz Rumiński <[email protected]>
38
 */
39
final class BracesFixer extends AbstractProxyFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
40
{
41
    /**
42
     * @internal
43
     */
44
    public const LINE_NEXT = 'next';
45
46
    /**
47
     * @internal
48
     */
49
    public const LINE_SAME = 'same';
50
51
    /**
52
     * {@inheritdoc}
53
     */
54
    public function getDefinition(): FixerDefinitionInterface
55
    {
56
        return new FixerDefinition(
57
            'The body of each structure MUST be enclosed by braces. Braces should be properly placed. Body of braces should be properly indented.',
58
            [
59
                new CodeSample(
60
                    '<?php
61
62
class Foo {
63
    public function bar($baz) {
64
        if ($baz = 900) echo "Hello!";
65
66
        if ($baz = 9000)
67
            echo "Wait!";
68
69
        if ($baz == true)
70
        {
71
            echo "Why?";
72
        }
73
        else
74
        {
75
            echo "Ha?";
76
        }
77
78
        if (is_array($baz))
79
            foreach ($baz as $b)
80
            {
81
                echo $b;
82
            }
83
    }
84
}
85
'
86
                ),
87
                new CodeSample(
88
                    '<?php
89
$positive = function ($item) { return $item >= 0; };
90
$negative = function ($item) {
91
                return $item < 0; };
92
',
93
                    ['allow_single_line_closure' => true]
94
                ),
95
                new CodeSample(
96
                    '<?php
97
98
class Foo
99
{
100
    public function bar($baz)
101
    {
102
        if ($baz = 900) echo "Hello!";
103
104
        if ($baz = 9000)
105
            echo "Wait!";
106
107
        if ($baz == true)
108
        {
109
            echo "Why?";
110
        }
111
        else
112
        {
113
            echo "Ha?";
114
        }
115
116
        if (is_array($baz))
117
            foreach ($baz as $b)
118
            {
119
                echo $b;
120
            }
121
    }
122
}
123
',
124
                    ['position_after_functions_and_oop_constructs' => self::LINE_SAME]
125
                ),
126
            ]
127
        );
128
    }
129
130
    /**
131
     * {@inheritdoc}
132
     *
133
     * Must run before ArrayIndentationFixer, MethodArgumentSpaceFixer, MethodChainingIndentationFixer.
134
     * Must run after ClassAttributesSeparationFixer, ClassDefinitionFixer, ElseifFixer, EmptyLoopBodyFixer, LineEndingFixer, NoAlternativeSyntaxFixer, NoEmptyStatementFixer, NoUselessElseFixer, SingleLineThrowFixer, SingleSpaceAfterConstructFixer, SingleTraitInsertPerStatementFixer.
135
     */
136
    public function getPriority(): int
137
    {
138
        return 35;
139
    }
140
141
    /**
142
     * {@inheritdoc}
143
     */
144
    public function isCandidate(Tokens $tokens): bool
145
    {
146
        return true;
147
    }
148
149
    /**
150
     * {@inheritdoc}
151
     */
152
    protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
153
    {
154
        $this->fixCommentBeforeBrace($tokens);
155
        $this->fixMissingControlBraces($tokens);
156
        $this->fixIndents($tokens);
157
        $this->fixControlContinuationBraces($tokens);
158
        $this->fixSpaceAroundToken($tokens);
159
        $this->fixDoWhile($tokens);
160
161
        parent::applyFix($file, $tokens);
162
    }
163
164
    /**
165
     * {@inheritdoc}
166
     */
167
    protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
168
    {
169
        return new FixerConfigurationResolver([
170
            (new FixerOptionBuilder('allow_single_line_anonymous_class_with_empty_body', 'Whether single line anonymous class with empty body notation should be allowed.'))
171
                ->setAllowedTypes(['bool'])
172
                ->setDefault(false)
173
                ->getOption(),
174
            (new FixerOptionBuilder('allow_single_line_closure', 'Whether single line lambda notation should be allowed.'))
175
                ->setAllowedTypes(['bool'])
176
                ->setDefault(false)
177
                ->getOption(),
178
            (new FixerOptionBuilder('position_after_functions_and_oop_constructs', 'whether the opening brace should be placed on "next" or "same" line after classy constructs (non-anonymous classes, interfaces, traits, methods and non-lambda functions).'))
179
                ->setAllowedValues([self::LINE_NEXT, self::LINE_SAME])
180
                ->setDefault(self::LINE_NEXT)
181
                ->getOption(),
182
            (new FixerOptionBuilder('position_after_control_structures', 'whether the opening brace should be placed on "next" or "same" line after control structures.'))
183
                ->setAllowedValues([self::LINE_NEXT, self::LINE_SAME])
184
                ->setDefault(self::LINE_SAME)
185
                ->getOption(),
186
            (new FixerOptionBuilder('position_after_anonymous_constructs', 'whether the opening brace should be placed on "next" or "same" line after anonymous constructs (anonymous classes and lambda functions).'))
187
                ->setAllowedValues([self::LINE_NEXT, self::LINE_SAME])
188
                ->setDefault(self::LINE_SAME)
189
                ->getOption(),
190
        ]);
191
    }
192
193
    protected function createProxyFixers(): array
194
    {
195
        return [
196
            new DeclareParenthesesFixer(),
197
        ];
198
    }
199
200
    private function fixCommentBeforeBrace(Tokens $tokens): void
201
    {
202
        $tokensAnalyzer = new TokensAnalyzer($tokens);
203
        $controlTokens = $this->getControlTokens();
204
205
        for ($index = $tokens->count() - 1; 0 <= $index; --$index) {
206
            $token = $tokens[$index];
207
208
            if ($token->isGivenKind($controlTokens)) {
209
                $prevIndex = $this->findParenthesisEnd($tokens, $index);
210
            } elseif (
211
                ($token->isGivenKind(T_FUNCTION) && $tokensAnalyzer->isLambda($index))
212
                || ($token->isGivenKind(T_CLASS) && $tokensAnalyzer->isAnonymousClass($index))
213
            ) {
214
                $prevIndex = $tokens->getNextTokenOfKind($index, ['{']);
215
                $prevIndex = $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

215
                $prevIndex = $tokens->getPrevMeaningfulToken(/** @scrutinizer ignore-type */ $prevIndex);
Loading history...
216
            } else {
217
                continue;
218
            }
219
220
            $commentIndex = $tokens->getNextNonWhitespace($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...:getNextNonWhitespace() 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

220
            $commentIndex = $tokens->getNextNonWhitespace(/** @scrutinizer ignore-type */ $prevIndex);
Loading history...
221
            $commentToken = $tokens[$commentIndex];
222
223
            if (!$commentToken->isGivenKind(T_COMMENT) || 0 === strpos($commentToken->getContent(), '/*')) {
224
                continue;
225
            }
226
227
            $braceIndex = $tokens->getNextMeaningfulToken($commentIndex);
0 ignored issues
show
Bug introduced by
It seems like $commentIndex 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

227
            $braceIndex = $tokens->getNextMeaningfulToken(/** @scrutinizer ignore-type */ $commentIndex);
Loading history...
228
            $braceToken = $tokens[$braceIndex];
229
230
            if (!$braceToken->equals('{')) {
231
                continue;
232
            }
233
234
            /** @var Token $tokenTmp */
235
            $tokenTmp = $tokens[$braceIndex];
236
237
            $newBraceIndex = $prevIndex + 1;
238
            for ($i = $braceIndex; $i > $newBraceIndex; --$i) {
239
                // we might be moving one white space next to another, these have to be merged
240
                /** @var Token $previousToken */
241
                $previousToken = $tokens[$i - 1];
242
                $tokens[$i] = $previousToken;
243
                if ($tokens[$i]->isWhitespace() && $tokens[$i + 1]->isWhitespace()) {
244
                    $tokens[$i] = new Token([T_WHITESPACE, $tokens[$i]->getContent().$tokens[$i + 1]->getContent()]);
245
                    $tokens->clearAt($i + 1);
246
                }
247
            }
248
249
            $tokens[$newBraceIndex] = $tokenTmp;
250
            $c = $tokens[$braceIndex]->getContent();
251
            if (substr_count($c, "\n") > 1) {
252
                // left trim till last line break
253
                $tokens[$braceIndex] = new Token([T_WHITESPACE, substr($c, strrpos($c, "\n"))]);
254
            }
255
        }
256
    }
257
258
    private function fixControlContinuationBraces(Tokens $tokens): void
259
    {
260
        $controlContinuationTokens = $this->getControlContinuationTokens();
261
262
        for ($index = \count($tokens) - 1; 0 <= $index; --$index) {
263
            $token = $tokens[$index];
264
265
            if (!$token->isGivenKind($controlContinuationTokens)) {
266
                continue;
267
            }
268
269
            $prevIndex = $tokens->getPrevNonWhitespace($index);
270
            $prevToken = $tokens[$prevIndex];
271
272
            if (!$prevToken->equals('}')) {
273
                continue;
274
            }
275
276
            $tokens->ensureWhitespaceAtIndex(
277
                $index - 1,
278
                1,
279
                self::LINE_NEXT === $this->configuration['position_after_control_structures'] ?
280
                    $this->whitespacesConfig->getLineEnding().WhitespacesAnalyzer::detectIndent($tokens, $index)
281
                    : ' '
282
            );
283
        }
284
    }
285
286
    private function fixDoWhile(Tokens $tokens): void
287
    {
288
        for ($index = \count($tokens) - 1; 0 <= $index; --$index) {
289
            $token = $tokens[$index];
290
291
            if (!$token->isGivenKind(T_DO)) {
292
                continue;
293
            }
294
295
            $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $index);
296
            $startBraceIndex = $tokens->getNextNonWhitespace($parenthesisEndIndex);
297
            $endBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $startBraceIndex);
0 ignored issues
show
Bug introduced by
It seems like $startBraceIndex 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

297
            $endBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, /** @scrutinizer ignore-type */ $startBraceIndex);
Loading history...
298
            $nextNonWhitespaceIndex = $tokens->getNextNonWhitespace($endBraceIndex);
299
            $nextNonWhitespaceToken = $tokens[$nextNonWhitespaceIndex];
300
301
            if (!$nextNonWhitespaceToken->isGivenKind(T_WHILE)) {
302
                continue;
303
            }
304
305
            $tokens->ensureWhitespaceAtIndex($nextNonWhitespaceIndex - 1, 1, ' ');
306
        }
307
    }
308
309
    private function fixIndents(Tokens $tokens): void
310
    {
311
        $classyTokens = Token::getClassyTokenKinds();
312
        $classyAndFunctionTokens = array_merge([T_FUNCTION], $classyTokens);
313
        $controlTokens = $this->getControlTokens();
314
        $indentTokens = array_filter(
315
            array_merge($classyAndFunctionTokens, $controlTokens),
316
            static function (int $item) {
317
                return T_SWITCH !== $item;
318
            }
319
        );
320
        $tokensAnalyzer = new TokensAnalyzer($tokens);
321
322
        for ($index = 0, $limit = \count($tokens); $index < $limit; ++$index) {
323
            $token = $tokens[$index];
324
325
            // if token is not a structure element - continue
326
            if (!$token->isGivenKind($indentTokens)) {
327
                continue;
328
            }
329
330
            // do not change indent for `while` in `do ... while ...`
331
            if (
332
                $token->isGivenKind(T_WHILE)
333
                && $tokensAnalyzer->isWhilePartOfDoWhile($index)
334
            ) {
335
                continue;
336
            }
337
338
            if (
339
                $this->configuration['allow_single_line_anonymous_class_with_empty_body']
340
                && $token->isGivenKind(T_CLASS)
341
            ) {
342
                $prevIndex = $tokens->getPrevMeaningfulToken($index);
343
                if ($tokens[$prevIndex]->isGivenKind(T_NEW)) {
344
                    $braceStartIndex = $tokens->getNextTokenOfKind($index, ['{']);
345
                    $braceEndIndex = $tokens->getNextMeaningfulToken($braceStartIndex);
0 ignored issues
show
Bug introduced by
It seems like $braceStartIndex 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

345
                    $braceEndIndex = $tokens->getNextMeaningfulToken(/** @scrutinizer ignore-type */ $braceStartIndex);
Loading history...
346
347
                    if ('}' === $tokens[$braceEndIndex]->getContent() && !$this->isMultilined($tokens, $index, $braceEndIndex)) {
0 ignored issues
show
Bug introduced by
It seems like $braceEndIndex can also be of type null; however, parameter $endParenthesisIndex of PhpCsFixer\Fixer\Basic\BracesFixer::isMultilined() 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

347
                    if ('}' === $tokens[$braceEndIndex]->getContent() && !$this->isMultilined($tokens, $index, /** @scrutinizer ignore-type */ $braceEndIndex)) {
Loading history...
348
                        $index = $braceEndIndex;
349
350
                        continue;
351
                    }
352
                }
353
            }
354
355
            if (
356
                $this->configuration['allow_single_line_closure']
357
                && $token->isGivenKind(T_FUNCTION)
358
                && $tokensAnalyzer->isLambda($index)
359
            ) {
360
                $braceEndIndex = $tokens->findBlockEnd(
361
                    Tokens::BLOCK_TYPE_CURLY_BRACE,
362
                    $tokens->getNextTokenOfKind($index, ['{'])
0 ignored issues
show
Bug introduced by
It seems like $tokens->getNextTokenOfKind($index, array('{')) 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

362
                    /** @scrutinizer ignore-type */ $tokens->getNextTokenOfKind($index, ['{'])
Loading history...
363
                );
364
365
                if (!$this->isMultilined($tokens, $index, $braceEndIndex)) {
366
                    $index = $braceEndIndex;
367
368
                    continue;
369
                }
370
            }
371
372
            if ($token->isGivenKind($classyAndFunctionTokens)) {
373
                $startBraceIndex = $tokens->getNextTokenOfKind($index, [';', '{']);
374
                $startBraceToken = $tokens[$startBraceIndex];
375
            } else {
376
                $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $index);
377
                $startBraceIndex = $tokens->getNextNonWhitespace($parenthesisEndIndex);
378
                $startBraceToken = $tokens[$startBraceIndex];
379
            }
380
381
            // structure without braces block - nothing to do, e.g. do { } while (true);
382
            if (!$startBraceToken->equals('{')) {
383
                continue;
384
            }
385
386
            $nextNonWhitespaceIndex = $tokens->getNextNonWhitespace($startBraceIndex, " \t");
0 ignored issues
show
Bug introduced by
It seems like $startBraceIndex can also be of type null; however, parameter $index of PhpCsFixer\Tokenizer\Tok...:getNextNonWhitespace() 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

386
            $nextNonWhitespaceIndex = $tokens->getNextNonWhitespace(/** @scrutinizer ignore-type */ $startBraceIndex, " \t");
Loading history...
387
            $nextNonWhitespace = $tokens[$nextNonWhitespaceIndex];
388
389
            /* if CLOSE_TAG is after { on the same line, do not indent. e.g. <?php if ($condition) { ?> */
390
            if ($nextNonWhitespace->isGivenKind(T_CLOSE_TAG)) {
391
                continue;
392
            }
393
394
            /* if CLOSE_TAG is after { on the next line and a comment on this line, do not indent. e.g. <?php if ($condition) { // \n?> */
395
            if ($nextNonWhitespace->isComment() && $tokens[$tokens->getNextMeaningfulToken($nextNonWhitespaceIndex)]->isGivenKind(T_CLOSE_TAG)) {
396
                continue;
397
            }
398
399
            $endBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $startBraceIndex);
400
401
            $indent = WhitespacesAnalyzer::detectIndent($tokens, $index);
402
403
            // fix indent near closing brace
404
            $tokens->ensureWhitespaceAtIndex($endBraceIndex - 1, 1, $this->whitespacesConfig->getLineEnding().$indent);
405
406
            // fix indent between braces
407
            $lastCommaIndex = $tokens->getPrevTokenOfKind($endBraceIndex - 1, [';', '}']);
408
409
            $nestLevel = 1;
410
            for ($nestIndex = $lastCommaIndex; $nestIndex >= $startBraceIndex; --$nestIndex) {
411
                $nestToken = $tokens[$nestIndex];
412
413
                if ($nestToken->equalsAny([')', [CT::T_BRACE_CLASS_INSTANTIATION_CLOSE]])) {
414
                    $nestIndex = $tokens->findBlockStart(
415
                        $nestToken->equals(')') ? Tokens::BLOCK_TYPE_PARENTHESIS_BRACE : Tokens::BLOCK_TYPE_BRACE_CLASS_INSTANTIATION,
416
                        $nestIndex
0 ignored issues
show
Bug introduced by
It seems like $nestIndex 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

416
                        /** @scrutinizer ignore-type */ $nestIndex
Loading history...
417
                    );
418
419
                    continue;
420
                }
421
422
                if (1 === $nestLevel) {
423
                    // Next token is the beginning of a line that can be indented when
424
                    // the current token is a `;`, a `}` or the opening `{` of current
425
                    // scope. Current token may also be a comment that follows `;` or
426
                    // `}`, in which case indentation will only be fixed if this
427
                    // comment is followed by a newline.
428
                    $nextLineCanBeIndented = false;
429
                    if ($nestToken->equalsAny([';', '}'])) {
430
                        $nextLineCanBeIndented = true;
431
                    } elseif ($this->isCommentWithFixableIndentation($tokens, $nestIndex)) {
0 ignored issues
show
Bug introduced by
It seems like $nestIndex can also be of type null; however, parameter $index of PhpCsFixer\Fixer\Basic\B...ithFixableIndentation() 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

431
                    } elseif ($this->isCommentWithFixableIndentation($tokens, /** @scrutinizer ignore-type */ $nestIndex)) {
Loading history...
432
                        for ($i = $nestIndex; $i > $startBraceIndex; --$i) {
433
                            if ($tokens[$i]->equalsAny([';', '}'])) {
434
                                $nextLineCanBeIndented = true;
435
436
                                break;
437
                            }
438
439
                            if (!$tokens[$i]->isWhitespace() && !$tokens[$i]->isComment()) {
440
                                break;
441
                            }
442
                        }
443
444
                        if ($nextLineCanBeIndented || $i === $startBraceIndex) {
445
                            $nextToken = $tokens[$nestIndex + 1];
446
                            $nextLineCanBeIndented = $nextToken->isWhitespace() && 1 === Preg::match('/\R/', $nextToken->getContent());
447
                        }
448
                    }
449
450
                    if (!$nextLineCanBeIndented) {
451
                        continue;
452
                    }
453
454
                    $nextNonWhitespaceNestIndex = $tokens->getNextNonWhitespace($nestIndex);
455
                    $nextNonWhitespaceNestToken = $tokens[$nextNonWhitespaceNestIndex];
456
457
                    if (
458
                        // next Token is not a comment on its own line
459
                        !($nextNonWhitespaceNestToken->isComment() && (
460
                            !$tokens[$nextNonWhitespaceNestIndex - 1]->isWhitespace()
461
                            || !Preg::match('/\R/', $tokens[$nextNonWhitespaceNestIndex - 1]->getContent())
462
                        ))
463
                        // and it is not a `$foo = function () {};` situation
464
                        && !($nestToken->equals('}') && $nextNonWhitespaceNestToken->equalsAny([';', ',', ']', [CT::T_ARRAY_SQUARE_BRACE_CLOSE]]))
465
                        // and it is not a `Foo::{bar}()` situation
466
                        && !($nestToken->equals('}') && $nextNonWhitespaceNestToken->equals('('))
467
                        // and it is not a `${"a"}->...` and `${"b{$foo}"}->...` situation
468
                        && !($nestToken->equals('}') && $tokens[$nestIndex - 1]->equalsAny(['"', "'", [T_CONSTANT_ENCAPSED_STRING], [T_VARIABLE]]))
469
                        // and next token is not a closing tag that would break heredoc/nowdoc syntax
470
                        && !($tokens[$nestIndex - 1]->isGivenKind(T_END_HEREDOC) && $nextNonWhitespaceNestToken->isGivenKind(T_CLOSE_TAG))
471
                    ) {
472
                        if (
473
                            (
474
                                self::LINE_NEXT !== $this->configuration['position_after_control_structures']
475
                                && $nextNonWhitespaceNestToken->isGivenKind($this->getControlContinuationTokens())
476
                                && !$tokens[$tokens->getPrevNonWhitespace($nextNonWhitespaceNestIndex)]->isComment()
0 ignored issues
show
Bug introduced by
It seems like $nextNonWhitespaceNestIndex can also be of type null; however, parameter $index of PhpCsFixer\Tokenizer\Tok...:getPrevNonWhitespace() 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

476
                                && !$tokens[$tokens->getPrevNonWhitespace(/** @scrutinizer ignore-type */ $nextNonWhitespaceNestIndex)]->isComment()
Loading history...
477
                            )
478
                            || $nextNonWhitespaceNestToken->isGivenKind(T_CLOSE_TAG)
479
                            || (
480
                                self::LINE_NEXT !== $this->configuration['position_after_control_structures']
481
                                && $nextNonWhitespaceNestToken->isGivenKind(T_WHILE)
482
                                && $tokensAnalyzer->isWhilePartOfDoWhile($nextNonWhitespaceNestIndex)
0 ignored issues
show
Bug introduced by
It seems like $nextNonWhitespaceNestIndex can also be of type null; however, parameter $index of PhpCsFixer\Tokenizer\Tok...:isWhilePartOfDoWhile() 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

482
                                && $tokensAnalyzer->isWhilePartOfDoWhile(/** @scrutinizer ignore-type */ $nextNonWhitespaceNestIndex)
Loading history...
483
                            )
484
                        ) {
485
                            $whitespace = ' ';
486
                        } else {
487
                            $nextToken = $tokens[$nestIndex + 1];
488
                            $nextWhitespace = '';
489
490
                            if ($nextToken->isWhitespace()) {
491
                                $nextWhitespace = rtrim($nextToken->getContent(), " \t");
492
493
                                if ('' !== $nextWhitespace) {
494
                                    $nextWhitespace = Preg::replace(
495
                                        sprintf('/%s$/', $this->whitespacesConfig->getLineEnding()),
496
                                        '',
497
                                        $nextWhitespace,
498
                                        1
499
                                    );
500
                                }
501
                            }
502
503
                            $whitespace = $nextWhitespace.$this->whitespacesConfig->getLineEnding().$indent;
504
505
                            if (!$nextNonWhitespaceNestToken->equals('}')) {
506
                                $determineIsIndentableBlockContent = static function (int $contentIndex) use ($tokens): bool {
507
                                    if (!$tokens[$contentIndex]->isComment()) {
508
                                        return true;
509
                                    }
510
511
                                    if (!$tokens[$tokens->getPrevMeaningfulToken($contentIndex)]->equals(';')) {
512
                                        return true;
513
                                    }
514
515
                                    $nextIndex = $tokens->getNextMeaningfulToken($contentIndex);
516
517
                                    if (!$tokens[$nextIndex]->equals('}')) {
518
                                        return true;
519
                                    }
520
521
                                    $nextNextIndex = $tokens->getNextMeaningfulToken($nextIndex);
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

521
                                    $nextNextIndex = $tokens->getNextMeaningfulToken(/** @scrutinizer ignore-type */ $nextIndex);
Loading history...
522
523
                                    if (null === $nextNextIndex) {
524
                                        return true;
525
                                    }
526
527
                                    if ($tokens[$nextNextIndex]->equalsAny([
528
                                        [T_ELSE],
529
                                        [T_ELSEIF],
530
                                        ',',
531
                                    ])) {
532
                                        return false;
533
                                    }
534
535
                                    return true;
536
                                };
537
538
                                // add extra indent only if current content is not a comment for content outside of current block
539
                                if ($determineIsIndentableBlockContent($nestIndex + 2)) {
540
                                    $whitespace .= $this->whitespacesConfig->getIndent();
541
                                }
542
                            }
543
                        }
544
545
                        $this->ensureWhitespaceAtIndexAndIndentMultilineComment($tokens, $nestIndex + 1, $whitespace);
546
                    }
547
                }
548
549
                if ($nestToken->equals('}')) {
550
                    ++$nestLevel;
551
552
                    continue;
553
                }
554
555
                if ($nestToken->equals('{')) {
556
                    --$nestLevel;
557
558
                    continue;
559
                }
560
            }
561
562
            // fix indent near opening brace
563
            if (isset($tokens[$startBraceIndex + 2]) && $tokens[$startBraceIndex + 2]->equals('}')) {
564
                $tokens->ensureWhitespaceAtIndex($startBraceIndex + 1, 0, $this->whitespacesConfig->getLineEnding().$indent);
565
            } else {
566
                $nextToken = $tokens[$startBraceIndex + 1];
567
                $nextNonWhitespaceToken = $tokens[$tokens->getNextNonWhitespace($startBraceIndex)];
568
569
                // set indent only if it is not a case, when comment is following { on same line
570
                if (
571
                    !$nextNonWhitespaceToken->isComment()
572
                    || ($nextToken->isWhitespace() && 1 === substr_count($nextToken->getContent(), "\n")) // preserve blank lines
573
                ) {
574
                    $this->ensureWhitespaceAtIndexAndIndentMultilineComment(
575
                        $tokens,
576
                        $startBraceIndex + 1,
577
                        $this->whitespacesConfig->getLineEnding().$indent.$this->whitespacesConfig->getIndent()
578
                    );
579
                }
580
            }
581
582
            if ($token->isGivenKind($classyTokens) && !$tokensAnalyzer->isAnonymousClass($index)) {
583
                if (self::LINE_SAME === $this->configuration['position_after_functions_and_oop_constructs'] && !$tokens[$tokens->getPrevNonWhitespace($startBraceIndex)]->isComment()) {
584
                    $ensuredWhitespace = ' ';
585
                } else {
586
                    $ensuredWhitespace = $this->whitespacesConfig->getLineEnding().$indent;
587
                }
588
589
                $tokens->ensureWhitespaceAtIndex($startBraceIndex - 1, 1, $ensuredWhitespace);
590
            } elseif (
591
                $token->isGivenKind(T_FUNCTION) && !$tokensAnalyzer->isLambda($index)
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($token->isGivenKind(Php...sAnonymousClass($index), Probably Intended Meaning: $token->isGivenKind(PhpC...AnonymousClass($index))
Loading history...
592
                || (
593
                    self::LINE_NEXT === $this->configuration['position_after_control_structures'] && $token->isGivenKind($controlTokens)
594
                    || (
595
                        self::LINE_NEXT === $this->configuration['position_after_anonymous_constructs']
596
                        && (
597
                            $token->isGivenKind(T_FUNCTION) && $tokensAnalyzer->isLambda($index)
598
                            || $token->isGivenKind(T_CLASS) && $tokensAnalyzer->isAnonymousClass($index)
599
                        )
600
                    )
601
                )
602
            ) {
603
                $isAnonymousClass = $token->isGivenKind($classyTokens) && $tokensAnalyzer->isAnonymousClass($index);
604
605
                $closingParenthesisIndex = $tokens->getPrevTokenOfKind($startBraceIndex, [')']);
606
                if (null === $closingParenthesisIndex && !$isAnonymousClass) {
607
                    continue;
608
                }
609
610
                if (
611
                    !$isAnonymousClass
612
                    && $tokens[$closingParenthesisIndex - 1]->isWhitespace()
613
                    && false !== strpos($tokens[$closingParenthesisIndex - 1]->getContent(), "\n")
614
                ) {
615
                    if (!$tokens[$startBraceIndex - 2]->isComment()) {
616
                        $tokens->ensureWhitespaceAtIndex($startBraceIndex - 1, 1, ' ');
617
                    }
618
                } else {
619
                    if (
620
                        self::LINE_SAME === $this->configuration['position_after_functions_and_oop_constructs']
621
                        && (
622
                            $token->isGivenKind(T_FUNCTION) && !$tokensAnalyzer->isLambda($index)
623
                            || $token->isGivenKind($classyTokens) && !$tokensAnalyzer->isAnonymousClass($index)
624
                        )
625
                        && !$tokens[$tokens->getPrevNonWhitespace($startBraceIndex)]->isComment()
626
                    ) {
627
                        $ensuredWhitespace = ' ';
628
                    } else {
629
                        $ensuredWhitespace = $this->whitespacesConfig->getLineEnding().$indent;
630
                    }
631
632
                    $tokens->ensureWhitespaceAtIndex($startBraceIndex - 1, 1, $ensuredWhitespace);
633
                }
634
            } else {
635
                $tokens->ensureWhitespaceAtIndex($startBraceIndex - 1, 1, ' ');
636
            }
637
638
            // reset loop limit due to collection change
639
            $limit = \count($tokens);
640
        }
641
    }
642
643
    private function fixMissingControlBraces(Tokens $tokens): void
644
    {
645
        $controlTokens = $this->getControlTokens();
646
647
        for ($index = $tokens->count() - 1; 0 <= $index; --$index) {
648
            $token = $tokens[$index];
649
650
            if (!$token->isGivenKind($controlTokens)) {
651
                continue;
652
            }
653
654
            $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $index);
655
            $nextAfterParenthesisEndIndex = $tokens->getNextMeaningfulToken($parenthesisEndIndex);
656
            $tokenAfterParenthesis = $tokens[$nextAfterParenthesisEndIndex];
657
658
            // if Token after parenthesis is { then we do not need to insert brace, but to fix whitespace before it
659
            if ($tokenAfterParenthesis->equals('{') && self::LINE_SAME === $this->configuration['position_after_control_structures']) {
660
                $tokens->ensureWhitespaceAtIndex($parenthesisEndIndex + 1, 0, ' ');
661
662
                continue;
663
            }
664
665
            // do not add braces for cases:
666
            // - structure without block, e.g. while ($iter->next());
667
            // - structure with block, e.g. while ($i) {...}, while ($i) : {...} endwhile;
668
            if ($tokenAfterParenthesis->equalsAny([';', '{', ':'])) {
669
                continue;
670
            }
671
672
            // do not add for 'short if' followed by alternative loop, for example: if ($a) while ($b): ? > X < ?php endwhile; ? >
673
            // or 'short if' after an alternative loop, for example:  foreach ($arr as $index => $item) if ($item):
674
            if ($tokenAfterParenthesis->isGivenKind([T_FOR, T_FOREACH, T_SWITCH, T_WHILE, T_IF])) {
675
                $tokenAfterParenthesisBlockEnd = $tokens->findBlockEnd( // go to ')'
676
                    Tokens::BLOCK_TYPE_PARENTHESIS_BRACE,
677
                    $tokens->getNextMeaningfulToken($nextAfterParenthesisEndIndex)
0 ignored issues
show
Bug introduced by
It seems like $nextAfterParenthesisEndIndex 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

677
                    $tokens->getNextMeaningfulToken(/** @scrutinizer ignore-type */ $nextAfterParenthesisEndIndex)
Loading history...
Bug introduced by
It seems like $tokens->getNextMeaningf...terParenthesisEndIndex) 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

677
                    /** @scrutinizer ignore-type */ $tokens->getNextMeaningfulToken($nextAfterParenthesisEndIndex)
Loading history...
678
                );
679
680
                if ($tokens[$tokens->getNextMeaningfulToken($tokenAfterParenthesisBlockEnd)]->equals(':')) {
681
                    continue;
682
                }
683
            }
684
685
            $statementEndIndex = $this->findStatementEnd($tokens, $parenthesisEndIndex);
686
687
            // insert closing brace
688
            $tokens->insertAt($statementEndIndex + 1, [new Token([T_WHITESPACE, ' ']), new Token('}')]);
689
690
            // insert missing `;` if needed
691
            if (!$tokens[$statementEndIndex]->equalsAny([';', '}'])) {
692
                $tokens->insertAt($statementEndIndex + 1, new Token(';'));
693
            }
694
695
            // insert opening brace
696
            $tokens->insertAt($parenthesisEndIndex + 1, new Token('{'));
697
            $tokens->ensureWhitespaceAtIndex($parenthesisEndIndex + 1, 0, ' ');
698
        }
699
    }
700
701
    private function fixSpaceAroundToken(Tokens $tokens): void
702
    {
703
        $controlTokens = $this->getControlTokens();
704
705
        for ($index = $tokens->count() - 1; 0 <= $index; --$index) {
706
            $token = $tokens[$index];
707
708
            // Declare tokens don't follow the same rules are other control statements
709
            if ($token->isGivenKind(T_DECLARE)) {
710
                continue; // delegated to DeclareParenthesesFixer
711
            }
712
713
            if ($token->isGivenKind($controlTokens) || $token->isGivenKind(CT::T_USE_LAMBDA)) {
714
                $nextNonWhitespaceIndex = $tokens->getNextNonWhitespace($index);
715
716
                if (!$tokens[$nextNonWhitespaceIndex]->equals(':')) {
717
                    $tokens->ensureWhitespaceAtIndex(
718
                        $index + 1,
719
                        0,
720
                        self::LINE_NEXT === $this->configuration['position_after_control_structures'] && !$tokens[$nextNonWhitespaceIndex]->equals('(') ?
721
                            $this->whitespacesConfig->getLineEnding().WhitespacesAnalyzer::detectIndent($tokens, $index)
722
                            : ' '
723
                    );
724
                }
725
726
                $prevToken = $tokens[$index - 1];
727
728
                if (!$prevToken->isWhitespace() && !$prevToken->isComment() && !$prevToken->isGivenKind(T_OPEN_TAG)) {
729
                    $tokens->ensureWhitespaceAtIndex($index - 1, 1, ' ');
730
                }
731
            }
732
        }
733
    }
734
735
    private function findParenthesisEnd(Tokens $tokens, int $structureTokenIndex): int
736
    {
737
        $nextIndex = $tokens->getNextMeaningfulToken($structureTokenIndex);
738
        $nextToken = $tokens[$nextIndex];
739
740
        // return if next token is not opening parenthesis
741
        if (!$nextToken->equals('(')) {
742
            return $structureTokenIndex;
743
        }
744
745
        return $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $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

745
        return $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, /** @scrutinizer ignore-type */ $nextIndex);
Loading history...
746
    }
747
748
    private function findStatementEnd(Tokens $tokens, int $parenthesisEndIndex): int
749
    {
750
        $nextIndex = $tokens->getNextMeaningfulToken($parenthesisEndIndex);
751
        $nextToken = $tokens[$nextIndex];
752
753
        if (!$nextToken) {
754
            return $parenthesisEndIndex;
755
        }
756
757
        if ($nextToken->equals('{')) {
758
            return $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $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

758
            return $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, /** @scrutinizer ignore-type */ $nextIndex);
Loading history...
759
        }
760
761
        if ($nextToken->isGivenKind($this->getControlTokens())) {
762
            $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $nextIndex);
0 ignored issues
show
Bug introduced by
It seems like $nextIndex can also be of type null; however, parameter $structureTokenIndex of PhpCsFixer\Fixer\Basic\B...r::findParenthesisEnd() 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

762
            $parenthesisEndIndex = $this->findParenthesisEnd($tokens, /** @scrutinizer ignore-type */ $nextIndex);
Loading history...
763
764
            $endIndex = $this->findStatementEnd($tokens, $parenthesisEndIndex);
765
766
            if ($nextToken->isGivenKind([T_IF, T_TRY, T_DO])) {
767
                $openingTokenKind = $nextToken->getId();
768
769
                while (true) {
770
                    $nextIndex = $tokens->getNextMeaningfulToken($endIndex);
771
                    $nextToken = isset($nextIndex) ? $tokens[$nextIndex] : null;
772
                    if ($nextToken && $nextToken->isGivenKind($this->getControlContinuationTokensForOpeningToken($openingTokenKind))) {
773
                        $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $nextIndex);
774
775
                        $endIndex = $this->findStatementEnd($tokens, $parenthesisEndIndex);
776
777
                        if ($nextToken->isGivenKind($this->getFinalControlContinuationTokensForOpeningToken($openingTokenKind))) {
778
                            return $endIndex;
779
                        }
780
                    } else {
781
                        break;
782
                    }
783
                }
784
            }
785
786
            return $endIndex;
787
        }
788
789
        $index = $parenthesisEndIndex;
790
791
        while (true) {
792
            $token = $tokens[++$index];
793
794
            // if there is some block in statement (eg lambda function) we need to skip it
795
            if ($token->equals('{')) {
796
                $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index);
797
798
                continue;
799
            }
800
801
            if ($token->equals(';')) {
802
                return $index;
803
            }
804
805
            if ($token->isGivenKind(T_CLOSE_TAG)) {
806
                return $tokens->getPrevNonWhitespace($index);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $tokens->getPrevNonWhitespace($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...
807
            }
808
        }
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return integer. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
809
    }
810
811
    private function getControlTokens(): array
812
    {
813
        static $tokens = [
814
            T_DECLARE,
815
            T_DO,
816
            T_ELSE,
817
            T_ELSEIF,
818
            T_FINALLY,
819
            T_FOR,
820
            T_FOREACH,
821
            T_IF,
822
            T_WHILE,
823
            T_TRY,
824
            T_CATCH,
825
            T_SWITCH,
826
        ];
827
828
        // @TODO: drop condition when PHP 8.0+ is required
829
        if (\defined('T_MATCH')) {
830
            $tokens['match'] = T_MATCH;
831
        }
832
833
        return $tokens;
834
    }
835
836
    private function getControlContinuationTokens(): array
837
    {
838
        static $tokens = [
839
            T_CATCH,
840
            T_ELSE,
841
            T_ELSEIF,
842
            T_FINALLY,
843
        ];
844
845
        return $tokens;
846
    }
847
848
    private function getControlContinuationTokensForOpeningToken(int $openingTokenKind): array
849
    {
850
        if (T_IF === $openingTokenKind) {
851
            return [
852
                T_ELSE,
853
                T_ELSEIF,
854
            ];
855
        }
856
857
        if (T_DO === $openingTokenKind) {
858
            return [T_WHILE];
859
        }
860
861
        if (T_TRY === $openingTokenKind) {
862
            return [
863
                T_CATCH,
864
                T_FINALLY,
865
            ];
866
        }
867
868
        return [];
869
    }
870
871
    private function getFinalControlContinuationTokensForOpeningToken(int $openingTokenKind): array
872
    {
873
        if (T_IF === $openingTokenKind) {
874
            return [T_ELSE];
875
        }
876
877
        if (T_TRY === $openingTokenKind) {
878
            return [T_FINALLY];
879
        }
880
881
        return [];
882
    }
883
884
    private function ensureWhitespaceAtIndexAndIndentMultilineComment(Tokens $tokens, int $index, string $whitespace): void
885
    {
886
        if ($tokens[$index]->isWhitespace()) {
887
            $nextTokenIndex = $tokens->getNextNonWhitespace($index);
888
        } else {
889
            $nextTokenIndex = $index;
890
        }
891
892
        $nextToken = $tokens[$nextTokenIndex];
893
        if ($nextToken->isComment()) {
894
            $previousToken = $tokens[$nextTokenIndex - 1];
895
            $nextTokenContent = $nextToken->getContent();
896
897
            // do not indent inline comments used to comment out unused code
898
            if (
899
                $previousToken->isWhitespace()
900
                && 1 === Preg::match('/\R$/', $previousToken->getContent())
901
                && (
902
                    (0 === strpos($nextTokenContent, '//'.$this->whitespacesConfig->getIndent()) || '//' === $nextTokenContent)
903
                    || (0 === strpos($nextTokenContent, '#'.$this->whitespacesConfig->getIndent()) || '#' === $nextTokenContent)
904
                )
905
            ) {
906
                return;
907
            }
908
909
            $tokens[$nextTokenIndex] = new Token([
910
                $nextToken->getId(),
911
                Preg::replace(
912
                    '/(\R)'.WhitespacesAnalyzer::detectIndent($tokens, $nextTokenIndex).'(\h*\S+.*)/',
0 ignored issues
show
Bug introduced by
It seems like $nextTokenIndex can also be of type null; however, parameter $index of PhpCsFixer\Tokenizer\Ana...nalyzer::detectIndent() 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

912
                    '/(\R)'.WhitespacesAnalyzer::detectIndent($tokens, /** @scrutinizer ignore-type */ $nextTokenIndex).'(\h*\S+.*)/',
Loading history...
913
                    '$1'.Preg::replace('/^.*\R(\h*)$/s', '$1', $whitespace).'$2',
914
                    $nextToken->getContent()
915
                ),
916
            ]);
917
        }
918
919
        $tokens->ensureWhitespaceAtIndex($index, 0, $whitespace);
920
    }
921
922
    private function isMultilined(Tokens $tokens, int $startParenthesisIndex, int $endParenthesisIndex): bool
923
    {
924
        for ($i = $startParenthesisIndex; $i < $endParenthesisIndex; ++$i) {
925
            if (false !== strpos($tokens[$i]->getContent(), "\n")) {
926
                return true;
927
            }
928
        }
929
930
        return false;
931
    }
932
933
    /**
934
     * Returns whether the token at given index is a comment whose indentation
935
     * can be fixed.
936
     *
937
     * Indentation of a comment is not changed when the comment is part of a
938
     * multi-line message whose lines are all single-line comments and at least
939
     * one line has meaningful content.
940
     */
941
    private function isCommentWithFixableIndentation(Tokens $tokens, int $index): bool
942
    {
943
        if (!$tokens[$index]->isComment()) {
944
            return false;
945
        }
946
947
        if (0 === strpos($tokens[$index]->getContent(), '/*')) {
948
            return true;
949
        }
950
951
        $firstCommentIndex = $index;
952
        while (true) {
953
            $i = $this->getSiblingContinuousSingleLineComment($tokens, $firstCommentIndex, false);
954
            if (null === $i) {
955
                break;
956
            }
957
958
            $firstCommentIndex = $i;
959
        }
960
961
        $lastCommentIndex = $index;
962
        while (true) {
963
            $i = $this->getSiblingContinuousSingleLineComment($tokens, $lastCommentIndex, true);
964
            if (null === $i) {
965
                break;
966
            }
967
968
            $lastCommentIndex = $i;
969
        }
970
971
        if ($firstCommentIndex === $lastCommentIndex) {
972
            return true;
973
        }
974
975
        for ($i = $firstCommentIndex + 1; $i < $lastCommentIndex; ++$i) {
976
            if (!$tokens[$i]->isWhitespace() && !$tokens[$i]->isComment()) {
977
                return false;
978
            }
979
        }
980
981
        return true;
982
    }
983
984
    private function getSiblingContinuousSingleLineComment(Tokens $tokens, int $index, bool $after): ?int
985
    {
986
        $siblingIndex = $index;
987
        do {
988
            $siblingIndex = $tokens->getTokenOfKindSibling($siblingIndex, $after ? 1 : -1, [[T_COMMENT]]);
989
990
            if (null === $siblingIndex) {
991
                return null;
992
            }
993
        } while (0 === strpos($tokens[$siblingIndex]->getContent(), '/*'));
994
995
        $newLines = 0;
996
        for ($i = min($siblingIndex, $index) + 1, $max = max($siblingIndex, $index); $i < $max; ++$i) {
997
            if ($tokens[$i]->isWhitespace() && Preg::match('/\R/', $tokens[$i]->getContent())) {
998
                if (1 === $newLines || Preg::match('/\R.*\R/', $tokens[$i]->getContent())) {
999
                    return null;
1000
                }
1001
1002
                ++$newLines;
1003
            }
1004
        }
1005
1006
        return $siblingIndex;
1007
    }
1008
}
1009