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

keradus / PHP-CS-Fixer / 13747606772

08 Mar 2025 10:38PM UTC coverage: 94.847% (-0.08%) from 94.929%
13747606772

push

github

web-flow
fix: `MbStrFunctionsFixer` - fix imports (#8474)

23 of 24 new or added lines in 1 file covered. (95.83%)

181 existing lines in 6 files now uncovered.

28127 of 29655 relevant lines covered (94.85%)

43.08 hits per line

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

67.62
/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\Analysis\NamespaceUseAnalysis;
22
use PhpCsFixer\Tokenizer\Analyzer\AttributeAnalyzer;
23
use PhpCsFixer\Tokenizer\Analyzer\FullyQualifiedNameAnalyzer;
24
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
25
use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer;
26
use PhpCsFixer\Tokenizer\CT;
27
use PhpCsFixer\Tokenizer\Token;
28
use PhpCsFixer\Tokenizer\Tokens;
29

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

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

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

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

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

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

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

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

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

71
        return $index;
54✔
72
    }
73

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

160
            $fullyQualifiedNameAnalyzer = new FullyQualifiedNameAnalyzer($tokens);
×
161

162
            foreach (AttributeAnalyzer::collect($tokens, $attributeIndex) as $attributeAnalysis) {
×
163
                foreach ($attributeAnalysis->getAttributes() as $attribute) {
×
UNCOV
164
                    $attributeName = strtolower($fullyQualifiedNameAnalyzer->getFullyQualifiedName($attribute['name'], $attribute['start'], NamespaceUseAnalysis::TYPE_CLASS));
×
UNCOV
165
                    if ('phpunit\framework\attributes\test' === $attributeName) {
×
UNCOV
166
                        return true;
×
167
                    }
168
                }
169
            }
170

171
            $currentIndex = $attributeIndex - 1;
×
172
        }
173

UNCOV
174
        return false;
×
175
    }
176

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

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

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

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

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

218
        $lines = $this->addInternalAnnotation($doc, $tokens, $docBlockIndex, $annotation);
11✔
219
        $lines = implode('', $lines);
11✔
220

221
        $tokens->getNamespaceDeclarations();
11✔
222
        $tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $lines]);
11✔
223
    }
224

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

234
        $modifiers = [T_FINAL];
54✔
235
        if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.2+ is required
54✔
236
            $modifiers[] = T_READONLY;
54✔
237
        }
238

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

247
        $fullyQualifiedNameAnalyzer = new FullyQualifiedNameAnalyzer($tokens);
28✔
248

249
        foreach (AttributeAnalyzer::collect($tokens, $index) as $attributeAnalysis) {
28✔
250
            foreach ($attributeAnalysis->getAttributes() as $attribute) {
28✔
251
                if (\in_array(strtolower($fullyQualifiedNameAnalyzer->getFullyQualifiedName($attribute['name'], $attribute['start'], NamespaceUseAnalysis::TYPE_CLASS)), $preventingAttributes, true)) {
28✔
252
                    return true;
23✔
253
                }
254
            }
255
        }
256

257
        return false;
5✔
258
    }
259

260
    /**
261
     * @return list<Line>
262
     */
263
    private function addInternalAnnotation(DocBlock $docBlock, Tokens $tokens, int $docBlockIndex, string $annotation): array
264
    {
265
        $lines = $docBlock->getLines();
11✔
266
        $originalIndent = WhitespacesAnalyzer::detectIndent($tokens, $docBlockIndex);
11✔
267
        $lineEnd = $this->whitespacesConfig->getLineEnding();
11✔
268
        array_splice($lines, -1, 0, $originalIndent.' * @'.$annotation.$lineEnd);
11✔
269

270
        return $lines;
11✔
271
    }
272

273
    private function makeDocBlockMultiLineIfNeeded(DocBlock $doc, Tokens $tokens, int $docBlockIndex, string $annotation): DocBlock
274
    {
275
        $lines = $doc->getLines();
11✔
276
        if (1 === \count($lines) && [] === $doc->getAnnotationsOfType($annotation)) {
11✔
277
            $indent = WhitespacesAnalyzer::detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex));
2✔
278
            $doc->makeMultiLine($indent, $this->whitespacesConfig->getLineEnding());
2✔
279

280
            return $doc;
2✔
281
        }
282

283
        return $doc;
9✔
284
    }
285
}
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