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

keradus / PHP-CS-Fixer / 17377459942

01 Sep 2025 12:19PM UTC coverage: 94.684% (-0.009%) from 94.693%
17377459942

push

github

web-flow
chore: `Tokens::offsetSet` - explicit validation of input (#9004)

1 of 5 new or added lines in 1 file covered. (20.0%)

306 existing lines in 60 files now uncovered.

28390 of 29984 relevant lines covered (94.68%)

45.5 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 _ClassReferenceInfo array{start: int, count: int, multiLine: bool}
37
 * @phpstan-type _AutogeneratedInputConfiguration array{
38
 *  inline_constructor_arguments?: bool,
39
 *  multi_line_extends_each_single_line?: bool,
40
 *  single_item_single_line?: bool,
41
 *  single_line?: bool,
42
 *  space_before_parenthesis?: bool,
43
 * }
44
 * @phpstan-type _AutogeneratedComputedConfiguration array{
45
 *  inline_constructor_arguments: bool,
46
 *  multi_line_extends_each_single_line: bool,
47
 *  single_item_single_line: bool,
48
 *  single_line: bool,
49
 *  space_before_parenthesis: bool,
50
 * }
51
 * @phpstan-type _ClassyDefinitionInfo array{
52
 *      start: int,
53
 *      classy: int,
54
 *      open: int,
55
 *      extends: false|_ClassReferenceInfo,
56
 *      implements: false|_ClassReferenceInfo,
57
 *      anonymousClass: bool,
58
 *      final: false|int,
59
 *      abstract: false|int,
60
 *      readonly: false|int,
61
 *  }
62
 *
63
 * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
64
 *
65
 * @no-named-arguments Parameter names are not covered by the backward compatibility promise.
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
                        <?php
80

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

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

89
                        trait  Foo
90
                        {
91
                        }
92

93
                        $foo = new  class  extends  Bar  implements  Baz,  BarBaz {};
94

95
                        PHP
3✔
96
                ),
3✔
97
                new CodeSample(
3✔
98
                    <<<'PHP'
3✔
99
                        <?php
100

101
                        class Foo
102
                        extends Bar
103
                        implements Baz, BarBaz
104
                        {}
105

106
                        PHP,
3✔
107
                    ['single_line' => true]
3✔
108
                ),
3✔
109
                new CodeSample(
3✔
110
                    <<<'PHP'
3✔
111
                        <?php
112

113
                        class Foo
114
                        extends Bar
115
                        implements Baz
116
                        {}
117

118
                        PHP,
3✔
119
                    ['single_item_single_line' => true]
3✔
120
                ),
3✔
121
                new CodeSample(
3✔
122
                    <<<'PHP'
3✔
123
                        <?php
124

125
                        interface Bar extends
126
                            Bar, BarBaz, FooBarBaz
127
                        {}
128

129
                        PHP,
3✔
130
                    ['multi_line_extends_each_single_line' => true]
3✔
131
                ),
3✔
132
                new CodeSample(
3✔
133
                    <<<'PHP'
3✔
134
                        <?php
135
                        $foo = new class(){};
136

137
                        PHP,
3✔
138
                    ['space_before_parenthesis' => true]
3✔
139
                ),
3✔
140
                new CodeSample(
3✔
141
                    "<?php\n\$foo = new class(\n    \$bar,\n    \$baz\n) {};\n",
3✔
142
                    ['inline_constructor_arguments' => true]
3✔
143
                ),
3✔
144
            ]
3✔
145
        );
3✔
146
    }
147

148
    /**
149
     * {@inheritdoc}
150
     *
151
     * Must run before BracesFixer, SingleLineEmptyBodyFixer.
152
     * Must run after NewWithBracesFixer, NewWithParenthesesFixer.
153
     */
154
    public function getPriority(): int
155
    {
156
        return 36;
1✔
157
    }
158

159
    public function isCandidate(Tokens $tokens): bool
160
    {
161
        return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds());
104✔
162
    }
163

164
    protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
165
    {
166
        // -4, one for count to index, 3 because min. of tokens for a classy location.
167
        for ($index = $tokens->getSize() - 4; $index > 0; --$index) {
104✔
168
            if ($tokens[$index]->isClassy()) {
104✔
169
                $this->fixClassyDefinition($tokens, $index);
104✔
170
            }
171
        }
172
    }
173

174
    protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
