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

keradus / PHP-CS-Fixer / 13466172931

21 Feb 2025 10:17PM UTC coverage: 94.942% (+0.01%) from 94.928%
13466172931

push

github

keradus
Merge remote-tracking branch 'upstream/master' into header

14 of 27 new or added lines in 3 files covered. (51.85%)

6 existing lines in 2 files now uncovered.

28064 of 29559 relevant lines covered (94.94%)

43.05 hits per line

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

70.8
/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\Indicator\PhpUnitTestCaseIndicator;
21
use PhpCsFixer\Tokenizer\Analyzer\AttributeAnalyzer;
22
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
23
use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer;
24
use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer;
25
use PhpCsFixer\Tokenizer\CT;
26
use PhpCsFixer\Tokenizer\Token;
27
use PhpCsFixer\Tokenizer\Tokens;
28

29
/**
30
 * @internal
31
 */
32
abstract class AbstractPhpUnitFixer extends AbstractFixer
33
{
34
    public function isCandidate(Tokens $tokens): bool
35
    {
36
        return $tokens->isAllTokenKindsFound([T_CLASS, T_STRING]);
59✔
37
    }
38

39
    final protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
40
    {
41
        $phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator();
59✔
42

43
        foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indices) {
59✔
44
            $this->applyPhpUnitClassFix($tokens, $indices[0], $indices[1]);
56✔
45
        }
46
    }
47

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

50
    final protected function getDocBlockIndex(Tokens $tokens, int $index): int
51
    {
52
        $modifiers = [T_PUBLIC, T_PROTECTED, T_PRIVATE, T_FINAL, T_ABSTRACT, T_COMMENT];
54✔
53

54
        if (\defined('T_ATTRIBUTE')) { // @TODO: drop condition when PHP 8.0+ is required
54✔
55
            $modifiers[] = T_ATTRIBUTE;
54✔
56
        }
57

58
        if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.2+ is required
54✔
59
            $modifiers[] = T_READONLY;
54✔
60
        }
61

62
        do {
63
            $index = $tokens->getPrevNonWhitespace($index);
54✔
64

65
            if ($tokens[$index]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) {
54✔
66
                $index = $tokens->getPrevTokenOfKind($index, [[T_ATTRIBUTE]]);
28✔
67
            }
68
        } while ($tokens[$index]->isGivenKind($modifiers));
54✔
69

70
        return $index;
54✔
71
    }
72

73
    /**
74
     * @param list<string>       $preventingAnnotations
75
     * @param list<class-string> $preventingAttributes
76
     */
77
    final protected function ensureIsDocBlockWithAnnotation(
78
        Tokens $tokens,
79
        int $index,
80
        string $annotation,
81
        array $preventingAnnotations,
82
        array $preventingAttributes
83
    ): void {
84
        $docBlockIndex = $this->getDocBlockIndex($tokens, $index);
54✔
85

86
        if (self::isPreventedByAttribute($tokens, $index, $preventingAttributes)) {
54✔
87
            return;
23✔
88
        }
89

90
        if ($this->isPHPDoc($tokens, $docBlockIndex)) {
31✔
91
            $this->updateDocBlockIfNeeded($tokens, $docBlockIndex, $annotation, $preventingAnnotations);
29✔
92
        } else {
93
            $this->createDocBlock($tokens, $docBlockIndex, $annotation);
16✔
94
        }
95
    }
96

97
    final protected function isPHPDoc(Tokens $tokens, int $index): bool
98
    {
99
        return $tokens[$index]->isGivenKind(T_DOC_COMMENT);
31✔
100
    }
101

102
    /**
103
     * @return iterable<array{
104
     *     index: int,
105
     *     loweredName: string,
106
     *     openBraceIndex: int,
107
     *     closeBraceIndex: int,
108
     * }>
109
     */
110
    protected function getPreviousAssertCall(Tokens $tokens, int $startIndex, int $endIndex): iterable
