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

keradus / PHP-CS-Fixer / 16253708939

13 Jul 2025 09:37PM UTC coverage: 94.807% (+0.001%) from 94.806%
16253708939

push

github

web-flow
Merge branch 'master' into native_constant_invocation__CS

2259 of 2334 new or added lines in 338 files covered. (96.79%)

7 existing lines in 5 files now uncovered.

28260 of 29808 relevant lines covered (94.81%)

45.39 hits per line

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

94.37
/src/Fixer/ClassNotation/ClassAttributesSeparationFixer.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\FixerDefinition\VersionSpecification;
28
use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
29
use PhpCsFixer\Preg;
30
use PhpCsFixer\Tokenizer\CT;
31
use PhpCsFixer\Tokenizer\FCT;
32
use PhpCsFixer\Tokenizer\Token;
33
use PhpCsFixer\Tokenizer\Tokens;
34
use PhpCsFixer\Tokenizer\TokensAnalyzer;
35
use PhpCsFixer\Utils;
36
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
37

38
/**
39
 * Make sure there is one blank line above and below class elements.
40
 *
41
 * The exception is when an element is the first or last item in a 'classy'.
42
 *
43
 * @phpstan-type _Class array{
44
 *      index: int,
45
 *      open: int,
46
 *      close: int,
47
 *      elements: non-empty-list<_Element>
48
 *  }
49
 * @phpstan-type _Element array{token: Token, type: string, index: int, start?: int, end?: int}
50
 * @phpstan-type _AutogeneratedInputConfiguration array{
51
 *  elements?: array<string, string>,
52
 * }
53
 * @phpstan-type _AutogeneratedComputedConfiguration array{
54
 *  elements: array<string, string>,
55
 * }
56
 *
57
 * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
58
 */
59
final class ClassAttributesSeparationFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
60
{
61
    /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */
62
    use ConfigurableFixerTrait;
63

64
    /**
65
     * @internal
66
     */
67
    public const SPACING_NONE = 'none';
68

69
    /**
70
     * @internal
71
     */
72
    public const SPACING_ONE = 'one';
73

74
    private const SPACING_ONLY_IF_META = 'only_if_meta';
75
    private const MODIFIER_TYPES = [\T_PRIVATE, \T_PROTECTED, \T_PUBLIC, \T_ABSTRACT, \T_FINAL, \T_STATIC, \T_STRING, \T_NS_SEPARATOR, \T_VAR, CT::T_NULLABLE_TYPE, CT::T_ARRAY_TYPEHINT, CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION, CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN, CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE, FCT::T_READONLY, FCT::T_PRIVATE_SET, FCT::T_PROTECTED_SET, FCT::T_PUBLIC_SET];
76

77
    /**
78
     * @var array<string, string>
79
     */
80
    private array $classElementTypes = [];
81

82
    public function getDefinition(): FixerDefinitionInterface
83
    {
84
        return new FixerDefinition(
3✔
85
            'Class, trait and interface elements must be separated with one or none blank line.',
3✔
86
            [
3✔
87
                new CodeSample(
3✔
88
                    '<?php
3✔
89
final class Sample
90
{
91
    protected function foo()
92
    {
93
    }
94
    protected function bar()
95
    {
96
    }
97

98

99
}
100
'
3✔
101
                ),
3✔
102
                new CodeSample(
3✔
103
                    '<?php
3✔
104
class Sample
105
{private $a; // foo
106
    /** second in a hour */
107
    private $b;
108
}
109
',
3✔
110
                    ['elements' => ['property' => self::SPACING_ONE]]
3✔
111
                ),
3✔
112
                new CodeSample(
3✔
113
                    '<?php
3✔
114
class Sample
115
{
116
    const A = 1;
117
    /** seconds in some hours */
118
    const B = 3600;
119
}
120
',
3✔
121
                    ['elements' => ['const' => self::SPACING_ONE]]
3✔
122
                ),
3✔
123
                new CodeSample(
3✔
124
                    '<?php
3✔
125
class Sample
126
{
127
    /** @var int */
128
    const SECOND = 1;
129
    /** @var int */
130
    const MINUTE = 60;
131

132
    const HOUR = 3600;
133

134
    const DAY = 86400;
135
}
136
',
3✔
137
                    ['elements' => ['const' => self::SPACING_ONLY_IF_META]]
3✔
138
                ),
3✔
139
                new VersionSpecificCodeSample(
3✔
140
                    '<?php
3✔
141
class Sample
142
{
143
    public $a;
144
    #[SetUp]
145
    public $b;
146
    /** @var string */
147
    public $c;
148
    /** @internal */
149
    #[Assert\String()]
150
    public $d;
151

152
    public $e;
153
}
154
',
3✔
155
                    new VersionSpecification(8_00_00),
3✔
156
                    ['elements' => ['property' => self::SPACING_ONLY_IF_META]]
3✔
157
                ),
3✔
158
            ]
3✔
159
        );
3✔
160
    }
161

162
    /**
163
     * {@inheritdoc}
164
     *
165
     * Must run before BracesFixer, IndentationTypeFixer, NoExtraBlankLinesFixer, StatementIndentationFixer.
166
     * Must run after OrderedClassElementsFixer, PhpUnitDataProviderMethodOrderFixer, SingleClassElementPerStatementFixer, VisibilityRequiredFixer.
167
     */
168
    public function getPriority(): int
169
    {
170
        return 55;
1✔
171
    }
172

173
    public function isCandidate(Tokens $tokens): bool
174
    {
175
        return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds());
95✔
176
    }
