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

keradus / PHP-CS-Fixer / 16253811376

13 Jul 2025 09:50PM UTC coverage: 94.794% (+0.001%) from 94.793%
16253811376

push

github

web-flow
Merge branch 'master' into sf8

2272 of 2348 new or added lines in 338 files covered. (96.76%)

7 existing lines in 5 files now uncovered.

28259 of 29811 relevant lines covered (94.79%)

45.39 hits per line

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

67.39
/src/Fixer/AbstractPhpUnitFixer.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;
16

17
use PhpCsFixer\AbstractFixer;
18
use PhpCsFixer\DocBlock\DocBlock;
19
use PhpCsFixer\DocBlock\Line;
20
use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceUseAnalysis;
21
use PhpCsFixer\Tokenizer\Analyzer\AttributeAnalyzer;
22
use PhpCsFixer\Tokenizer\Analyzer\FullyQualifiedNameAnalyzer;
23
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
24
use PhpCsFixer\Tokenizer\Analyzer\PhpUnitTestCaseAnalyzer;
25
use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer;
26
use PhpCsFixer\Tokenizer\CT;
27
use PhpCsFixer\Tokenizer\FCT;
28
use PhpCsFixer\Tokenizer\Token;
29
use PhpCsFixer\Tokenizer\Tokens;
30

31
/**
32
 * @internal
33
 */
34
abstract class AbstractPhpUnitFixer extends AbstractFixer
35
{
36
    private const DOC_BLOCK_MODIFIERS = [\T_PUBLIC, \T_PROTECTED, \T_PRIVATE, \T_FINAL, \T_ABSTRACT, \T_COMMENT, FCT::T_ATTRIBUTE, FCT::T_READONLY];
37
    private const ATTRIBUTE_MODIFIERS = [\T_FINAL, FCT::T_READONLY];
38

39
    public function isCandidate(Tokens $tokens): bool
40
    {
41
        return $tokens->isAllTokenKindsFound([\T_CLASS, \T_STRING]);
60✔
42
    }
43

44
    final protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
45
    {
46
        foreach ((new PhpUnitTestCaseAnalyzer())->findPhpUnitClasses($tokens) as $indices) {
60✔
47
            $this->applyPhpUnitClassFix($tokens, $indices[0], $indices[1]);
57✔
48
        }
49
    }
50

51
    abstract protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void;
52

53
    final protected function getDocBlockIndex(Tokens $tokens, int $index): int
54
    {
55
        do {
56
            $index = $tokens->getPrevNonWhitespace($index);
55✔
57

58
            if ($tokens[$index]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) {
55✔
59
                $index = $tokens->getPrevTokenOfKind($index, [[\T_ATTRIBUTE]]);
29✔
60
            }
61
        } while ($tokens[$index]->isGivenKind(self::DOC_BLOCK_MODIFIERS));
55✔
62

63
        return $index;
55✔
64
    }
65

66
    /**
67
     * @param list<string>       $preventingAnnotations
68
     * @param list<class-string> $preventingAttributes
69
     */
70
    final protected function ensureIsDocBlockWithAnnotation(
71
        Tokens $tokens,
72
        int $index,
73
        string $annotation,
74
        array $preventingAnnotations,
75
        array $preventingAttributes
76
    ): void {
77
        $docBlockIndex = $this->getDocBlockIndex($tokens, $index);
55✔
78

79
        if (self::isPreventedByAttribute($tokens, $index, $preventingAttributes)) {
55✔
80
            return;
24✔
81
        }
82

83
        if ($this->isPHPDoc($tokens, $docBlockIndex)) {
31✔
84
            $this->updateDocBlockIfNeeded($tokens, $docBlockIndex, $annotation, $preventingAnnotations);
29✔
85
        } else {
86
            $this->createDocBlock($tokens, $docBlockIndex, $annotation);
16✔
87
        }
88
    }
89

90
    final protected function isPHPDoc(Tokens $tokens, int $index): bool
91
    {
92
        return $tokens[$index]->isGivenKind(\T_DOC_COMMENT);
31✔
93
    }
94

95
    /**
96
     * @return iterable<array{
97
     *     index: int,
98
     *     loweredName: string,
99
     *     openBraceIndex: int,
100
     *     closeBraceIndex: int,
101
     * }>
102
     */
103
    protected function getPreviousAssertCall(Tokens $tokens, int $startIndex, int $endIndex): iterable
104
    {
105
        $functionsAnalyzer = new FunctionsAnalyzer();
×
106

107
        for ($index = $endIndex; $index > $startIndex; --$index) {
×
NEW
108
            $index = $tokens->getPrevTokenOfKind($index, [[\T_STRING]]);
×
109

110
            if (null === $index) {
×
111
                return;
×
112
            }
113

114
            // test if "assert" something call
115
            $loweredContent = strtolower($tokens[$index]->getContent());
×
116

117
            if (!str_starts_with($loweredContent, 'assert')) {
×
118
                continue;
×
119
            }
120

121
            // test candidate for simple calls like: ([\]+'some fixable call'(...))
122
            $openBraceIndex = $tokens->getNextMeaningfulToken($index);
×
123

124
            if (!$tokens[$openBraceIndex]->equals('(')) {
×
125
                continue;
×
126
            }
127

128
            if (!$functionsAnalyzer->isTheSameClassCall($tokens, $index)) {
×
129
                continue;
×
130
            }
131

132
            yield [
×
133
                'index' => $index,
×
134
                'loweredName' => $loweredContent,
×
135
                'openBraceIndex' => $openBraceIndex,
×
136
                'closeBraceIndex' => $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openBraceIndex),
×
137
            ];
×
138
        }
139
    }
