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

keradus / PHP-CS-Fixer / 17193291398

24 Aug 2025 08:15PM UTC coverage: 94.745% (+0.005%) from 94.74%
17193291398

push

github

web-flow
chore: deprecate `Annotation::getTagsWithTypes` in favor of `TAGS_WITH_TYPES` constant (#8977)

4 of 4 new or added lines in 3 files covered. (100.0%)

108 existing lines in 8 files now uncovered.

28322 of 29893 relevant lines covered (94.74%)

45.84 hits per line

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

99.15
/src/Fixer/PhpUnit/PhpUnitDataProviderMethodOrderFixer.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\PhpUnit;
16

17
use PhpCsFixer\Fixer\AbstractPhpUnitFixer;
18
use PhpCsFixer\Fixer\ClassNotation\OrderedClassElementsFixer;
19
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
20
use PhpCsFixer\Fixer\ConfigurableFixerTrait;
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\Tokenizer\Analyzer\DataProviderAnalyzer;
28
use PhpCsFixer\Tokenizer\Tokens;
29

30
/**
31
 * @phpstan-type _AutogeneratedInputConfiguration array{
32
 *  placement?: 'after'|'before',
33
 * }
34
 * @phpstan-type _AutogeneratedComputedConfiguration array{
35
 *  placement: 'after'|'before',
36
 * }
37
 *
38
 * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
39
 *
40
 * @phpstan-import-type _ClassElement from OrderedClassElementsFixer
41
 */
42
final class PhpUnitDataProviderMethodOrderFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface
43
{
44
    /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */
45
    use ConfigurableFixerTrait;
46

47
    public function getDefinition(): FixerDefinitionInterface
48
    {
49
        return new FixerDefinition(
3✔
50
            'Data provider method must be placed after/before the last/first test where used.',
3✔
51
            [
3✔
52
                new CodeSample(
3✔
53
                    '<?php
3✔
54
class FooTest extends TestCase {
55
    public function dataProvider() {}
56
    /**
57
     * @dataProvider dataProvider
58
     */
59
    public function testSomething($expected, $actual) {}
60
}
61
',
3✔
62
                ),
3✔
63
                new CodeSample(
3✔
64
                    '<?php
3✔
65
class FooTest extends TestCase {
66
    /**
67
     * @dataProvider dataProvider
68
     */
69
    public function testSomething($expected, $actual) {}
70
    public function dataProvider() {}
71
}
72
',
3✔
73
                    [
3✔
74
                        'placement' => 'before',
3✔
75
                    ]
3✔
76
                ),
3✔
77
            ]
3✔
78
        );
3✔
79
    }
80

81
    /**
82
     * {@inheritdoc}
83
     *
84
     * Must run before ClassAttributesSeparationFixer, NoBlankLinesAfterClassOpeningFixer.
85
     * Must run after OrderedClassElementsFixer.
86
     */
87
    public function getPriority(): int
88
    {
89
        return 64;
1✔
90
    }
91

92
    protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
93
    {
94
        return new FixerConfigurationResolver([
30✔
95
            (new FixerOptionBuilder('placement', 'Where to place the data provider relative to the test where used.'))
30✔
96
                ->setAllowedValues(['after', 'before'])
30✔
97
                ->setDefault('after')
30✔
98
                ->getOption(),
30✔
99
        ]);
30✔
100
    }
101

102
    protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void
103
    {
104
        $elements = $this->getElements($tokens, $startIndex);
19✔
105

106
        if (0 === \count($elements)) {
19✔
107
            return;
1✔
108
        }
109

110
        $endIndex = $elements[array_key_last($elements)]['end'];
18✔
111

112
        $dataProvidersWithUsagePairs = $this->getDataProvidersWithUsagePairs($tokens, $startIndex, $endIndex);
18✔
113
        $origUsageDataProviderOrderPairs = $this->getOrigUsageDataProviderOrderPairs($dataProvidersWithUsagePairs);
18✔
114

115
        $sorted = $elements;
18✔
116
        $providersPlaced = [];
18✔
117
        if ('before' === $this->configuration['placement']) {
18✔
118
            foreach ($origUsageDataProviderOrderPairs as [$usageName, $providerName]) {
5✔
119
                if (!isset($providersPlaced[$providerName])) {
5✔
120
                    $providersPlaced[$providerName] = true;
5✔
121

122
                    $sorted = $this->moveMethodElement($sorted, $usageName, $providerName, false);
5✔
123
                }
124
            }
125
        } else {
126
            $sameUsageName = false;
14✔
127
            $sameProviderName = false;
14✔
128
            foreach ($origUsageDataProviderOrderPairs as [$usageName, $providerName]) {
14✔
129
                if (!isset($providersPlaced[$providerName])) {
13✔
130
                    $providersPlaced[$providerName] = true;
13✔
131

132
                    $sortedBefore = $sorted;
13✔
133
                    $sorted = $this->moveMethodElement(
13✔
134
                        $sorted,
13✔
135
                        $usageName === $sameUsageName // @phpstan-ignore argument.type (https://github.com/phpstan/phpstan/issues/12482)
13✔
136
                            ? $sameProviderName
2✔
137
                            : $usageName,
13✔
138
                        $providerName,
13✔
139
                        true
13✔
140
                    );
13✔
141

142
                    // honor multiple providers order for one test
143
                    $sameUsageName = $usageName;
13✔
144
                    $sameProviderName = $providerName;
13✔
145

146
                    // keep placement after the first test
147
                    if ($sortedBefore !== $sorted) {
13✔
148
                        unset($providersPlaced[$providerName]);
12✔
149
                    }
150
                }
151
            }
152
        }
153

154
        if ($sorted !== $elements) {
18✔
155
            $this->sortTokens($tokens, $startIndex, $endIndex, $sorted);
16✔
156
        }
157
    }
158

159
    /**
160
     * @return list<_ClassElement>
161
     */
162
    private function getElements(Tokens $tokens, int $startIndex): array
163
    {
164
        $methodOrderFixer = new OrderedClassElementsFixer();
19✔
165

166
        return \Closure::bind(static fn () => $methodOrderFixer->getElements($tokens, $startIndex), null, OrderedClassElementsFixer::class)();
19✔
167
    }
168

169
    /**
170
     * @param list<_ClassElement> $elements
171
     */
172
    private function sortTokens(Tokens $tokens, int $startIndex, int $endIndex, array $elements): void
173
    {
174
        $methodOrderFixer = new OrderedClassElementsFixer();
16✔
175

176
        \Closure::bind(static fn () => $methodOrderFixer->sortTokens($tokens, $startIndex, $endIndex, $elements), null, OrderedClassElementsFixer::class)();
16✔
177
    }
178

179
    /**
180
     * @param list<_ClassElement> $elements
181
     *
182
     * @return list<_ClassElement>
183
     */
184
    private function moveMethodElement(array $elements, string $nameKeep, string $nameToMove, bool $after): array
185
    {
186
        $i = 0;
17✔
187
        $iKeep = false;
17✔
188
        $iToMove = false;
17✔
189
        foreach ($elements as $element) {
17✔
190
            if ('method' === $element['type']) {
17✔
191
                if ($element['name'] === $nameKeep) {
17✔
192
                    $iKeep = $i;
17✔
193
                } elseif ($element['name'] === $nameToMove) {
17✔
194
                    $iToMove = $i;
17✔
195
                }
196
            }
197

198
            ++$i;
17✔
199
        }
200
        \assert(false !== $iKeep);
17✔
201
        \assert(false !== $iToMove);
17✔
202

203
        if ($iToMove === $iKeep + ($after ? 1 : -1)) {
17✔
204
            return $elements;
15✔
205
        }
206

207
        $elementToMove = $elements[$iToMove]; // @phpstan-ignore offsetAccess.notFound
16✔
208
        unset($elements[$iToMove]);
16✔
209

210
        $c = $iKeep
16✔
211
            + ($after ? 1 : 0)
16✔
212
            + ($iToMove < $iKeep ? -1 : 0);
16✔
213

214
        return [
16✔
215
            ...\array_slice($elements, 0, $c),
16✔
216
            $elementToMove,
16✔
217
            ...\array_slice($elements, $c),
16✔
218
        ];
16✔
219
    }
220

221
    /**
222
     * @return list<array{
223
     *   array{int, string},
224
     *   non-empty-array<int, array{int, string, int}>
225
     * }>
226
     */
227
    private function getDataProvidersWithUsagePairs(Tokens $tokens, int $startIndex, int $endIndex): array
228
    {
229
        $dataProvidersWithUsagePairs = [];
18✔
230

231
        $dataProviderAnalyzer = new DataProviderAnalyzer();
18✔
232
        foreach ($dataProviderAnalyzer->getDataProviders($tokens, $startIndex, $endIndex) as $dataProviderAnalysis) {
18✔
233
            $usages = [];
17✔
234
            foreach ($dataProviderAnalysis->getUsageIndices() as $usageIndex) {
17✔
235
                $methodNameTokens = $tokens->findSequence([[\T_FUNCTION], [\T_STRING]], $usageIndex[0], $endIndex);
17✔
236
                if (null === $methodNameTokens) {
17✔
UNCOV
237
                    continue;
×
238
                }
239

240
                $usages[array_key_last($methodNameTokens)] = [
17✔
241
                    array_key_last($methodNameTokens),
17✔
242
                    end($methodNameTokens)->getContent(),
17✔
243
                    $usageIndex[1],
17✔
244
                ];
17✔
245
            }
246
            \assert([] !== $usages);
17✔
247

248
            $dataProvidersWithUsagePairs[] = [
17✔
249
                [$dataProviderAnalysis->getNameIndex(), $dataProviderAnalysis->getName()],
17✔
250
                $usages,
17✔
251
            ];
17✔
252
        }
253

254
        return $dataProvidersWithUsagePairs;
18✔
255
    }
256

257
    /**
258
     * @param list<array{
259
     *   array{int, string},
260
     *   non-empty-array<int, array{int, string, int}>
261
     * }> $dataProvidersWithUsagePairs
262
     *
263
     * @return list<array{string, string}>
264
     */
265
    private function getOrigUsageDataProviderOrderPairs(array $dataProvidersWithUsagePairs): array
266
    {
267
        $origUsagesOrderPairs = [];
18✔
268
        foreach ($dataProvidersWithUsagePairs as [$dataProviderPair, $usagePairs]) {
18✔
269
            foreach ($usagePairs as $usagePair) {
17✔
270
                $origUsagesOrderPairs[] = [$usagePair, $dataProviderPair[1]];
17✔
271
            }
272
        }
273
        uasort($origUsagesOrderPairs, static function (array $a, array $b): int {
18✔
274
            $cmp = $a[0][0] <=> $b[0][0];
5✔
275

276
            return 0 !== $cmp
5✔
277
                ? $cmp
4✔
278
                : $a[0][2] <=> $b[0][2];
5✔
279
        });
18✔
280

281
        $origUsageDataProviderOrderPairs = [];
18✔
282
        foreach (array_map(static fn (array $v): array => [$v[0][1], $v[1]], $origUsagesOrderPairs) as [$usageName, $providerName]) {
18✔
283
            $origUsageDataProviderOrderPairs[] = [$usageName, $providerName];
17✔
284
        }
285

286
        return $origUsageDataProviderOrderPairs;
18✔
287
    }
288
}
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