Skip to content

Commit ef333de

Browse files
authored
[DeadCode] Add RemoveUselessReadOnlyTagRector (#5790)
* [DeadCode] Add RemoveUselessReadOnlyDocRector * roll * final touch: rename to RemoveUselessReadOnlyTagRector * final touch: doc
1 parent 717e3e0 commit ef333de

10 files changed

Lines changed: 299 additions & 2 deletions

File tree

build/target-repository/docs/rector_rules_overview.md

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# 369 Rules Overview
1+
# 370 Rules Overview
22

33
<br>
44

@@ -10,7 +10,7 @@
1010

1111
- [CodingStyle](#codingstyle) (28)
1212

13-
- [DeadCode](#deadcode) (43)
13+
- [DeadCode](#deadcode) (44)
1414

1515
- [EarlyReturn](#earlyreturn) (9)
1616

@@ -2886,6 +2886,29 @@ Remove `@param` docblock with same type as parameter type
28862886

28872887
<br>
28882888

2889+
### RemoveUselessReadOnlyTagRector
2890+
2891+
Remove useless `@readonly` annotation on native readonly type
2892+
2893+
- class: [`Rector\DeadCode\Rector\Property\RemoveUselessReadOnlyTagRector`](../rules/DeadCode/Rector/Property/RemoveUselessReadOnlyTagRector.php)
2894+
2895+
```diff
2896+
final class SomeClass
2897+
{
2898+
- /**
2899+
- * @readonly
2900+
- */
2901+
private readonly string $name;
2902+
2903+
public function __construct(string $name)
2904+
{
2905+
$this->name = $name;
2906+
}
2907+
}
2908+
```
2909+
2910+
<br>
2911+
28892912
### RemoveUselessReturnExprInConstructRector
28902913

28912914
Remove useless return Expr in `__construct()`
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace Rector\Tests\DeadCode\Rector\Property\RemoveUselessReadOnlyTagRector\Fixture;
4+
5+
/**
6+
* @readonly
7+
*/
8+
final readonly class RemoveOnClass
9+
{
10+
public function __construct(
11+
private string $name
12+
)
13+
{
14+
}
15+
}
16+
17+
?>
18+
-----
19+
<?php
20+
21+
namespace Rector\Tests\DeadCode\Rector\Property\RemoveUselessReadOnlyTagRector\Fixture;
22+
23+
final readonly class RemoveOnClass
24+
{
25+
public function __construct(
26+
private string $name
27+
)
28+
{
29+
}
30+
}
31+
32+
?>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace Rector\Tests\DeadCode\Rector\Property\RemoveUselessReadOnlyTagRector\Fixture;
4+
5+
final class RemoveOnParamConstruct
6+
{
7+
public function __construct(
8+
/**
9+
* @readonly
10+
*/
11+
private readonly string $name
12+
)
13+
{
14+
}
15+
}
16+
17+
?>
18+
-----
19+
<?php
20+
21+
namespace Rector\Tests\DeadCode\Rector\Property\RemoveUselessReadOnlyTagRector\Fixture;
22+
23+
final class RemoveOnParamConstruct
24+
{
25+
public function __construct(
26+
private readonly string $name
27+
)
28+
{
29+
}
30+
}
31+
32+
?>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace Rector\Tests\DeadCode\Rector\Property\RemoveUselessReadOnlyTagRector\Fixture;
4+
5+
final class RemoveOnProperty
6+
{
7+
/**
8+
* @readonly
9+
*/
10+
private readonly string $name;
11+
12+
public function __construct(string $name)
13+
{
14+
$this->name = $name;
15+
}
16+
}
17+
18+
?>
19+
-----
20+
<?php
21+
22+
namespace Rector\Tests\DeadCode\Rector\Property\RemoveUselessReadOnlyTagRector\Fixture;
23+
24+
final class RemoveOnProperty
25+
{
26+
private readonly string $name;
27+
28+
public function __construct(string $name)
29+
{
30+
$this->name = $name;
31+
}
32+
}
33+
34+
?>
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\Property\RemoveUselessReadOnlyTagRector\Fixture;
4+
5+
class SkipNoReadonlyDoc
6+
{
7+
private readonly string $name;
8+
9+
public function __construct(string $name)
10+
{
11+
$this->name = $name;
12+
}
13+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Rector\Tests\DeadCode\Rector\Property\RemoveUselessReadOnlyTagRector\Fixture;
4+
5+
class SkipHasDescription
6+
{
7+
/**
8+
* @readonly some desc
9+
*/
10+
private readonly string $name;
11+
12+
public function __construct(string $name)
13+
{
14+
$this->name = $name;
15+
}
16+
}
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\Tests\DeadCode\Rector\Property\RemoveUselessReadOnlyTagRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class RemoveUselessReadOnlyTagRectorTest 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\Property\RemoveUselessReadOnlyTagRector;
7+
8+
return RectorConfig::configure()
9+
->withRules([RemoveUselessReadOnlyTagRector::class]);
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\DeadCode\Rector\Property;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Param;
9+
use PhpParser\Node\Stmt\Class_;
10+
use PhpParser\Node\Stmt\Property;
11+
use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode;
12+
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
13+
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
14+
use Rector\Comments\NodeDocBlock\DocBlockUpdater;
15+
use Rector\Privatization\NodeManipulator\VisibilityManipulator;
16+
use Rector\Rector\AbstractRector;
17+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
18+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
19+
20+
/**
21+
* @see \Rector\Tests\DeadCode\Rector\Property\RemoveUselessReadOnlyTagRector\RemoveUselessReadOnlyTagRectorTest
22+
*/
23+
final class RemoveUselessReadOnlyTagRector extends AbstractRector
24+
{
25+
public function __construct(
26+
private readonly VisibilityManipulator $visibilityManipulator,
27+
private readonly PhpDocInfoFactory $phpDocInfoFactory,
28+
private readonly DocBlockUpdater $docBlockUpdater
29+
) {
30+
}
31+
32+
public function getRuleDefinition(): RuleDefinition
33+
{
34+
return new RuleDefinition('Remove useless @readonly annotation on native readonly type', [
35+
new CodeSample(
36+
<<<'CODE_SAMPLE'
37+
final class SomeClass
38+
{
39+
/**
40+
* @readonly
41+
*/
42+
private readonly string $name;
43+
44+
public function __construct(string $name)
45+
{
46+
$this->name = $name;
47+
}
48+
}
49+
CODE_SAMPLE
50+
51+
,
52+
<<<'CODE_SAMPLE'
53+
final class SomeClass
54+
{
55+
private readonly string $name;
56+
57+
public function __construct(string $name)
58+
{
59+
$this->name = $name;
60+
}
61+
}
62+
CODE_SAMPLE
63+
),
64+
]);
65+
}
66+
67+
/**
68+
* @return array<class-string<Node>>
69+
*/
70+
public function getNodeTypes(): array
71+
{
72+
return [Class_::class, Property::class, Param::class];
73+
}
74+
75+
/**
76+
* @param Class_|Property|Param $node
77+
*/
78+
public function refactor(Node $node): ?Node
79+
{
80+
// for param, only on property promotion
81+
if ($node instanceof Param && $node->flags === 0) {
82+
return null;
83+
}
84+
85+
if (! $this->visibilityManipulator->isReadonly($node)) {
86+
return null;
87+
}
88+
89+
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node);
90+
$readonlyDoc = $phpDocInfo->getByName('readonly');
91+
if (! $readonlyDoc instanceof PhpDocTagNode) {
92+
return null;
93+
}
94+
95+
if (! $readonlyDoc->value instanceof GenericTagValueNode) {
96+
return null;
97+
}
98+
99+
if ($readonlyDoc->value->value !== '') {
100+
return null;
101+
}
102+
103+
$phpDocInfo->removeByName('readonly');
104+
$this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node);
105+
106+
return $node;
107+
}
108+
}

src/Config/Level/DeadCodeLevel.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
use Rector\DeadCode\Rector\Node\RemoveNonExistingVarAnnotationRector;
4040
use Rector\DeadCode\Rector\Plus\RemoveDeadZeroAndOneOperationRector;
4141
use Rector\DeadCode\Rector\Property\RemoveUnusedPrivatePropertyRector;
42+
use Rector\DeadCode\Rector\Property\RemoveUselessReadOnlyTagRector;
4243
use Rector\DeadCode\Rector\Property\RemoveUselessVarTagRector;
4344
use Rector\DeadCode\Rector\PropertyProperty\RemoveNullPropertyInitializationRector;
4445
use Rector\DeadCode\Rector\Return_\RemoveDeadConditionAboveReturnRector;
@@ -90,6 +91,7 @@ final class DeadCodeLevel
9091
// docblock
9192
RemoveUselessParamTagRector::class,
9293
RemoveUselessReturnTagRector::class,
94+
RemoveUselessReadOnlyTagRector::class,
9395
RemoveNonExistingVarAnnotationRector::class,
9496
RemoveUselessVarTagRector::class,
9597
RemovePhpVersionIdCheckRector::class,

0 commit comments

Comments
 (0)