Skip to content

Commit 0dbbde8

Browse files
authored
[DeadCode] Add RemoveUselessUnionReturnDocblockRector (#8126)
Remove @return union docblock when a more specific, non-array native return type already covers it, e.g. @return bool|mixed|string over false|string.
1 parent c9bf9cd commit 0dbbde8

10 files changed

Lines changed: 283 additions & 0 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Rector\Tests\DeadCode\Rector\ClassMethod\RemoveUselessUnionReturnDocblockRector\Fixture;
4+
5+
final class SkipArrayNative
6+
{
7+
/**
8+
* @return int[]|string[]
9+
*/
10+
public function run(): array
11+
{
12+
}
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Rector\Tests\DeadCode\Rector\ClassMethod\RemoveUselessUnionReturnDocblockRector\Fixture;
4+
5+
final class SkipDescription
6+
{
7+
/**
8+
* @return bool|mixed|string the escaped value
9+
*/
10+
public function run(): false|string
11+
{
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Rector\Tests\DeadCode\Rector\ClassMethod\RemoveUselessUnionReturnDocblockRector\Fixture;
4+
5+
final class SkipMoreSpecificDocblock
6+
{
7+
/**
8+
* @return \DateTime|\DateTimeImmutable
9+
*/
10+
public function run(): object
11+
{
12+
}
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Rector\Tests\DeadCode\Rector\ClassMethod\RemoveUselessUnionReturnDocblockRector\Fixture;
4+
5+
final class SkipNoNativeType
6+
{
7+
/**
8+
* @return bool|mixed|string
9+
*/
10+
public function run()
11+
{
12+
}
13+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace Rector\Tests\DeadCode\Rector\ClassMethod\RemoveUselessUnionReturnDocblockRector\Fixture;
4+
5+
final class UnionOverSingleNative
6+
{
7+
/**
8+
* @return bool|float|mixed|string
9+
*/
10+
private function escapeQueryValue(string $value): string
11+
{
12+
}
13+
}
14+
15+
?>
16+
-----
17+
<?php
18+
19+
namespace Rector\Tests\DeadCode\Rector\ClassMethod\RemoveUselessUnionReturnDocblockRector\Fixture;
20+
21+
final class UnionOverSingleNative
22+
{
23+
private function escapeQueryValue(string $value): string
24+
{
25+
}
26+
}
27+
28+
?>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace Rector\Tests\DeadCode\Rector\ClassMethod\RemoveUselessUnionReturnDocblockRector\Fixture;
4+
5+
final class UnionWithMixed
6+
{
7+
/**
8+
* @return bool|mixed|string
9+
*/
10+
protected function setContactToSync(&$checkEmailsInSF, $lead): false|string
11+
{
12+
}
13+
}
14+
15+
?>
16+
-----
17+
<?php
18+
19+
namespace Rector\Tests\DeadCode\Rector\ClassMethod\RemoveUselessUnionReturnDocblockRector\Fixture;
20+
21+
final class UnionWithMixed
22+
{
23+
protected function setContactToSync(&$checkEmailsInSF, $lead): false|string
24+
{
25+
}
26+
}
27+
28+
?>
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\RemoveUselessUnionReturnDocblockRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class RemoveUselessUnionReturnDocblockRectorTest 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: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Config\RectorConfig;
6+
use Rector\DeadCode\Rector\ClassMethod\RemoveUselessUnionReturnDocblockRector;
7+
use Rector\ValueObject\PhpVersionFeature;
8+
9+
return RectorConfig::configure()
10+
->withPhpVersion(PhpVersionFeature::UNION_TYPES)
11+
->withRules([RemoveUselessUnionReturnDocblockRector::class]);
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\DeadCode\Rector\ClassMethod;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Stmt\ClassMethod;
9+
use PhpParser\Node\Stmt\Function_;
10+
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
11+
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
12+
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
13+
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
14+
use Rector\Comments\NodeDocBlock\DocBlockUpdater;
15+
use Rector\Rector\AbstractRector;
16+
use Rector\StaticTypeMapper\StaticTypeMapper;
17+
use Rector\ValueObject\PhpVersionFeature;
18+
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
19+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
20+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
21+
22+
/**
23+
* @see \Rector\Tests\DeadCode\Rector\ClassMethod\RemoveUselessUnionReturnDocblockRector\RemoveUselessUnionReturnDocblockRectorTest
24+
*/
25+
final class RemoveUselessUnionReturnDocblockRector extends AbstractRector implements MinPhpVersionInterface
26+
{
27+
public function __construct(
28+
private readonly PhpDocInfoFactory $phpDocInfoFactory,
29+
private readonly DocBlockUpdater $docBlockUpdater,
30+
private readonly StaticTypeMapper $staticTypeMapper,
31+
) {
32+
}
33+
34+
public function getRuleDefinition(): RuleDefinition
35+
{
36+
return new RuleDefinition(
37+
'Remove @return union docblock that is broader than a more specific, non-array native return type',
38+
[
39+
new CodeSample(
40+
<<<'CODE_SAMPLE'
41+
final class SomeClass
42+
{
43+
/**
44+
* @return bool|mixed|string
45+
*/
46+
public function run(): false|string
47+
{
48+
}
49+
}
50+
CODE_SAMPLE
51+
,
52+
<<<'CODE_SAMPLE'
53+
final class SomeClass
54+
{
55+
public function run(): false|string
56+
{
57+
}
58+
}
59+
CODE_SAMPLE
60+
),
61+
]
62+
);
63+
}
64+
65+
/**
66+
* @return array<class-string<Node>>
67+
*/
68+
public function getNodeTypes(): array
69+
{
70+
return [ClassMethod::class, Function_::class];
71+
}
72+
73+
/**
74+
* @param ClassMethod|Function_ $node
75+
*/
76+
public function refactor(Node $node): ?Node
77+
{
78+
if ($node->returnType === null) {
79+
return null;
80+
}
81+
82+
// skip as no comments
83+
if ($node->getComments() === []) {
84+
return null;
85+
}
86+
87+
$phpDocInfo = $this->phpDocInfoFactory->createFromNode($node);
88+
if (! $phpDocInfo instanceof PhpDocInfo) {
89+
return null;
90+
}
91+
92+
$returnTagValueNode = $phpDocInfo->getReturnTagValue();
93+
if (! $returnTagValueNode instanceof ReturnTagValueNode) {
94+
return null;
95+
}
96+
97+
// keep description, it carries info the native type cannot
98+
if ($returnTagValueNode->description !== '') {
99+
return null;
100+
}
101+
102+
// only union docblock types are handled here
103+
if (! $returnTagValueNode->type instanceof UnionTypeNode) {
104+
return null;
105+
}
106+
107+
$nativeReturnType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($node->returnType);
108+
109+
// keep array native type, the docblock may carry element types
110+
if ($nativeReturnType->isArray()->yes()) {
111+
return null;
112+
}
113+
114+
$docblockReturnType = $phpDocInfo->getReturnType();
115+
116+
// the docblock adds nothing when it is broader than the native type
117+
if (! $docblockReturnType->isSuperTypeOf($nativeReturnType)->yes()) {
118+
return null;
119+
}
120+
121+
if (! $phpDocInfo->removeByType(ReturnTagValueNode::class)) {
122+
return null;
123+
}
124+
125+
$this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node);
126+
127+
return $node;
128+
}
129+
130+
public function provideMinPhpVersion(): int
131+
{
132+
return PhpVersionFeature::UNION_TYPES;
133+
}
134+
}

src/Config/Level/DeadCodeLevel.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
use Rector\DeadCode\Rector\ClassMethod\RemoveUselessParamTagRector;
2929
use Rector\DeadCode\Rector\ClassMethod\RemoveUselessReturnExprInConstructRector;
3030
use Rector\DeadCode\Rector\ClassMethod\RemoveUselessReturnTagRector;
31+
use Rector\DeadCode\Rector\ClassMethod\RemoveUselessUnionReturnDocblockRector;
3132
use Rector\DeadCode\Rector\ClassMethod\RemoveVoidDocblockFromMagicMethodRector;
3233
use Rector\DeadCode\Rector\Closure\RemoveUnusedClosureVariableUseRector;
3334
use Rector\DeadCode\Rector\Concat\RemoveConcatAutocastRector;
@@ -119,6 +120,7 @@ final class DeadCodeLevel
119120
RemoveUselessReturnTagRector::class,
120121
RemoveDuplicatedReturnSelfDocblockRector::class,
121122
RemoveMixedDocblockOverruledByNativeTypeRector::class,
123+
RemoveUselessUnionReturnDocblockRector::class,
122124
RemoveUselessReadOnlyTagRector::class,
123125
RemoveNonExistingVarAnnotationRector::class,
124126
RemoveUselessVarTagRector::class,

0 commit comments

Comments
 (0)