[PHP 8.0] Add ClassPropertyAssignToConstructorPromotionRector

This commit is contained in:
TomasVotruba 2020-06-08 11:28:03 +02:00
parent 7bca3f8591
commit dec0975c22
11 changed files with 392 additions and 5 deletions

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
use Rector\Php80\Rector\Catch_\RemoveUnusedVariableInCatchRector;
use Rector\Php80\Rector\Class_\AnnotationToAttributeRector;
use Rector\Php80\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector;
use Rector\Php80\Rector\Class_\StringableForToStringRector;
use Rector\Php80\Rector\FuncCall\ClassOnObjectRector;
use Rector\Php80\Rector\FuncCall\TokenGetAllToObjectRector;
@ -36,4 +37,6 @@ return static function (ContainerConfigurator $containerConfigurator): void {
$services->set(TokenGetAllToObjectRector::class);
$services->set(RemoveUnusedVariableInCatchRector::class);
$services->set(ClassPropertyAssignToConstructorPromotionRector::class);
};

View File

@ -2543,6 +2543,7 @@ $someVariable
* `$variadic` - `/** @var bool Whether this is a variadic argument */`
* `$var` - `/** @var Expr\Variable|Expr\Error Parameter variable */`
* `$default` - `/** @var null|Expr Default value */`
* `$flags` - `/** @var int */`
<br>
### `PhpParser\Node\UnionType`

View File

