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

keradus / PHP-CS-Fixer / 17377459942

01 Sep 2025 12:19PM UTC coverage: 94.684% (-0.009%) from 94.693%
17377459942

push

github

web-flow
chore: `Tokens::offsetSet` - explicit validation of input (#9004)

1 of 5 new or added lines in 1 file covered. (20.0%)

306 existing lines in 60 files now uncovered.

28390 of 29984 relevant lines covered (94.68%)

45.5 hits per line

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

98.68
/src/Fixer/PhpUnit/PhpUnitDataProviderNameFixer.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\ConfigurableFixerInterface;
19
use PhpCsFixer\Fixer\ConfigurableFixerTrait;
20
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
21
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
22
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
23
use PhpCsFixer\FixerDefinition\CodeSample;
24
use PhpCsFixer\FixerDefinition\FixerDefinition;
25
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
26
use PhpCsFixer\Preg;
27
use PhpCsFixer\Tokenizer\Analyzer\DataProviderAnalyzer;
28
use PhpCsFixer\Tokenizer\FCT;
29
use PhpCsFixer\Tokenizer\Token;
30
use PhpCsFixer\Tokenizer\Tokens;
31

32
/**
33
 * @phpstan-type _AutogeneratedInputConfiguration array{
34
 *  prefix?: string,
35
 *  suffix?: string,
36
 * }
37
 * @phpstan-type _AutogeneratedComputedConfiguration array{
38
 *  prefix: string,
39
 *  suffix: string,
40
 * }
41
 *
42
 * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
43
 *
44
 * @author Kuba Werłos <werlos@gmail.com>
45
 *
46
 * @no-named-arguments Parameter names are not covered by the backward compatibility promise.
47
 */
48
final class PhpUnitDataProviderNameFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface
49
{
50
    /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */
51
    use ConfigurableFixerTrait;
52

53
    public function getDefinition(): FixerDefinitionInterface
54
    {
55
        return new FixerDefinition(
3✔
56
            'Data provider names must match the name of the test.',
3✔
57
            [
3✔
58
                new CodeSample(
3✔
59
                    <<<'PHP'
3✔
60
                        <?php
61
                        class FooTest extends TestCase {
62
                            /**
63
                             * @dataProvider dataProvider
64
                             */
65
                            public function testSomething($expected, $actual) {}
66
                            public function dataProvider() {}
67
                        }
68

69
                        PHP,
3✔
70
                ),
3✔
71
                new CodeSample(
3✔
72
                    <<<'PHP'
3✔
73
                        <?php
74
                        class FooTest extends TestCase {
75
                            /**
76
                             * @dataProvider dt_prvdr_ftr
77
                             */
78
                            public function test_feature($expected, $actual) {}
79
                            public function dt_prvdr_ftr() {}
80
                        }
81

82
                        PHP,
3✔
83
                    [
3✔
84
                        'prefix' => 'data_',
3✔
85
                        'suffix' => '',
3✔
86
                    ]
3✔
87
                ),
3✔
88
                new CodeSample(
3✔
89
                    <<<'PHP'
3✔
90
                        <?php
91
                        class FooTest extends TestCase {
92
                            /**
93
                             * @dataProvider dataProviderUsedInMultipleTests
94
                             */
95
                            public function testA($expected, $actual) {}
96
                            /**
97
                             * @dataProvider dataProviderUsedInMultipleTests
98
                             */
99
                            public function testB($expected, $actual) {}
100
                            /**
101
                             * @dataProvider dataProviderUsedInSingleTest
102
                             */
103
                            public function testC($expected, $actual) {}
104
                            /**
105
                             * @dataProvider dataProviderUsedAsFirstInTest
106
                             * @dataProvider dataProviderUsedAsSecondInTest
107
                             */
108
                            public function testD($expected, $actual) {}
109

110
                            public function dataProviderUsedInMultipleTests() {}
111
                            public function dataProviderUsedInSingleTest() {}
112
                            public function dataProviderUsedAsFirstInTest() {}
113
                            public function dataProviderUsedAsSecondInTest() {}
114
                        }
115

116
                        PHP,
3✔
117
                    [
3✔
118
                        'prefix' => 'provides',
3✔
119
                        'suffix' => 'Data',
3✔
120
                    ]
3✔
121
                ),
3✔
122
            ],
3✔
123
            null,
3✔
124
            'Fixer could be risky if one is calling data provider by name as function.'
3✔
125
        );
3✔
126
    }
127

128
    public function isRisky(): bool
129
    {
130
        return true;
1✔
131
    }
132

133
    protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
134
    {
135
        return new FixerConfigurationResolver([
46✔
136
            (new FixerOptionBuilder('prefix', 'Prefix that replaces "test".'))
46✔
137
                ->setAllowedTypes(['string'])
46✔
138
                ->setDefault('provide')
46✔
139
                ->getOption(),
46✔
140
            (new FixerOptionBuilder('suffix', 'Suffix to be present at the end.'))
46✔
141
                ->setAllowedTypes(['string'])
46✔
142
                ->setDefault('Cases')
46✔
143
                ->getOption(),
46✔
144
        ]);
46✔
145
    }
146

147
    protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void
148
    {
149
        $dataProviders = (new DataProviderAnalyzer())->getDataProviders($tokens, $startIndex, $endIndex);
37✔
150

151
        $methodsProviders = [];
37✔
152
        $providersMethods = [];
37✔
153
        foreach ($dataProviders as $dataProviderAnalysis) {
37✔
154
            foreach ($dataProviderAnalysis->getUsageIndices() as [$usageIndex]) {
34✔
155
                $methodIndex = $tokens->getNextTokenOfKind($usageIndex, [[\T_FUNCTION]]);
34✔
156
                $methodsProviders[$methodIndex][$dataProviderAnalysis->getName()] = $usageIndex;
34✔
157
                $providersMethods[$dataProviderAnalysis->getName()][$methodIndex] = $usageIndex;
34✔
158
            }
159
        }
160

161
        foreach ($dataProviders as $dataProviderAnalysis) {
37✔
162
            // @phpstan-ignore offsetAccess.notFound
163
            if (\count($providersMethods[$dataProviderAnalysis->getName()]) > 1) {
34✔
164
                continue;
4✔
165
            }
166

167
            $methodIndex = $tokens->getNextTokenOfKind($dataProviderAnalysis->getUsageIndices()[0][0], [[\T_FUNCTION]]);
32✔
168
            // @phpstan-ignore offsetAccess.notFound
169
            if (\count($methodsProviders[$methodIndex]) > 1) {
32✔
170
                continue;
5✔
171
            }
172

173
            $dataProviderNewName = $this->getDataProviderNameForUsageIndex($tokens, $methodIndex);
29✔
174
            if (null !== $tokens->findSequence([[\T_FUNCTION], [\T_STRING, $dataProviderNewName]], $startIndex, $endIndex)) {
29✔
175
                continue;
28✔
176
            }
177

178
            foreach ($dataProviderAnalysis->getUsageIndices() as [$usageIndex]) {
28✔
179
                $tokens[$dataProviderAnalysis->getNameIndex()] = new Token([\T_STRING, $dataProviderNewName]);
28✔
180

181
                $newContent = $tokens[$usageIndex]->isGivenKind(\T_DOC_COMMENT)
28✔
182
                    ? Preg::replace(
27✔
183
                        \sprintf('/(@dataProvider\s+)%s/', $dataProviderAnalysis->getName()),
27✔
184
                        \sprintf('$1%s', $dataProviderNewName),
27✔
185
                        $tokens[$usageIndex]->getContent(),
27✔
186
                    )
27✔
187
                    : \sprintf('%1$s%2$s%1$s', $tokens[$usageIndex]->getContent()[0], $dataProviderNewName);
2✔
188

189
                $tokens[$usageIndex] = new Token([$tokens[$usageIndex]->getId(), $newContent]);
28✔
190
            }
191
        }
192
    }
193

194
    private function getDataProviderNameForUsageIndex(Tokens $tokens, int $index): string
195
    {
196
        do {
197
            if ($tokens[$index]->isGivenKind(FCT::T_ATTRIBUTE)) {
29✔
UNCOV
198
                $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ATTRIBUTE, $index);
×
199
            }
200
            $index = $tokens->getNextMeaningfulToken($index);
29✔
201
        } while (!$tokens[$index]->isGivenKind(\T_STRING));
29✔
202

203
        $name = $tokens[$index]->getContent();
29✔
204

205
        $name = Preg::replace('/^test_*/i', '', $name);
29✔
206

207
        if ('' === $this->configuration['prefix']) {
29✔
208
            $name = lcfirst($name);
2✔
209
        } elseif ('_' !== substr($this->configuration['prefix'], -1)) {
27✔
210
            $name = ucfirst($name);
21✔
211
        }
212

213
        return $this->configuration['prefix'].$name.$this->configuration['suffix'];
29✔
214
    }
215
}
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