175
    {
176
        return new FixerConfigurationResolver([
129✔
177
            (new FixerOptionBuilder('multi_line_extends_each_single_line', 'Whether definitions should be multiline.'))
129✔
178
                ->setAllowedTypes(['bool'])
129✔
179
                ->setDefault(false)
129✔
180
                ->getOption(),
129✔
181
            (new FixerOptionBuilder('single_item_single_line', 'Whether definitions should be single line when including a single item.'))
129✔
182
                ->setAllowedTypes(['bool'])
129✔
183
                ->setDefault(false)
129✔
184
                ->getOption(),
129✔
185
            (new FixerOptionBuilder('single_line', 'Whether definitions should be single line.'))
129✔
186
                ->setAllowedTypes(['bool'])
129✔
187
                ->setDefault(false)
129✔
188
                ->getOption(),
129✔
189
            (new FixerOptionBuilder('space_before_parenthesis', 'Whether there should be a single space after the parenthesis of anonymous class (PSR12) or not.'))
129✔
190
                ->setAllowedTypes(['bool'])
129✔
191
                ->setDefault(false)
129✔
192
                ->getOption(),
129✔
193
            (new FixerOptionBuilder('inline_constructor_arguments', 'Whether constructor argument list in anonymous classes should be single line.'))
129✔
194
                ->setAllowedTypes(['bool'])
129✔
195
                ->setDefault(true)
129✔
196
                ->getOption(),
129✔
197
        ]);
129✔
198
    }
199

200
    /**
201
     * @param int $classyIndex Class definition token start index
202
     */
203
    private function fixClassyDefinition(Tokens $tokens, int $classyIndex): void
204
    {
205
        $classDefInfo = $this->getClassyDefinitionInfo($tokens, $classyIndex);
104✔
206

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

210
        if (false !== $classDefInfo['implements']) {
104✔
211
            $classDefInfo['implements'] = $this->fixClassyDefinitionImplements(
34✔
212
                $tokens,
34✔
213
                $classDefInfo['open'],
34✔
214
                $classDefInfo['implements']
34✔
215
            );
34✔
216
        }
217

218
        if (false !== $classDefInfo['extends']) {
104✔
219
            $classDefInfo['extends'] = $this->fixClassyDefinitionExtends(
28✔
220
                $tokens,
28✔
221
                false === $classDefInfo['implements'] ? $classDefInfo['open'] : $classDefInfo['implements']['start'],
28✔
222
                $classDefInfo['extends']
28✔
223
            );
28✔
224
        }
225

226
        // PSR2: class definition open curly brace must go on a new line.
227
        // PSR12: anonymous class curly brace on same line if not multi line implements.
228

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

231
        if (false !== $classDefInfo['implements']) {
104✔
232
            $end = $classDefInfo['implements']['start'];
34✔
233
        } elseif (false !== $classDefInfo['extends']) {
71✔
234
            $end = $classDefInfo['extends']['start'];
15✔
235
        } else {
236
            $end = $tokens->getPrevNonWhitespace($classDefInfo['open']);
57✔
237
        }
238

239
        if ($classDefInfo['anonymousClass'] && false === $this->configuration['inline_constructor_arguments']) {
104✔
240
            if (!$tokens[$end]->equals(')')) { // anonymous class with `extends` and/or `implements`
8✔
241
                $start = $tokens->getPrevMeaningfulToken($end);
3✔
242
                $this->makeClassyDefinitionSingleLine($tokens, $start, $end);
3✔
243
                $end = $start;
3✔
244
            }
245

246
            if ($tokens[$end]->equals(')')) { // skip constructor arguments of anonymous class
8✔
247
                $end = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $end);
7✔
248
            }
249
        }
250

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

254
        $this->sortClassModifiers($tokens, $classDefInfo);
104✔
255
    }
256

257
    /**
258
     * @param _ClassReferenceInfo $classExtendsInfo
259
     *
260
     * @return _ClassReferenceInfo
261
     */
262
    private function fixClassyDefinitionExtends(Tokens $tokens, int $classOpenIndex, array $classExtendsInfo): array
263
    {
264
        $endIndex = $tokens->getPrevNonWhitespace($classOpenIndex);
28✔
265

266
        if (true === $this->configuration['single_line'] || false === $classExtendsInfo['multiLine']) {
28✔
267
            $this->makeClassyDefinitionSingleLine($tokens, $classExtendsInfo['start'], $endIndex);
17✔
268
            $classExtendsInfo['multiLine'] = false;
17✔
269
        } elseif (true === $this->configuration['single_item_single_line'] && 1 === $classExtendsInfo['count']) {
15✔
270
            $this->makeClassyDefinitionSingleLine($tokens, $classExtendsInfo['start'], $endIndex);
2✔
271
            $classExtendsInfo['multiLine'] = false;
2✔
272
        } elseif (true === $this->configuration['multi_line_extends_each_single_line'] && $classExtendsInfo['multiLine']) {
14✔
273
            $this->makeClassyInheritancePartMultiLine($tokens, $classExtendsInfo['start'], $endIndex);
2✔
274
            $classExtendsInfo['multiLine'] = true;
2✔
275
        }
276

277
        return $classExtendsInfo;
28✔
278
    }
