• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PHP-CS-Fixer / PHP-CS-Fixer / 15273528065

27 May 2025 11:01AM UTC coverage: 94.859% (-0.06%) from 94.915%
15273528065

push

github

web-flow
DX: introduce `FCT` class for tokens not present in the lowest supported PHP version (#8706)

Co-authored-by: Dariusz Rumiński <dariusz.ruminski@gmail.com>

186 of 192 new or added lines in 52 files covered. (96.88%)

1 existing line in 1 file now uncovered.

28102 of 29625 relevant lines covered (94.86%)

45.33 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.59
/src/Fixer/ClassNotation/ClassDefinitionFixer.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of PHP CS Fixer.
7
 *
8
 * (c) Fabien Potencier <fabien@symfony.com>
9
 *     Dariusz Rumiński <dariusz.ruminski@gmail.com>
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\ClassNotation;
16

17
use PhpCsFixer\AbstractFixer;
18
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
19
use PhpCsFixer\Fixer\ConfigurableFixerTrait;
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\Tokenizer\CT;
28
use PhpCsFixer\Tokenizer\FCT;
29
use PhpCsFixer\Tokenizer\Token;
30
use PhpCsFixer\Tokenizer\Tokens;
31
use PhpCsFixer\Tokenizer\TokensAnalyzer;
32

33
/**
34
 * Fixer for part of the rules defined in PSR2 ¶4.1 Extends and Implements and PSR12 ¶8. Anonymous Classes.
35
 *
36
 * @phpstan-type _ClassExtendsInfo array{start: int, numberOfExtends: int, multiLine: bool}
37
 * @phpstan-type _ClassImplementsInfo array{start: int, numberOfImplements: int, multiLine: bool}
38
 *
39
 * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
40
 *
41
 * @phpstan-type _AutogeneratedInputConfiguration array{
42
 *  inline_constructor_arguments?: bool,
43
 *  multi_line_extends_each_single_line?: bool,
44
 *  single_item_single_line?: bool,
45
 *  single_line?: bool,
46
 *  space_before_parenthesis?: bool,
47
 * }
48
 * @phpstan-type _AutogeneratedComputedConfiguration array{
49
 *  inline_constructor_arguments: bool,
50
 *  multi_line_extends_each_single_line: bool,
51
 *  single_item_single_line: bool,
52
 *  single_line: bool,
53
 *  space_before_parenthesis: bool,
54
 * }
55
 * @phpstan-type _ClassyDefinitionInfo array{
56
 *      start: int,
57
 *      classy: int,
58
 *      open: int,
59
 *      extends: false|_ClassExtendsInfo,
60
 *      implements: false|_ClassImplementsInfo,
61
 *      anonymousClass: bool,
62
 *      final: false|int,
63
 *      abstract: false|int,
64
 *      readonly: false|int,
65
 *  }
66
 */
67
final class ClassDefinitionFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
68
{
69
    /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */
70
    use ConfigurableFixerTrait;
71

72
    public function getDefinition(): FixerDefinitionInterface
73
    {
74
        return new FixerDefinition(
3✔
75
            'Whitespace around the keywords of a class, trait, enum or interfaces definition should be one space.',
3✔
76
            [
3✔
77
                new CodeSample(
3✔
78
                    '<?php
3✔
79

80
class  Foo  extends  Bar  implements  Baz,  BarBaz
81
{
82
}
83

84
final  class  Foo  extends  Bar  implements  Baz,  BarBaz
85
{
86
}
87

88
trait  Foo
89
{
90
}
91

92
$foo = new  class  extends  Bar  implements  Baz,  BarBaz {};
93
'
3✔
94
                ),
3✔
95
                new CodeSample(
3✔
96
                    '<?php
3✔
97

98
class Foo
99
extends Bar
100
implements Baz, BarBaz
101
{}
102
',
3✔
103
                    ['single_line' => true]
3✔
104
                ),
3✔
105
                new CodeSample(
3✔
106
                    '<?php
3✔
107

108
class Foo
109
extends Bar
110
implements Baz
111
{}
112
',
3✔
113
                    ['single_item_single_line' => true]
3✔
114
                ),
3✔
115
                new CodeSample(
3✔
116
                    '<?php
3✔
117

118
interface Bar extends
119
    Bar, BarBaz, FooBarBaz
120
{}
121
',
3✔
122
                    ['multi_line_extends_each_single_line' => true]
3✔
123
                ),
3✔
124
                new CodeSample(
3✔
125
                    '<?php
3✔
126
$foo = new class(){};
127
',
3✔
128
                    ['space_before_parenthesis' => true]
3✔
129
                ),
3✔
130
                new CodeSample(
3✔
131
                    "<?php\n\$foo = new class(\n    \$bar,\n    \$baz\n) {};\n",
3✔
132
                    ['inline_constructor_arguments' => true]
3✔
133
                ),
3✔
134
            ]
3✔
135
        );
3✔
136
    }
137

138
    /**
139
     * {@inheritdoc}
140
     *
141
     * Must run before BracesFixer, SingleLineEmptyBodyFixer.
142
     * Must run after NewWithBracesFixer, NewWithParenthesesFixer.
143
     */
144
    public function getPriority(): int
145
    {
146
        return 36;
1✔
147
    }
148

149
    public function isCandidate(Tokens $tokens): bool
150
    {
151
        return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds());
104✔
152
    }
153

154
    protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
155
    {
156
        // -4, one for count to index, 3 because min. of tokens for a classy location.
157
        for ($index = $tokens->getSize() - 4; $index > 0; --$index) {
104✔
158
            if ($tokens[$index]->isClassy()) {
104✔
159
                $this->fixClassyDefinition($tokens, $index);
104✔
160
            }
161
        }
162
    }
163

164
    protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
165
    {
166
        return new FixerConfigurationResolver([
129✔
167
            (new FixerOptionBuilder('multi_line_extends_each_single_line', 'Whether definitions should be multiline.'))
129✔
168
                ->setAllowedTypes(['bool'])
129✔
169
                ->setDefault(false)
129✔
170
                ->getOption(),
129✔
171
            (new FixerOptionBuilder('single_item_single_line', 'Whether definitions should be single line when including a single item.'))
129✔
172
                ->setAllowedTypes(['bool'])
129✔
173
                ->setDefault(false)
129✔
174
                ->getOption(),
129✔
175
            (new FixerOptionBuilder('single_line', 'Whether definitions should be single line.'))
129✔
176
                ->setAllowedTypes(['bool'])
129✔
177
                ->setDefault(false)
129✔
178
                ->getOption(),
129✔
179
            (new FixerOptionBuilder('space_before_parenthesis', 'Whether there should be a single space after the parenthesis of anonymous class (PSR12) or not.'))
129✔
180
                ->setAllowedTypes(['bool'])
129✔
181
                ->setDefault(false)
129✔
182
                ->getOption(),
129✔
183
            (new FixerOptionBuilder('inline_constructor_arguments', 'Whether constructor argument list in anonymous classes should be single line.'))
129✔
184
                ->setAllowedTypes(['bool'])
129✔
185
                ->setDefault(true)
129✔
186
                ->getOption(),
129✔
187
        ]);
129✔
188
    }
189

190
    /**
191
     * @param int $classyIndex Class definition token start index
192
     */
193
    private function fixClassyDefinition(Tokens $tokens, int $classyIndex): void
194
    {
195
        $classDefInfo = $this->getClassyDefinitionInfo($tokens, $classyIndex);
104✔
196

197
        // PSR2 4.1 Lists of implements MAY be split across multiple lines, where each subsequent line is indented once.
198
        // When doing so, the first item in the list MUST be on the next line, and there MUST be only one interface per line.
199

200
        if (false !== $classDefInfo['implements']) {
104✔
201
            $classDefInfo['implements'] = $this->fixClassyDefinitionImplements(
34✔
202
                $tokens,
34✔
203
                $classDefInfo['open'],
34✔
204
                $classDefInfo['implements']
34✔
205
            );
34✔
206
        }
207

208
        if (false !== $classDefInfo['extends']) {
104✔
209
            $classDefInfo['extends'] = $this->fixClassyDefinitionExtends(
28✔
210
                $tokens,
28✔
211
                false === $classDefInfo['implements'] ? $classDefInfo['open'] : $classDefInfo['implements']['start'],
28✔
212
                $classDefInfo['extends']
28✔
213
            );
28✔
214
        }
215

216
        // PSR2: class definition open curly brace must go on a new line.
217
        // PSR12: anonymous class curly brace on same line if not multi line implements.
218

219
        $classDefInfo['open'] = $this->fixClassyDefinitionOpenSpacing($tokens, $classDefInfo);
104✔
220

221
        if (false !== $classDefInfo['implements']) {
104✔
222
            $end = $classDefInfo['implements']['start'];
34✔
223
        } elseif (false !== $classDefInfo['extends']) {
71✔
224
            $end = $classDefInfo['extends']['start'];
15✔
225
        } else {
226
            $end = $tokens->getPrevNonWhitespace($classDefInfo['open']);
57✔
227
        }
228

229
        if ($classDefInfo['anonymousClass'] && false === $this->configuration['inline_constructor_arguments']) {
104✔
230
            if (!$tokens[$end]->equals(')')) { // anonymous class with `extends` and/or `implements`
8✔
231
                $start = $tokens->getPrevMeaningfulToken($end);
3✔
232
                $this->makeClassyDefinitionSingleLine($tokens, $start, $end);
3✔
233
                $end = $start;
3✔
234
            }
235

236
            if ($tokens[$end]->equals(')')) { // skip constructor arguments of anonymous class
8✔
237
                $end = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $end);
7✔
238
            }
239
        }
240

241
        // 4.1 The extends and implements keywords MUST be declared on the same line as the class name.
242
        $this->makeClassyDefinitionSingleLine($tokens, $classDefInfo['start'], $end);
104✔
243

244
        $this->sortClassModifiers($tokens, $classDefInfo);
104✔
245
    }