@ -1,4 +1,4 @@
# All 533 Rectors Overview
# All 534 Rectors Overview
- [Projects](#projects)
- [General](#general)
@ -55,7 +55,7 @@
- [Php72](#php72) (11)
- [Php73](#php73) (10)
- [Php74](#php74) (15)
- [Php80](#php80) (10)
- [Php80](#php80) (11)
- [PhpDeglobalize](#phpdeglobalize) (1)
- [PhpSpecToPHPUnit](#phpspectophpunit) (7)
- [Polyfill](#polyfill) (2)
@ -9113,6 +9113,38 @@ Change get_class($object) to faster `$object::class`
<br><br>
### `ClassPropertyAssignToConstructorPromotionRector`
- class: [`Rector\Php80\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector`](/../master/rules/php80/src/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector.php)
- [test fixtures](/../master/rules/php80/tests/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture)
Change simple property init and assign to constructor promotion
```diff
class SomeClass
{
- public float $x;
- public float $y;
- public float $z;
-
public function __construct(
- float $x = 0.0,
- float $y = 0.0,
- float $z = 0.0
- ) {
- $this->x = $x;
- $this->y = $y;
- $this->z = $z;
- }
+ public float $x = 0.0,
+ public float $y = 0.0,
+ public float $z = 0.0,
+ ) {}
}
```
<br><br>
### `GetDebugTypeRector`
- class: [`Rector\Php80\Rector\Ternary\GetDebugTypeRector`](/../master/rules/php80/src/Rector/Ternary/GetDebugTypeRector.php)

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Rector\Php71;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\BinaryOp\BooleanOr;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\Instanceof_;
@ -47,12 +48,12 @@ final class IsArrayAndDualCheckToAble
/** @var FuncCall $funcCallNode */
[$instanceOfNode, $funcCallNode] = $matchedNodes;
$instanceOfNodeClass = $instanceOfNode->class;
if (! $instanceOfNodeClass instanceof Name) {
$instanceOfClass = $instanceOfNode->class;
if ($instanceOfClass instanceof Expr) {
return null;
}
if ((string) $instanceOfNodeClass !== $type) {
if ((string) $instanceOfClass !== $type) {
return null;
}

View File

@ -0,0 +1,177 @@
<?php
declare(strict_types=1);
namespace Rector\Php80\Rector\Class_;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Property;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\Php80\ValueObject\PromotionCandidate;
/**
* @see https://wiki.php.net/rfc/constructor_promotion
* @see https://github.com/php/php-src/pull/5291
*
* @see \Rector\Php80\Tests\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector\ClassPropertyAssignToConstructorPromotionRectorTest
*/
final class ClassPropertyAssignToConstructorPromotionRector extends AbstractRector
{
/**
* @var PromotionCandidate[]
*/
private $promotionCandidates = [];
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Change simple property init and assign to constructor promotion', [
new CodeSample(
<<<'PHP'
class SomeClass
{
public float $x;
public float $y;
public float $z;
public function __construct(
float $x = 0.0,
float $y = 0.0,
float $z = 0.0
) {
$this->x = $x;
$this->y = $y;
$this->z = $z;
}
}
PHP
,
<<<'PHP'
class SomeClass
{
public function __construct(
public float $x = 0.0,
public float $y = 0.0,
public float $z = 0.0,
) {}
}
PHP
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Class_::class];
}
/**
* @param Class_ $node
*/
public function refactor(Node $node): ?Node
{
$promotionCandidates = $this->collectPromotionCandidatesFromClass($node);
if ($promotionCandidates === []) {
return null;
}
foreach ($this->promotionCandidates as $promotionCandidate) {
$this->removeNode($promotionCandidate->getProperty());
$this->removeNode($promotionCandidate->getAssign());
$property = $promotionCandidate->getProperty();
$param = $promotionCandidate->getParam();
// property name has higher priority
$param->var->name = $property->props[0]->name;
// @todo add visibility - needs https://github.com/nikic/PHP-Parser/pull/667
$param->flags = $property->flags;
}
return $node;
}
private function matchClassMethodParamByAssignedVariable(
ClassMethod $classMethod,
Expr $assignedExpr
): ?Param {
foreach ($classMethod->params as $param) {
if (! $this->areNodesEqual($assignedExpr, $param->var)) {
continue;
}
return $param;
}
return null;
}
private function collectPromotionCandidate(Property $property, ClassMethod $constructClassMethod): void
{
$onlyProperty = $property->props[0];
$propertyName = $this->getName($onlyProperty);
// match property name to assign in constructor
foreach ((array) $constructClassMethod->stmts as $stmt) {
if ($stmt instanceof Expression) {
$stmt = $stmt->expr;
}
if (! $stmt instanceof Assign) {
continue;
}
$assign = $stmt;
if (! $this->isLocalPropertyFetchNamed($assign->var, $propertyName)) {
continue;
}
// 1. is param
// @todo 2. is default value
$assignedExpr = $assign->expr;
$matchedParam = $this->matchClassMethodParamByAssignedVariable($constructClassMethod, $assignedExpr);
if ($matchedParam === null) {
continue;
}
$this->promotionCandidates[] = new PromotionCandidate($property, $assign, $matchedParam);
}
}
/**
* @return PromotionCandidate[]
*/
private function collectPromotionCandidatesFromClass(Class_ $class): array
{
$constructClassMethod = $class->getMethod('__construct');
if ($constructClassMethod === null) {
return [];
}
$this->promotionCandidates = [];
foreach ($class->getProperties() as $property) {
if (count($property->props) !== 1) {
continue;
}
$this->collectPromotionCandidate($property, $constructClassMethod);
}
return $this->promotionCandidates;
}
}

View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace Rector\Php80\ValueObject;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\Property;
final class PromotionCandidate
{
/**
* @var Property
*/
private $property;
/**
* @var Assign
*/
private $assign;
/**
* @var Param
*/
private $param;
public function __construct(Property $property, Assign $assign, Param $param)
{
$this->property = $property;
$this->assign = $assign;
$this->param = $param;
}
public function getProperty(): Property
{
return $this->property;
}
public function getAssign(): Assign
{
return $this->assign;
}
public function getParam(): Param
{
return $this->param;
}
}

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Rector\Php80\Tests\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector;
use Iterator;
use Rector\Core\Testing\PHPUnit\AbstractRectorTestCase;
use Rector\Php80\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector;
use Symplify\SmartFileSystem\SmartFileInfo;
final class ClassPropertyAssignToConstructorPromotionRectorTest extends AbstractRectorTestCase
{
/**
* @requires PHP >= 7.4
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
protected function getRectorClass(): string
{
return ClassPropertyAssignToConstructorPromotionRector::class;
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace Rector\Php80\Tests\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector\Fixture;
class SomeClass
{
public float $x;
public float $y;
public float $z;
public function __construct(
float $x = 0.0,
float $y = 0.0,
float $z = 0.0
) {
$this->x = $x;
$this->y = $y;
$this->z = $z;
}
}
?>
-----
<?php
namespace Rector\Php80\Tests\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector\Fixture;
class SomeClass
{
public function __construct(public float $x = 0.0, public float $y = 0.0, public float $z = 0.0)
{
}
}
?>

View File

@ -0,0 +1,31 @@
<?php
namespace Rector\Php80\Tests\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector\Fixture;
class MatchChangedVariable
{
/**
* @var float
*/
public $x;
public function __construct(float $a = 0.0)
{
$this->x = $a;
}
}
?>
-----
<?php
namespace Rector\Php80\Tests\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector\Fixture;
class MatchChangedVariable
{
public function __construct(public float $x = 0.0)
{
}
}
?>

View File

@ -0,0 +1,13 @@
<?php
namespace Rector\Php80\Tests\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector\Fixture;
class SkipNonConstructor
{
public $x;
public function nonConstructor(float $x = 0.0)
{
$this->x = $x;
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace Rector\Php80\Tests\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector\Fixture;
class SkipStatic
{
public static $x;
public function __construct(float $x = 0.0)
{
self::$x = $x;
}
}