177

178
    protected function configurePostNormalisation(): void
179
    {
180
        $this->classElementTypes = []; // reset previous configuration
114✔
181

182
        foreach ($this->configuration['elements'] as $elementType => $spacing) {
114✔
183
            $this->classElementTypes[$elementType] = $spacing;
114✔
184
        }
185
    }
186

187
    protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
188
    {
189
        foreach ($this->getElementsByClass($tokens) as $class) {
95✔
190
            $elements = $class['elements'];
93✔
191
            $elementCount = \count($elements);
93✔
192

193
            if (0 === $elementCount) {
93✔
194
                continue;
×
195
            }
196

197
            if (isset($this->classElementTypes[$elements[0]['type']])) {
93✔
198
                $this->fixSpaceBelowClassElement($tokens, $class);
89✔
199
                $this->fixSpaceAboveClassElement($tokens, $class, 0);
89✔
200
            }
201

202
            for ($index = 1; $index < $elementCount; ++$index) {
93✔
203
                if (isset($this->classElementTypes[$elements[$index]['type']])) {
64✔
204
                    $this->fixSpaceAboveClassElement($tokens, $class, $index);
63✔
205
                }
206
            }
207
        }
208
    }
209

210
    protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
211
    {
212
        return new FixerConfigurationResolver([
114✔
213
            (new FixerOptionBuilder('elements', 'Dictionary of `const|method|property|trait_import|case` => `none|one|only_if_meta` values.'))
114✔
214
                ->setAllowedTypes(['array<string, string>'])
114✔
215
                ->setAllowedValues([static function (array $option): bool {
114✔
216
                    foreach ($option as $type => $spacing) {
114✔
217
                        $supportedTypes = ['const', 'method', 'property', 'trait_import', 'case'];
114✔
218

219
                        if (!\in_array($type, $supportedTypes, true)) {
114✔
220
                            throw new InvalidOptionsException(
2✔
221
                                \sprintf(
2✔
222
                                    'Unexpected element type, expected any of %s, got "%s".',
2✔
223
                                    Utils::naturalLanguageJoin($supportedTypes),
2✔
224
                                    \gettype($type).'#'.$type
2✔
225
                                )
2✔
226
                            );
2✔
227
                        }
228

229
                        $supportedSpacings = [self::SPACING_NONE, self::SPACING_ONE, self::SPACING_ONLY_IF_META];
114✔
230

231
                        if (!\in_array($spacing, $supportedSpacings, true)) {
114✔
232
                            throw new InvalidOptionsException(
1✔
233
                                \sprintf(
1✔
234
                                    'Unexpected spacing for element type "%s", expected any of %s, got "%s".',
1✔
235
                                    $spacing,
1✔
236
                                    Utils::naturalLanguageJoin($supportedSpacings),
1✔
237
                                    \is_object($spacing) ? \get_class($spacing) : (null === $spacing ? 'null' : \gettype($spacing).'#'.$spacing)
1✔
238
                                )
1✔
239
                            );
1✔
240
                        }
241
                    }
242

243
                    return true;
114✔
244
                }])
114✔
245
                ->setDefault([
114✔
246
                    'const' => self::SPACING_ONE,
114✔
247
                    'method' => self::SPACING_ONE,
114✔
248
                    'property' => self::SPACING_ONE,
114✔
249
                    'trait_import' => self::SPACING_NONE,
114✔
250
                    'case' => self::SPACING_NONE,
114✔
251
                ])
114✔
252
                ->getOption(),
114✔
253
        ]);
114✔
254
    }
