mirror of
https://github.com/rectorphp/rector.git
synced 2025-04-20 23:41:57 +02:00
🎉 Rule #300 - [CodeQuality] Add CompleteDynamicPropertiesRector
This commit is contained in:
commit
d62174b181
@ -35,3 +35,4 @@ services:
|
||||
Rector\CodeQuality\Rector\LogicalAnd\AndAssignsToSeparateLinesRector: ~
|
||||
Rector\CodeQuality\Rector\For_\ForToForeachRector: ~
|
||||
Rector\CodeQuality\Rector\FuncCall\CompactToVariablesRector: ~
|
||||
Rector\CodeQuality\Rector\Class_\CompleteDynamicPropertiesRector: ~
|
||||
|
@ -1,4 +1,4 @@
|
||||
# All 299 Rectors Overview
|
||||
# All 300 Rectors Overview
|
||||
|
||||
- [Projects](#projects)
|
||||
- [General](#general)
|
||||
@ -769,6 +769,28 @@ Joins concat of 2 strings
|
||||
|
||||
<br>
|
||||
|
||||
### `CompleteDynamicPropertiesRector`
|
||||
|
||||
- class: `Rector\CodeQuality\Rector\Class_\CompleteDynamicPropertiesRector`
|
||||
|
||||
Add missing dynamic properties
|
||||
|
||||
```diff
|
||||
class SomeClass
|
||||
{
|
||||
+ /**
|
||||
+ * @var int
|
||||
+ */
|
||||
+ public $value;
|
||||
public function set()
|
||||
{
|
||||
$this->value = 5;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
## CodingStyle
|
||||
|
||||
### `SplitDoubleAssignRector`
|
||||
|
@ -0,0 +1,189 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\CodeQuality\Rector\Class_;
|
||||
|
||||
use Nette\Utils\Arrays;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\PropertyFetch;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\Property;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
|
||||
use Rector\NodeTypeResolver\PHPStan\Type\TypeToStringResolver;
|
||||
use Rector\PhpParser\NodeTraverser\CallableNodeTraverser;
|
||||
use Rector\Rector\AbstractRector;
|
||||
use Rector\RectorDefinition\CodeSample;
|
||||
use Rector\RectorDefinition\RectorDefinition;
|
||||
|
||||
/**
|
||||
* @see https://3v4l.org/GL6II
|
||||
* @see https://3v4l.org/eTrhZ
|
||||
* @see https://3v4l.org/C554W
|
||||
*/
|
||||
final class CompleteDynamicPropertiesRector extends AbstractRector
|
||||
{
|
||||
/**
|
||||
* @var CallableNodeTraverser
|
||||
*/
|
||||
private $callableNodeTraverser;
|
||||
|
||||
/**
|
||||
* @var TypeToStringResolver
|
||||
*/
|
||||
private $typeToStringResolver;
|
||||
|
||||
/**
|
||||
* @var DocBlockManipulator
|
||||
*/
|
||||
private $docBlockManipulator;
|
||||
|
||||
public function __construct(
|
||||
CallableNodeTraverser $callableNodeTraverser,
|
||||
TypeToStringResolver $typeToStringResolver,
|
||||
DocBlockManipulator $docBlockManipulator
|
||||
) {
|
||||
$this->callableNodeTraverser = $callableNodeTraverser;
|
||||
$this->typeToStringResolver = $typeToStringResolver;
|
||||
$this->docBlockManipulator = $docBlockManipulator;
|
||||
}
|
||||
|
||||
public function getDefinition(): RectorDefinition
|
||||
{
|
||||
return new RectorDefinition('Add missing dynamic properties', [
|
||||
new CodeSample(
|
||||
<<<'CODE_SAMPLE'
|
||||
class SomeClass
|
||||
{
|
||||
public function set()
|
||||
{
|
||||
$this->value = 5;
|
||||
}
|
||||
}
|
||||
CODE_SAMPLE
|
||||
,
|
||||
<<<'CODE_SAMPLE'
|
||||
class SomeClass
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $value;
|
||||
public function set()
|
||||
{
|
||||
$this->value = 5;
|
||||
}
|
||||
}
|
||||
CODE_SAMPLE
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getNodeTypes(): array
|
||||
{
|
||||
return [Class_::class];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Class_ $node
|
||||
*/
|
||||
public function refactor(Node $node): ?Node
|
||||
{
|
||||
$fetchedLocalPropertyNameToTypes = $this->resolveFetchedLocalPropertyNameToTypes($node);
|
||||
|
||||
$propertyNames = [];
|
||||
$this->callableNodeTraverser->traverseNodesWithCallable($node->stmts, function (Node $node) use (
|
||||
&$propertyNames
|
||||
) {
|
||||
if (! $node instanceof Property) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$propertyNames[] = $this->getName($node);
|
||||
});
|
||||
|
||||
$fetchedLocalPropertyNames = array_keys($fetchedLocalPropertyNameToTypes);
|
||||
$propertiesToComplete = array_diff($fetchedLocalPropertyNames, $propertyNames);
|
||||
|
||||
$newProperties = $this->createNewProperties($fetchedLocalPropertyNameToTypes, $propertiesToComplete);
|
||||
|
||||
$node->stmts = array_merge_recursive($newProperties, $node->stmts);
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[][][] $fetchedLocalPropertyNameToTypes
|
||||
* @param string[] $propertiesToComplete
|
||||
* @return Property[]
|
||||
*/
|
||||
private function createNewProperties(array $fetchedLocalPropertyNameToTypes, array $propertiesToComplete): array
|
||||
{
|
||||
$newProperties = [];
|
||||
foreach ($fetchedLocalPropertyNameToTypes as $propertyName => $propertyTypes) {
|
||||
if (! in_array($propertyName, $propertiesToComplete, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$propertyTypes = Arrays::flatten($propertyTypes);
|
||||
$propertyTypesAsString = implode('|', $propertyTypes);
|
||||
|
||||
$propertyBuilder = $this->builderFactory->property($propertyName)
|
||||
->makePublic();
|
||||
|
||||
if ($this->isAtLeastPhpVersion('7.4') && count($propertyTypes) === 1) {
|
||||
$newProperty = $propertyBuilder->setType($propertyTypes[0])
|
||||
->getNode();
|
||||
} else {
|
||||
$newProperty = $propertyBuilder->getNode();
|
||||
$this->docBlockManipulator->changeVarTag($newProperty, $propertyTypesAsString);
|
||||
}
|
||||
|
||||
$newProperties[] = $newProperty;
|
||||
}
|
||||
return $newProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[][][]
|
||||
*/
|
||||
private function resolveFetchedLocalPropertyNameToTypes(Class_ $class): array
|
||||
{
|
||||
$fetchedLocalPropertyNameToTypes = [];
|
||||
|
||||
$this->callableNodeTraverser->traverseNodesWithCallable($class->stmts, function (Node $node) use (
|
||||
&$fetchedLocalPropertyNameToTypes
|
||||
) {
|
||||
if (! $node instanceof PropertyFetch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $this->isName($node->var, 'this')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parentNode = $node->getAttribute(AttributeKey::PARENT_NODE);
|
||||
|
||||
// fallback type
|
||||
$propertyFetchType = ['mixed'];
|
||||
|
||||
// possible get type
|
||||
if ($parentNode instanceof Assign) {
|
||||
$assignedValueStaticType = $this->getStaticType($parentNode->expr);
|
||||
if ($assignedValueStaticType) {
|
||||
$propertyFetchType = $this->typeToStringResolver->resolve($assignedValueStaticType);
|
||||
}
|
||||
}
|
||||
|
||||
/** @var string $propertyName */
|
||||
$propertyName = $this->getName($node->name);
|
||||
|
||||
$fetchedLocalPropertyNameToTypes[$propertyName][] = $propertyFetchType;
|
||||
});
|
||||
|
||||
return $fetchedLocalPropertyNameToTypes;
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\CodeQuality\Tests\Rector\Class_\CompleteDynamicPropertiesRector;
|
||||
|
||||
use Rector\CodeQuality\Rector\Class_\CompleteDynamicPropertiesRector;
|
||||
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
|
||||
|
||||
final class CompleteDynamicPropertiesRectorTest extends AbstractRectorTestCase
|
||||
{
|
||||
public function test(): void
|
||||
{
|
||||
$this->doTestFiles([
|
||||
__DIR__ . '/Fixture/fixture.php.inc',
|
||||
__DIR__ . '/Fixture/multiple_types.php.inc',
|
||||
__DIR__ . '/Fixture/skip_defined.php.inc',
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getRectorClass(): string
|
||||
{
|
||||
return CompleteDynamicPropertiesRector::class;
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\CodeQuality\Tests\Rector\Class_\CompleteDynamicPropertiesRector\Fixture;
|
||||
|
||||
class SomeClass
|
||||
{
|
||||
public function set()
|
||||
{
|
||||
$this->value = 5;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\CodeQuality\Tests\Rector\Class_\CompleteDynamicPropertiesRector\Fixture;
|
||||
|
||||
class SomeClass
|
||||
{
|
||||
public int $value;
|
||||
public function set()
|
||||
{
|
||||
$this->value = 5;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\CodeQuality\Tests\Rector\Class_\CompleteDynamicPropertiesRector\Fixture;
|
||||
|
||||
class MultipleTypes
|
||||
{
|
||||
public function set()
|
||||
{
|
||||
$this->value = 5;
|
||||
|
||||
$this->value = 'hey';
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\CodeQuality\Tests\Rector\Class_\CompleteDynamicPropertiesRector\Fixture;
|
||||
|
||||
class MultipleTypes
|
||||
{
|
||||
/**
|
||||
* @var int|string
|
||||
*/
|
||||
public $value;
|
||||
public function set()
|
||||
{
|
||||
$this->value = 5;
|
||||
|
||||
$this->value = 'hey';
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\CodeQuality\Tests\Rector\Class_\CompleteDynamicPropertiesRector\Fixture;
|
||||
|
||||
class SkipDefined
|
||||
{
|
||||
private $value;
|
||||
public function set()
|
||||
{
|
||||
$this->value = 5;
|
||||
}
|
||||
}
|
@ -96,6 +96,7 @@ CODE_SAMPLE
|
||||
return null;
|
||||
}
|
||||
|
||||
// type is already set → skip
|
||||
if ($node->type !== null) {
|
||||
return null;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user