111
    {
112
        $functionsAnalyzer = new FunctionsAnalyzer();
×
113

114
        for ($index = $endIndex; $index > $startIndex; --$index) {
×
115
            $index = $tokens->getPrevTokenOfKind($index, [[T_STRING]]);
×
116

117
            if (null === $index) {
×
118
                return;
×
119
            }
120

121
            // test if "assert" something call
122
            $loweredContent = strtolower($tokens[$index]->getContent());
×
123

124
            if (!str_starts_with($loweredContent, 'assert')) {
×
125
                continue;
×
126
            }
127

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

131
            if (!$tokens[$openBraceIndex]->equals('(')) {
×
132
                continue;
×
133
            }
134

135
            if (!$functionsAnalyzer->isTheSameClassCall($tokens, $index)) {
×
136
                continue;
×
137
            }
138

139
            yield [
×
140
                'index' => $index,
×
141
                'loweredName' => $loweredContent,
×
142
                'openBraceIndex' => $openBraceIndex,
×
143
                'closeBraceIndex' => $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openBraceIndex),
×
144
            ];
×
145
        }
146
    }
147

148
    final protected function isTestAttributePresent(Tokens $tokens, int $index): bool
149
    {
NEW
150
        $prevMethodEndIndex = $tokens->getPrevTokenOfKind($index, ['}']);
×
NEW
151
        $currentIndex = $index - 1;
×
152

NEW
153
        while (true) {
×
NEW
154
            $attributeIndex = $tokens->getPrevTokenOfKind($currentIndex, [[T_ATTRIBUTE]]);
×
NEW
155
            if (null === $attributeIndex || $attributeIndex <= $prevMethodEndIndex) {
×
NEW
156
                break;
×
157
            }
158

NEW
159
            foreach (AttributeAnalyzer::collect($tokens, $attributeIndex) as $attributeAnalysis) {
×
NEW
160
                foreach ($attributeAnalysis->getAttributes() as $attribute) {
×
NEW
161
                    $attributeName = ltrim(self::getFullyQualifiedName($tokens, $attribute['name']), '\\');
×
NEW
162
                    if ('phpunit\framework\attributes\test' === $attributeName) {
×
NEW
163
                        return true;
×
164
                    }
165
                }
166
            }
167

NEW
168
            $currentIndex = $attributeIndex - 1;
×
169
        }
170

NEW
171
        return false;
×
172
    }
173

174
    private function createDocBlock(Tokens $tokens, int $docBlockIndex, string $annotation): void
175
    {
176
        $lineEnd = $this->whitespacesConfig->getLineEnding();
16✔
177
        $originalIndent = WhitespacesAnalyzer::detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex));
16✔
178
        $toInsert = [
16✔
179
            new Token([T_DOC_COMMENT, "/**{$lineEnd}{$originalIndent} * @{$annotation}{$lineEnd}{$originalIndent} */"]),
16✔
180
            new Token([T_WHITESPACE, $lineEnd.$originalIndent]),
16✔
181
        ];
16✔
182
        $index = $tokens->getNextMeaningfulToken($docBlockIndex);
16✔
183
        $tokens->insertAt($index, $toInsert);
16✔
184

185
        if (!$tokens[$index - 1]->isGivenKind(T_WHITESPACE)) {
16✔
186
            $extraNewLines = $this->whitespacesConfig->getLineEnding();
3✔
187

188
            if (!$tokens[$index - 1]->isGivenKind(T_OPEN_TAG)) {
3✔
189
                $extraNewLines .= $this->whitespacesConfig->getLineEnding();
1✔
190
            }
191

192
            $tokens->insertAt($index, [
3✔
193
                new Token([T_WHITESPACE, $extraNewLines.WhitespacesAnalyzer::detectIndent($tokens, $index)]),
3✔
194
            ]);
3✔
195
        }
196
    }
197

198
    /**
199
     * @param list<string> $preventingAnnotations
200
     */