255

256
    /**
257
     * Fix spacing above an element of a class, interface or trait.
258
     *
259
     * Deals with comments, PHPDocs and spaces above the element with respect to the position of the
260
     * element within the class, interface or trait.
261
     *
262
     * @param _Class $class
263
     */
264
    private function fixSpaceAboveClassElement(Tokens $tokens, array $class, int $elementIndex): void
265
    {
266
        $element = $class['elements'][$elementIndex];
92✔
267
        $elementAboveEnd = isset($class['elements'][$elementIndex + 1]) ? $class['elements'][$elementIndex + 1]['end'] : 0;
92✔
268
        $nonWhiteAbove = $tokens->getPrevNonWhitespace($element['start']);
92✔
269

270
        // element is directly after class open brace
271
        if ($nonWhiteAbove === $class['open']) {
92✔
272
            $this->correctLineBreaks($tokens, $nonWhiteAbove, $element['start'], 1);
65✔
273

274
            return;
65✔
275
        }
276

277
        // deal with comments above an element
278
        if ($tokens[$nonWhiteAbove]->isGivenKind(\T_COMMENT)) {
81✔
279
            // check if the comment belongs to the previous element
280
            if ($elementAboveEnd === $nonWhiteAbove) {
25✔
281
                $this->correctLineBreaks($tokens, $nonWhiteAbove, $element['start'], $this->determineRequiredLineCount($tokens, $class, $elementIndex));
8✔
282

283
                return;
8✔
284
            }
285

286
            // more than one line break, always bring it back to 2 line breaks between the element start and what is above it
287
            if ($tokens[$nonWhiteAbove + 1]->isWhitespace() && substr_count($tokens[$nonWhiteAbove + 1]->getContent(), "\n") > 1) {
19✔
288
                $this->correctLineBreaks($tokens, $nonWhiteAbove, $element['start'], 2);
8✔
289

290
                return;
8✔
291
            }
292

293
            // there are 2 cases:
294
            if (
295
                1 === $element['start'] - $nonWhiteAbove
13✔
296
                || $tokens[$nonWhiteAbove - 1]->isWhitespace() && substr_count($tokens[$nonWhiteAbove - 1]->getContent(), "\n") > 0
13✔
297
                || $tokens[$nonWhiteAbove + 1]->isWhitespace() && substr_count($tokens[$nonWhiteAbove + 1]->getContent(), "\n") > 0
13✔
298
            ) {
299
                // 1. The comment is meant for the element (although not a PHPDoc),
300
                //    make sure there is one line break between the element and the comment...
301
                $this->correctLineBreaks($tokens, $nonWhiteAbove, $element['start'], 1);
13✔
302
                //    ... and make sure there is blank line above the comment (with the exception when it is directly after a class opening)
303
                $nonWhiteAbove = $this->findCommentBlockStart($tokens, $nonWhiteAbove, $elementAboveEnd);
13✔
304
                $nonWhiteAboveComment = $tokens->getPrevNonWhitespace($nonWhiteAbove);
13✔
305

306
                if ($nonWhiteAboveComment === $class['open']) {
13✔
307
                    if ($tokens[$nonWhiteAboveComment - 1]->isWhitespace() && substr_count($tokens[$nonWhiteAboveComment - 1]->getContent(), "\n") > 0) {
10✔
308
                        $this->correctLineBreaks($tokens, $nonWhiteAboveComment, $nonWhiteAbove, 1);
7✔
309
                    }
310
                } else {
311
                    $this->correctLineBreaks($tokens, $nonWhiteAboveComment, $nonWhiteAbove, 2);
3✔
312
                }
313
            } else {
314
                // 2. The comment belongs to the code above the element,
315
                //    make sure there is a blank line above the element (i.e. 2 line breaks)
316
                $this->correctLineBreaks($tokens, $nonWhiteAbove, $element['start'], 2);
×
317
            }
318

319
            return;
13✔
320
        }
321

322
        // deal with element with a PHPDoc/attribute above it
323
        if ($tokens[$nonWhiteAbove]->isGivenKind([\T_DOC_COMMENT, CT::T_ATTRIBUTE_CLOSE])) {
64✔
324
            // there should be one linebreak between the element and the attribute above it
325
            $this->correctLineBreaks($tokens, $nonWhiteAbove, $element['start'], 1);
24✔
326

327
            // make sure there is blank line above the comment (with the exception when it is directly after a class opening)
328
            $nonWhiteAbove = $this->findCommentBlockStart($tokens, $nonWhiteAbove, $elementAboveEnd);
24✔
329
            $nonWhiteAboveComment = $tokens->getPrevNonWhitespace($nonWhiteAbove);
24✔
330

331
            $this->correctLineBreaks($tokens, $nonWhiteAboveComment, $nonWhiteAbove, $nonWhiteAboveComment === $class['open'] ? 1 : 2);
24✔
332

333
            return;
24✔
334
        }
335

336
        $this->correctLineBreaks($tokens, $nonWhiteAbove, $element['start'], $this->determineRequiredLineCount($tokens, $class, $elementIndex));
54✔
337
    }