279

280
    /**
281
     * @param _ClassReferenceInfo $classImplementsInfo
282
     *
283
     * @return _ClassReferenceInfo
284
     */
285
    private function fixClassyDefinitionImplements(Tokens $tokens, int $classOpenIndex, array $classImplementsInfo): array
286
    {
287
        $endIndex = $tokens->getPrevNonWhitespace($classOpenIndex);
34✔
288

289
        if (true === $this->configuration['single_line'] || false === $classImplementsInfo['multiLine']) {
34✔
290
            $this->makeClassyDefinitionSingleLine($tokens, $classImplementsInfo['start'], $endIndex);
19✔
291
            $classImplementsInfo['multiLine'] = false;
19✔
292
        } elseif (true === $this->configuration['single_item_single_line'] && 1 === $classImplementsInfo['count']) {
17✔
293
            $this->makeClassyDefinitionSingleLine($tokens, $classImplementsInfo['start'], $endIndex);
3✔
294
            $classImplementsInfo['multiLine'] = false;
3✔
295
        } else {
296
            $this->makeClassyInheritancePartMultiLine($tokens, $classImplementsInfo['start'], $endIndex);
14✔
297
            $classImplementsInfo['multiLine'] = true;
14✔
298
        }
299

300
        return $classImplementsInfo;
34✔
301
    }
302

303
    /**
304
     * @param _ClassyDefinitionInfo $classDefInfo
305
     */
306
    private function fixClassyDefinitionOpenSpacing(Tokens $tokens, array $classDefInfo): int
307
    {
308
        if ($classDefInfo['anonymousClass']) {
104✔
309
            if (false !== $classDefInfo['implements']) {
44✔
310
                $spacing = $classDefInfo['implements']['multiLine'] ? $this->whitespacesConfig->getLineEnding() : ' ';
14✔
311
            } elseif (false !== $classDefInfo['extends']) {
31✔
312
                $spacing = $classDefInfo['extends']['multiLine'] ? $this->whitespacesConfig->getLineEnding() : ' ';
1✔
313
            } else {
314
                $spacing = ' ';
30✔
315
            }
316
        } else {
317
            $spacing = $this->whitespacesConfig->getLineEnding();
61✔
318
        }
319

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

322
        if (' ' !== $spacing && str_contains($tokens[$openIndex - 1]->getContent(), "\n")) {
104✔
323
            return $openIndex;
68✔
324
        }
325

326
        if ($tokens[$openIndex - 1]->isWhitespace()) {
80✔
327
            if (' ' !== $spacing || !$tokens[$tokens->getPrevNonWhitespace($openIndex - 1)]->isComment()) {
57✔
328
                $tokens[$openIndex - 1] = new Token([\T_WHITESPACE, $spacing]);
55✔
329
            }
330

331
            return $openIndex;
57✔
332
        }
333

334
        $tokens->insertAt($openIndex, new Token([\T_WHITESPACE, $spacing]));
39✔
335

336
        return $openIndex + 1;
39✔
337
    }
338

339
    /**
340
     * @return _ClassyDefinitionInfo
341
     */
342
    private function getClassyDefinitionInfo(Tokens $tokens, int $classyIndex): array
