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

keradus / PHP-CS-Fixer / 17252691116

26 Aug 2025 11:09PM UTC coverage: 94.743% (-0.01%) from 94.755%
17252691116

push

github

keradus
chore: apply phpdoc_tag_no_named_arguments

28313 of 29884 relevant lines covered (94.74%)

45.64 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
class FooTest extends TestCase {
57
    public function dataProvider() {}
58
    /**
59
     * @dataProvider dataProvider
60
     */
61
    public function testSomething($expected, $actual) {}
62
}
63
',
3✔
64
                ),
3✔
65
                new CodeSample(
3✔
66
                    '<?php
3✔
67
class FooTest extends TestCase {
68
    /**
69
     * @dataProvider dataProvider
70
     */
71
    public function testSomething($expected, $actual) {}
72
    public function dataProvider() {}
73
}
74
',
3✔
75
                    [
3✔
76
                        'placement' => 'before',
3✔
77
                    ]
3✔
78
                ),
3✔
79
            ]
3✔
80
        );
3✔
81
    }
82

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

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

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

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

112
        $endIndex = $elements[array_key_last($elements)]['end'];
18✔
113

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

256
        return $dataProvidersWithUsagePairs;
18✔
257
    }
258

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

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

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

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