338

339
    /**
340
     * @param _Class $class
341
     */
342
    private function determineRequiredLineCount(Tokens $tokens, array $class, int $elementIndex): int
343
    {
344
        $type = $class['elements'][$elementIndex]['type'];
55✔
345
        $spacing = $this->classElementTypes[$type];
55✔
346

347
        if (self::SPACING_ONE === $spacing) {
55✔
348
            return 2;
40✔
349
        }
350

351
        if (self::SPACING_NONE === $spacing) {
21✔
352
            if (!isset($class['elements'][$elementIndex + 1])) {
14✔
353
                return 1;
×
354
            }
355

356
            $aboveElement = $class['elements'][$elementIndex + 1];
14✔
357

358
            if ($aboveElement['type'] !== $type) {
14✔
359
                return 2;
2✔
360
            }
361

362
            $aboveElementDocCandidateIndex = $tokens->getPrevNonWhitespace($aboveElement['start']);
14✔
363

364
            return $tokens[$aboveElementDocCandidateIndex]->isGivenKind([\T_DOC_COMMENT, CT::T_ATTRIBUTE_CLOSE]) ? 2 : 1;
14✔
365
        }
366

367
        if (self::SPACING_ONLY_IF_META === $spacing) {
7✔
368
            $aboveElementDocCandidateIndex = $tokens->getPrevNonWhitespace($class['elements'][$elementIndex]['start']);
7✔
369

370
            return $tokens[$aboveElementDocCandidateIndex]->isGivenKind([\T_DOC_COMMENT, CT::T_ATTRIBUTE_CLOSE]) ? 2 : 1;
7✔
371
        }
372

373
        throw new \RuntimeException(\sprintf('Unknown spacing "%s".', $spacing));
×
374
    }
375

376
    /**
377
     * @param _Class $class
378
     */
379
    private function fixSpaceBelowClassElement(Tokens $tokens, array $class): void
380
    {
381
        $element = $class['elements'][0];
89✔
382

383
        // if this is last element fix; fix to the class end `}` here if appropriate
384
        if ($class['close'] === $tokens->getNextNonWhitespace($element['end'])) {
89✔
385
            $this->correctLineBreaks($tokens, $element['end'], $class['close'], 1);
88✔
386
        }
387
    }
388

389
    private function correctLineBreaks(Tokens $tokens, int $startIndex, int $endIndex, int $reqLineCount): void