343
    {
344
        $tokensAnalyzer = new TokensAnalyzer($tokens);
109✔
345
        $openIndex = $tokens->getNextTokenOfKind($classyIndex, ['{']);
109✔
346
        $def = [
109✔
347
            'classy' => $classyIndex,
109✔
348
            'open' => $openIndex,
109✔
349
            'extends' => false,
109✔
350
            'implements' => false,
109✔
351
            'anonymousClass' => false,
109✔
352
            'final' => false,
109✔
353
            'abstract' => false,
109✔
354
            'readonly' => false,
109✔
355
        ];
109✔
356

357
        if (!$tokens[$classyIndex]->isGivenKind(\T_TRAIT)) {
109✔
358
            $extends = $tokens->findGivenKind(\T_EXTENDS, $classyIndex, $openIndex);
102✔
359
            $def['extends'] = [] !== $extends ? $this->getClassyInheritanceInfo($tokens, array_key_first($extends)) : false;
102✔
360

361
            if (!$tokens[$classyIndex]->isGivenKind(\T_INTERFACE)) {
102✔
362
                $implements = $tokens->findGivenKind(\T_IMPLEMENTS, $classyIndex, $openIndex);
88✔
363
                $def['implements'] = [] !== $implements ? $this->getClassyInheritanceInfo($tokens, array_key_first($implements)) : false;
88✔
364
                $def['anonymousClass'] = $tokensAnalyzer->isAnonymousClass($classyIndex);
88✔
365
            }
366
        }
367

368
        if ($def['anonymousClass']) {
109✔
369
            $startIndex = $tokens->getPrevTokenOfKind($classyIndex, [[\T_NEW]]); // go to "new" for anonymous class
44✔
370
        } else {
371
            $modifiers = $tokensAnalyzer->getClassyModifiers($classyIndex);
66✔
372
            $startIndex = $classyIndex;
66✔
373

374
            foreach (['final', 'abstract', 'readonly'] as $modifier) {
66✔
375
                if (isset($modifiers[$modifier])) {
66✔
376
                    $def[$modifier] = $modifiers[$modifier];
10✔
377
                    $startIndex = min($startIndex, $modifiers[$modifier]);
10✔
378
                } else {
379
                    $def[$modifier] = false;
66✔
380
                }
381
            }
382
        }
383

384
        $def['start'] = $startIndex;
109✔
385

386
        return $def;
109✔
387
    }
388

389
    /**
390
     * @return _ClassReferenceInfo
391
     */
392
    private function getClassyInheritanceInfo(Tokens $tokens, int $startIndex): array
393
    {
394
        $implementsInfo = ['start' => $startIndex, 'count' => 1, 'multiLine' => false];
59✔
395
        ++$startIndex;
59✔
396
        $endIndex = $tokens->getNextTokenOfKind($startIndex, ['{', [\T_IMPLEMENTS], [\T_EXTENDS]]);
59✔
397
        $endIndex = $tokens[$endIndex]->equals('{') ? $tokens->getPrevNonWhitespace($endIndex) : $endIndex;
59✔
398

399
        for ($i = $startIndex; $i < $endIndex; ++$i) {
59✔
400
            if ($tokens[$i]->equals(',')) {
59✔
401
                ++$implementsInfo['count'];
32✔
402

403
                continue;
32✔
404
            }
405

406
            if (!$implementsInfo['multiLine'] && str_contains($tokens[$i]->getContent(), "\n")) {
59✔
407
                $implementsInfo['multiLine'] = true;
39✔
408
            }
409
        }
410

411
        return $implementsInfo;
59✔
412
    }
413

414
    private function makeClassyDefinitionSingleLine(Tokens $tokens, int $startIndex, int $endIndex): void
415
    {
416
        for ($i = $endIndex; $i >= $startIndex; --$i) {
104✔
417
            if ($tokens[$i]->isWhitespace()) {
104✔
418
                if (str_contains($tokens[$i]->getContent(), "\n")) {
104✔
419
                    if ($tokens[$i - 1]->isGivenKind(CT::T_ATTRIBUTE_CLOSE) || $tokens[$i + 1]->isGivenKind(FCT::T_ATTRIBUTE)) {
42✔
420
                        continue;
4✔
421
                    }
422
                    if (($tokens[$i - 1]->isComment() && str_ends_with($tokens[$i - 1]->getContent(), ']'))
40✔
423
                        || ($tokens[$i + 1]->isComment() && str_starts_with($tokens[$i + 1]->getContent(), '#['))
40✔
424
                    ) {
UNCOV
425
                        continue;
×
426
                    }
427

428
                    if ($tokens[$i - 1]->isGivenKind(\T_DOC_COMMENT) || $tokens[$i + 1]->isGivenKind(\T_DOC_COMMENT)) {
40✔
429
                        continue;
4✔
430
                    }
431
                }
432

433
                if ($tokens[$i - 1]->isComment()) {
99✔
434
                    $content = $tokens[$i - 1]->getContent();
20✔
435
                    if (!str_starts_with($content, '//') && !str_starts_with($content, '#')) {
20✔
436
                        $tokens[$i] = new Token([\T_WHITESPACE, ' ']);
15✔
437
                    }
438

439
                    continue;
20✔
440
                }
441

442
                if ($tokens[$i + 1]->isComment()) {
99✔
443
                    $content = $tokens[$i + 1]->getContent();
24✔
444
                    if (!str_starts_with($content, '//')) {
24✔
445
                        $tokens[$i] = new Token([\T_WHITESPACE, ' ']);
19✔
446
                    }
447

448
                    continue;
24✔
449
                }
450

451
                if ($tokens[$i - 1]->isGivenKind(\T_CLASS) && $tokens[$i + 1]->equals('(')) {
91✔
452
                    if (true === $this->configuration['space_before_parenthesis']) {
7✔
453
                        $tokens[$i] = new Token([\T_WHITESPACE, ' ']);
3✔
454
                    } else {
455
                        $tokens->clearAt($i);
4✔
456
                    }
457

458
                    continue;
7✔
459
                }
460

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

464
                    continue;
9✔
465
                }
466

467
                $tokens[$i] = new Token([\T_WHITESPACE, ' ']);
90✔
468

469
                continue;
90✔
470
            }
471

472
            if ($tokens[$i]->equals(',') && !$tokens[$i + 1]->isWhitespace()) {
104✔
473
                $tokens->insertAt($i + 1, new Token([\T_WHITESPACE, ' ']));
3✔
474

475
                continue;
3✔
476
            }
477

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

481
                continue;
2✔
482
            }