201
    private function updateDocBlockIfNeeded(
202
        Tokens $tokens,
203
        int $docBlockIndex,
204
        string $annotation,
205
        array $preventingAnnotations
206
    ): void {
207
        $doc = new DocBlock($tokens[$docBlockIndex]->getContent());
29✔
208
        foreach ($preventingAnnotations as $preventingAnnotation) {
29✔
209
            if ([] !== $doc->getAnnotationsOfType($preventingAnnotation)) {
29✔
210
                return;
29✔
211
            }
212
        }
213
        $doc = $this->makeDocBlockMultiLineIfNeeded($doc, $tokens, $docBlockIndex, $annotation);
11✔
214

215
        $lines = $this->addInternalAnnotation($doc, $tokens, $docBlockIndex, $annotation);
11✔
216
        $lines = implode('', $lines);
11✔
217

218
        $tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $lines]);
11✔
219
    }
220

221
    /**
222
     * @param list<class-string> $preventingAttributes
223
     */
224
    private static function isPreventedByAttribute(Tokens $tokens, int $index, array $preventingAttributes): bool
225
    {
226
        if ([] === $preventingAttributes) {
54✔
UNCOV
227
            return false;
×
228
        }
229

230
        $modifiers = [T_FINAL];
54✔
231
        if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.2+ is required
54✔
232
            $modifiers[] = T_READONLY;
54✔
233
        }
234

235
        do {
236
            $index = $tokens->getPrevMeaningfulToken($index);
54✔
237
        } while ($tokens[$index]->isGivenKind($modifiers));
54✔
238
        if (!$tokens[$index]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) {
54✔
239
            return false;
26✔
240
        }
241
        $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_ATTRIBUTE, $index);
28✔
242

243
        foreach (AttributeAnalyzer::collect($tokens, $index) as $attributeAnalysis) {
28✔
244
            foreach ($attributeAnalysis->getAttributes() as $attribute) {
28✔
245
                if (\in_array(ltrim(self::getFullyQualifiedName($tokens, $attribute['name']), '\\'), $preventingAttributes, true)) {
28✔
246
                    return true;
23✔
247
                }
248
            }
249
        }
250

251
        return false;
5✔
252
    }
253

254
    private static function getFullyQualifiedName(Tokens $tokens, string $name): string
255
    {
256
        $name = strtolower($name);
28✔
257

258
        $names = [];
28✔
259
        foreach ((new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens) as $namespaceUseAnalysis) {
28✔
260
            $names[strtolower($namespaceUseAnalysis->getShortName())] = strtolower($namespaceUseAnalysis->getFullName());
15✔
261
        }
262

263
        foreach ($names as $shortName => $fullName) {
28✔
264
            if ($name === $shortName) {
15✔
265
                return $fullName;
8✔
266
            }
267

268
            if (!str_starts_with($name, $shortName.'\\')) {
10✔
269
                continue;
3✔
270
            }
271

272
            return $fullName.substr($name, \strlen($shortName));
7✔
273
        }
274

275
        return $name;
13✔
276
    }
277

278
    /**
279
     * @return list<Line>
280
     */
281
    private function addInternalAnnotation(DocBlock $docBlock, Tokens $tokens, int $docBlockIndex, string $annotation): array
282
    {
283
        $lines = $docBlock->getLines();
11✔
284
        $originalIndent = WhitespacesAnalyzer::detectIndent($tokens, $docBlockIndex);
11✔
285
        $lineEnd = $this->whitespacesConfig->getLineEnding();
11✔
286
        array_splice($lines, -1, 0, $originalIndent.' * @'.$annotation.$lineEnd);
11✔
287

288
        return $lines;
11✔
289
    }
290

291
    private function makeDocBlockMultiLineIfNeeded(DocBlock $doc, Tokens $tokens, int $docBlockIndex, string $annotation): DocBlock
292
    {
293
        $lines = $doc->getLines();
11✔
294
        if (1 === \count($lines) && [] === $doc->getAnnotationsOfType($annotation)) {
11✔
295
            $indent = WhitespacesAnalyzer::detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex));
2✔
296
            $doc->makeMultiLine($indent, $this->whitespacesConfig->getLineEnding());
2✔
297

298
            return $doc;
2✔
299
        }
300

301
        return $doc;
9✔
302
    }
303
}
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