246

247
    /**
248
     * @param _ClassExtendsInfo $classExtendsInfo
249
     *
250
     * @return _ClassExtendsInfo
251
     */
252
    private function fixClassyDefinitionExtends(Tokens $tokens, int $classOpenIndex, array $classExtendsInfo): array
253
    {
254
        $endIndex = $tokens->getPrevNonWhitespace($classOpenIndex);
28✔
255

256
        if (true === $this->configuration['single_line'] || false === $classExtendsInfo['multiLine']) {
28✔
257
            $this->makeClassyDefinitionSingleLine($tokens, $classExtendsInfo['start'], $endIndex);
17✔
258
            $classExtendsInfo['multiLine'] = false;
17✔
259
        } elseif (true === $this->configuration['single_item_single_line'] && 1 === $classExtendsInfo['numberOfExtends']) {
15✔
260
            $this->makeClassyDefinitionSingleLine($tokens, $classExtendsInfo['start'], $endIndex);
2✔
261
            $classExtendsInfo['multiLine'] = false;
2✔
262
        } elseif (true === $this->configuration['multi_line_extends_each_single_line'] && $classExtendsInfo['multiLine']) {
14✔
263
            $this->makeClassyInheritancePartMultiLine($tokens, $classExtendsInfo['start'], $endIndex);
2✔
264
            $classExtendsInfo['multiLine'] = true;
2✔
265
        }
266

267
        return $classExtendsInfo;
28✔
268
    }
