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

keradus / PHP-CS-Fixer / 13441767116

20 Feb 2025 06:06PM UTC coverage: 94.969% (+0.006%) from 94.963%
13441767116

push

github

web-flow
Merge branch 'master' into x_template

118 of 119 new or added lines in 3 files covered. (99.16%)

3 existing lines in 1 file now uncovered.

27976 of 29458 relevant lines covered (94.97%)

43.08 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
 * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
32
 *
33
 * @phpstan-type _AutogeneratedInputConfiguration array{
34
 *  placement?: 'after'|'before'
35
 * }
36
 * @phpstan-type _AutogeneratedComputedConfiguration array{
37
 *  placement: 'after'|'before'
38
 * }
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([
27✔
95
            (new FixerOptionBuilder('placement', 'Where to place the data provider relative to the test where used.'))
27✔
96
                ->setAllowedValues(['after', 'before'])
27✔
97
                ->setDefault('after')
27✔
98
                ->getOption(),
27✔
99
        ]);
27✔
100
    }
101

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

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

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

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

115
        $sorted = $elements;
16✔
116
        $providersPlaced = [];
16✔
117
        if ('before' === $this->configuration['placement']) {
16✔
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;
12✔
127
            $sameProviderName = false;
12✔
128
            foreach ($origUsageDataProviderOrderPairs as [$usageName, $providerName]) {
12✔
129
                if (!isset($providersPlaced[$providerName])) {
10✔
130
                    $providersPlaced[$providerName] = true;
10✔
131

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

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

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

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

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

166
        return \Closure::bind(static fn () => $methodOrderFixer->getElements($tokens, $startIndex), null, OrderedClassElementsFixer::class)();
17✔
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();
13✔
175

176
        \Closure::bind(static fn () => $methodOrderFixer->sortTokens($tokens, $startIndex, $endIndex, $elements), null, OrderedClassElementsFixer::class)();
13✔
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;
14✔
187
        $iKeep = false;
14✔
188
        $iToMove = false;
14✔
189
        foreach ($elements as $element) {
14✔
190
            if ('method' === $element['type']) {
14✔
191
                if ($element['name'] === $nameKeep) {
14✔
192
                    $iKeep = $i;
14✔
193
                } elseif ($element['name'] === $nameToMove) {
14✔
194
                    $iToMove = $i;
14✔
195
                }
196
            }
197

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

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

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

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

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

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

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

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

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

254
        return $dataProvidersWithUsagePairs;
16✔
255
    }
256

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

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

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

286
        return $origUsageDataProviderOrderPairs;
16✔
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