140

141
    final protected function isTestAttributePresent(Tokens $tokens, int $index): bool
142
    {
143
        $attributeIndex = $tokens->getPrevTokenOfKind($index, ['{', [FCT::T_ATTRIBUTE]]);
×
144
        if (!$tokens[$attributeIndex]->isGivenKind(FCT::T_ATTRIBUTE)) {
×
145
            return false;
×
146
        }
147

148
        $fullyQualifiedNameAnalyzer = new FullyQualifiedNameAnalyzer($tokens);
×
149

150
        foreach (AttributeAnalyzer::collect($tokens, $attributeIndex) as $attributeAnalysis) {
×
151
            foreach ($attributeAnalysis->getAttributes() as $attribute) {
×
152
                $attributeName = strtolower($fullyQualifiedNameAnalyzer->getFullyQualifiedName($attribute['name'], $attribute['start'], NamespaceUseAnalysis::TYPE_CLASS));
×
153
                if ('phpunit\framework\attributes\test' === $attributeName) {
×
154
                    return true;
×
155
                }
156
            }
157
        }
158

159
        return false;
×
160
    }
161

162
    private function createDocBlock(Tokens $tokens, int $docBlockIndex, string $annotation): void
163
    {
164
        $lineEnd = $this->whitespacesConfig->getLineEnding();
16✔
165
        $originalIndent = WhitespacesAnalyzer::detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex));
16✔
166
        $toInsert = [
16✔
167
            new Token([\T_DOC_COMMENT, "/**{$lineEnd}{$originalIndent} * @{$annotation}{$lineEnd}{$originalIndent} */"]),
16✔
168
            new Token([\T_WHITESPACE, $lineEnd.$originalIndent]),
16✔
169
        ];
16✔
170
        $index = $tokens->getNextMeaningfulToken($docBlockIndex);
16✔
171
        $tokens->insertAt($index, $toInsert);
16✔
172

173
        if (!$tokens[$index - 1]->isGivenKind(\T_WHITESPACE)) {
16✔
174
            $extraNewLines = $this->whitespacesConfig->getLineEnding();
3✔
175

176
            if (!$tokens[$index - 1]->isGivenKind(\T_OPEN_TAG)) {
3✔
177
                $extraNewLines .= $this->whitespacesConfig->getLineEnding();
1✔
178
            }
179

180
            $tokens->insertAt($index, [
3✔
181
                new Token([\T_WHITESPACE, $extraNewLines.WhitespacesAnalyzer::detectIndent($tokens, $index)]),
3✔
182
            ]);
3✔
183
        }