269

270
    /**
271
     * @param _ClassImplementsInfo $classImplementsInfo
272
     *
273
     * @return _ClassImplementsInfo
274
     */
275
    private function fixClassyDefinitionImplements(Tokens $tokens, int $classOpenIndex, array $classImplementsInfo): array
276
    {
277
        $endIndex = $tokens->getPrevNonWhitespace($classOpenIndex);
34✔
278

279
        if (true === $this->configuration['single_line'] || false === $classImplementsInfo['multiLine']) {
34✔
280
            $this->makeClassyDefinitionSingleLine($tokens, $classImplementsInfo['start'], $endIndex);
19✔
281
            $classImplementsInfo['multiLine'] = false;
19✔
282
        } elseif (true === $this->configuration['single_item_single_line'] && 1 === $classImplementsInfo['numberOfImplements']) {
17✔
283
            $this->makeClassyDefinitionSingleLine($tokens, $classImplementsInfo['start'], $endIndex);
3✔
284
            $classImplementsInfo['multiLine'] = false;
3✔
285
        } else {
286
            $this->makeClassyInheritancePartMultiLine($tokens, $classImplementsInfo['start'], $endIndex);
14✔
287
            $classImplementsInfo['multiLine'] = true;
14✔
288
        }
289

290
        return $classImplementsInfo;
34✔
291
    }
292

293
    /**
294
     * @param _ClassyDefinitionInfo $classDefInfo
295
     */
