Skip to content

Commit d636d90

Browse files
committed
[code-quality] Add ParamTypeFromDependsRector
1 parent 8bfa606 commit d636d90

File tree

7 files changed

+302
-0
lines changed

7 files changed

+302
-0
lines changed

config/sets/phpunit-code-quality.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
declare(strict_types=1);
44

55
use Rector\Config\RectorConfig;
6+
use Rector\PHPUnit\CodeQuality\Rector\Class_\AddParamTypeFromDependsRector;
67
use Rector\PHPUnit\CodeQuality\Rector\Class_\ConstructClassMethodToSetUpTestCaseRector;
78
use Rector\PHPUnit\CodeQuality\Rector\Class_\NarrowUnusedSetUpDefinedPropertyRector;
89
use Rector\PHPUnit\CodeQuality\Rector\Class_\PreferPHPUnitThisCallRector;
@@ -70,6 +71,7 @@
7071
// type declarations
7172
TypeWillReturnCallableArrowFunctionRector::class,
7273
StringCastAssertStringContainsStringRector::class,
74+
AddParamTypeFromDependsRector::class,
7375

7476
NarrowUnusedSetUpDefinedPropertyRector::class,
7577

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_\AddParamTypeFromDependsRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class AddParamTypeFromDependsRectorTest 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: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\AddParamTypeFromDependsRector\Fixture;
4+
5+
use PHPUnit\Framework\TestCase;
6+
7+
final class DependsWithBrackets extends TestCase
8+
{
9+
public function test(): \stdClass
10+
{
11+
return new \stdClass();
12+
}
13+
14+
/**
15+
* @depends test()
16+
*/
17+
public function testWithDepends($value)
18+
{
19+
}
20+
}
21+
22+
?>
23+
-----
24+
<?php
25+
26+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\AddParamTypeFromDependsRector\Fixture;
27+
28+
use PHPUnit\Framework\TestCase;
29+
30+
final class DependsWithBrackets extends TestCase
31+
{
32+
public function test(): \stdClass
33+
{
34+
return new \stdClass();
35+
}
36+
37+
/**
38+
* @depends test()
39+
*/
40+
public function testWithDepends(\stdClass $value)
41+
{
42+
}
43+
}
44+
45+
?>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\AddParamTypeFromDependsRector\Fixture;
4+
5+
use PHPUnit\Framework\TestCase;
6+
7+
final class SkipKnownParam extends TestCase
8+
{
9+
public function test(): \stdClass
10+
{
11+
return new \stdClass();
12+
}
13+
14+
/**
15+
* @depends test
16+
*/
17+
public function testWithDepends(\stdClass $value)
18+
{
19+
}
20+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\AddParamTypeFromDependsRector\Fixture;
4+
5+
use PHPUnit\Framework\TestCase;
6+
7+
final class SomeFileTest extends TestCase
8+
{
9+
public function test(): \stdClass
10+
{
11+
return new \stdClass();
12+
}
13+
14+
/**
15+
* @depends test
16+
*/
17+
public function testWithDepends($value)
18+
{
19+
}
20+
}
21+
22+
?>
23+
-----
24+
<?php
25+
26+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\AddParamTypeFromDependsRector\Fixture;
27+
28+
use PHPUnit\Framework\TestCase;
29+
30+
final class SomeFileTest extends TestCase
31+
{
32+
public function test(): \stdClass
33+
{
34+
return new \stdClass();
35+
}
36+
37+
/**
38+
* @depends test
39+
*/
40+
public function testWithDepends(\stdClass $value)
41+
{
42+
}
43+
}
44+
45+
?>
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_\AddParamTypeFromDependsRector;
7+
8+
return RectorConfig::configure()
9+
->withRules([AddParamTypeFromDependsRector::class]);
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
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\Stmt\Class_;
9+
use PhpParser\Node\Stmt\ClassMethod;
10+
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
11+
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
12+
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
13+
use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer;
14+
use Rector\Rector\AbstractRector;
15+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
16+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
17+
18+
/**
19+
* @see \Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\AddParamTypeFromDependsRector\AddParamTypeFromDependsRectorTest
20+
*/
21+
final class AddParamTypeFromDependsRector extends AbstractRector
22+
{
23+
public function __construct(
24+
private readonly TestsNodeAnalyzer $testsNodeAnalyzer,
25+
private readonly PhpDocInfoFactory $phpDocInfoFactory,
26+
) {
27+
}
28+
29+
public function getRuleDefinition(): RuleDefinition
30+
{
31+
return new RuleDefinition(
32+
'Add param type declaration based on @depends test method return type',
33+
[
34+
new CodeSample(
35+
<<<'CODE_SAMPLE'
36+
use PHPUnit\Framework\TestCase;
37+
38+
final class SomeTest extends TestCase
39+
{
40+
public function test()
41+
{
42+
return new \stdClass();
43+
}
44+
45+
/**
46+
* @depends test
47+
*/
48+
public function testAnother($someObject)
49+
{
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()
61+
{
62+
return new \stdClass();
63+
}
64+
65+
/**
66+
* @depends test
67+
*/
68+
public function testAnother(\stdClass $someObject)
69+
{
70+
}
71+
}
72+
CODE_SAMPLE
73+
),
74+
]
75+
);
76+
}
77+
78+
/**
79+
* @return array<class-string<Node>>
80+
*/
81+
public function getNodeTypes(): array
82+
{
83+
return [Class_::class];
84+
}
85+
86+
/**
87+
* @param Class_ $node
88+
*/
89+
public function refactor(Node $node): ?Node
90+
{
91+
if (! $this->testsNodeAnalyzer->isInTestClass($node)) {
92+
return null;
93+
}
94+
95+
$hasChanged = false;
96+
97+
foreach ($node->getMethods() as $classMethod) {
98+
if (! $classMethod->isPublic()) {
99+
continue;
100+
}
101+
102+
if (count($classMethod->params) !== 1) {
103+
continue;
104+
}
105+
106+
$soleParam = $classMethod->getParams()[0];
107+
108+
// already known type
109+
if ($soleParam->type instanceof Node) {
110+
continue;
111+
}
112+
113+
$dependsReturnType = $this->resolveReturnTypeOfDependsMethod($classMethod, $node);
114+
if (! $dependsReturnType instanceof Node) {
115+
continue;
116+
}
117+
118+
$soleParam->type = $dependsReturnType;
119+
$hasChanged = true;
120+
}
121+
122+
if ($hasChanged === false) {
123+
return null;
124+
}
125+
126+
return $node;
127+
}
128+
129+
private function resolveReturnTypeOfDependsMethod(ClassMethod $classMethod, Class_ $class): ?Node
130+
{
131+
$phpDocInfo = $this->phpDocInfoFactory->createFromNode($classMethod);
132+
if (! $phpDocInfo instanceof PhpDocInfo) {
133+
return null;
134+
}
135+
136+
$dependsTagValueNode = $phpDocInfo->getByName('depends');
137+
if (! $dependsTagValueNode instanceof PhpDocTagNode) {
138+
return null;
139+
}
140+
141+
$dependsMethodName = (string) $dependsTagValueNode->value;
142+
$dependsMethodName = trim($dependsMethodName, '()');
143+
144+
$dependsClassMethod = $class->getMethod($dependsMethodName);
145+
146+
if (! $dependsClassMethod instanceof ClassMethod) {
147+
return null;
148+
}
149+
150+
// resolve return type here
151+
return $dependsClassMethod->returnType;
152+
}
153+
}

0 commit comments

Comments
 (0)