390
    {
391
        $lineEnding = $this->whitespacesConfig->getLineEnding();
92✔
392

393
        ++$startIndex;
92✔
394
        $numbOfWhiteTokens = $endIndex - $startIndex;
92✔
395

396
        if (0 === $numbOfWhiteTokens) {
92✔
397
            $tokens->insertAt($startIndex, new Token([\T_WHITESPACE, str_repeat($lineEnding, $reqLineCount)]));
17✔
398

399
            return;
17✔
400
        }
401

402
        $lineBreakCount = $this->getLineBreakCount($tokens, $startIndex, $endIndex);
92✔
403

404
        if ($reqLineCount === $lineBreakCount) {
92✔
405
            return;
92✔
406
        }
407

408
        if ($lineBreakCount < $reqLineCount) {
69✔
409
            $tokens[$startIndex] = new Token([
50✔
410
                \T_WHITESPACE,
50✔
411
                str_repeat($lineEnding, $reqLineCount - $lineBreakCount).$tokens[$startIndex]->getContent(),
50✔
412
            ]);
50✔
413

414
            return;
50✔
415
        }
416

417
        // $lineCount = > $reqLineCount : check the one Token case first since this one will be true most of the time
418
        if (1 === $numbOfWhiteTokens) {
39✔
419
            $tokens[$startIndex] = new Token([
39✔
420
                \T_WHITESPACE,
39✔
421
                Preg::replace('/\r\n|\n/', '', $tokens[$startIndex]->getContent(), $lineBreakCount - $reqLineCount),
39✔
422
            ]);
39✔
423

424
            return;
39✔
425
        }
426

427
        // $numbOfWhiteTokens = > 1
428
        $toReplaceCount = $lineBreakCount - $reqLineCount;
×
429

430
        for ($i = $startIndex; $i < $endIndex && $toReplaceCount > 0; ++$i) {
×
431
            $tokenLineCount = substr_count($tokens[$i]->getContent(), "\n");
×
432

433
            if ($tokenLineCount > 0) {
×
434
                $tokens[$i] = new Token([
×
NEW
435
                    \T_WHITESPACE,
×
436
                    Preg::replace('/\r\n|\n/', '', $tokens[$i]->getContent(), min($toReplaceCount, $tokenLineCount)),
×
437
                ]);
×
438
                $toReplaceCount -= $tokenLineCount;
×
439
            }
440
        }
441
    }
442

443
    private function getLineBreakCount(Tokens $tokens, int $startIndex, int $endIndex): int
444
    {
445
        $lineCount = 0;
100✔
446

447
        for ($i = $startIndex; $i < $endIndex; ++$i) {
100✔
448
            $lineCount += substr_count($tokens[$i]->getContent(), "\n");
100✔
449
        }
450

451
        return $lineCount;
100✔
452
    }
453

454
    private function findCommentBlockStart(Tokens $tokens, int $start, int $elementAboveEnd): int
455
    {
456
        for ($i = $start; $i > $elementAboveEnd; --$i) {
44✔
457
            if ($tokens[$i]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) {
44✔
458
                $start = $i = $tokens->findBlockStart(Tokens::BLOCK_TYPE_ATTRIBUTE, $i);
7✔
459

460
                continue;
7✔
461
            }
462

463
            if ($tokens[$i]->isComment()) {
44✔
464
                $start = $i;
39✔
465

466
                continue;
39✔
467
            }
468

469
            if (!$tokens[$i]->isWhitespace() || $this->getLineBreakCount($tokens, $i, $i + 1) > 1) {
44✔
470
                break;
40✔
471
            }
472
        }
473

474
        return $start;
44✔
475
    }
476

477
    /**
478
     * @TODO Introduce proper DTO instead of an array
479
     *
480
     * @return \Generator<_Class>
481
     */
482
    private function getElementsByClass(Tokens $tokens): \Generator
483
    {
484
        $tokensAnalyzer = new TokensAnalyzer($tokens);
95✔
485
        $class = $classIndex = false;
95✔
486

487
        foreach (array_reverse($tokensAnalyzer->getClassyElements(), true) as $index => $element) {
95✔
488
            $element['index'] = $index;
93✔
489

490
            if ($element['classIndex'] !== $classIndex) {
93✔
491
                if (false !== $class) {
93✔
492
                    yield $class;
4✔
493
                }
494

495
                $classIndex = $element['classIndex'];
93✔
496
                $classOpen = $tokens->getNextTokenOfKind($classIndex, ['{']);
93✔
497
                $classEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classOpen);
93✔
498
                $class = [
93✔
499
                    'index' => $classIndex,
93✔
500
                    'open' => $classOpen,
93✔
501
                    'close' => $classEnd,
93✔
502
                    'elements' => [],
93✔
503
                ];
93✔
504
            }
505

506
            unset($element['classIndex']);
93✔
507
            $element['start'] = $this->getFirstTokenIndexOfClassElement($tokens, $class, $element);
93✔
508
            $element['end'] = $this->getLastTokenIndexOfClassElement($tokens, $class, $element, $tokensAnalyzer);
93✔
509

510
            $class['elements'][] = $element; // reset the key by design
93✔
511
        }