296
    private function fixClassyDefinitionOpenSpacing(Tokens $tokens, array $classDefInfo): int
297
    {
298
        if ($classDefInfo['anonymousClass']) {
104✔
299
            if (false !== $classDefInfo['implements']) {
44✔
300
                $spacing = $classDefInfo['implements']['multiLine'] ? $this->whitespacesConfig->getLineEnding() : ' ';
14✔
301
            } elseif (false !== $classDefInfo['extends']) {
31✔
302
                $spacing = $classDefInfo['extends']['multiLine'] ? $this->whitespacesConfig->getLineEnding() : ' ';
1✔
303
            } else {
304
                $spacing = ' ';
30✔
305
            }
306
        } else {
307
            $spacing = $this->whitespacesConfig->getLineEnding();
61✔
308
        }
309

310
        $openIndex = $tokens->getNextTokenOfKind($classDefInfo['classy'], ['{']);
104✔
311

312
        if (' ' !== $spacing && str_contains($tokens[$openIndex - 1]->getContent(), "\n")) {
104✔
313
            return $openIndex;
68✔
314
        }
315

316
        if ($tokens[$openIndex - 1]->isWhitespace()) {
80✔
317
            if (' ' !== $spacing || !$tokens[$tokens->getPrevNonWhitespace($openIndex - 1)]->isComment()) {
57✔
318
                $tokens[$openIndex - 1] = new Token([T_WHITESPACE, $spacing]);
55✔
319
            }
320

321
            return $openIndex;
57✔
322
        }
323

324
        $tokens->insertAt($openIndex, new Token([T_WHITESPACE, $spacing]));
39✔
325

326
        return $openIndex + 1;
39✔
327
    }
328

329
    /**
330
     * @return _ClassyDefinitionInfo
331
     */
332
    private function getClassyDefinitionInfo(Tokens $tokens, int $classyIndex): array
333
    {
334
        $tokensAnalyzer = new TokensAnalyzer($tokens);
109✔
335
        $openIndex = $tokens->getNextTokenOfKind($classyIndex, ['{']);
109✔
336
        $def = [
109✔
337
            'classy' => $classyIndex,
109✔
338
            'open' => $openIndex,
109✔
339
            'extends' => false,
109✔
340
            'implements' => false,
109✔
341
            'anonymousClass' => false,
109✔
342
            'final' => false,
109✔
343
            'abstract' => false,
109✔
344
            'readonly' => false,
109✔
345
        ];
109✔
346

347
        if (!$tokens[$classyIndex]->isGivenKind(T_TRAIT)) {
109✔
348
            $extends = $tokens->findGivenKind(T_EXTENDS, $classyIndex, $openIndex);
102✔
349
            $def['extends'] = [] !== $extends ? $this->getClassyInheritanceInfo($tokens, array_key_first($extends), 'numberOfExtends') : false;
102✔
350

351
            if (!$tokens[$classyIndex]->isGivenKind(T_INTERFACE)) {
102✔
352
                $implements = $tokens->findGivenKind(T_IMPLEMENTS, $classyIndex, $openIndex);
88✔
353
                $def['implements'] = [] !== $implements ? $this->getClassyInheritanceInfo($tokens, array_key_first($implements), 'numberOfImplements') : false;
88✔
354
                $def['anonymousClass'] = $tokensAnalyzer->isAnonymousClass($classyIndex);
88✔
355
            }
356
        }
357

358
        if ($def['anonymousClass']) {
109✔
359
            $startIndex = $tokens->getPrevTokenOfKind($classyIndex, [[T_NEW]]); // go to "new" for anonymous class
44✔
360
        } else {
361
            $modifiers = $tokensAnalyzer->getClassyModifiers($classyIndex);
66✔
362
            $startIndex = $classyIndex;
66✔
363

364
            foreach (['final', 'abstract', 'readonly'] as $modifier) {
66✔
365
                if (isset($modifiers[$modifier])) {
66✔
366
                    $def[$modifier] = $modifiers[$modifier];
10✔
367
                    $startIndex = min($startIndex, $modifiers[$modifier]);
10✔
368
                } else {
369
                    $def[$modifier] = false;
66✔
370
                }
371
            }
372
        }
373

374
        $def['start'] = $startIndex;
109✔
375

376
        return $def;
109✔
377
    }
378

