[SOLID] Add ChangeReadOnlyPropertyWithDefaultValueToConstantRector

This commit is contained in:
TomasVotruba 2020-02-14 16:15:29 +01:00
parent 4c47186b76
commit 00a3fb3a17
8 changed files with 333 additions and 2 deletions

View File

@ -3,3 +3,4 @@ services:
Rector\SOLID\Rector\ClassConst\PrivatizeLocalClassConstantRector: null
Rector\SOLID\Rector\Class_\MakeUnusedClassesWithChildrenAbstractRector: null
Rector\SOLID\Rector\Property\ChangeReadOnlyPropertyWithDefaultValueToConstantRector: null

View File

@ -1,4 +1,4 @@
# All 450 Rectors Overview
# All 451 Rectors Overview
- [Projects](#projects)
- [General](#general)
@ -7662,6 +7662,36 @@ Change nested ifs to early return
<br>
### `ChangeReadOnlyPropertyWithDefaultValueToConstantRector`
- class: `Rector\SOLID\Rector\Property\ChangeReadOnlyPropertyWithDefaultValueToConstantRector`
Change property with read only status with default value to constant
```diff
class SomeClass
{
/**
* @var string[]
*/
- private $magicMethods = [
+ private const MAGIC_METHODS = [
'__toString',
'__wakeup',
];
public function run()
{
- foreach ($this->magicMethods as $magicMethod) {
+ foreach (self::MAGIC_METHODS as $magicMethod) {
echo $magicMethod;
}
}
}
```
<br>
### `FinalizeClassesWithoutChildrenRector`
- class: `Rector\SOLID\Rector\Class_\FinalizeClassesWithoutChildrenRector`

View File

@ -0,0 +1,176 @@
<?php
declare(strict_types=1);
namespace Rector\SOLID\Rector\Property;
use PhpParser\Node;
use PhpParser\Node\Const_ as ConstConst;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\ClassConst;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\PropertyProperty;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\PhpParser\Node\Manipulator\PropertyManipulator;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\Core\Util\RectorStrings;
use Rector\NodeTypeResolver\Node\AttributeKey;
/**
* @see \Rector\SOLID\Tests\Rector\Property\ChangeReadOnlyPropertyWithDefaultValueToConstantRector\ChangeReadOnlyPropertyWithDefaultValueToConstantRectorTest
*/
final class ChangeReadOnlyPropertyWithDefaultValueToConstantRector extends AbstractRector
{
/**
* @var PropertyManipulator
*/
private $propertyManipulator;
public function __construct(PropertyManipulator $propertyManipulator)
{
$this->propertyManipulator = $propertyManipulator;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Change property with read only status with default value to constant', [
new CodeSample(
<<<'PHP'
class SomeClass
{
/**
* @var string[]
*/
private $magicMethods = [
'__toString',
'__wakeup',
];
public function run()
{
foreach ($this->magicMethods as $magicMethod) {
echo $magicMethod;
}
}
}
PHP
,
<<<'PHP'
class SomeClass
{
/**
* @var string[]
*/
private const MAGIC_METHODS = [
'__toString',
'__wakeup',
];
public function run()
{
foreach (self::MAGIC_METHODS as $magicMethod) {
echo $magicMethod;
}
}
}
PHP
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Property::class];
}
/**
* @param Property $node
*/
public function refactor(Node $node): ?Node
{
if (count($node->props) !== 1) {
return null;
}
/** @var PropertyProperty $onlyProperty */
$onlyProperty = $node->props[0];
// we need default value
if ($onlyProperty->default === null) {
return null;
}
if (! $node->isPrivate()) {
return null;
}
// is property read only?
if (! $this->propertyManipulator->isReadyOnlyProperty($onlyProperty)) {
return null;
}
$this->replacePropertyFetchWithClassConstFetch($node, $onlyProperty);
return $this->createClassConst($node, $onlyProperty);
}
private function createClassConst(Property $property, PropertyProperty $propertyProperty): ClassConst
{
$constantName = $this->createConstantNameFromProperty($propertyProperty);
/** @var Expr $defaultValue */
$defaultValue = $propertyProperty->default;
$constant = new ConstConst($constantName, $defaultValue);
$classConst = new ClassConst([$constant]);
$classConst->flags = $property->flags;
$classConst->setAttribute(AttributeKey::PHP_DOC_INFO, $property->getAttribute(AttributeKey::PHP_DOC_INFO));
return $classConst;
}
private function createConstantNameFromProperty(PropertyProperty $propertyProperty): string
{
$propertyName = $this->getName($propertyProperty);
$constantName = RectorStrings::camelCaseToUnderscore($propertyName);
return strtoupper($constantName);
}
private function replacePropertyFetchWithClassConstFetch(Node $node, PropertyProperty $propertyProperty): void
{
$classNode = $node->getAttribute(AttributeKey::CLASS_NODE);
if ($classNode === null) {
throw new ShouldNotHappenException();
}
$propertyName = $this->getName($propertyProperty);
$constantName = $this->createConstantNameFromProperty($propertyProperty);
$this->traverseNodesWithCallable($classNode, function (Node $node) use ($propertyName, $constantName) {
if (! $node instanceof PropertyFetch) {
return null;
}
if (! $this->isName($node->var, 'this')) {
return null;
}
if (! $this->isName($node->name, $propertyName)) {
return null;
}
// replace with constant fetch
return new ClassConstFetch(new Name('self'), $constantName);
});
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Rector\SOLID\Tests\Rector\Property\ChangeReadOnlyPropertyWithDefaultValueToConstantRector;
use Iterator;
use Rector\Core\Testing\PHPUnit\AbstractRectorTestCase;
use Rector\SOLID\Rector\Property\ChangeReadOnlyPropertyWithDefaultValueToConstantRector;
final class ChangeReadOnlyPropertyWithDefaultValueToConstantRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(string $file): void
{
$this->doTestFile($file);
}
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
protected function getRectorClass(): string
{
return ChangeReadOnlyPropertyWithDefaultValueToConstantRector::class;
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace Rector\SOLID\Tests\Rector\Property\ChangeReadOnlyPropertyWithDefaultValueToConstantRector\Fixture;
class SomeClass
{
/**
* @var string[]
*/
private $magicMethods = [
'__toString',
'__wakeup',
];
public function run()
{
foreach ($this->magicMethods as $magicMethod) {
echo $magicMethod;
}
}
}
?>
-----
<?php
namespace Rector\SOLID\Tests\Rector\Property\ChangeReadOnlyPropertyWithDefaultValueToConstantRector\Fixture;
class SomeClass
{
/**
* @var string[]
*/
private const MAGIC_METHODS = [
'__toString',
'__wakeup',
];
public function run()
{
foreach (self::MAGIC_METHODS as $magicMethod) {
echo $magicMethod;
}
}
}
?>

View File

@ -0,0 +1,14 @@
<?php
namespace Rector\SOLID\Tests\Rector\Property\ChangeReadOnlyPropertyWithDefaultValueToConstantRector\Fixture;
class SkipMultipleProperties
{
/**
* @var string[]
*/
private $moreMagic, $magicMethods = [
'__toString',
'__wakeup',
];
}

View File

@ -0,0 +1,23 @@
<?php
namespace Rector\SOLID\Tests\Rector\Property\ChangeReadOnlyPropertyWithDefaultValueToConstantRector\Fixture;
class SkipOverriden
{
/**
* @var string[]
*/
private $magicMethods = [
'__toString',
'__wakeup',
];
public function run()
{
foreach ($this->magicMethods as $magicMethod) {
echo $magicMethod;
}
$this->magicMethods = 123;
}
}

View File

@ -107,10 +107,20 @@ final class PropertyManipulator
return $propertyFetches;
}
public function isReadyOnlyProperty(PropertyProperty $propertyProperty): bool
{
foreach ($this->getAllPropertyFetch($propertyProperty) as $propertyFetch) {
if (! $this->isReadContext($propertyFetch)) {
return false;
}
}
return true;
}
public function isPropertyUsedInReadContext(PropertyProperty $propertyProperty): bool
{
$property = $this->getProperty($propertyProperty);
if ($this->isDoctrineProperty($property)) {
return true;
}