2020-06-08 09:28:03 +00:00
|
|
|
<?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;
|
2020-12-02 14:26:14 +00:00
|
|
|
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
|
2020-06-08 09:28:03 +00:00
|
|
|
use Rector\Core\Rector\AbstractRector;
|
2020-08-02 13:17:33 +00:00
|
|
|
use Rector\Core\ValueObject\MethodName;
|
2020-12-02 14:26:14 +00:00
|
|
|
use Rector\NodeTypeResolver\Node\AttributeKey;
|
2020-06-08 09:28:03 +00:00
|
|
|
use Rector\Php80\ValueObject\PromotionCandidate;
|
2020-11-16 17:50:38 +00:00
|
|
|
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
|
|
|
|
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
|
2020-06-08 09:28:03 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @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 = [];
|
|
|
|
|
2020-11-16 17:50:38 +00:00
|
|
|
public function getRuleDefinition(): RuleDefinition
|
2020-06-08 09:28:03 +00:00
|
|
|
{
|
2020-11-16 17:50:38 +00:00
|
|
|
return new RuleDefinition(
|
|
|
|
'Change simple property init and assign to constructor promotion',
|
|
|
|
[
|
|
|
|
new CodeSample(
|
|
|
|
<<<'CODE_SAMPLE'
|
2020-06-08 09:28:03 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2020-09-15 08:23:13 +00:00
|
|
|
CODE_SAMPLE
|
2020-06-08 09:28:03 +00:00
|
|
|
,
|
2020-11-16 17:50:38 +00:00
|
|
|
<<<'CODE_SAMPLE'
|
2020-06-08 09:28:03 +00:00
|
|
|
class SomeClass
|
|
|
|
{
|
|
|
|
public function __construct(
|
|
|
|
public float $x = 0.0,
|
|
|
|
public float $y = 0.0,
|
|
|
|
public float $z = 0.0,
|
|
|
|
) {}
|
|
|
|
}
|
2020-09-15 08:23:13 +00:00
|
|
|
CODE_SAMPLE
|
2020-11-16 17:50:38 +00:00
|
|
|
),
|
|
|
|
|
|
|
|
]);
|
2020-06-08 09:28:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @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) {
|
2020-12-02 14:26:14 +00:00
|
|
|
// does property have some useful annotations?
|
|
|
|
$property = $promotionCandidate->getProperty();
|
|
|
|
|
|
|
|
$this->removeNode($property);
|
|
|
|
|
2020-06-08 09:28:03 +00:00
|
|
|
$this->removeNode($promotionCandidate->getAssign());
|
|
|
|
|
|
|
|
$property = $promotionCandidate->getProperty();
|
|
|
|
$param = $promotionCandidate->getParam();
|
|
|
|
|
2020-12-02 14:26:14 +00:00
|
|
|
$this->decorateParamWithPropertyPhpDocInfo($property, $param);
|
|
|
|
|
2020-06-08 09:28:03 +00:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2020-08-05 20:45:36 +00:00
|
|
|
/**
|
|
|
|
* @return PromotionCandidate[]
|
|
|
|
*/
|
|
|
|
private function collectPromotionCandidatesFromClass(Class_ $class): array
|
|
|
|
{
|
|
|
|
$constructClassMethod = $class->getMethod(MethodName::CONSTRUCT);
|
|
|
|
if ($constructClassMethod === null) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->promotionCandidates = [];
|
|
|
|
|
|
|
|
foreach ($class->getProperties() as $property) {
|
2020-12-09 22:25:53 +00:00
|
|
|
if (count((array) $property->props) !== 1) {
|
2020-06-08 09:28:03 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-08-05 20:45:36 +00:00
|
|
|
$this->collectPromotionCandidate($property, $constructClassMethod);
|
2020-06-08 09:28:03 +00:00
|
|
|
}
|
|
|
|
|
2020-08-05 20:45:36 +00:00
|
|
|
return $this->promotionCandidates;
|
2020-06-08 09:28:03 +00:00
|
|
|
}
|
|
|
|
|
2020-12-02 14:26:14 +00:00
|
|
|
private function decorateParamWithPropertyPhpDocInfo(Property $property, Param $param): void
|
|
|
|
{
|
|
|
|
$propertyPhpDocInfo = $property->getAttribute(AttributeKey::PHP_DOC_INFO);
|
|
|
|
if (! $propertyPhpDocInfo instanceof PhpDocInfo) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// make sure the docblock is useful
|
|
|
|
$param->setAttribute(AttributeKey::PHP_DOC_INFO, $propertyPhpDocInfo);
|
|
|
|
}
|
|
|
|
|
2020-06-08 09:28:03 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-05 20:45:36 +00:00
|
|
|
private function matchClassMethodParamByAssignedVariable(
|
|
|
|
ClassMethod $classMethod,
|
|
|
|
Expr $assignedExpr
|
|
|
|
): ?Param {
|
|
|
|
foreach ($classMethod->params as $param) {
|
|
|
|
if (! $this->areNodesEqual($assignedExpr, $param->var)) {
|
2020-06-08 09:28:03 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-08-05 20:45:36 +00:00
|
|
|
return $param;
|
2020-06-08 09:28:03 +00:00
|
|
|
}
|
|
|
|
|
2020-08-05 20:45:36 +00:00
|
|
|
return null;
|
2020-06-08 09:28:03 +00:00
|
|
|
}
|
|
|
|
}
|