379
    /**
380
     * @return array<string, 1>|array{start: int, multiLine: bool}
381
     */
382
    private function getClassyInheritanceInfo(Tokens $tokens, int $startIndex, string $label): array
383
    {
384
        $implementsInfo = ['start' => $startIndex, $label => 1, 'multiLine' => false];
59✔
385
        ++$startIndex;
59✔
386
        $endIndex = $tokens->getNextTokenOfKind($startIndex, ['{', [T_IMPLEMENTS], [T_EXTENDS]]);
59✔
387
        $endIndex = $tokens[$endIndex]->equals('{') ? $tokens->getPrevNonWhitespace($endIndex) : $endIndex;
59✔
388

389
        for ($i = $startIndex; $i < $endIndex; ++$i) {
59✔
390
            if ($tokens[$i]->equals(',')) {
59✔
391
                ++$implementsInfo[$label];
32✔
392

393
                continue;
32✔
394
            }
395

396
            if (!$implementsInfo['multiLine'] && str_contains($tokens[$i]->getContent(), "\n")) {
59✔
397
                $implementsInfo['multiLine'] = true;
39✔
398
            }
399
        }
400

401
        return $implementsInfo;
59✔
402
    }
403

404
    private function makeClassyDefinitionSingleLine(Tokens $tokens, int $startIndex, int $endIndex): void
405
    {
406
        for ($i = $endIndex; $i >= $startIndex; --$i) {
104✔
407
            if ($tokens[$i]->isWhitespace()) {
104✔
408
                if (str_contains($tokens[$i]->getContent(), "\n")) {
104✔
409
                    if ($tokens[$i - 1]->isGivenKind(CT::T_ATTRIBUTE_CLOSE) || $tokens[$i + 1]->isGivenKind(FCT::T_ATTRIBUTE)) {
42✔
410
                        continue;
4✔
411
                    }
412
                    if (($tokens[$i - 1]->isComment() && str_ends_with($tokens[$i - 1]->getContent(), ']'))
40✔
413
                        || ($tokens[$i + 1]->isComment() && str_starts_with($tokens[$i + 1]->getContent(), '#['))
40✔
414
                    ) {
NEW
415
                        continue;
×
416
                    }
417

418
                    if ($tokens[$i - 1]->isGivenKind(T_DOC_COMMENT) || $tokens[$i + 1]->isGivenKind(T_DOC_COMMENT)) {
40✔
419
                        continue;
4✔
420
                    }
421
                }
422

423
                if ($tokens[$i - 1]->isComment()) {
99✔
424
                    $content = $tokens[$i - 1]->getContent();
20✔
425
                    if (!str_starts_with($content, '//') && !str_starts_with($content, '#')) {
20✔
426
                        $tokens[$i] = new Token([T_WHITESPACE, ' ']);
15✔
427
                    }
428

429
                    continue;
20✔
430
                }
431

432
                if ($tokens[$i + 1]->isComment()) {
99✔
433
                    $content = $tokens[$i + 1]->getContent();
24✔
434
                    if (!str_starts_with($content, '//')) {
24✔
435
                        $tokens[$i] = new Token([T_WHITESPACE, ' ']);
19✔
436
                    }
437

438
                    continue;
24✔
439
                }
440

441
                if ($tokens[$i - 1]->isGivenKind(T_CLASS) && $tokens[$i + 1]->equals('(')) {
91✔
442
                    if (true === $this->configuration['space_before_parenthesis']) {
7✔
443
                        $tokens[$i] = new Token([T_WHITESPACE, ' ']);
3✔
444
                    } else {
445
                        $tokens->clearAt($i);
4✔
446
                    }
447

448
                    continue;
7✔
449
                }
450

451
                if (!$tokens[$i - 1]->equals(',') && $tokens[$i + 1]->equalsAny([',', ')']) || $tokens[$i - 1]->equals('(')) {
91✔
452
                    $tokens->clearAt($i);
9✔
453

454
                    continue;
9✔
455
                }
456

457
                $tokens[$i] = new Token([T_WHITESPACE, ' ']);
90✔
458

459
                continue;
90✔
460
            }
461

462
            if ($tokens[$i]->equals(',') && !$tokens[$i + 1]->isWhitespace()) {
104✔
463
                $tokens->insertAt($i + 1, new Token([T_WHITESPACE, ' ']));
3✔
464

465
                continue;
3✔
466
            }
467

468
            if (true === $this->configuration['space_before_parenthesis'] && $tokens[$i]->isGivenKind(T_CLASS) && !$tokens[$i + 1]->isWhitespace()) {
104✔
469
                $tokens->insertAt($i + 1, new Token([T_WHITESPACE, ' ']));
2✔
470

471
                continue;
2✔
472
            }
473

474
            if (!$tokens[$i]->isComment()) {
104✔
475
                continue;
104✔
476
            }
477

478
            if (!$tokens[$i + 1]->isWhitespace() && !$tokens[$i + 1]->isComment() && !str_contains($tokens[$i]->getContent(), "\n")) {
28✔
479
                $tokens->insertAt($i + 1, new Token([T_WHITESPACE, ' ']));
13✔
480
            }
481

482
            if (!$tokens[$i - 1]->isWhitespace() && !$tokens[$i - 1]->isComment()) {
28✔
483
                $tokens->insertAt($i, new Token([T_WHITESPACE, ' ']));
20✔
484
            }
485
        }
486
    }
