Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DeadCode] Skip clone and new self on RemoveUnusedPrivatePropertyRector #1215

Merged
merged 11 commits into from
Nov 12, 2021
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace Rector\Tests\DeadCode\Rector\Property\RemoveUnusedPrivatePropertyRector\Fixture;

class SkipNewSameClassNameUsage
{
private $var;

public function __construct($var)
{
$this->var = $var;
}

public function run($var)
{
$obj = new SkipNewSameClassNameUsage($var);
echo $obj->var;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace Rector\Tests\DeadCode\Rector\Property\RemoveUnusedPrivatePropertyRector\Fixture;

class SkipCloneUsage
{
private $var;

public function __construct($var)
{
$this->var = $var;
}

public function run()
{
$obj = clone $this;
echo $obj->var;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Rector\Tests\DeadCode\Rector\Property\RemoveUnusedPrivatePropertyRector\Fixture;

class SkipNewSelfUsage
{
private $var;
private static $var2;

public function __construct($var, $var2)
{
$this->var = $var;
self::$var2 = $var2;
}

public function run()
{
$obj = new self('a', 'b');
echo $obj->var;
echo $obj::$var2;
}
}
157 changes: 119 additions & 38 deletions rules/Removing/NodeManipulator/ComplexNodeRemover.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,18 @@

use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\Clone_;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\StaticPropertyFetch;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Property;
use PHPStan\Type\ObjectType;
use PHPStan\Type\ThisType;
use Rector\Core\NodeAnalyzer\PropertyFetchAnalyzer;
use Rector\Core\PhpParser\Comparing\NodeComparator;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\Core\PhpParser\NodeFinder\PropertyFetchFinder;
Expand All @@ -20,7 +26,7 @@
use Rector\NodeRemoval\AssignRemover;
use Rector\NodeRemoval\NodeRemover;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\PostRector\Collector\NodesToRemoveCollector;
use Rector\NodeTypeResolver\NodeTypeResolver;

final class ComplexNodeRemover
{
Expand All @@ -30,8 +36,9 @@ public function __construct(
private NodeNameResolver $nodeNameResolver,
private BetterNodeFinder $betterNodeFinder,
private NodeRemover $nodeRemover,
private NodesToRemoveCollector $nodesToRemoveCollector,
private NodeComparator $nodeComparator
private NodeComparator $nodeComparator,
private NodeTypeResolver $nodeTypeResolver,
private PropertyFetchAnalyzer $propertyFetchAnalyzer
) {
}

Expand All @@ -43,9 +50,7 @@ public function removePropertyAndUsages(Property $property, array $classMethodNa
$shouldKeepProperty = false;

$propertyFetches = $this->propertyFetchFinder->findPrivatePropertyFetches($property);

$assigns = [];
$propertyNames = [];
foreach ($propertyFetches as $propertyFetch) {
if ($this->shouldSkipPropertyForClassMethod($propertyFetch, $classMethodNamesToSkip)) {
$shouldKeepProperty = true;
Expand All @@ -57,46 +62,122 @@ public function removePropertyAndUsages(Property $property, array $classMethodNa
return;
}

$propertyName = (string) $this->nodeNameResolver->getName($propertyFetch);
$propertyNames[] = $propertyName;
$assigns[$propertyName][] = $assign;
$assigns[] = $assign;
}

$this->processRemovePropertyAssigns($assigns, $propertyNames);
$this->processRemovePropertyAssigns($assigns);

if ($shouldKeepProperty) {
return;
}

// remove __construct param

/** @var Property $property */
$this->nodeRemover->removeNode($property);
}

foreach ($property->props as $prop) {
if (! $this->nodesToRemoveCollector->isNodeRemoved($prop)) {
// if the property has at least one node left -> return
return;
/**
* @param Assign[] $assigns
*/
private function processRemovePropertyAssigns(array $assigns): void
{
foreach ($assigns as $assign) {
// remove assigns
$this->assignRemover->removeAssignNode($assign);
$this->removeConstructorDependency($assign);
}
}

private function isPropertyNameInNewCurrentClassNameSelfClone(string $propertyName, ?ClassLike $classLike): bool
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you extract this to own service? The logic seems quite complex

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

implemented, I moved to new service ForbiddenPropertyRemovalAnalyzer 12bf694

{
if (! $classLike instanceof ClassLike) {
return false;
}

$methods = $classLike->getMethods();
foreach ($methods as $method) {
$isInNewCurrentClassNameSelfClone = (bool) $this->betterNodeFinder->findFirst(
(array) $method->getStmts(),
function (Node $subNode) use ($classLike, $propertyName): bool {
if ($subNode instanceof New_) {
return $this->isPropertyNameUsedAfterNewOrClone($subNode, $classLike, $propertyName);
}

if ($subNode instanceof Clone_) {
return $this->isPropertyNameUsedAfterNewOrClone($subNode, $classLike, $propertyName);
}

return false;
}
);

if ($isInNewCurrentClassNameSelfClone) {
return true;
}
}

$this->nodeRemover->removeNode($property);
return false;
}

/**
* @param array<string, Assign[]> $assigns
* @param string[] $propertyNames
*/
private function processRemovePropertyAssigns(array $assigns, array $propertyNames): void
{
$propertyNames = array_unique($propertyNames);
foreach ($propertyNames as $propertyName) {
foreach ($assigns[$propertyName] as $propertyNameAssign) {
// remove assigns
$this->assignRemover->removeAssignNode($propertyNameAssign);
$this->removeConstructorDependency($propertyNameAssign);
private function isFoundAfterCloneOrNew(
ObjectType $objectType,
Clone_|New_ $expr,
Assign $parentAssign,
string $className,
string $propertyName
): bool {
if ($objectType->getClassName() !== $className) {
return false;
}

return (bool) $this->betterNodeFinder->findFirstNext($expr, function (Node $subNode) use (
$parentAssign,
$propertyName
): bool {
if (! $this->propertyFetchAnalyzer->isPropertyFetch($subNode)) {
return false;
}

/** @var PropertyFetch|StaticPropertyFetch $subNode */
$propertyFetchName = (string) $this->nodeNameResolver->getName($subNode);
if ($subNode instanceof PropertyFetch) {
if (! $this->nodeComparator->areNodesEqual($subNode->var, $parentAssign->var)) {
return false;
}

return $propertyFetchName === $propertyName;
}

if (! $this->nodeComparator->areNodesEqual($subNode->class, $parentAssign->var)) {
return false;
}

return $propertyFetchName === $propertyName;
});
}

private function isPropertyNameUsedAfterNewOrClone(
New_|Clone_ $expr,
ClassLike $classLike,
string $propertyName
): bool {
$parentAssign = $this->betterNodeFinder->findParentType($expr, Assign::class);
if (! $parentAssign instanceof Assign) {
return false;
}

$className = (string) $this->nodeNameResolver->getName($classLike);
$type = $expr instanceof New_
? $this->nodeTypeResolver->getType($expr->class)
: $this->nodeTypeResolver->getType($expr->expr);

if ($expr instanceof Clone_ && $type instanceof ThisType) {
$type = $type->getStaticObjectType();
}

if ($type instanceof ObjectType) {
return $this->isFoundAfterCloneOrNew($type, $expr, $parentAssign, $className, $propertyName);
}

return false;
}

/**
Expand Down Expand Up @@ -136,6 +217,13 @@ private function resolveAssign(PropertyFetch | StaticPropertyFetch $expr): ?Assi
return null;
}

$classLike = $this->betterNodeFinder->findParentType($expr, ClassLike::class);
$propertyName = (string) $this->nodeNameResolver->getName($expr);

if ($this->isPropertyNameInNewCurrentClassNameSelfClone($propertyName, $classLike)) {
return null;
}

return $assign;
}

Expand Down Expand Up @@ -215,14 +303,7 @@ private function processRemoveParamWithKeys(array $params, array $paramKeysToBeR

private function isExpressionVariableNotAssign(Node $node): bool
{
if ($node !== null) {
$expressionVariable = $node->getAttribute(AttributeKey::PARENT_NODE);

if (! $expressionVariable instanceof Assign) {
return true;
}
}

return false;
$expressionVariable = $node->getAttribute(AttributeKey::PARENT_NODE);
return ! $expressionVariable instanceof Assign;
}
}