Skip to content

Commit ea6d363

Browse files
authored
Merge pull request #127 from kubawerlos/feature/data-provider-return-type
Add DataProviderReturnTypeFixer
2 parents 3e2e7e6 + c02f3c5 commit ea6d363

File tree

4 files changed

+325
-1
lines changed

4 files changed

+325
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## [Unreleased]
44
- Add CommentSurroundedBySpacesFixer
5+
- Add DataProviderReturnTypeFixer
56
- Add NoDuplicatedImportsFixer
67

78
## v1.14.0 - *2019-07-25*

README.md

Lines changed: 13 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-1052-brightgreen.svg)
11+
![Tests](https://img.shields.io/badge/tests-1076-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).
@@ -60,6 +60,18 @@ Name of data provider that is used only once must match name of test.
6060
}
6161
```
6262

63+
#### DataProviderReturnTypeFixer
64+
Return type of data provider must be `iterable`.
65+
*Risky: when relying on signature of data provider.*
66+
```diff
67+
* @dataProvider provideHappyPathCases
68+
*/
69+
function testHappyPath() {}
70+
- function provideHappyPathCases(): array {}
71+
+ function provideHappyPathCases(): iterable {}
72+
}
73+
```
74+
6375
#### ImplodeCallFixer
6476
Function `implode` must be called with 2 arguments in the documented order.
6577
DEPRECATED: use `implode_call` instead.
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
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\Analyzer\FunctionsAnalyzer;
13+
use PhpCsFixer\Tokenizer\CT;
14+
use PhpCsFixer\Tokenizer\Token;
15+
use PhpCsFixer\Tokenizer\Tokens;
16+
17+
final class DataProviderReturnTypeFixer extends AbstractFixer
18+
{
19+
/**
20+
* {@inheritdoc}
21+
*/
22+
public function getDefinition(): FixerDefinitionInterface
23+
{
24+
return new FixerDefinition(
25+
'Return type of data provider must be `iterable`.',
26+
[
27+
new CodeSample(
28+
'<?php
29+
class FooTest extends TestCase {
30+
/**
31+
* @dataProvider provideHappyPathCases
32+
*/
33+
function testHappyPath() {}
34+
function provideHappyPathCases(): array {}
35+
}
36+
'
37+
),
38+
],
39+
null,
40+
'when relying on signature of data provider'
41+
);
42+
}
43+
44+
public function isCandidate(Tokens $tokens): bool
45+
{
46+
return $tokens->isTokenKindFound(T_DOC_COMMENT);
47+
}
48+
49+
public function getPriority(): int
50+
{
51+
// must be run before MethodArgumentSpaceFixer
52+
return 0;
53+
}
54+
55+
public function isRisky(): bool
56+
{
57+
return true;
58+
}
59+
60+
public function fix(\SplFileInfo $file, Tokens $tokens): void
61+
{
62+
$phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator();
63+
foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indexes) {
64+
$this->fixNames($tokens, $indexes[0], $indexes[1]);
65+
}
66+
}
67+
68+
private function fixNames(Tokens $tokens, int $startIndex, int $endIndex): void
69+
{
70+
$functionsAnalyzer = new FunctionsAnalyzer();
71+
72+
$dataProviderNames = $this->getDataProviderNames($tokens, $startIndex, $endIndex);
73+
74+
for ($index = $startIndex; $index < $endIndex; $index++) {
75+
if (!$tokens[$index]->isGivenKind(T_FUNCTION)) {
76+
continue;
77+
}
78+
79+
/** @var int $functionNameIndex */
80+
$functionNameIndex = $tokens->getNextNonWhitespace($index);
81+
82+
if (!$tokens[$functionNameIndex]->isGivenKind(T_STRING)) {
83+
continue;
84+
}
85+
86+
if (!isset($dataProviderNames[$tokens[$functionNameIndex]->getContent()])) {
87+
continue;
88+
}
89+
90+
$typeAnalysis = $functionsAnalyzer->getFunctionReturnType($tokens, $functionNameIndex);
91+
92+
if ($typeAnalysis === null) {
93+
/** @var int $argumentsStart */
94+
$argumentsStart = $tokens->getNextTokenOfKind($functionNameIndex, ['(']);
95+
$argumentsEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $argumentsStart);
96+
$tokens->insertAt(
97+
$argumentsEnd + 1,
98+
[
99+
new Token([CT::T_TYPE_COLON, ':']),
100+
new Token([T_WHITESPACE, ' ']),
101+
new Token([T_STRING, 'iterable']),
102+
]
103+
);
104+
continue;
105+
}
106+
107+
if ($typeAnalysis->getName() !== 'iterable') {
108+
/** @var int $startIndex */
109+
$startIndex = $tokens->getNextMeaningfulToken($typeAnalysis->getStartIndex() - 1);
110+
$tokens->clearRange($startIndex, $typeAnalysis->getEndIndex());
111+
112+
$tokens->insertAt($typeAnalysis->getEndIndex(), new Token([T_STRING, 'iterable']));
113+
}
114+
}
115+
}
116+
117+
/**
118+
* @return string[]
119+
*/
120+
private function getDataProviderNames(Tokens $tokens, int $startIndex, int $endIndex): array
121+
{
122+
$dataProviderNames = [];
123+
124+
for ($index = $startIndex; $index < $endIndex; $index++) {
125+
if (!$tokens[$index]->isGivenKind(T_DOC_COMMENT)) {
126+
continue;
127+
}
128+
129+
/** @var int $functionIndex */
130+
$functionIndex = $tokens->getTokenNotOfKindSibling(
131+
$index,
132+
1,
133+
[[T_WHITESPACE], [T_COMMENT], [T_DOC_COMMENT], [T_ABSTRACT], [T_FINAL], [T_PUBLIC], [T_PROTECTED], [T_PRIVATE], [T_STATIC]]
134+
);
135+
136+
if (!$tokens[$functionIndex]->isGivenKind(T_FUNCTION)) {
137+
continue;
138+
}
139+
140+
$functionNameIndex = $tokens->getNextNonWhitespace($functionIndex);
141+
if (!$tokens[$functionNameIndex]->isGivenKind(T_STRING)) {
142+
continue;
143+
}
144+
145+
Preg::matchAll('/@dataProvider\s+([a-zA-Z0-9._:-\\\\x7f-\xff]+)/', $tokens[$index]->getContent(), $matches);
146+
147+
/** @var string[] $matches */
148+
$matches = $matches[1];
149+
150+
foreach ($matches as $match) {
151+
$dataProviderNames[$match] = $match;
152+
}
153+
}
154+
155+
return $dataProviderNames;
156+
}
157+
}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace Tests\Fixer;
6+
7+
use PhpCsFixer\Fixer\FunctionNotation\MethodArgumentSpaceFixer;
8+
9+
/**
10+
* @internal
11+
*
12+
* @covers \PhpCsFixerCustomFixers\Fixer\DataProviderReturnTypeFixer
13+
*/
14+
final class DataProviderReturnTypeFixerTest extends AbstractFixerTestCase
15+
{
16+
public function testPriority(): void
17+
{
18+
static::assertGreaterThan((new MethodArgumentSpaceFixer())->getPriority(), $this->fixer->getPriority());
19+
}
20+
21+
public function testIsRisky(): void
22+
{
23+
static::assertTrue($this->fixer->isRisky());
24+
}
25+
26+
/**
27+
* @param string $expected
28+
* @param null|string $input
29+
*
30+
* @dataProvider provideFixCases
31+
*/
32+
public function testFix(string $expected, ?string $input = null): void
33+
{
34+
$this->doTest($expected, $input);
35+
}
36+
37+
public function provideFixCases(): iterable
38+
{
39+
yield 'data provider with iterable return type' => [
40+
'<?php
41+
class FooTest extends TestCase {
42+
/**
43+
* @dataProvider provideFooCases
44+
*/
45+
public function testFoo() {}
46+
public function provideFooCases() : iterable {}
47+
}',
48+
];
49+
50+
$template = '<?php
51+
class FooTest extends TestCase {
52+
/**
53+
* @dataProvider provideFooCases
54+
*/
55+
public function testFoo() {}
56+
public function provideFooCases()%s {}
57+
}';
58+
59+
$cases = [
60+
'data provider without return type' => [
61+
': iterable',
62+
'',
63+
],
64+
'data provider with array return type' => [
65+
': iterable',
66+
': array',
67+
],
68+
'data provider with return type and comment' => [
69+
': /* TODO: add more cases */ iterable',
70+
': /* TODO: add more cases */ array',
71+
],
72+
'data provider with return type namespaced class' => [
73+
': iterable',
74+
': Foo\Bar',
75+
],
76+
'data provider with return type namespaced class and comments' => [
77+
': iterable',
78+
': Foo/* Some info */\/* More info */Bar',
79+
],
80+
'data provider with iterable return type in different case' => [
81+
': iterable',
82+
': Iterable',
83+
],
84+
];
85+
86+
foreach ($cases as $key => $case) {
87+
yield $key => \array_map(
88+
static function (string $code) use ($template): string {
89+
return \sprintf($template, $code);
90+
},
91+
$case
92+
);
93+
}
94+
95+
yield 'advanced case' => [
96+
'<?php
97+
class FooTest extends TestCase {
98+
/**
99+
* @dataProvider provideFooCases
100+
* @dataProvider provideFooCases2
101+
*/
102+
public function testFoo()
103+
{
104+
/**
105+
* @dataProvider someFunction
106+
*/
107+
$foo = /** foo */ function ($x) { return $x + 1; };
108+
/**
109+
* @dataProvider someFunction2
110+
*/
111+
/* foo */someFunction2();
112+
}
113+
/**
114+
* @dataProvider provideFooCases3
115+
*/
116+
public function testBar() {}
117+
118+
public function provideFooCases(): iterable {}
119+
public function provideFooCases2(): iterable {}
120+
public function provideFooCases3(): iterable {}
121+
public function someFunction() {}
122+
public function someFunction2() {}
123+
}',
124+
'<?php
125+
class FooTest extends TestCase {
126+
/**
127+
* @dataProvider provideFooCases
128+
* @dataProvider provideFooCases2
129+
*/
130+
public function testFoo()
131+
{
132+
/**
133+
* @dataProvider someFunction
134+
*/
135+
$foo = /** foo */ function ($x) { return $x + 1; };
136+
/**
137+
* @dataProvider someFunction2
138+
*/
139+
/* foo */someFunction2();
140+
}
141+
/**
142+
* @dataProvider provideFooCases3
143+
*/
144+
public function testBar() {}
145+
146+
public function provideFooCases() {}
147+
public function provideFooCases2() {}
148+
public function provideFooCases3() {}
149+
public function someFunction() {}
150+
public function someFunction2() {}
151+
}',
152+
];
153+
}
154+
}

0 commit comments

Comments
 (0)