Skip to content

Commit f3da56d

Browse files
committed
[code-quality] Add AddReturnTypeToDependedRector
1 parent 838ff1e commit f3da56d

File tree

8 files changed

+289
-0
lines changed

8 files changed

+289
-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: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
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 Rector\PhpParser\Node\BetterNodeFinder;
11+
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
12+
use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer;
13+
use Rector\Rector\AbstractRector;
14+
use Rector\StaticTypeMapper\StaticTypeMapper;
15+
use Rector\TypeDeclaration\TypeInferer\SilentVoidResolver;
16+
use Rector\TypePerfect\NodeFinder\ReturnNodeFinder;
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 SilentVoidResolver $silentVoidResolver,
28+
private readonly ReturnNodeFinder $returnNodeFinder,
29+
private readonly StaticTypeMapper $staticTypeMapper,
30+
private readonly BetterNodeFinder $betterNodeFinder
31+
) {
32+
}
33+
34+
public function getRuleDefinition(): RuleDefinition
35+
{
36+
return new RuleDefinition(
37+
'Add return type declaration to a test method that returns type',
38+
[
39+
new CodeSample(
40+
<<<'CODE_SAMPLE'
41+
use PHPUnit\Framework\TestCase;
42+
43+
final class SomeTest extends TestCase
44+
{
45+
public function test()
46+
{
47+
$value = new \stdClass();
48+
49+
return $value;
50+
}
51+
}
52+
CODE_SAMPLE
53+
54+
,
55+
<<<'CODE_SAMPLE'
56+
use PHPUnit\Framework\TestCase;
57+
58+
final class SomeTest extends TestCase
59+
{
60+
public function test(): \stdClass
61+
{
62+
$value = new \stdClass();
63+
64+
return $value;
65+
}
66+
}
67+
CODE_SAMPLE
68+
),
69+
]
70+
);
71+
}
72+
73+
/**
74+
* @return array<class-string<Node>>
75+
*/
76+
public function getNodeTypes(): array
77+
{
78+
return [Class_::class];
79+
}
80+
81+
/**
82+
* @param Class_ $node
83+
*/
84+
public function refactor(Node $node): ?Node
85+
{
86+
if (! $this->testsNodeAnalyzer->isInTestClass($node)) {
87+
return null;
88+
}
89+
90+
$hasChanged = false;
91+
92+
foreach ($node->getMethods() as $classMethod) {
93+
if (! $classMethod->isPublic()) {
94+
continue;
95+
}
96+
97+
if (! $this->testsNodeAnalyzer->isTestClassMethod($classMethod)) {
98+
continue;
99+
}
100+
101+
if (! $this->haveAlLReturnsExpr($classMethod)) {
102+
continue;
103+
}
104+
105+
// already known return type
106+
if ($classMethod->returnType instanceof Node) {
107+
continue;
108+
}
109+
110+
if ($this->silentVoidResolver->hasSilentVoid($classMethod)) {
111+
continue;
112+
}
113+
114+
$soleReturnExpr = $this->returnNodeFinder->findOnlyReturnsExpr($classMethod);
115+
if (! $soleReturnExpr instanceof Expr) {
116+
continue;
117+
}
118+
119+
// does return a type?
120+
$returnedExprType = $this->getType($soleReturnExpr);
121+
$returnType = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($returnedExprType, TypeKind::RETURN);
122+
123+
if (! $returnType instanceof Node) {
124+
continue;
125+
}
126+
127+
$classMethod->returnType = $returnType;
128+
$hasChanged = true;
129+
}
130+
131+
if ($hasChanged === false) {
132+
return null;
133+
}
134+
135+
return $node;
136+
}
137+
138+
private function haveAlLReturnsExpr(mixed $classMethod): bool
139+
{
140+
$returns = $this->betterNodeFinder->findReturnsScoped($classMethod);
141+
foreach ($returns as $return) {
142+
if (! $return->expr instanceof Expr) {
143+
return false;
144+
}
145+
}
146+
147+
return true;
148+
}
149+
}

0 commit comments

Comments
 (0)