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

keradus / PHP-CS-Fixer / 14008489983

22 Mar 2025 12:21PM UTC coverage: 94.866%. First build
14008489983

push

github

web-flow
Merge branch 'master' into Preg_types

36 of 47 new or added lines in 13 files covered. (76.6%)

28143 of 29666 relevant lines covered (94.87%)

43.2 hits per line

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

70.3
/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
    {
113
        $functionsAnalyzer = new FunctionsAnalyzer();
×
114

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

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

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

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

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

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

136
            if (!$functionsAnalyzer->isTheSameClassCall($tokens, $index)) {
×
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),
×
145
            ];
×
146
        }
147
    }
148

149
    final protected function isTestAttributePresent(Tokens $tokens, int $index): bool
150
    {
NEW
151
        $attributeIndex = $tokens->getPrevTokenOfKind($index, ['{', [T_ATTRIBUTE]]);
×
NEW
152
        if (!$tokens[$attributeIndex]->isGivenKind(T_ATTRIBUTE)) {
×
NEW
153
            return false;
×
154
        }
155

NEW
156
        $fullyQualifiedNameAnalyzer = new FullyQualifiedNameAnalyzer($tokens);
×
157

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

167
        return false;
×
168
    }
169

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

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

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

188
            $tokens->insertAt($index, [
3✔
189
                new Token([T_WHITESPACE, $extraNewLines.WhitespacesAnalyzer::detectIndent($tokens, $index)]),
3✔
190
            ]);
3✔
191
        }
192
    }
193

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

211
        $lines = $this->addInternalAnnotation($doc, $tokens, $docBlockIndex, $annotation);
11✔
212
        $lines = implode('', $lines);
11✔
213

214
        $tokens->getNamespaceDeclarations();
11✔
215
        $tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $lines]);
11✔
216
    }
217

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

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

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

240
        $fullyQualifiedNameAnalyzer = new FullyQualifiedNameAnalyzer($tokens);
28✔
241

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

250
        return false;
5✔
251
    }
252

253
    /**
254
     * @return list<Line>
255
     */
256
    private function addInternalAnnotation(DocBlock $docBlock, Tokens $tokens, int $docBlockIndex, string $annotation): array
257
    {
258
        $lines = $docBlock->getLines();
11✔
259
        $originalIndent = WhitespacesAnalyzer::detectIndent($tokens, $docBlockIndex);
11✔
260
        $lineEnd = $this->whitespacesConfig->getLineEnding();
11✔
261
        array_splice($lines, -1, 0, $originalIndent.' * @'.$annotation.$lineEnd);
11✔
262

263
        return $lines;
11✔
264
    }
265

266
    private function makeDocBlockMultiLineIfNeeded(DocBlock $doc, Tokens $tokens, int $docBlockIndex, string $annotation): DocBlock
267
    {
268
        $lines = $doc->getLines();
11✔
269
        if (1 === \count($lines) && [] === $doc->getAnnotationsOfType($annotation)) {
11✔
270
            $indent = WhitespacesAnalyzer::detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex));
2✔
271
            $doc->makeMultiLine($indent, $this->whitespacesConfig->getLineEnding());
2✔
272

273
            return $doc;
2✔
274
        }
275

276
        return $doc;
9✔
277
    }
278
}
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