Skip to content

Commit 2a7e3ec

Browse files
authored
[code-quality] Add AddReturnTypeToDependedRector (#528)
1 parent 838ff1e commit 2a7e3ec

File tree

8 files changed

+290
-0
lines changed

8 files changed

+290
-0
lines changed

config/sets/phpunit-code-quality.php

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

55
use Rector\Config\RectorConfig;
66
use Rector\PHPUnit\CodeQuality\Rector\Class_\AddParamTypeFromDependsRector;
7+
use Rector\PHPUnit\CodeQuality\Rector\Class_\AddReturnTypeToDependedRector;
78
use Rector\PHPUnit\CodeQuality\Rector\Class_\ConstructClassMethodToSetUpTestCaseRector;
89
use Rector\PHPUnit\CodeQuality\Rector\Class_\NarrowUnusedSetUpDefinedPropertyRector;
910
use Rector\PHPUnit\CodeQuality\Rector\Class_\PreferPHPUnitThisCallRector;
@@ -72,6 +73,7 @@
7273
TypeWillReturnCallableArrowFunctionRector::class,
7374
StringCastAssertStringContainsStringRector::class,
7475
AddParamTypeFromDependsRector::class,
76+
AddReturnTypeToDependedRector::class,
7577

7678
NarrowUnusedSetUpDefinedPropertyRector::class,
7779

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\PHPUnit\Tests\CodeQuality\Rector\Class_\AddReturnTypeToDependedRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class AddReturnTypeToDependedRectorTest 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: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\AddReturnTypeToDependedRector\Fixture;
4+
5+
use PHPUnit\Framework\TestCase;
6+
7+
final class SkipNonTestMethods extends TestCase
8+
{
9+
public function nonTestMethod()
10+
{
11+
return new \stdClass();
12+
}
13+
14+
private function testNotMethod()
15+
{
16+
return new \stdClass();
17+
}
18+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\AddReturnTypeToDependedRector\Fixture;
4+
5+
use PHPUnit\Framework\TestCase;
6+
7+
final class SkipSilentVoid extends TestCase
8+
{
9+
public function test()
10+
{
11+
if (rand(0, 1)) {
12+
return;
13+
}
14+
15+
return new \stdClass();
16+
}
17+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\AddReturnTypeToDependedRector\Fixture;
4+
5+
use PHPUnit\Framework\TestCase;
6+
7+
final class SomeFileTest extends TestCase
8+
{
9+
public function test()
10+
{
11+
return new \stdClass();
12+
}
13+
}
14+
15+
?>
16+
-----
17+
<?php
18+
19+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\AddReturnTypeToDependedRector\Fixture;
20+
21+
use PHPUnit\Framework\TestCase;
22+
23+
final class SomeFileTest extends TestCase
24+
{
25+
public function test(): \stdClass
26+
{
27+
return new \stdClass();
28+
}
29+
}
30+
31+
?>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\AddReturnTypeToDependedRector\Fixture;
4+
5+
use PHPUnit\Framework\TestCase;
6+
7+
final class VaribleAssigned extends TestCase
8+
{
9+
public function test()
10+
{
11+
$someValue = rand(0, 1);
12+
13+
return $someValue;
14+
}
15+
}
16+
17+
?>
18+
-----
19+
<?php
20+
21+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\AddReturnTypeToDependedRector\Fixture;
22+
23+
use PHPUnit\Framework\TestCase;
24+
25+
final class VaribleAssigned extends TestCase
26+
{
27+
public function test(): int
28+
{
29+
$someValue = rand(0, 1);
30+
31+
return $someValue;
32+
}
33+
}
34+
35+
?>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Config\RectorConfig;
6+
use Rector\PHPUnit\CodeQuality\Rector\Class_\AddReturnTypeToDependedRector;
7+
8+
return RectorConfig::configure()
9+
->withRules([AddReturnTypeToDependedRector::class]);
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\PHPUnit\CodeQuality\Rector\Class_;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Expr;
9+
use PhpParser\Node\Stmt\Class_;
10+
use PhpParser\Node\Stmt\ClassMethod;
11+
use Rector\PhpParser\Node\BetterNodeFinder;
12+
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
13+
use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer;
14+
use Rector\Rector\AbstractRector;
15+
use Rector\StaticTypeMapper\StaticTypeMapper;
16+
use Rector\TypeDeclaration\NodeAnalyzer\ReturnAnalyzer;
17+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
18+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
19+
20+
/**
21+
* @see \Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\AddReturnTypeToDependedRector\AddReturnTypeToDependedRectorTest
22+
*/
23+
final class AddReturnTypeToDependedRector extends AbstractRector
24+
{
25+
public function __construct(
26+
private readonly TestsNodeAnalyzer $testsNodeAnalyzer,
27+
private readonly ReturnAnalyzer $returnAnalyzer,
28+
private readonly StaticTypeMapper $staticTypeMapper,
29+
private readonly BetterNodeFinder $betterNodeFinder
30+
) {
31+
}
32+
33+
public function getRuleDefinition(): RuleDefinition
34+
{
35+
return new RuleDefinition(
36+
'Add return type declaration to a test method that returns type',
37+
[
38+
new CodeSample(
39+
<<<'CODE_SAMPLE'
40+
use PHPUnit\Framework\TestCase;
41+
42+
final class SomeTest extends TestCase
43+
{
44+
public function test()
45+
{
46+
$value = new \stdClass();
47+
48+
return $value;
49+
}
50+
}
51+
CODE_SAMPLE
52+
53+
,
54+
<<<'CODE_SAMPLE'
55+
use PHPUnit\Framework\TestCase;
56+
57+
final class SomeTest extends TestCase
58+
{
59+
public function test(): \stdClass
60+
{
61+
$value = new \stdClass();
62+
63+
return $value;
64+
}
65+
}
66+
CODE_SAMPLE
67+
),
68+
]
69+
);
70+
}
71+
72+
/**
73+
* @return array<class-string<Node>>
74+
*/
75+
public function getNodeTypes(): array
76+
{
77+
return [Class_::class];
78+
}
79+
80+
/**
81+
* @param Class_ $node
82+
*/
83+
public function refactor(Node $node): ?Node
84+
{
85+
if (! $this->testsNodeAnalyzer->isInTestClass($node)) {
86+
return null;
87+
}
88+
89+
$hasChanged = false;
90+
91+
foreach ($node->getMethods() as $classMethod) {
92+
if (! $classMethod->isPublic()) {
93+
continue;
94+
}
95+
96+
if (! $this->testsNodeAnalyzer->isTestClassMethod($classMethod)) {
97+
continue;
98+
}
99+
100+
if (! $this->haveAllReturnsExpr($classMethod)) {
101+
continue;
102+
}
103+
104+
// already known return type
105+
if ($classMethod->returnType instanceof Node) {
106+
continue;
107+
}
108+
109+
$returns = $this->betterNodeFinder->findReturnsScoped($classMethod);
110+
if (! $this->returnAnalyzer->hasOnlyReturnWithExpr($classMethod, $returns)) {
111+
return null;
112+
}
113+
114+
if (count($returns) !== 1) {
115+
continue;
116+
}
117+
118+
$soleReturnExpr = $returns[0]->expr;
119+
120+
// does return a type?
121+
$returnedExprType = $this->getType($soleReturnExpr);
122+
$returnType = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($returnedExprType, TypeKind::RETURN);
123+
124+
if (! $returnType instanceof Node) {
125+
continue;
126+
}
127+
128+
$classMethod->returnType = $returnType;
129+
$hasChanged = true;
130+
}
131+
132+
if ($hasChanged === false) {
133+
return null;
134+
}
135+
136+
return $node;
137+
}
138+
139+
private function haveAllReturnsExpr(ClassMethod $classMethod): bool
140+
{
141+
$returns = $this->betterNodeFinder->findReturnsScoped($classMethod);
142+
foreach ($returns as $return) {
143+
if (! $return->expr instanceof Expr) {
144+
return false;
145+
}
146+
}
147+
148+
return true;
149+
}
150+
}

0 commit comments

Comments
 (0)