483

484
            if (!$tokens[$i]->isComment()) {
104✔
485
                continue;
104✔
486
            }
487

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

492
            if (!$tokens[$i - 1]->isWhitespace() && !$tokens[$i - 1]->isComment()) {
28✔
493
                $tokens->insertAt($i, new Token([\T_WHITESPACE, ' ']));
20✔
494
            }
495
        }
496
    }
497

498
    private function makeClassyInheritancePartMultiLine(Tokens $tokens, int $startIndex, int $endIndex): void
499
    {
500
        for ($i = $endIndex; $i > $startIndex; --$i) {
16✔
501
            $previousInterfaceImplementingIndex = $tokens->getPrevTokenOfKind($i, [',', [\T_IMPLEMENTS], [\T_EXTENDS]]);
16✔
502
            $breakAtIndex = $tokens->getNextMeaningfulToken($previousInterfaceImplementingIndex);
16✔
503

504
            // make the part of a ',' or 'implements' single line
505
            $this->makeClassyDefinitionSingleLine(
16✔
506
                $tokens,
16✔
507
                $breakAtIndex,
16✔
508
                $i
16✔
509
            );
16✔
510

511
            // make sure the part is on its own line
512
            $isOnOwnLine = false;
16✔
513

514
            for ($j = $breakAtIndex; $j > $previousInterfaceImplementingIndex; --$j) {
16✔
515
                if (str_contains($tokens[$j]->getContent(), "\n")) {
16✔
516
                    $isOnOwnLine = true;
16✔
517

518
                    break;
16✔
519
                }
520
            }
521

522
            if (!$isOnOwnLine) {
16✔
523
                if ($tokens[$breakAtIndex - 1]->isWhitespace()) {
12✔
524
                    $tokens[$breakAtIndex - 1] = new Token([
9✔
525
                        \T_WHITESPACE,
9✔
526
                        $this->whitespacesConfig->getLineEnding().$this->whitespacesConfig->getIndent(),
9✔
527
                    ]);
9✔
528
                } else {
529
                    $tokens->insertAt($breakAtIndex, new Token([\T_WHITESPACE, $this->whitespacesConfig->getLineEnding().$this->whitespacesConfig->getIndent()]));
6✔
530
                }
531
            }
532

533
            $i = $previousInterfaceImplementingIndex + 1;
16✔
534
        }
535
    }
536

537
    /**
538
     * @param array{
539
     *     final: false|int,
540
     *     abstract: false|int,
541
     *     readonly: false|int,
542
     * } $classDefInfo
543
     */
544
    private function sortClassModifiers(Tokens $tokens, array $classDefInfo): void
545
    {
546
        if (false === $classDefInfo['readonly']) {
104✔
547
            return;
101✔
548
        }
549

550
        $readonlyIndex = $classDefInfo['readonly'];
3✔
551

552
        foreach (['final', 'abstract'] as $accessModifier) {
3✔
553
            if (false === $classDefInfo[$accessModifier] || $classDefInfo[$accessModifier] < $readonlyIndex) {
3✔
554
                continue;
3✔
555
            }
556

557
            $accessModifierIndex = $classDefInfo[$accessModifier];
2✔
558

559
            $readonlyToken = clone $tokens[$readonlyIndex];
2✔
560
            $accessToken = clone $tokens[$accessModifierIndex];
2✔
561

562
            $tokens[$readonlyIndex] = $accessToken;
2✔
563
            $tokens[$accessModifierIndex] = $readonlyToken;
2✔
564

565
            break;
2✔
566
        }
567
    }
568
}
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