-
-
Notifications
You must be signed in to change notification settings - Fork 340
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Php82] Add ReadOnlyClassRector (#2296)
* [Php82] Add ReadOnlyClassRector * skip has non-readonly property * note * note * note * [ci-review] Rector Rectify * skip has AllowDynamicProperties attribute * skip already readonly * [ci-review] Rector Rectify * skip property promotion not readonly * no params means no property promotion, skip if no property defined * note * note * skip final class, possibly extendable * add fixture * add @see * visibility union ndoe rules/Privatization/NodeManipulator/VisibilityManipulator.php * visibility union ndoe rules/Privatization/NodeManipulator/VisibilityManipulator.php * remove already readonly fixture * comment * skip anonymous class fixture * skip non-final class fixture * skip allow dynamic fixture * class check * skip has writable property fixture * skip no properties * skip property promotion writable * debug * [ci-review] Rector Rectify * fix * eol * fix * [ci-review] Rector Rectify * final touch: add up-to-php82 level setlist Co-authored-by: GitHub Action <action@github.com>
- Loading branch information
1 parent
93cf392
commit 78aaf7e
Showing
18 changed files
with
363 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
use Rector\Config\RectorConfig; | ||
use Rector\Core\ValueObject\PhpVersion; | ||
use Rector\Set\ValueObject\LevelSetList; | ||
use Rector\Set\ValueObject\SetList; | ||
|
||
return static function (RectorConfig $rectorConfig): void { | ||
$rectorConfig->sets([SetList::PHP_82, LevelSetList::UP_TO_PHP_81]); | ||
|
||
// parameter must be defined after import, to override imported param version | ||
$rectorConfig->phpVersion(PhpVersion::PHP_82); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
use Rector\Config\RectorConfig; | ||
use Rector\Php82\Rector\Class_\ReadOnlyClassRector; | ||
|
||
return static function (RectorConfig $rectorConfig): void { | ||
$rectorConfig->rule(ReadOnlyClassRector::class); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
21 changes: 21 additions & 0 deletions
21
rules-tests/Php82/Rector/Class_/ReadOnlyClassRector/Fixture/only_readonly_property.php.inc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<?php | ||
|
||
namespace Rector\Tests\Php82\Rector\Class_\ReadOnlyClassRector\Fixture; | ||
|
||
final class OnlyReadonlyProperty | ||
{ | ||
private readonly string $property; | ||
} | ||
|
||
?> | ||
----- | ||
<?php | ||
|
||
namespace Rector\Tests\Php82\Rector\Class_\ReadOnlyClassRector\Fixture; | ||
|
||
final readonly class OnlyReadonlyProperty | ||
{ | ||
private string $property; | ||
} | ||
|
||
?> |
25 changes: 25 additions & 0 deletions
25
rules-tests/Php82/Rector/Class_/ReadOnlyClassRector/Fixture/only_readonly_property2.php.inc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<?php | ||
|
||
namespace Rector\Tests\Php82\Rector\Class_\ReadOnlyClassRector\Fixture; | ||
|
||
final class OnlyReadonlyProperty2 | ||
{ | ||
public function __construct(private readonly string $property) | ||
{ | ||
} | ||
} | ||
|
||
?> | ||
----- | ||
<?php | ||
|
||
namespace Rector\Tests\Php82\Rector\Class_\ReadOnlyClassRector\Fixture; | ||
|
||
final readonly class OnlyReadonlyProperty2 | ||
{ | ||
public function __construct(private string $property) | ||
{ | ||
} | ||
} | ||
|
||
?> |
9 changes: 9 additions & 0 deletions
9
rules-tests/Php82/Rector/Class_/ReadOnlyClassRector/Fixture/skip_allow_dynamic.php.inc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<?php | ||
|
||
namespace Rector\Tests\Php82\Rector\Class_\ReadOnlyClassRector\Fixture; | ||
|
||
#[\AllowDynamicProperties] | ||
final class SkipAllowDynamic | ||
{ | ||
private readonly string $property; | ||
} |
13 changes: 13 additions & 0 deletions
13
rules-tests/Php82/Rector/Class_/ReadOnlyClassRector/Fixture/skip_anonymous_class.php.inc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<?php | ||
|
||
namespace Rector\Tests\Php82\Rector\Class_\ReadOnlyClassRector\Fixture; | ||
|
||
class SkipAnonymousClass | ||
{ | ||
public function run() | ||
{ | ||
new class { | ||
private readonly string $foo; | ||
}; | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
...-tests/Php82/Rector/Class_/ReadOnlyClassRector/Fixture/skip_has_writable_property.php.inc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<?php | ||
|
||
namespace Rector\Tests\Php82\Rector\Class_\ReadOnlyClassRector\Fixture; | ||
|
||
final class SkipHasWritableProperty | ||
{ | ||
private string $property; | ||
} |
7 changes: 7 additions & 0 deletions
7
rules-tests/Php82/Rector/Class_/ReadOnlyClassRector/Fixture/skip_no_properties.php.inc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
<?php | ||
|
||
namespace Rector\Tests\Php82\Rector\Class_\ReadOnlyClassRector\Fixture; | ||
|
||
final class SkipNoProperties | ||
{ | ||
} |
10 changes: 10 additions & 0 deletions
10
rules-tests/Php82/Rector/Class_/ReadOnlyClassRector/Fixture/skip_no_properties_2.php.inc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<?php | ||
|
||
namespace Rector\Tests\Php82\Rector\Class_\ReadOnlyClassRector\Fixture; | ||
|
||
final class SkipNoProperties2 | ||
{ | ||
public function __construct() | ||
{ | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
rules-tests/Php82/Rector/Class_/ReadOnlyClassRector/Fixture/skip_non_final_class.php.inc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<?php | ||
|
||
namespace Rector\Tests\Php82\Rector\Class_\ReadOnlyClassRector\Fixture; | ||
|
||
class SkipNonFinalClass | ||
{ | ||
private readonly string $property; | ||
} |
10 changes: 10 additions & 0 deletions
10
.../Php82/Rector/Class_/ReadOnlyClassRector/Fixture/skip_property_promotion_writable.php.inc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<?php | ||
|
||
namespace Rector\Tests\Php82\Rector\Class_\ReadOnlyClassRector\Fixture; | ||
|
||
final class SkipPropertyPromotionWritable | ||
{ | ||
public function __construct(private string $data) | ||
{ | ||
} | ||
} |
33 changes: 33 additions & 0 deletions
33
rules-tests/Php82/Rector/Class_/ReadOnlyClassRector/ReadOnlyClassRectorTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Rector\Tests\Php82\Rector\Class_\ReadOnlyClassRector; | ||
|
||
use Iterator; | ||
use Rector\Testing\PHPUnit\AbstractRectorTestCase; | ||
use Symplify\SmartFileSystem\SmartFileInfo; | ||
|
||
final class ReadOnlyClassRectorTest extends AbstractRectorTestCase | ||
{ | ||
/** | ||
* @dataProvider provideData() | ||
*/ | ||
public function test(SmartFileInfo $fileInfo): void | ||
{ | ||
$this->doTestFileInfo($fileInfo); | ||
} | ||
|
||
/** | ||
* @return Iterator<SmartFileInfo> | ||
*/ | ||
public function provideData(): Iterator | ||
{ | ||
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); | ||
} | ||
|
||
public function provideConfigFilePath(): string | ||
{ | ||
return __DIR__ . '/config/configured_rule.php'; | ||
} | ||
} |
10 changes: 10 additions & 0 deletions
10
rules-tests/Php82/Rector/Class_/ReadOnlyClassRector/config/configured_rule.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
use Rector\Config\RectorConfig; | ||
use Rector\Php82\Rector\Class_\ReadOnlyClassRector; | ||
|
||
return static function (RectorConfig $rectorConfig): void { | ||
$rectorConfig->rule(ReadOnlyClassRector::class); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Rector\Php82\Rector\Class_; | ||
|
||
use PhpParser\Node; | ||
use PhpParser\Node\Stmt\Class_; | ||
use PhpParser\Node\Stmt\ClassMethod; | ||
use PhpParser\Node\Stmt\Property; | ||
use Rector\Core\NodeAnalyzer\ClassAnalyzer; | ||
use Rector\Core\Rector\AbstractRector; | ||
use Rector\Core\ValueObject\MethodName; | ||
use Rector\Core\ValueObject\PhpVersionFeature; | ||
use Rector\Core\ValueObject\Visibility; | ||
use Rector\Php80\NodeAnalyzer\PhpAttributeAnalyzer; | ||
use Rector\Privatization\NodeManipulator\VisibilityManipulator; | ||
use Rector\VersionBonding\Contract\MinPhpVersionInterface; | ||
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; | ||
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; | ||
|
||
/** | ||
* @changelog https://wiki.php.net/rfc/readonly_classes | ||
* | ||
* @see \Rector\Tests\Php82\Rector\Class_\ReadOnlyClassRector\ReadOnlyClassRectorTest | ||
*/ | ||
final class ReadOnlyClassRector extends AbstractRector implements MinPhpVersionInterface | ||
{ | ||
/** | ||
* @var string | ||
*/ | ||
private const ATTRIBUTE = 'AllowDynamicProperties'; | ||
|
||
public function __construct( | ||
private readonly ClassAnalyzer $classAnalyzer, | ||
private readonly VisibilityManipulator $visibilityManipulator, | ||
private readonly PhpAttributeAnalyzer $phpAttributeAnalyzer | ||
) { | ||
} | ||
|
||
public function getRuleDefinition(): RuleDefinition | ||
{ | ||
return new RuleDefinition('Decorate read-only class with `readonly` attribute', [ | ||
new CodeSample( | ||
<<<'CODE_SAMPLE' | ||
final class SomeClass | ||
{ | ||
public function __construct( | ||
private readonly string $name | ||
) { | ||
} | ||
} | ||
CODE_SAMPLE | ||
|
||
, | ||
<<<'CODE_SAMPLE' | ||
final readonly class SomeClass | ||
{ | ||
public function __construct( | ||
private string $name | ||
) { | ||
} | ||
} | ||
CODE_SAMPLE | ||
), | ||
]); | ||
} | ||
|
||
/** | ||
* @return array<class-string<Node>> | ||
*/ | ||
public function getNodeTypes(): array | ||
{ | ||
return [Class_::class]; | ||
} | ||
|
||
/** | ||
* @param Class_ $node | ||
*/ | ||
public function refactor(Node $node): ?Node | ||
{ | ||
if ($this->shouldSkip($node)) { | ||
return null; | ||
} | ||
|
||
$this->visibilityManipulator->makeReadonly($node); | ||
|
||
$constructClassMethod = $node->getMethod(MethodName::CONSTRUCT); | ||
if ($constructClassMethod instanceof ClassMethod) { | ||
foreach ($constructClassMethod->getParams() as $param) { | ||
$this->visibilityManipulator->removeReadonly($param); | ||
} | ||
} | ||
|
||
foreach ($node->getProperties() as $property) { | ||
$this->visibilityManipulator->removeReadonly($property); | ||
} | ||
|
||
return $node; | ||
} | ||
|
||
public function provideMinPhpVersion(): int | ||
{ | ||
return PhpVersionFeature::READONLY_CLASS; | ||
} | ||
|
||
private function shouldSkip(Class_ $class): bool | ||
{ | ||
// need to have test fixture once feature added to nikic/PHP-Parser | ||
if ($this->visibilityManipulator->hasVisibility($class, Visibility::READONLY)) { | ||
return true; | ||
} | ||
|
||
if ($this->classAnalyzer->isAnonymousClass($class)) { | ||
return true; | ||
} | ||
|
||
if (! $class->isFinal()) { | ||
return true; | ||
} | ||
|
||
if ($this->phpAttributeAnalyzer->hasPhpAttribute($class, self::ATTRIBUTE)) { | ||
return true; | ||
} | ||
|
||
$properties = $class->getProperties(); | ||
if ($this->hasWritableProperty($properties)) { | ||
return true; | ||
} | ||
|
||
$constructClassMethod = $class->getMethod(MethodName::CONSTRUCT); | ||
if (! $constructClassMethod instanceof ClassMethod) { | ||
// no __construct means no property promotion, skip if class has no property defined | ||
return $properties === []; | ||
} | ||
|
||
$params = $constructClassMethod->getParams(); | ||
if ($params === []) { | ||
// no params means no property promotion, skip if class has no property defined | ||
return $properties === []; | ||
} | ||
|
||
foreach ($params as $param) { | ||
// has non-property promotion, skip | ||
if (! $this->visibilityManipulator->hasVisibility($param, Visibility::READONLY)) { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* @param Property[] $properties | ||
*/ | ||
private function hasWritableProperty(array $properties): bool | ||
{ | ||
foreach ($properties as $property) { | ||
if (! $property->isReadonly()) { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
} |
Oops, something went wrong.