487

488
    private function makeClassyInheritancePartMultiLine(Tokens $tokens, int $startIndex, int $endIndex): void
489
    {
490
        for ($i = $endIndex; $i > $startIndex; --$i) {
16✔
491
            $previousInterfaceImplementingIndex = $tokens->getPrevTokenOfKind($i, [',', [T_IMPLEMENTS], [T_EXTENDS]]);
16✔
492
            $breakAtIndex = $tokens->getNextMeaningfulToken($previousInterfaceImplementingIndex);
16✔
493

494
            // make the part of a ',' or 'implements' single line
495
            $this->makeClassyDefinitionSingleLine(
16✔
496
                $tokens,
16✔
497
                $breakAtIndex,
16✔
498
                $i
16✔
499
            );
16✔
500

501
            // make sure the part is on its own line
502
            $isOnOwnLine = false;
16✔
503

504
            for ($j = $breakAtIndex; $j > $previousInterfaceImplementingIndex; --$j) {
16✔
505
                if (str_contains($tokens[$j]->getContent(), "\n")) {
16✔
506
                    $isOnOwnLine = true;
16✔
507

508
                    break;
16✔
509
                }
510
            }
511

512
            if (!$isOnOwnLine) {
16✔
513
                if ($tokens[$breakAtIndex - 1]->isWhitespace()) {
12✔
514
                    $tokens[$breakAtIndex - 1] = new Token([
9✔
515
                        T_WHITESPACE,
9✔
516
                        $this->whitespacesConfig->getLineEnding().$this->whitespacesConfig->getIndent(),
9✔
517
                    ]);
9✔
518
                } else {
519
                    $tokens->insertAt($breakAtIndex, new Token([T_WHITESPACE, $this->whitespacesConfig->getLineEnding().$this->whitespacesConfig->getIndent()]));
6✔
520
                }
521
            }
522

523
            $i = $previousInterfaceImplementingIndex + 1;
16✔
524
        }
525
    }
526

527
    /**
528
     * @param array{
529
     *     final: false|int,
530
     *     abstract: false|int,
531
     *     readonly: false|int,
532
     * } $classDefInfo
533
     */
534
    private function sortClassModifiers(Tokens $tokens, array $classDefInfo): void
535
    {
536
        if (false === $classDefInfo['readonly']) {
104✔
537
            return;
101✔
538
        }
539

540
        $readonlyIndex = $classDefInfo['readonly'];
3✔
541

542
        foreach (['final', 'abstract'] as $accessModifier) {
3✔
543
            if (false === $classDefInfo[$accessModifier] || $classDefInfo[$accessModifier] < $readonlyIndex) {
3✔
544
                continue;
3✔
545
            }
546

547
            $accessModifierIndex = $classDefInfo[$accessModifier];
2✔
548

549
            /** @var Token $readonlyToken */
550
            $readonlyToken = clone $tokens[$readonlyIndex];
2✔
551

552
            /** @var Token $accessToken */
553
            $accessToken = clone $tokens[$accessModifierIndex];
2✔
554

555
            $tokens[$readonlyIndex] = $accessToken;
2✔
556
            $tokens[$accessModifierIndex] = $readonlyToken;
2✔
557

558
            break;
2✔
559
        }
560
    }
561
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc