Skip to content

Commit 8654b74

Browse files
authored
[type-declaration] Add ReturnIteratorInDataProviderRector (#7593)
1 parent 0bbb4c7 commit 8654b74

File tree

7 files changed

+249
-0
lines changed

7 files changed

+249
-0
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclaration\Rector\Class_\ReturnIteratorInDataProviderRector\Fixture;
4+
5+
use PHPUnit\Framework\TestCase;
6+
7+
final class SkipAlreadyKnown extends TestCase
8+
{
9+
/**
10+
* @dataProvider someDataProvider
11+
*/
12+
public function testSomething()
13+
{
14+
}
15+
16+
public function someDataProvider(): \Generator
17+
{
18+
yield ['data1'];
19+
yield ['data2'];
20+
}
21+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclaration\Rector\Class_\ReturnIteratorInDataProviderRector\Fixture;
4+
5+
use PHPUnit\Framework\TestCase;
6+
7+
final class SkipNonDataProvider extends TestCase
8+
{
9+
public function testSomething()
10+
{
11+
}
12+
13+
public function someDataProvider()
14+
{
15+
yield ['data1'];
16+
yield ['data2'];
17+
}
18+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclaration\Rector\Class_\ReturnIteratorInDataProviderRector\Fixture;
4+
5+
use PHPUnit\Framework\TestCase;
6+
7+
final class SomeClass extends TestCase
8+
{
9+
/**
10+
* @dataProvider someDataProvider
11+
*/
12+
public function testSomething()
13+
{
14+
}
15+
16+
public function someDataProvider()
17+
{
18+
yield ['data1'];
19+
yield ['data2'];
20+
}
21+
}
22+
23+
?>
24+
-----
25+
<?php
26+
27+
namespace Rector\Tests\TypeDeclaration\Rector\Class_\ReturnIteratorInDataProviderRector\Fixture;
28+
29+
use PHPUnit\Framework\TestCase;
30+
31+
final class SomeClass extends TestCase
32+
{
33+
/**
34+
* @dataProvider someDataProvider
35+
*/
36+
public function testSomething()
37+
{
38+
}
39+
40+
public function someDataProvider(): \Iterator
41+
{
42+
yield ['data1'];
43+
yield ['data2'];
44+
}
45+
}
46+
47+
?>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Tests\TypeDeclaration\Rector\Class_\ReturnIteratorInDataProviderRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class ReturnIteratorInDataProviderRectorTest extends AbstractRectorTestCase
12+
{
13+
#[DataProvider('provideData')]
14+
public function test(string $filePath): void
15+
{
16+
$this->doTestFile($filePath);
17+
}
18+
19+
public static function provideData(): Iterator
20+
{
21+
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
22+
}
23+
24+
public function provideConfigFilePath(): string
25+
{
26+
return __DIR__ . '/config/configured_rule.php';
27+
}
28+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\CodeQuality\Rector\Class_\ReturnIteratorInDataProviderRector;
6+
use Rector\Config\RectorConfig;
7+
8+
return static function (RectorConfig $rectorConfig): void {
9+
$rectorConfig->rule(ReturnIteratorInDataProviderRector::class);
10+
};
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\CodeQuality\Rector\Class_;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Expr\Yield_;
9+
use PhpParser\Node\Name\FullyQualified;
10+
use PhpParser\Node\Stmt\Class_;
11+
use PhpParser\Node\Stmt\ClassMethod;
12+
use Rector\PhpParser\Node\BetterNodeFinder;
13+
use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer;
14+
use Rector\Rector\AbstractRector;
15+
use Rector\TypeDeclarationDocblocks\NodeFinder\DataProviderMethodsFinder;
16+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
17+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
18+
19+
/**
20+
* @see \Rector\Tests\TypeDeclaration\Rector\Class_\ReturnIteratorInDataProviderRector\ReturnIteratorInDataProviderRectorTest
21+
*/
22+
final class ReturnIteratorInDataProviderRector extends AbstractRector
23+
{
24+
public function __construct(
25+
private readonly TestsNodeAnalyzer $testsNodeAnalyzer,
26+
private readonly DataProviderMethodsFinder $dataProviderMethodsFinder,
27+
private readonly BetterNodeFinder $betterNodeFinder
28+
) {
29+
}
30+
31+
public function getRuleDefinition(): RuleDefinition
32+
{
33+
return new RuleDefinition('Add Iterator type on known PHPUnit data providers', [
34+
new CodeSample(
35+
<<<'CODE_SAMPLE'
36+
use PHPUnit\Framework\TestCase;
37+
38+
final class SomeTest extends TestCase
39+
{
40+
/**
41+
* @dataProvider provideData()
42+
*/
43+
public function testSomething($value)
44+
{
45+
}
46+
47+
public function provideData()
48+
{
49+
yield [5];
50+
}
51+
}
52+
CODE_SAMPLE
53+
,
54+
<<<'CODE_SAMPLE'
55+
use PHPUnit\Framework\TestCase;
56+
57+
final class SomeTest extends TestCase
58+
{
59+
/**
60+
* @dataProvider provideData()
61+
*/
62+
public function testSomething($value)
63+
{
64+
}
65+
66+
public function provideData(): \Iterator
67+
{
68+
yield [5];
69+
}
70+
}
71+
CODE_SAMPLE
72+
),
73+
]);
74+
}
75+
76+
/**
77+
* @return array<class-string<Node>>
78+
*/
79+
public function getNodeTypes(): array
80+
{
81+
return [Class_::class];
82+
}
83+
84+
/**
85+
* @param Class_ $node
86+
*/
87+
public function refactor(Node $node): ?Node
88+
{
89+
if (! $this->testsNodeAnalyzer->isInTestClass($node)) {
90+
return null;
91+
}
92+
93+
$dataProviderClassMethods = $this->dataProviderMethodsFinder->findDataProviderNodesInClass($node);
94+
95+
$hasChanged = false;
96+
97+
foreach ($dataProviderClassMethods as $dataProviderClassMethod) {
98+
if ($dataProviderClassMethod->returnType instanceof Node) {
99+
continue;
100+
}
101+
102+
if (! $this->hasYields($dataProviderClassMethod)) {
103+
continue;
104+
}
105+
106+
$dataProviderClassMethod->returnType = new FullyQualified('Iterator');
107+
$hasChanged = true;
108+
}
109+
110+
if ($hasChanged) {
111+
return $node;
112+
}
113+
114+
return null;
115+
}
116+
117+
private function hasYields(ClassMethod $classMethod): bool
118+
{
119+
$yields = $this->betterNodeFinder->findInstancesOfScoped((array) $classMethod->stmts, Yield_::class);
120+
121+
return $yields !== [];
122+
}
123+
}

src/Config/Level/TypeDeclarationLevel.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Rector\Config\Level;
66

7+
use Rector\CodeQuality\Rector\Class_\ReturnIteratorInDataProviderRector;
78
use Rector\Contract\Rector\RectorInterface;
89
use Rector\Symfony\CodeQuality\Rector\ClassMethod\ResponseReturnTypeControllerActionRector;
910
use Rector\TypeDeclaration\Rector\ArrowFunction\AddArrowFunctionReturnTypeRector;
@@ -78,6 +79,7 @@ final class TypeDeclarationLevel
7879
AddClosureVoidReturnTypeWhereNoReturnRector::class,
7980
AddFunctionVoidReturnTypeWhereNoReturnRector::class,
8081
AddTestsVoidReturnTypeWhereNoReturnRector::class,
82+
ReturnIteratorInDataProviderRector::class,
8183

8284
ReturnTypeFromMockObjectRector::class,
8385
TypedPropertyFromCreateMockAssignRector::class,

0 commit comments

Comments
 (0)