Skip to content

Commit 767b4f3

Browse files
authored
Add DataProviderNameFixer (#117)
1 parent a6f8e4a commit 767b4f3

File tree

6 files changed

+401
-5
lines changed

6 files changed

+401
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## [Unreleased]
44
- Add SingleLineThrowFixer
55
- Add PhpUnitNoUselessReturnFixer
6+
- Add DataProviderNameFixer
67
- Feature: NoCommentedOutCodeFixer - handle class method
78
- Deprecate SingleLineThrowFixer
89

README.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
[![Build status](https://img.shields.io/travis/kubawerlos/php-cs-fixer-custom-fixers/master.svg)](https://travis-ci.org/kubawerlos/php-cs-fixer-custom-fixers)
1010
[![Code coverage](https://img.shields.io/coveralls/github/kubawerlos/php-cs-fixer-custom-fixers/master.svg)](https://coveralls.io/github/kubawerlos/php-cs-fixer-custom-fixers?branch=master)
11-
![Tests](https://img.shields.io/badge/tests-930-brightgreen.svg)
11+
![Tests](https://img.shields.io/badge/tests-957-brightgreen.svg)
1212
[![Mutation testing badge](https://badge.stryker-mutator.io/github.com/kubawerlos/php-cs-fixer-custom-fixers/master)](https://stryker-mutator.github.io)
1313

1414
A set of custom fixers for [PHP CS Fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer).
@@ -36,6 +36,21 @@ In your PHP CS Fixer configuration register fixers and use them:
3636

3737

3838
## Fixers
39+
- **DataProviderNameFixer** - name of data provider that is used only once must match name of test.
40+
*Risky: when relying on name of data provider function.*
41+
```diff
42+
<?php
43+
class FooTest extends TestCase {
44+
/**
45+
- * @dataProvider dataProvider
46+
+ * @dataProvider provideHappyPathCases
47+
*/
48+
function testHappyPath() {}
49+
- function dataProvider() {}
50+
+ function provideHappyPathCases() {}
51+
}
52+
```
53+
3954
- **ImplodeCallFixer** - function `implode` must be called with 2 arguments in the documented order.
4055
DEPRECATED: use `implode_call` instead.
4156
*Risky: when the function `implode` is overridden.*
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace PhpCsFixerCustomFixers\Fixer;
6+
7+
use PhpCsFixer\FixerDefinition\CodeSample;
8+
use PhpCsFixer\FixerDefinition\FixerDefinition;
9+
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
10+
use PhpCsFixer\Indicator\PhpUnitTestCaseIndicator;
11+
use PhpCsFixer\Preg;
12+
use PhpCsFixer\Tokenizer\Token;
13+
use PhpCsFixer\Tokenizer\Tokens;
14+
15+
final class DataProviderNameFixer extends AbstractFixer
16+
{
17+
/**
18+
* {@inheritdoc}
19+
*/
20+
public function getDefinition(): FixerDefinitionInterface
21+
{
22+
return new FixerDefinition(
23+
'name of data provider that is used only once must match name of test',
24+
[
25+
new CodeSample(
26+
'<?php
27+
class FooTest extends TestCase {
28+
/**
29+
* @dataProvider dataProvider
30+
*/
31+
function testHappyPath() {}
32+
function dataProvider() {}
33+
}
34+
'
35+
),
36+
],
37+
null,
38+
'when relying on name of data provider function'
39+
);
40+
}
41+
42+
public function isCandidate(Tokens $tokens): bool
43+
{
44+
return $tokens->isAllTokenKindsFound([T_CLASS, T_DOC_COMMENT]);
45+
}
46+
47+
public function getPriority(): int
48+
{
49+
return 0;
50+
}
51+
52+
public function isRisky(): bool
53+
{
54+
return true;
55+
}
56+
57+
public function fix(\SplFileInfo $file, Tokens $tokens): void
58+
{
59+
$phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator();
60+
foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indexes) {
61+
$this->fixNames($tokens, $indexes[0], $indexes[1]);
62+
}
63+
}
64+
65+
private function fixNames(Tokens $tokens, int $startIndex, int $endIndex): void
66+
{
67+
$dataProviderCallIndices = [];
68+
$dataProviderUsagesCounts = [];
69+
$dataProviderUsingFunctionNames = [];
70+
$functionDefinitionIndices = [];
71+
for ($index = $startIndex; $index < $endIndex; $index++) {
72+
// if it's the function and string follows then it's function's definition
73+
if ($tokens[$index]->isGivenKind(T_FUNCTION)) {
74+
$functionNameIndex = $tokens->getNextNonWhitespace($index);
75+
if ($tokens[$functionNameIndex]->isGivenKind(T_STRING)) {
76+
$functionDefinitionIndices[$tokens[$functionNameIndex]->getContent()] = $functionNameIndex;
77+
}
78+
continue;
79+
}
80+
81+
// as it's not function's definition we search for data provider usage
82+
83+
if (!$tokens[$index]->isGivenKind(T_DOC_COMMENT)) {
84+
continue;
85+
}
86+
87+
/** @var int $functionIndex */
88+
$functionIndex = $tokens->getTokenNotOfKindSibling(
89+
$index,
90+
1,
91+
[[T_WHITESPACE], [T_COMMENT], [T_DOC_COMMENT], [T_ABSTRACT], [T_FINAL], [T_PUBLIC], [T_PROTECTED], [T_PRIVATE], [T_STATIC]]
92+
);
93+
if (!$tokens[$functionIndex]->isGivenKind(T_FUNCTION)) {
94+
continue;
95+
}
96+
97+
$functionNameIndex = $tokens->getNextNonWhitespace($functionIndex);
98+
if (!$tokens[$functionNameIndex]->isGivenKind(T_STRING)) {
99+
continue;
100+
}
101+
102+
Preg::matchAll('/@dataProvider\s+([a-zA-Z0-9._:-\\\\x7f-\xff]+)/', $tokens[$index]->getContent(), $matches);
103+
104+
/** @var string[] $matches */
105+
$matches = $matches[1];
106+
107+
foreach ($matches as $match) {
108+
if (!isset($dataProviderUsagesCounts[$match])) {
109+
$dataProviderUsagesCounts[$match] = 0;
110+
}
111+
$dataProviderUsagesCounts[$match]++;
112+
113+
$dataProviderCallIndices[$match] = $index;
114+
115+
$dataProviderUsingFunctionNames[$match] = $tokens[$functionNameIndex]->getContent();
116+
}
117+
}
118+
119+
foreach ($dataProviderUsagesCounts as $dataProviderName => $numberOfCalls) {
120+
if ($numberOfCalls > 1) {
121+
continue;
122+
}
123+
124+
if (!isset($functionDefinitionIndices[$dataProviderName])) {
125+
continue;
126+
}
127+
128+
$dataProviderNewName = $this->getProviderNameForTestName($dataProviderUsingFunctionNames[$dataProviderName]);
129+
if (isset($functionDefinitionIndices[$dataProviderNewName])) {
130+
continue;
131+
}
132+
133+
$tokens[$functionDefinitionIndices[$dataProviderName]] = new Token([T_STRING, $dataProviderNewName]);
134+
135+
/** @var string $newCommentContent */
136+
$newCommentContent = Preg::replace(
137+
\sprintf('/(@dataProvider\s+)%s/', $dataProviderName),
138+
\sprintf('$1%s', $dataProviderNewName),
139+
$tokens[$dataProviderCallIndices[$dataProviderName]]->getContent()
140+
);
141+
142+
$tokens[$dataProviderCallIndices[$dataProviderName]] = new Token([T_DOC_COMMENT, $newCommentContent]);
143+
$functionDefinitionIndices[$dataProviderNewName] = $dataProviderCallIndices[$dataProviderName];
144+
}
145+
}
146+
147+
private function getProviderNameForTestName(string $name): string
148+
{
149+
if (Preg::match('/^test/', $name) === 1) {
150+
$name = \substr($name, 4);
151+
}
152+
153+
return 'provide' . \ucfirst($name) . 'Cases';
154+
}
155+
}

0 commit comments

Comments
 (0)