Skip to content

Commit add182e

Browse files
committed
[DeadCode] Add RemoveDuplicatedReturnSelfDocblockRector to remove @return docblock duplicating native self/static return type
1 parent b8dc17e commit add182e

9 files changed

Lines changed: 316 additions & 0 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace Rector\Tests\DeadCode\Rector\ClassMethod\RemoveDuplicatedReturnSelfDocblockRector\Fixture;
4+
5+
final class RemoveReturnCurrentObjectDocblock
6+
{
7+
/**
8+
* @return RemoveReturnCurrentObjectDocblock
9+
*/
10+
public function test(): self
11+
{
12+
return $this;
13+
}
14+
}
15+
16+
?>
17+
-----
18+
<?php
19+
20+
namespace Rector\Tests\DeadCode\Rector\ClassMethod\RemoveDuplicatedReturnSelfDocblockRector\Fixture;
21+
22+
final class RemoveReturnCurrentObjectDocblock
23+
{
24+
public function test(): self
25+
{
26+
return $this;
27+
}
28+
}
29+
30+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace Rector\Tests\DeadCode\Rector\ClassMethod\RemoveDuplicatedReturnSelfDocblockRector\Fixture;
4+
5+
final class RemoveReturnSelfDocblock
6+
{
7+
/**
8+
* @return self
9+
*/
10+
public function test(): self
11+
{
12+
return $this;
13+
}
14+
}
15+
16+
?>
17+
-----
18+
<?php
19+
20+
namespace Rector\Tests\DeadCode\Rector\ClassMethod\RemoveDuplicatedReturnSelfDocblockRector\Fixture;
21+
22+
final class RemoveReturnSelfDocblock
23+
{
24+
public function test(): self
25+
{
26+
return $this;
27+
}
28+
}
29+
30+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace Rector\Tests\DeadCode\Rector\ClassMethod\RemoveDuplicatedReturnSelfDocblockRector\Fixture;
4+
5+
class RemoveReturnStaticDocblock
6+
{
7+
/**
8+
* @return static
9+
*/
10+
public function test(): static
11+
{
12+
$obj = new static();
13+
return $obj;
14+
}
15+
}
16+
17+
?>
18+
-----
19+
<?php
20+
21+
namespace Rector\Tests\DeadCode\Rector\ClassMethod\RemoveDuplicatedReturnSelfDocblockRector\Fixture;
22+
23+
class RemoveReturnStaticDocblock
24+
{
25+
public function test(): static
26+
{
27+
$obj = new static();
28+
return $obj;
29+
}
30+
}
31+
32+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace Rector\Tests\DeadCode\Rector\ClassMethod\RemoveDuplicatedReturnSelfDocblockRector\Fixture;
4+
5+
final class RemoveReturnThisDocblock
6+
{
7+
/**
8+
* @return $this
9+
*/
10+
public function test(): self
11+
{
12+
return $this;
13+
}
14+
}
15+
16+
?>
17+
-----
18+
<?php
19+
20+
namespace Rector\Tests\DeadCode\Rector\ClassMethod\RemoveDuplicatedReturnSelfDocblockRector\Fixture;
21+
22+
final class RemoveReturnThisDocblock
23+
{
24+
public function test(): self
25+
{
26+
return $this;
27+
}
28+
}
29+
30+
?>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace Rector\Tests\DeadCode\Rector\ClassMethod\RemoveDuplicatedReturnSelfDocblockRector\Fixture;
4+
5+
final class SkipOtherType
6+
{
7+
/**
8+
* @return \stdClass
9+
*/
10+
public function test(): self
11+
{
12+
return $this;
13+
}
14+
}
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\DeadCode\Rector\ClassMethod\RemoveDuplicatedReturnSelfDocblockRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class RemoveDuplicatedReturnSelfDocblockRectorTest 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: 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\DeadCode\Rector\ClassMethod\RemoveDuplicatedReturnSelfDocblockRector;
7+
8+
return RectorConfig::configure()
9+
->withRules([RemoveDuplicatedReturnSelfDocblockRector::class]);
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\DeadCode\Rector\ClassMethod;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Identifier;
9+
use PhpParser\Node\Name;
10+
use PhpParser\Node\Stmt\ClassMethod;
11+
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
12+
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
13+
use PHPStan\Reflection\ClassReflection;
14+
use PHPStan\Type\ObjectType;
15+
use PHPStan\Type\StaticType;
16+
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
17+
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
18+
use Rector\Comments\NodeDocBlock\DocBlockUpdater;
19+
use Rector\Rector\AbstractRector;
20+
use Rector\Reflection\ReflectionResolver;
21+
use Rector\StaticTypeMapper\StaticTypeMapper;
22+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
23+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
24+
25+
/**
26+
* @see \Rector\Tests\DeadCode\Rector\ClassMethod\RemoveDuplicatedReturnSelfDocblockRector\RemoveDuplicatedReturnSelfDocblockRectorTest
27+
*/
28+
final class RemoveDuplicatedReturnSelfDocblockRector extends AbstractRector
29+
{
30+
public function __construct(
31+
private readonly PhpDocInfoFactory $phpDocInfoFactory,
32+
private readonly DocBlockUpdater $docBlockUpdater,
33+
private readonly StaticTypeMapper $staticTypeMapper,
34+
private readonly ReflectionResolver $reflectionResolver,
35+
) {
36+
}
37+
38+
public function getRuleDefinition(): RuleDefinition
39+
{
40+
return new RuleDefinition(
41+
'Remove @return docblock that duplicates the native self/static return type of the current object',
42+
[
43+
new CodeSample(
44+
<<<'CODE_SAMPLE'
45+
final class SomeClass
46+
{
47+
/**
48+
* @return $this
49+
*/
50+
public function some(): self
51+
{
52+
return $this;
53+
}
54+
}
55+
CODE_SAMPLE
56+
,
57+
<<<'CODE_SAMPLE'
58+
final class SomeClass
59+
{
60+
public function some(): self
61+
{
62+
return $this;
63+
}
64+
}
65+
CODE_SAMPLE
66+
),
67+
]
68+
);
69+
}
70+
71+
/**
72+
* @return array<class-string<Node>>
73+
*/
74+
public function getNodeTypes(): array
75+
{
76+
return [ClassMethod::class];
77+
}
78+
79+
/**
80+
* @param ClassMethod $node
81+
*/
82+
public function refactor(Node $node): ?Node
83+
{
84+
if (! $node->returnType instanceof Identifier && ! $node->returnType instanceof Name) {
85+
return null;
86+
}
87+
88+
$returnTypeName = $node->returnType->toString();
89+
if (! in_array($returnTypeName, ['self', 'static'], true)) {
90+
return null;
91+
}
92+
93+
$classReflection = $this->reflectionResolver->resolveClassReflection($node);
94+
if (! $classReflection instanceof ClassReflection) {
95+
return null;
96+
}
97+
98+
$phpDocInfo = $this->phpDocInfoFactory->createFromNode($node);
99+
if (! $phpDocInfo instanceof PhpDocInfo) {
100+
return null;
101+
}
102+
103+
$returnTagValueNode = $phpDocInfo->getReturnTagValue();
104+
if (! $returnTagValueNode instanceof ReturnTagValueNode) {
105+
return null;
106+
}
107+
108+
if ($returnTagValueNode->description !== '') {
109+
return null;
110+
}
111+
112+
if (! $this->isCurrentObjectReturnDocType($returnTagValueNode->type, $node, $classReflection)) {
113+
return null;
114+
}
115+
116+
$phpDocInfo->removeByType(ReturnTagValueNode::class);
117+
$this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node);
118+
119+
return $node;
120+
}
121+
122+
private function isCurrentObjectReturnDocType(
123+
TypeNode $typeNode,
124+
ClassMethod $classMethod,
125+
ClassReflection $classReflection
126+
): bool {
127+
$docType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($typeNode, $classMethod);
128+
129+
// covers @return $this and @return static
130+
if ($docType instanceof StaticType) {
131+
return $docType->getClassName() === $classReflection->getName();
132+
}
133+
134+
// covers @return self and @return CurrentObject
135+
if ($docType instanceof ObjectType) {
136+
return $docType->getClassName() === $classReflection->getName();
137+
}
138+
139+
return false;
140+
}
141+
}