184
    }
185

186
    /**
187
     * @param list<string> $preventingAnnotations
188
     */
189
    private function updateDocBlockIfNeeded(
190
        Tokens $tokens,
191
        int $docBlockIndex,
192
        string $annotation,
193
        array $preventingAnnotations
194
    ): void {
195
        $doc = new DocBlock($tokens[$docBlockIndex]->getContent());
29✔
196
        foreach ($preventingAnnotations as $preventingAnnotation) {
29✔
197
            if ([] !== $doc->getAnnotationsOfType($preventingAnnotation)) {
29✔
198
                return;
29✔
199
            }
200
        }
201
        $doc = $this->makeDocBlockMultiLineIfNeeded($doc, $tokens, $docBlockIndex, $annotation);
11✔
202

203
        $lines = $this->addInternalAnnotation($doc, $tokens, $docBlockIndex, $annotation);
11✔
204
        $lines = implode('', $lines);
11✔
205

206
        $tokens->getNamespaceDeclarations();
11✔
207
        $tokens[$docBlockIndex] = new Token([\T_DOC_COMMENT, $lines]);
11✔
208
    }
209

210
    /**
211
     * @param list<class-string> $preventingAttributes
212
     */
213
    private static function isPreventedByAttribute(Tokens $tokens, int $index, array $preventingAttributes): bool
214
    {
215
        if ([] === $preventingAttributes) {
55✔
216
            return false;
×
217
        }
218

219
        do {
220
            $index = $tokens->getPrevMeaningfulToken($index);
55✔
221
        } while ($tokens[$index]->isGivenKind(self::ATTRIBUTE_MODIFIERS));
55✔
222
        if (!$tokens[$index]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) {
55✔
223
            return false;
26✔
224
        }
225
        $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_ATTRIBUTE, $index);
29✔
226

227
        $fullyQualifiedNameAnalyzer = new FullyQualifiedNameAnalyzer($tokens);
29✔
228

229
        foreach (AttributeAnalyzer::collect($tokens, $index) as $attributeAnalysis) {
29✔
230
            foreach ($attributeAnalysis->getAttributes() as $attribute) {
29✔
231
                if (\in_array(strtolower($fullyQualifiedNameAnalyzer->getFullyQualifiedName($attribute['name'], $attribute['start'], NamespaceUseAnalysis::TYPE_CLASS)), $preventingAttributes, true)) {
29✔
232
                    return true;
24✔
233
                }
234
            }
235
        }
236

237
        return false;
5✔
238
    }
239

240
    /**
241
     * @return list<Line>
242
     */
243
    private function addInternalAnnotation(DocBlock $docBlock, Tokens $tokens, int $docBlockIndex, string $annotation): array
244
    {
245
        $lines = $docBlock->getLines();
11✔
246
        $originalIndent = WhitespacesAnalyzer::detectIndent($tokens, $docBlockIndex);
11✔
247
        $lineEnd = $this->whitespacesConfig->getLineEnding();
11✔
248
        array_splice($lines, -1, 0, $originalIndent.' * @'.$annotation.$lineEnd);
11✔
249

250
        return $lines;
11✔
251
    }
252

253
    private function makeDocBlockMultiLineIfNeeded(DocBlock $doc, Tokens $tokens, int $docBlockIndex, string $annotation): DocBlock
254
    {
255
        $lines = $doc->getLines();
11✔
256
        if (1 === \count($lines) && [] === $doc->getAnnotationsOfType($annotation)) {
11✔
257
            $indent = WhitespacesAnalyzer::detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex));
2✔
258
            $doc->makeMultiLine($indent, $this->whitespacesConfig->getLineEnding());
2✔
259

260
            return $doc;
2✔
261
        }
262

263
        return $doc;
9✔
264
    }
265
}
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