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

keradus / PHP-CS-Fixer / 17678835382

12 Sep 2025 03:24PM UTC coverage: 94.69% (-0.06%) from 94.75%
17678835382

push

github

keradus
fix typo

1 of 1 new or added line in 1 file covered. (100.0%)

1042 existing lines in 177 files now uncovered.

28424 of 30018 relevant lines covered (94.69%)

45.5 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
 * @no-named-arguments Parameter names are not covered by the backward compatibility promise.
43
 */
44
final class PhpUnitDataProviderMethodOrderFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface
45
{
46
    /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */
47
    use ConfigurableFixerTrait;
48

49
    public function getDefinition(): FixerDefinitionInterface
50
    {
51
        return new FixerDefinition(
3✔
52
            'Data provider method must be placed after/before the last/first test where used.',
3✔
53
            [
3✔
54
                new CodeSample(
3✔
55
                    <<<'PHP'
3✔
56
                        <?php
57
                        class FooTest extends TestCase {
58
                            public function dataProvider() {}
59
                            /**
60
                             * @dataProvider dataProvider
61
                             */
62
                            public function testSomething($expected, $actual) {}
63
                        }
64

65
                        PHP,
3✔
66
                ),
3✔
67
                new CodeSample(
3✔
68
                    <<<'PHP'
3✔
69
                        <?php
70
                        class FooTest extends TestCase {
71
                            /**
72
                             * @dataProvider dataProvider
73
                             */
74
                            public function testSomething($expected, $actual) {}
75
                            public function dataProvider() {}
76
                        }
77

78
                        PHP,
3✔
79
                    [
3✔
80
                        'placement' => 'before',
3✔
81
                    ]
3✔
82
                ),
3✔
83
            ]
3✔
84
        );
3✔
85
    }
86

87
    /**
88
     * {@inheritdoc}
89
     *
90
     * Must run before ClassAttributesSeparationFixer, NoBlankLinesAfterClassOpeningFixer.
91
     * Must run after OrderedClassElementsFixer.
92
     */
93
    public function getPriority(): int
94
    {
95
        return 64;
1✔
96
    }
97

98
    protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
99
    {
100
        return new FixerConfigurationResolver([
30✔
101
            (new FixerOptionBuilder('placement', 'Where to place the data provider relative to the test where used.'))
30✔
102
                ->setAllowedValues(['after', 'before'])
30✔
103
                ->setDefault('after')
30✔
104
                ->getOption(),
30✔
105
        ]);
30✔
106
    }
107

108
    protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void
109
    {
110
        $elements = $this->getElements($tokens, $startIndex);
19✔
111

112
        if (0 === \count($elements)) {
19✔
113
            return;
1✔
114
        }
115

116
        $endIndex = $elements[array_key_last($elements)]['end'];
18✔
117

118
        $dataProvidersWithUsagePairs = $this->getDataProvidersWithUsagePairs($tokens, $startIndex, $endIndex);
18✔
119
        $origUsageDataProviderOrderPairs = $this->getOrigUsageDataProviderOrderPairs($dataProvidersWithUsagePairs);
18✔
120

121
        $sorted = $elements;
18✔
122
        $providersPlaced = [];
18✔
123
        if ('before' === $this->configuration['placement']) {
18✔
124
            foreach ($origUsageDataProviderOrderPairs as [$usageName, $providerName]) {
5✔
125
                if (!isset($providersPlaced[$providerName])) {
5✔
126
                    $providersPlaced[$providerName] = true;
5✔
127

128
                    $sorted = $this->moveMethodElement($sorted, $usageName, $providerName, false);
5✔
129
                }
130
            }
131
        } else {
132
            $sameUsageName = false;
14✔
133
            $sameProviderName = false;
14✔
134
            foreach ($origUsageDataProviderOrderPairs as [$usageName, $providerName]) {
14✔
135
                if (!isset($providersPlaced[$providerName])) {
13✔
136
                    $providersPlaced[$providerName] = true;
13✔
137

138
                    $sortedBefore = $sorted;
13✔
139
                    $sorted = $this->moveMethodElement(
13✔
140
                        $sorted,
13✔
141
                        $usageName === $sameUsageName // @phpstan-ignore argument.type (https://github.com/phpstan/phpstan/issues/12482)
13✔
142
                            ? $sameProviderName
2✔
143
                            : $usageName,
13✔
144
                        $providerName,
13✔
145
                        true
13✔
146
                    );
13✔
147

148
                    // honor multiple providers order for one test
149
                    $sameUsageName = $usageName;
13✔
150
                    $sameProviderName = $providerName;
13✔
151

152
                    // keep placement after the first test
153
                    if ($sortedBefore !== $sorted) {
13✔
154
                        unset($providersPlaced[$providerName]);
12✔
155
                    }
156
                }
157
            }
158
        }
159

160
        if ($sorted !== $elements) {
18✔
161
            $this->sortTokens($tokens, $startIndex, $endIndex, $sorted);
16✔
162
        }
163
    }
164

165
    /**
166
     * @return list<_ClassElement>
167
     */
168
    private function getElements(Tokens $tokens, int $startIndex): array
169
    {
170
        $methodOrderFixer = new OrderedClassElementsFixer();
19✔
171

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

175
    /**
176
     * @param list<_ClassElement> $elements
177
     */
178
    private function sortTokens(Tokens $tokens, int $startIndex, int $endIndex, array $elements): void
179
    {
180
        $methodOrderFixer = new OrderedClassElementsFixer();
16✔
181

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

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

204
            ++$i;
17✔
205
        }
206
        \assert(false !== $iKeep);
17✔
207
        \assert(false !== $iToMove);
17✔
208

209
        if ($iToMove === $iKeep + ($after ? 1 : -1)) {
17✔
210
            return $elements;
15✔
211
        }
212

213
        $elementToMove = $elements[$iToMove]; // @phpstan-ignore offsetAccess.notFound
16✔
214
        unset($elements[$iToMove]);
16✔
215

216
        $c = $iKeep
16✔
217
            + ($after ? 1 : 0)
16✔
218
            + ($iToMove < $iKeep ? -1 : 0);
16✔
219

220
        return [
16✔
221
            ...\array_slice($elements, 0, $c),
16✔
222
            $elementToMove,
16✔
223
            ...\array_slice($elements, $c),
16✔
224
        ];
16✔
225
    }
226

227
    /**
228
     * @return list<array{
229
     *   array{int, string},
230
     *   non-empty-array<int, array{int, string, int}>
231
     * }>
232
     */
233
    private function getDataProvidersWithUsagePairs(Tokens $tokens, int $startIndex, int $endIndex): array
234
    {
235
        $dataProvidersWithUsagePairs = [];
18✔
236

237
        $dataProviderAnalyzer = new DataProviderAnalyzer();
18✔
238
        foreach ($dataProviderAnalyzer->getDataProviders($tokens, $startIndex, $endIndex) as $dataProviderAnalysis) {
18✔
239
            $usages = [];
17✔
240
            foreach ($dataProviderAnalysis->getUsageIndices() as $usageIndex) {
17✔
241
                $methodNameTokens = $tokens->findSequence([[\T_FUNCTION], [\T_STRING]], $usageIndex[0], $endIndex);
17✔
242
                if (null === $methodNameTokens) {
17✔
UNCOV
243
                    continue;
×
244
                }
245

246
                $usages[array_key_last($methodNameTokens)] = [
17✔
247
                    array_key_last($methodNameTokens),
17✔
248
                    end($methodNameTokens)->getContent(),
17✔
249
                    $usageIndex[1],
17✔
250
                ];
17✔
251
            }
252
            \assert([] !== $usages);
17✔
253

254
            $dataProvidersWithUsagePairs[] = [
17✔
255
                [$dataProviderAnalysis->getNameIndex(), $dataProviderAnalysis->getName()],
17✔
256
                $usages,
17✔
257
            ];
17✔
258
        }
259

260
        return $dataProvidersWithUsagePairs;
18✔
261
    }
262

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

282
            return 0 !== $cmp
5✔
283
                ? $cmp
4✔
284
                : $a[0][2] <=> $b[0][2];
5✔
285
        });
18✔
286

287
        $origUsageDataProviderOrderPairs = [];
18✔
288
        foreach (array_map(static fn (array $v): array => [$v[0][1], $v[1]], $origUsagesOrderPairs) as [$usageName, $providerName]) {
18✔
289
            $origUsageDataProviderOrderPairs[] = [$usageName, $providerName];
17✔
290
        }
291

292
        return $origUsageDataProviderOrderPairs;
18✔
293
    }
294
}
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