512

513
        if (false !== $class) {
95✔
514
            yield $class;
93✔
515
        }
516
    }
517

518
    /**
519
     * including trailing single line comments if belonging to the class element.
520
     *
521
     * @param _Class   $class
522
     * @param _Element $element
523
     */
524
    private function getFirstTokenIndexOfClassElement(Tokens $tokens, array $class, array $element): int
525
    {
526
        $firstElementAttributeIndex = $element['index'];
93✔
527

528
        do {
529
            $nonWhiteAbove = $tokens->getPrevMeaningfulToken($firstElementAttributeIndex);
93✔
530

531
            if (null !== $nonWhiteAbove && $tokens[$nonWhiteAbove]->isGivenKind(self::MODIFIER_TYPES)) {
93✔
532
                $firstElementAttributeIndex = $nonWhiteAbove;
77✔
533
            } else {
534
                break;
93✔
535
            }
536
        } while ($firstElementAttributeIndex > $class['open']);
77✔
537

538
        return $firstElementAttributeIndex;
93✔
539
    }
540

541
    /**
542
     * including trailing single line comments if belonging to the class element.
543
     *
544
     * @param _Class   $class
545
     * @param _Element $element
546
     */
547
    private function getLastTokenIndexOfClassElement(Tokens $tokens, array $class, array $element, TokensAnalyzer $tokensAnalyzer): int
548
    {
549
        // find last token of the element
550
        if ('method' === $element['type'] && !$tokens[$class['index']]->isGivenKind(\T_INTERFACE)) {
93✔
551
            $attributes = $tokensAnalyzer->getMethodAttributes($element['index']);
53✔
552

553
            if (true === $attributes['abstract']) {
53✔
554
                $elementEndIndex = $tokens->getNextTokenOfKind($element['index'], [';']);
5✔
555
            } else {
556
                $elementEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $tokens->getNextTokenOfKind($element['index'], ['{']));
51✔
557
            }
558
        } elseif ('trait_import' === $element['type']) {
63✔
559
            $elementEndIndex = $element['index'];
13✔
560

561
            do {
562
                $elementEndIndex = $tokens->getNextMeaningfulToken($elementEndIndex);
13✔
563
            } while ($tokens[$elementEndIndex]->isGivenKind([\T_STRING, \T_NS_SEPARATOR]) || $tokens[$elementEndIndex]->equals(','));
13✔
564

565
            if (!$tokens[$elementEndIndex]->equals(';')) {
13✔
566
                $elementEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $tokens->getNextTokenOfKind($element['index'], ['{']));
4✔
567
            }
568
        } else { // 'const', 'property', enum-'case', or 'method' of an interface
569
            $elementEndIndex = $tokens->getNextTokenOfKind($element['index'], [';', '{']);
52✔
570
        }
571

572
        $singleLineElement = true;
93✔
573

574
        for ($i = $element['index'] + 1; $i < $elementEndIndex; ++$i) {
93✔
575
            if (str_contains($tokens[$i]->getContent(), "\n")) {
73✔
576
                $singleLineElement = false;
31✔
577

578
                break;
31✔
579
            }
580
        }
581

582
        if ($singleLineElement) {
93✔
583
            while (true) {
77✔
584
                $nextToken = $tokens[$elementEndIndex + 1];
77✔
585

586
                if (($nextToken->isComment() || $nextToken->isWhitespace()) && !str_contains($nextToken->getContent(), "\n")) {
77✔
587
                    ++$elementEndIndex;
14✔
588
                } else {
589
                    break;
77✔
590
                }
591
            }
592

593
            if ($tokens[$elementEndIndex]->isWhitespace()) {
77✔
594
                $elementEndIndex = $tokens->getPrevNonWhitespace($elementEndIndex);
2✔
595
            }
596
        }
597

598
        return $elementEndIndex;
93✔
599
    }
600
}
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