‎src/Config/Level/DeadCodeLevel.php‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Rector\DeadCode\Rector\ClassConst\RemoveUnusedPrivateClassConstantRector;
1616
use Rector\DeadCode\Rector\ClassLike\RemoveTypedPropertyNonMockDocblockRector;
1717
use Rector\DeadCode\Rector\ClassMethod\RemoveArgumentFromDefaultParentCallRector;
18+
use Rector\DeadCode\Rector\ClassMethod\RemoveDuplicatedReturnSelfDocblockRector;
1819
use Rector\DeadCode\Rector\ClassMethod\RemoveEmptyClassMethodRector;
1920
use Rector\DeadCode\Rector\ClassMethod\RemoveNullTagValueNodeRector;
2021
use Rector\DeadCode\Rector\ClassMethod\RemoveParentDelegatingConstructorRector;
@@ -116,6 +117,7 @@ final class DeadCodeLevel
116117
RemoveVoidDocblockFromMagicMethodRector::class,
117118
RemoveUselessParamTagRector::class,
118119
RemoveUselessReturnTagRector::class,
120+
RemoveDuplicatedReturnSelfDocblockRector::class,
119121
RemoveUselessReadOnlyTagRector::class,
120122
RemoveNonExistingVarAnnotationRector::class,
121123
RemoveUselessVarTagRector::class,

0 commit comments

Comments
 (0)