mirror of https://github.com/rectorphp/rector.git
240 lines
8.5 KiB
PHP
240 lines
8.5 KiB
PHP
<?php
|
|
|
|
declare (strict_types=1);
|
|
namespace Rector\DeadCode\Rector\StmtsAwareInterface;
|
|
|
|
use PhpParser\Node;
|
|
use PhpParser\Node\Arg;
|
|
use PhpParser\Node\Expr\Assign;
|
|
use PhpParser\Node\Expr\AssignOp\Concat;
|
|
use PhpParser\Node\Expr\Closure;
|
|
use PhpParser\Node\Expr\ClosureUse;
|
|
use PhpParser\Node\Expr\FuncCall;
|
|
use PhpParser\Node\Expr\PropertyFetch;
|
|
use PhpParser\Node\Expr\Variable;
|
|
use PhpParser\Node\Stmt;
|
|
use PhpParser\Node\Stmt\Expression;
|
|
use PhpParser\Node\Stmt\While_;
|
|
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
|
|
use PHPStan\Type\ObjectType;
|
|
use Rector\Core\Contract\PhpParser\Node\StmtsAwareInterface;
|
|
use Rector\Core\Rector\AbstractRector;
|
|
use Rector\DeadCode\ValueObject\PropertyFetchToVariableAssign;
|
|
use Rector\NodeTypeResolver\Node\AttributeKey;
|
|
use Rector\ReadWrite\NodeAnalyzer\ReadExprAnalyzer;
|
|
use Rector\ReadWrite\NodeFinder\NodeUsageFinder;
|
|
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
|
|
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
|
|
/**
|
|
* @see \Rector\Tests\DeadCode\Rector\StmtsAwareInterface\RemoveJustPropertyFetchRector\RemoveJustPropertyFetchRectorTest
|
|
*/
|
|
final class RemoveJustPropertyFetchRector extends AbstractRector
|
|
{
|
|
/**
|
|
* @readonly
|
|
* @var \Rector\ReadWrite\NodeFinder\NodeUsageFinder
|
|
*/
|
|
private $nodeUsageFinder;
|
|
/**
|
|
* @readonly
|
|
* @var \Rector\ReadWrite\NodeAnalyzer\ReadExprAnalyzer
|
|
*/
|
|
private $readExprAnalyzer;
|
|
public function __construct(NodeUsageFinder $nodeUsageFinder, ReadExprAnalyzer $readExprAnalyzer)
|
|
{
|
|
$this->nodeUsageFinder = $nodeUsageFinder;
|
|
$this->readExprAnalyzer = $readExprAnalyzer;
|
|
}
|
|
public function getRuleDefinition() : RuleDefinition
|
|
{
|
|
return new RuleDefinition('Inline property fetch assign to a variable, that has no added value', [new CodeSample(<<<'CODE_SAMPLE'
|
|
final class SomeClass
|
|
{
|
|
private $name;
|
|
|
|
public function run()
|
|
{
|
|
$name = $this->name;
|
|
|
|
return $name;
|
|
}
|
|
}
|
|
CODE_SAMPLE
|
|
, <<<'CODE_SAMPLE'
|
|
final class SomeClass
|
|
{
|
|
private $name;
|
|
|
|
public function run()
|
|
{
|
|
return $this->name;
|
|
}
|
|
}
|
|
CODE_SAMPLE
|
|
)]);
|
|
}
|
|
/**
|
|
* @return array<class-string<Node>>
|
|
*/
|
|
public function getNodeTypes() : array
|
|
{
|
|
return [StmtsAwareInterface::class];
|
|
}
|
|
/**
|
|
* @param StmtsAwareInterface $node
|
|
*/
|
|
public function refactor(Node $node) : ?Node
|
|
{
|
|
$stmts = (array) $node->stmts;
|
|
if ($stmts === []) {
|
|
return null;
|
|
}
|
|
$variableUsages = [];
|
|
$currentStmtKey = null;
|
|
$variableToPropertyAssign = null;
|
|
foreach ($stmts as $key => $stmt) {
|
|
$variableToPropertyAssign = $this->matchVariableToPropertyAssign($stmt);
|
|
if (!$variableToPropertyAssign instanceof PropertyFetchToVariableAssign) {
|
|
continue;
|
|
}
|
|
$assignPhpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($stmt);
|
|
// there is a @var tag on purpose, keep the assign
|
|
if ($assignPhpDocInfo->getVarTagValueNode() instanceof VarTagValueNode) {
|
|
continue;
|
|
}
|
|
$followingStmts = \array_slice($stmts, $key + 1);
|
|
if ($this->isFollowingStatementStaticClosure($followingStmts)) {
|
|
// can not replace usages in anonymous static functions
|
|
continue;
|
|
}
|
|
if (!$this->readExprAnalyzer->isExprRead($variableToPropertyAssign->getVariable())) {
|
|
continue;
|
|
}
|
|
$variableUsages = $this->nodeUsageFinder->findVariableUsages($followingStmts, $variableToPropertyAssign->getVariable());
|
|
$currentStmtKey = $key;
|
|
break;
|
|
}
|
|
// filter out variable usages that are part of nested property fetch, or change variable
|
|
$variableUsages = $this->filterOutReferencedVariableUsages($variableUsages);
|
|
if ($variableUsages === []) {
|
|
return null;
|
|
}
|
|
if ($this->isOverridenWithAssign($variableUsages)) {
|
|
return null;
|
|
}
|
|
if (!$variableToPropertyAssign instanceof PropertyFetchToVariableAssign) {
|
|
return null;
|
|
}
|
|
/** @var int $currentStmtKey */
|
|
return $this->replaceVariablesWithPropertyFetch($node, $currentStmtKey, $variableUsages, $variableToPropertyAssign->getPropertyFetch());
|
|
}
|
|
/**
|
|
* @param Stmt[] $followingStmts
|
|
*/
|
|
private function isFollowingStatementStaticClosure(array $followingStmts) : bool
|
|
{
|
|
if ($followingStmts === []) {
|
|
return \false;
|
|
}
|
|
return $followingStmts[0] instanceof Expression && $followingStmts[0]->expr instanceof Closure && $followingStmts[0]->expr->static;
|
|
}
|
|
/**
|
|
* @param Variable[] $variableUsages
|
|
*/
|
|
private function replaceVariablesWithPropertyFetch(StmtsAwareInterface $stmtsAware, int $currentStmtsKey, array $variableUsages, PropertyFetch $propertyFetch) : StmtsAwareInterface
|
|
{
|
|
// remove assign node
|
|
unset($stmtsAware->stmts[$currentStmtsKey]);
|
|
$this->traverseNodesWithCallable($stmtsAware, function (Node $node) use($variableUsages, $propertyFetch) : ?PropertyFetch {
|
|
if (!\in_array($node, $variableUsages, \true)) {
|
|
return null;
|
|
}
|
|
$parentNode = $node->getAttribute(AttributeKey::PARENT_NODE);
|
|
if ($parentNode instanceof ClosureUse) {
|
|
// remove closure use which will be replaced by a property fetch
|
|
$this->removeNode($parentNode);
|
|
return null;
|
|
}
|
|
return $propertyFetch;
|
|
});
|
|
return $stmtsAware;
|
|
}
|
|
/**
|
|
* @param Variable[] $variableUsages
|
|
* @return Variable[]
|
|
*/
|
|
private function filterOutReferencedVariableUsages(array $variableUsages) : array
|
|
{
|
|
return \array_filter($variableUsages, function (Variable $variable) : bool {
|
|
$variableUsageParent = $variable->getAttribute(AttributeKey::PARENT_NODE);
|
|
if ($variableUsageParent instanceof Arg) {
|
|
$variableUsageParent = $variableUsageParent->getAttribute(AttributeKey::PARENT_NODE);
|
|
}
|
|
// skip nested property fetch, the assign is for purpose of named variable
|
|
if ($variableUsageParent instanceof PropertyFetch) {
|
|
return \false;
|
|
}
|
|
// skip, as assign can be used in a loop
|
|
$parentWhile = $this->betterNodeFinder->findParentType($variable, While_::class);
|
|
if ($parentWhile instanceof While_) {
|
|
return \false;
|
|
}
|
|
if (!$variableUsageParent instanceof FuncCall) {
|
|
return \true;
|
|
}
|
|
return !$this->isName($variableUsageParent, 'array_pop');
|
|
});
|
|
}
|
|
private function matchVariableToPropertyAssign(Stmt $stmt) : ?PropertyFetchToVariableAssign
|
|
{
|
|
if (!$stmt instanceof Expression) {
|
|
return null;
|
|
}
|
|
if (!$stmt->expr instanceof Assign) {
|
|
return null;
|
|
}
|
|
$assign = $stmt->expr;
|
|
if (!$assign->expr instanceof PropertyFetch) {
|
|
return null;
|
|
}
|
|
if ($this->isPropertyFetchCallerNode($assign->expr)) {
|
|
return null;
|
|
}
|
|
// keep property fetch nesting
|
|
if ($assign->expr->var instanceof PropertyFetch) {
|
|
return null;
|
|
}
|
|
if (!$assign->var instanceof Variable) {
|
|
return null;
|
|
}
|
|
return new PropertyFetchToVariableAssign($assign->var, $assign->expr);
|
|
}
|
|
private function isPropertyFetchCallerNode(PropertyFetch $propertyFetch) : bool
|
|
{
|
|
// skip nodes as mostly used with public property fetches
|
|
$propertyFetchCallerType = $this->getType($propertyFetch->var);
|
|
if (!$propertyFetchCallerType instanceof ObjectType) {
|
|
return \false;
|
|
}
|
|
$nodeObjectType = new ObjectType('PhpParser\\Node');
|
|
return $nodeObjectType->isSuperTypeOf($propertyFetchCallerType)->yes();
|
|
}
|
|
/**
|
|
* @param Variable[] $variables
|
|
*/
|
|
private function isOverridenWithAssign(array $variables) : bool
|
|
{
|
|
foreach ($variables as $variable) {
|
|
$parentNode = $variable->getAttribute(AttributeKey::PARENT_NODE);
|
|
if ($parentNode instanceof Concat && $parentNode->var === $variable) {
|
|
return \true;
|
|
}
|
|
// variable is overriden
|
|
if ($parentNode instanceof Assign && $parentNode->var === $variable) {
|
|
return \true;
|
|
}
|
|
}
|
|
return \false;
|
|
}
|
|
}
|