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

keradus / PHP-CS-Fixer / 16999983712

15 Aug 2025 09:42PM UTC coverage: 94.75% (-0.09%) from 94.839%
16999983712

push

github

keradus
ci: more self-fixing checks on lowest/highest PHP

28263 of 29829 relevant lines covered (94.75%)

45.88 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
final class ClassDefinitionFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
66
{
67
    /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */
68
    use ConfigurableFixerTrait;
69

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

78
class  Foo  extends  Bar  implements  Baz,  BarBaz
79
{
80
}
81

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

86
trait  Foo
87
{
88
}
89

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

242
        $this->sortClassModifiers($tokens, $classDefInfo);
104✔
243
    }
244

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

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

265
        return $classExtendsInfo;
28✔
266
    }
267

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

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

288
        return $classImplementsInfo;
34✔
289
    }
290

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

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

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

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

319
            return $openIndex;
57✔
320
        }
321

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

324
        return $openIndex + 1;
39✔
325
    }
326

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

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

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

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

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

372
        $def['start'] = $startIndex;
109✔
373

374
        return $def;
109✔
375
    }
376

377
    /**
378
     * @return _ClassReferenceInfo
379
     */
380
    private function getClassyInheritanceInfo(Tokens $tokens, int $startIndex): array
381
    {
382
        $implementsInfo = ['start' => $startIndex, 'count' => 1, 'multiLine' => false];
59✔
383
        ++$startIndex;
59✔
384
        $endIndex = $tokens->getNextTokenOfKind($startIndex, ['{', [\T_IMPLEMENTS], [\T_EXTENDS]]);
59✔
385
        $endIndex = $tokens[$endIndex]->equals('{') ? $tokens->getPrevNonWhitespace($endIndex) : $endIndex;
59✔
386

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

391
                continue;
32✔
392
            }
393

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

399
        return $implementsInfo;
59✔
400
    }
401

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

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

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

427
                    continue;
20✔
428
                }
429

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

436
                    continue;
24✔
437
                }
438

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

446
                    continue;
7✔
447
                }
448

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

452
                    continue;
9✔
453
                }
454

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

457
                continue;
90✔
458
            }
459

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

463
                continue;
3✔
464
            }
465

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

469
                continue;
2✔
470
            }
471

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

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

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

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

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

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

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

506
                    break;
16✔
507
                }
508
            }
509

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

521
            $i = $previousInterfaceImplementingIndex + 1;
16✔
522
        }
523
    }
524

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

538
        $readonlyIndex = $classDefInfo['readonly'];
3✔
539

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

545
            $accessModifierIndex = $classDefInfo[$accessModifier];
2✔
546

547
            $readonlyToken = clone $tokens[$readonlyIndex];
2✔
548
            $accessToken = clone $tokens[$accessModifierIndex];
2✔
549

550
            $tokens[$readonlyIndex] = $accessToken;
2✔
551
            $tokens[$accessModifierIndex] = $readonlyToken;
2✔
552

553
            break;
2✔
554
        }
555
    }
556
}
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