Updated Rector to commit d9cf3d4b9f427691349d4c1fbe2160241c1585b8

d9cf3d4b9f Remove removeNode() from RemoveUnusedPrivatePropertyRector (#4092)
This commit is contained in:
Tomas Votruba 2023-06-06 11:55:37 +00:00
parent be6e24fe8c
commit 687f5efeef
18 changed files with 246 additions and 362 deletions

View File

@ -180,4 +180,12 @@ final class AttributeKey
* @var string
*/
public const CHILD_OF_NODE_TYPE = 'child_of_node_type';
/**
* @var string
*/
public const IS_BEING_ASSIGNED = 'is_being_assigned';
/**
* @var string
*/
public const IS_MULTI_ASSIGN = 'is_multi_assign';
}

View File

@ -18,7 +18,12 @@ final class AssignedToNodeVisitor extends NodeVisitorAbstract implements ScopeRe
if (!$node instanceof Assign) {
return null;
}
$node->var->setAttribute(AttributeKey::IS_BEING_ASSIGNED, \true);
$node->expr->setAttribute(AttributeKey::IS_ASSIGNED_TO, \true);
if ($node->expr instanceof Assign) {
$node->var->setAttribute(AttributeKey::IS_MULTI_ASSIGN, \true);
$node->expr->setAttribute(AttributeKey::IS_MULTI_ASSIGN, \true);
}
return null;
}
}

View File

@ -35,15 +35,15 @@ final class ByRefReturnNodeVisitor extends NodeVisitorAbstract implements ScopeR
if ($stmts === null) {
return null;
}
$this->simpleCallableNodeTraverser->traverseNodesWithCallable($stmts, static function (Node $subNode) {
if ($subNode instanceof Class_ || $subNode instanceof FunctionLike) {
$this->simpleCallableNodeTraverser->traverseNodesWithCallable($stmts, static function (Node $node) {
if ($node instanceof Class_ || $node instanceof FunctionLike) {
return NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
}
if (!$subNode instanceof Return_) {
if (!$node instanceof Return_) {
return null;
}
$subNode->setAttribute(AttributeKey::IS_BYREF_RETURN, \true);
return $subNode;
$node->setAttribute(AttributeKey::IS_BYREF_RETURN, \true);
return $node;
});
return null;
}

View File

@ -7,6 +7,7 @@ use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Expression;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitorAbstract;
final class CallableNodeVisitor extends NodeVisitorAbstract
{
@ -14,6 +15,10 @@ final class CallableNodeVisitor extends NodeVisitorAbstract
* @var callable(Node): (int|Node|null)
*/
private $callable;
/**
* @var string|null
*/
private $nodeHashToRemove;
/**
* @param callable(Node $node): (int|Node|null) $callable
*/
@ -27,9 +32,24 @@ final class CallableNodeVisitor extends NodeVisitorAbstract
$callable = $this->callable;
/** @var int|Node|null $newNode */
$newNode = $callable($node);
if ($newNode === NodeTraverser::REMOVE_NODE) {
$this->nodeHashToRemove = \spl_object_hash($originalNode);
return $originalNode;
}
if ($originalNode instanceof Stmt && $newNode instanceof Expr) {
return new Expression($newNode);
}
return $newNode;
}
/**
* @return int|\PhpParser\Node
*/
public function leaveNode(Node $node)
{
if ($this->nodeHashToRemove === \spl_object_hash($node)) {
$this->nodeHashToRemove = null;
return NodeTraverser::REMOVE_NODE;
}
return $node;
}
}

View File

@ -0,0 +1,52 @@
<?php
declare (strict_types=1);
namespace Rector\DeadCode\NodeAnalyzer;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\StaticPropertyFetch;
use PhpParser\Node\Stmt\Class_;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\NodeTypeResolver\Node\AttributeKey;
final class PropertyWriteonlyAnalyzer
{
/**
* @readonly
* @var \Rector\Core\PhpParser\Node\BetterNodeFinder
*/
private $betterNodeFinder;
public function __construct(BetterNodeFinder $betterNodeFinder)
{
$this->betterNodeFinder = $betterNodeFinder;
}
public function hasClassDynamicPropertyNames(Class_ $class) : bool
{
return (bool) $this->betterNodeFinder->findFirst($class, static function (Node $node) : bool {
if (!$node instanceof PropertyFetch) {
return \false;
}
// has dynamic name - could be anything
return $node->name instanceof Expr;
});
}
/**
* The property fetches are always only assigned to, nothing else
*
* @param array<PropertyFetch|StaticPropertyFetch> $propertyFetches
*/
public function arePropertyFetchesExclusivelyBeingAssignedTo(array $propertyFetches) : bool
{
foreach ($propertyFetches as $propertyFetch) {
if ((bool) $propertyFetch->getAttribute(AttributeKey::IS_MULTI_ASSIGN, \false)) {
return \false;
}
if ((bool) $propertyFetch->getAttribute(AttributeKey::IS_BEING_ASSIGNED, \false)) {
continue;
}
return \false;
}
return \true;
}
}

View File

@ -7,13 +7,14 @@ use PhpParser\Node;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\TraitUse;
use PHPStan\Analyser\Scope;
use Rector\Core\NodeManipulator\PropertyManipulator;
use Rector\Core\PhpParser\NodeFinder\PropertyFetchFinder;
use Rector\Core\Rector\AbstractScopeAwareRector;
use Rector\Core\ValueObject\MethodName;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\Core\ValueObject\Visibility;
use Rector\DeadCode\NodeAnalyzer\PropertyWriteonlyAnalyzer;
use Rector\Privatization\NodeManipulator\VisibilityManipulator;
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
@ -28,21 +29,21 @@ final class RemoveUnusedPromotedPropertyRector extends AbstractScopeAwareRector
* @var \Rector\Core\PhpParser\NodeFinder\PropertyFetchFinder
*/
private $propertyFetchFinder;
/**
* @readonly
* @var \Rector\Core\NodeManipulator\PropertyManipulator
*/
private $propertyManipulator;
/**
* @readonly
* @var \Rector\Privatization\NodeManipulator\VisibilityManipulator
*/
private $visibilityManipulator;
public function __construct(PropertyFetchFinder $propertyFetchFinder, PropertyManipulator $propertyManipulator, VisibilityManipulator $visibilityManipulator)
/**
* @readonly
* @var \Rector\DeadCode\NodeAnalyzer\PropertyWriteonlyAnalyzer
*/
private $propertyWriteonlyAnalyzer;
public function __construct(PropertyFetchFinder $propertyFetchFinder, VisibilityManipulator $visibilityManipulator, PropertyWriteonlyAnalyzer $propertyWriteonlyAnalyzer)
{
$this->propertyFetchFinder = $propertyFetchFinder;
$this->propertyManipulator = $propertyManipulator;
$this->visibilityManipulator = $visibilityManipulator;
$this->propertyWriteonlyAnalyzer = $propertyWriteonlyAnalyzer;
}
public function getRuleDefinition() : RuleDefinition
{
@ -93,8 +94,7 @@ CODE_SAMPLE
if (!$constructClassMethod instanceof ClassMethod) {
return null;
}
// is attribute? skip it
if ($node->attrGroups !== []) {
if ($this->shouldSkipClass($node)) {
return null;
}
$hasChanged = \false;
@ -103,14 +103,14 @@ CODE_SAMPLE
if (!$this->visibilityManipulator->hasVisibility($param, Visibility::PRIVATE)) {
continue;
}
if ($this->propertyManipulator->isPropertyUsedInReadContext($node, $param, $scope)) {
continue;
}
$paramName = $this->getName($param);
$propertyFetches = $this->propertyFetchFinder->findLocalPropertyFetchesByName($node, $paramName);
if ($propertyFetches !== []) {
continue;
}
if (!$this->propertyWriteonlyAnalyzer->arePropertyFetchesExclusivelyBeingAssignedTo($propertyFetches)) {
continue;
}
// is variable used? only remove property, keep param
$variable = $this->betterNodeFinder->findVariableOfName((array) $constructClassMethod->stmts, $paramName);
if ($variable instanceof Variable) {
@ -130,4 +130,16 @@ CODE_SAMPLE
{
return PhpVersionFeature::PROPERTY_PROMOTION;
}
private function shouldSkipClass(Class_ $class) : bool
{
if ($class->attrGroups !== []) {
return \true;
}
foreach ($class->stmts as $stmt) {
if ($stmt instanceof TraitUse) {
return \true;
}
}
return \false;
}
}

View File

@ -4,56 +4,43 @@ declare (strict_types=1);
namespace Rector\DeadCode\Rector\Property;
use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\TraitUse;
use PhpParser\NodeTraverser;
use PHPStan\Analyser\Scope;
use Rector\Core\Contract\Rector\AllowEmptyConfigurableRectorInterface;
use Rector\Core\NodeManipulator\PropertyManipulator;
use Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\Core\PhpParser\NodeFinder\PropertyFetchFinder;
use Rector\Core\Rector\AbstractScopeAwareRector;
use Rector\Removing\NodeManipulator\ComplexNodeRemover;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
use Rector\DeadCode\NodeAnalyzer\PropertyWriteonlyAnalyzer;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see \Rector\Tests\DeadCode\Rector\Property\RemoveUnusedPrivatePropertyRector\RemoveUnusedPrivatePropertyRectorTest
*/
final class RemoveUnusedPrivatePropertyRector extends AbstractScopeAwareRector implements AllowEmptyConfigurableRectorInterface
final class RemoveUnusedPrivatePropertyRector extends AbstractScopeAwareRector
{
/**
* @api
* @var string
* @readonly
* @var \Rector\Core\PhpParser\NodeFinder\PropertyFetchFinder
*/
public const REMOVE_ASSIGN_SIDE_EFFECT = 'remove_assign_side_effect';
/**
* Default to true, which apply remove assign even has side effect.
* Set to false will allow to skip when assign has side effect.
* @var bool
*/
private $removeAssignSideEffect = \true;
private $propertyFetchFinder;
/**
* @readonly
* @var \Rector\Core\NodeManipulator\PropertyManipulator
* @var \Rector\DeadCode\NodeAnalyzer\PropertyWriteonlyAnalyzer
*/
private $propertyManipulator;
/**
* @readonly
* @var \Rector\Removing\NodeManipulator\ComplexNodeRemover
*/
private $complexNodeRemover;
public function __construct(PropertyManipulator $propertyManipulator, ComplexNodeRemover $complexNodeRemover)
private $propertyWriteonlyAnalyzer;
public function __construct(PropertyFetchFinder $propertyFetchFinder, PropertyWriteonlyAnalyzer $propertyWriteonlyAnalyzer)
{
$this->propertyManipulator = $propertyManipulator;
$this->complexNodeRemover = $complexNodeRemover;
}
/**
* @param mixed[] $configuration
*/
public function configure(array $configuration) : void
{
$this->removeAssignSideEffect = $configuration[self::REMOVE_ASSIGN_SIDE_EFFECT] ?? (bool) \current($configuration);
$this->propertyFetchFinder = $propertyFetchFinder;
$this->propertyWriteonlyAnalyzer = $propertyWriteonlyAnalyzer;
}
public function getRuleDefinition() : RuleDefinition
{
return new RuleDefinition('Remove unused private properties', [new ConfiguredCodeSample(<<<'CODE_SAMPLE'
return new RuleDefinition('Remove unused private properties', [new CodeSample(<<<'CODE_SAMPLE'
class SomeClass
{
private $property;
@ -64,7 +51,7 @@ class SomeClass
{
}
CODE_SAMPLE
, [self::REMOVE_ASSIGN_SIDE_EFFECT => \true])]);
)]);
}
/**
* @return array<class-string<Node>>
@ -78,6 +65,9 @@ CODE_SAMPLE
*/
public function refactorWithScope(Node $node, Scope $scope) : ?Node
{
if ($this->shouldSkipClass($node)) {
return null;
}
$hasChanged = \false;
foreach ($node->stmts as $key => $stmt) {
if (!$stmt instanceof Property) {
@ -86,23 +76,76 @@ CODE_SAMPLE
if ($this->shouldSkipProperty($stmt)) {
continue;
}
if ($this->propertyManipulator->isPropertyUsedInReadContext($node, $stmt, $scope)) {
if (!$this->shouldRemoveProperty($node, $stmt)) {
continue;
}
// use different variable to avoid re-assign back $hasRemoved to false
// when already asssigned to true
$isRemoved = $this->complexNodeRemover->removePropertyAndUsages($node, $stmt, $this->removeAssignSideEffect, $scope, $key);
if ($isRemoved) {
$hasChanged = \true;
}
// remove property
unset($node->stmts[$key]);
$propertyName = $this->getName($stmt);
$this->removePropertyAssigns($node, $propertyName);
$hasChanged = \true;
}
return $hasChanged ? $node : null;
if ($hasChanged) {
return $node;
}
return null;
}
private function shouldSkipProperty(Property $property) : bool
{
// has some attribute logic
if ($property->attrGroups !== []) {
return \true;
}
if (\count($property->props) !== 1) {
return \true;
}
return !$property->isPrivate();
if (!$property->isPrivate()) {
return \true;
}
// has some possible magic
if ($property->isStatic()) {
return \true;
}
$propertyPhpDocInfo = $this->phpDocInfoFactory->createFromNode($property);
if (!$propertyPhpDocInfo instanceof PhpDocInfo) {
return \false;
}
// skip as might contain important metadata
return $propertyPhpDocInfo->hasByType(DoctrineAnnotationTagValueNode::class);
}
private function shouldRemoveProperty(Class_ $class, Property $property) : bool
{
$propertyName = $this->getName($property);
$propertyFetches = $this->propertyFetchFinder->findLocalPropertyFetchesByName($class, $propertyName);
if ($propertyFetches === []) {
return \true;
}
return $this->propertyWriteonlyAnalyzer->arePropertyFetchesExclusivelyBeingAssignedTo($propertyFetches);
}
private function shouldSkipClass(Class_ $class) : bool
{
foreach ($class->stmts as $stmt) {
// unclear what property can be used there
if ($stmt instanceof TraitUse) {
return \true;
}
}
return $this->propertyWriteonlyAnalyzer->hasClassDynamicPropertyNames($class);
}
private function removePropertyAssigns(Class_ $class, string $propertyName) : void
{
$this->traverseNodesWithCallable($class, function (Node $node) use($propertyName) : ?int {
if (!$node instanceof Expression) {
return null;
}
if (!$node->expr instanceof Assign) {
return null;
}
$assign = $node->expr;
if (!$this->propertyFetchFinder->isLocalPropertyFetchByName($assign->var, $propertyName)) {
return null;
}
return NodeTraverser::REMOVE_NODE;
});
}
}

View File

@ -6,6 +6,7 @@ namespace Rector\Php81\Rector\Property;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\Clone_;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Param;
@ -106,6 +107,10 @@ CODE_SAMPLE
public function refactorWithScope(Node $node, Scope $scope) : ?Node
{
$hasChanged = \false;
// skip "clone $this" cases, as can create unexpected write to local constructor property
if ($this->hasCloneThis($node)) {
return null;
}
foreach ($node->getMethods() as $classMethod) {
foreach ($classMethod->params as $param) {
$justChanged = $this->refactorParam($node, $classMethod, $param, $scope);
@ -227,4 +232,16 @@ CODE_SAMPLE
});
return $isAssigned;
}
private function hasCloneThis(Class_ $class) : bool
{
return (bool) $this->betterNodeFinder->findFirst($class, function (Node $node) : bool {
if (!$node instanceof Clone_) {
return \false;
}
if (!$node->expr instanceof Variable) {
return \false;
}
return $this->isName($node->expr, 'this');
});
}
}

View File

@ -3,127 +3,9 @@
declare (strict_types=1);
namespace Rector\Removing\NodeManipulator;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\StaticPropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Property;
use PHPStan\Analyser\Scope;
use Rector\Core\NodeAnalyzer\PropertyFetchAnalyzer;
use Rector\Core\PhpParser\Comparing\NodeComparator;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\Core\ValueObject\MethodName;
use Rector\DeadCode\SideEffect\SideEffectNodeDetector;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeRemoval\NodeRemover;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\PhpDocParser\NodeTraverser\SimpleCallableNodeTraverser;
final class ComplexNodeRemover
{
/**
* @readonly
* @var \Rector\NodeNameResolver\NodeNameResolver
*/
private $nodeNameResolver;
/**
* @readonly
* @var \Rector\Core\PhpParser\Node\BetterNodeFinder
*/
private $betterNodeFinder;
/**
* @readonly
* @var \Rector\NodeRemoval\NodeRemover
*/
private $nodeRemover;
/**
* @readonly
* @var \Rector\DeadCode\SideEffect\SideEffectNodeDetector
*/
private $sideEffectNodeDetector;
/**
* @readonly
* @var \Rector\PhpDocParser\NodeTraverser\SimpleCallableNodeTraverser
*/
private $simpleCallableNodeTraverser;
/**
* @readonly
* @var \Rector\Core\NodeAnalyzer\PropertyFetchAnalyzer
*/
private $propertyFetchAnalyzer;
/**
* @readonly
* @var \Rector\Core\PhpParser\Comparing\NodeComparator
*/
private $nodeComparator;
public function __construct(NodeNameResolver $nodeNameResolver, BetterNodeFinder $betterNodeFinder, NodeRemover $nodeRemover, SideEffectNodeDetector $sideEffectNodeDetector, SimpleCallableNodeTraverser $simpleCallableNodeTraverser, PropertyFetchAnalyzer $propertyFetchAnalyzer, NodeComparator $nodeComparator)
{
$this->nodeNameResolver = $nodeNameResolver;
$this->betterNodeFinder = $betterNodeFinder;
$this->nodeRemover = $nodeRemover;
$this->sideEffectNodeDetector = $sideEffectNodeDetector;
$this->simpleCallableNodeTraverser = $simpleCallableNodeTraverser;
$this->propertyFetchAnalyzer = $propertyFetchAnalyzer;
$this->nodeComparator = $nodeComparator;
}
public function removePropertyAndUsages(Class_ $class, Property $property, bool $removeAssignSideEffect, Scope $scope, int $propertyStmtKey) : bool
{
$propertyName = $this->nodeNameResolver->getName($property);
$totalPropertyFetch = $this->propertyFetchAnalyzer->countLocalPropertyFetchName($class, $propertyName);
$expressions = [];
$this->simpleCallableNodeTraverser->traverseNodesWithCallable($class->stmts, function (Node $node) use($removeAssignSideEffect, $propertyName, &$totalPropertyFetch, &$expressions, $scope) : ?Node {
// here should be checked all expr like stmts that can hold assign, e.f. if, foreach etc. etc.
if (!$node instanceof Expression) {
return null;
}
$nodeExpr = $node->expr;
// remove direct assigns
if (!$nodeExpr instanceof Assign) {
return null;
}
$assign = $nodeExpr;
// skip double assigns
if ($assign->expr instanceof Assign) {
return null;
}
$originalNode = $assign->getAttribute(AttributeKey::ORIGINAL_NODE);
if (!$this->nodeComparator->areNodesEqual($originalNode, $assign)) {
return null;
}
$propertyFetches = $this->resolvePropertyFetchFromDimFetch($assign->var);
if ($propertyFetches === []) {
return null;
}
$currentTotalPropertyFetch = $totalPropertyFetch;
foreach ($propertyFetches as $propertyFetch) {
if ($this->nodeNameResolver->isName($propertyFetch->name, $propertyName)) {
if (!$removeAssignSideEffect && $this->sideEffectNodeDetector->detect($assign->expr, $scope)) {
return null;
}
--$totalPropertyFetch;
}
}
if ($totalPropertyFetch < $currentTotalPropertyFetch) {
$expressions[] = $node;
}
return null;
});
// not all property fetch with name removed
if ($totalPropertyFetch > 0) {
return \false;
}
$this->removeConstructorDependency($class, $propertyName);
foreach ($expressions as $expression) {
$this->nodeRemover->removeNode($expression);
}
unset($class->stmts[$propertyStmtKey]);
return \true;
}
/**
* @param int[] $paramKeysToBeRemoved
* @return int[]
@ -150,93 +32,4 @@ final class ComplexNodeRemover
}
return $removedParamKeys;
}
private function removeConstructorDependency(Class_ $class, string $propertyName) : void
{
$classMethod = $class->getMethod(MethodName::CONSTRUCT);
if (!$classMethod instanceof ClassMethod) {
return;
}
$stmts = (array) $classMethod->stmts;
$paramKeysToBeRemoved = [];
foreach ($stmts as $key => $stmt) {
if (!$stmt instanceof Expression) {
continue;
}
$stmtExpr = $stmt->expr;
if (!$stmtExpr instanceof Assign) {
continue;
}
if (!$this->propertyFetchAnalyzer->isLocalPropertyFetch($stmtExpr->var)) {
continue;
}
/** @var StaticPropertyFetch|PropertyFetch $propertyFetch */
$propertyFetch = $stmtExpr->var;
if (!$this->nodeNameResolver->isName($propertyFetch, $propertyName)) {
continue;
}
unset($classMethod->stmts[$key]);
if (!$stmtExpr->expr instanceof Variable) {
continue;
}
$key = $this->resolveToBeClearedParamFromConstructor($classMethod, $stmtExpr->expr);
if (\is_int($key)) {
$paramKeysToBeRemoved[] = $key;
}
}
if ($paramKeysToBeRemoved === []) {
return;
}
$this->processRemoveParamWithKeys($classMethod, $paramKeysToBeRemoved);
}
/**
* @return StaticPropertyFetch[]|PropertyFetch[]
*/
private function resolvePropertyFetchFromDimFetch(Expr $expr) : array
{
// unwrap array dim fetch, till we get to parent too caller node
/** @var PropertyFetch[]|StaticPropertyFetch[] $propertyFetches */
$propertyFetches = [];
while ($expr instanceof ArrayDimFetch) {
$propertyFetches = $this->collectPropertyFetches($expr->dim, $propertyFetches);
$expr = $expr->var;
}
return $this->collectPropertyFetches($expr, $propertyFetches);
}
/**
* @param StaticPropertyFetch[]|PropertyFetch[] $propertyFetches
* @return PropertyFetch[]|StaticPropertyFetch[]
*/
private function collectPropertyFetches(?Expr $expr, array $propertyFetches) : array
{
if (!$expr instanceof Expr) {
return $propertyFetches;
}
if ($this->propertyFetchAnalyzer->isLocalPropertyFetch($expr)) {
/** @var StaticPropertyFetch|PropertyFetch $expr */
return \array_merge($propertyFetches, [$expr]);
}
return $propertyFetches;
}
private function resolveToBeClearedParamFromConstructor(ClassMethod $classMethod, Variable $assignedVariable) : ?int
{
// is variable used somewhere else? skip it
$variables = $this->betterNodeFinder->findInstanceOf($classMethod, Variable::class);
$paramNamedVariables = \array_filter($variables, function (Variable $variable) use($assignedVariable) : bool {
return $this->nodeNameResolver->areNamesEqual($variable, $assignedVariable);
});
// there is more than 1 use, keep it in the constructor
if (\count($paramNamedVariables) > 1) {
return null;
}
$paramName = $this->nodeNameResolver->getName($assignedVariable);
if (!\is_string($paramName)) {
return null;
}
foreach ($classMethod->params as $paramKey => $param) {
if ($this->nodeNameResolver->isName($param->var, $paramName)) {
return $paramKey;
}
}
return null;
}
}

View File

@ -19,12 +19,12 @@ final class VersionResolver
* @api
* @var string
*/
public const PACKAGE_VERSION = 'ff21394bc2448bae7cfcf7e6b4be462a4bae104f';
public const PACKAGE_VERSION = 'd9cf3d4b9f427691349d4c1fbe2160241c1585b8';
/**
* @api
* @var string
*/
public const RELEASE_DATE = '2023-06-06 11:27:10';
public const RELEASE_DATE = '2023-06-06 11:51:40';
/**
* @var int
*/

View File

@ -15,7 +15,7 @@ final class RectorKernel
/**
* @var string
*/
private const CACHE_KEY = 'v58';
private const CACHE_KEY = 'v61';
/**
* @var \Symfony\Component\DependencyInjection\ContainerInterface|null
*/

View File

@ -16,10 +16,8 @@ use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\Trait_;
use PhpParser\NodeTraverser;
use PHPStan\Type\ObjectType;
use PHPStan\Type\ThisType;
use Rector\Core\Enum\ObjectReference;
@ -28,7 +26,6 @@ use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\Core\ValueObject\MethodName;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\NodeTypeResolver;
use Rector\PhpDocParser\NodeTraverser\SimpleCallableNodeTraverser;
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
final class PropertyFetchAnalyzer
{
@ -51,22 +48,16 @@ final class PropertyFetchAnalyzer
* @var \Rector\Core\PhpParser\AstResolver
*/
private $astResolver;
/**
* @readonly
* @var \Rector\PhpDocParser\NodeTraverser\SimpleCallableNodeTraverser
*/
private $simpleCallableNodeTraverser;
/**
* @readonly
* @var \Rector\NodeTypeResolver\NodeTypeResolver
*/
private $nodeTypeResolver;
public function __construct(NodeNameResolver $nodeNameResolver, BetterNodeFinder $betterNodeFinder, AstResolver $astResolver, SimpleCallableNodeTraverser $simpleCallableNodeTraverser, NodeTypeResolver $nodeTypeResolver)
public function __construct(NodeNameResolver $nodeNameResolver, BetterNodeFinder $betterNodeFinder, AstResolver $astResolver, NodeTypeResolver $nodeTypeResolver)
{
$this->nodeNameResolver = $nodeNameResolver;
$this->betterNodeFinder = $betterNodeFinder;
$this->astResolver = $astResolver;
$this->simpleCallableNodeTraverser = $simpleCallableNodeTraverser;
$this->nodeTypeResolver = $nodeTypeResolver;
}
public function isLocalPropertyFetch(Node $node) : bool
@ -97,22 +88,6 @@ final class PropertyFetchAnalyzer
}
return $this->isLocalPropertyFetch($node);
}
public function countLocalPropertyFetchName(Class_ $class, string $propertyName) : int
{
$total = 0;
$this->simpleCallableNodeTraverser->traverseNodesWithCallable($class->getMethods(), function (Node $subNode) use($propertyName, &$total) {
// skip anonymous classes and inner function
if ($subNode instanceof Class_ || $subNode instanceof Function_) {
return NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
}
if (!$this->isLocalPropertyFetchName($subNode, $propertyName)) {
return null;
}
++$total;
return $subNode;
});
return $total;
}
public function containsLocalPropertyFetchName(Trait_ $trait, string $propertyName) : bool
{
if ($trait->getProperty($propertyName) instanceof Property) {

View File

@ -3,11 +3,9 @@
declare (strict_types=1);
namespace Rector\Core\NodeManipulator;
use RectorPrefix202306\Doctrine\ORM\Mapping\ManyToMany;
use RectorPrefix202306\Doctrine\ORM\Mapping\Table;
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\PostDec;
@ -19,7 +17,6 @@ use PhpParser\Node\Expr\StaticCall;
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 PhpParser\Node\Stmt\Trait_;
@ -55,10 +52,6 @@ final class PropertyManipulator
* @var string[]|class-string<Table>[]
*/
private const ALLOWED_NOT_READONLY_ANNOTATION_CLASS_OR_ATTRIBUTES = ['Doctrine\\ORM\\Mapping\\Entity', 'Doctrine\\ORM\\Mapping\\Table', 'Doctrine\\ORM\\Mapping\\MappedSuperclass'];
/**
* @var string[]|class-string<ManyToMany>[]
*/
private const ALLOWED_READONLY_ANNOTATION_CLASS_OR_ATTRIBUTES = ['Doctrine\\ORM\\Mapping\\Id', 'Doctrine\\ORM\\Mapping\\Column', 'Doctrine\\ORM\\Mapping\\OneToMany', 'Doctrine\\ORM\\Mapping\\ManyToMany', 'Doctrine\\ORM\\Mapping\\ManyToOne', 'Doctrine\\ORM\\Mapping\\OneToOne', 'JMS\\Serializer\\Annotation\\Type'];
/**
* @readonly
* @var \Rector\Core\NodeManipulator\AssignManipulator
@ -152,36 +145,6 @@ final class PropertyManipulator
$this->propertyFetchAnalyzer = $propertyFetchAnalyzer;
$this->multiInstanceofChecker = $multiInstanceofChecker;
}
/**
* @param \PhpParser\Node\Stmt\Property|\PhpParser\Node\Param $propertyOrPromotedParam
*/
public function isPropertyUsedInReadContext(Class_ $class, $propertyOrPromotedParam, Scope $scope) : bool
{
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($propertyOrPromotedParam);
if ($this->isAllowedReadOnly($propertyOrPromotedParam, $phpDocInfo)) {
return \true;
}
$privatePropertyFetches = $this->propertyFetchFinder->findPrivatePropertyFetches($class, $propertyOrPromotedParam);
foreach ($privatePropertyFetches as $privatePropertyFetch) {
if ($this->readWritePropertyAnalyzer->isRead($privatePropertyFetch, $scope)) {
return \true;
}
}
// has classLike $this->$variable call?
$classLike = $this->betterNodeFinder->findParentType($propertyOrPromotedParam, ClassLike::class);
if (!$classLike instanceof ClassLike) {
return \false;
}
return (bool) $this->betterNodeFinder->findFirst($classLike->stmts, function (Node $node) use($scope) : bool {
if (!$node instanceof PropertyFetch) {
return \false;
}
if (!$this->readWritePropertyAnalyzer->isRead($node, $scope)) {
return \false;
}
return $node->name instanceof Expr;
});
}
/**
* @param \PhpParser\Node\Stmt\Property|\PhpParser\Node\Param $propertyOrParam
*/
@ -247,16 +210,6 @@ final class PropertyManipulator
}
return \false;
}
/**
* @param \PhpParser\Node\Stmt\Property|\PhpParser\Node\Param $propertyOrPromotedParam
*/
private function isAllowedReadOnly($propertyOrPromotedParam, PhpDocInfo $phpDocInfo) : bool
{
if ($phpDocInfo->hasByAnnotationClasses(self::ALLOWED_READONLY_ANNOTATION_CLASS_OR_ATTRIBUTES)) {
return \true;
}
return $this->phpAttributeAnalyzer->hasPhpAttributes($propertyOrPromotedParam, self::ALLOWED_READONLY_ANNOTATION_CLASS_OR_ATTRIBUTES);
}
private function isPropertyAssignedOnlyInConstructor(Class_ $class, string $propertyName, ?ClassMethod $classMethod) : bool
{
if (!$classMethod instanceof ClassMethod) {

View File

@ -3,6 +3,7 @@
declare (strict_types=1);
namespace Rector\Core\PhpParser\NodeFinder;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\PropertyFetch;
@ -21,10 +22,6 @@ use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\NodeTypeResolver;
final class PropertyFetchFinder
{
/**
* @var string
*/
private const THIS = 'this';
/**
* @readonly
* @var \Rector\Core\PhpParser\Node\BetterNodeFinder
@ -65,7 +62,7 @@ final class PropertyFetchFinder
$this->propertyFetchAnalyzer = $propertyFetchAnalyzer;
}
/**
* @return PropertyFetch[]|StaticPropertyFetch[]
* @return array<PropertyFetch|StaticPropertyFetch>
* @param \PhpParser\Node\Stmt\Property|\PhpParser\Node\Param $propertyOrPromotedParam
*/
public function findPrivatePropertyFetches(Class_ $class, $propertyOrPromotedParam) : array
@ -120,6 +117,16 @@ final class PropertyFetchFinder
}
return $propertyArrayDimFetches;
}
public function isLocalPropertyFetchByName(Expr $expr, string $propertyName) : bool
{
if (!$expr instanceof PropertyFetch) {
return \false;
}
if (!$this->nodeNameResolver->isName($expr->name, $propertyName)) {
return \false;
}
return $this->nodeNameResolver->isName($expr->var, 'this');
}
/**
* @param Stmt[] $stmts
* @return PropertyFetch[]|StaticPropertyFetch[]
@ -162,12 +169,9 @@ final class PropertyFetchFinder
{
// early check if property fetch name is not equals with property name
// so next check is check var name and var type only
if (!$this->nodeNameResolver->isName($propertyFetch->name, $propertyName)) {
if (!$this->isLocalPropertyFetchByName($propertyFetch, $propertyName)) {
return \false;
}
if ($this->nodeNameResolver->isName($propertyFetch->var, self::THIS)) {
return \true;
}
$propertyFetchVarType = $this->nodeTypeResolver->getType($propertyFetch->var);
if (!$propertyFetchVarType instanceof TypeWithClassName) {
return \false;

2
vendor/autoload.php vendored
View File

@ -22,4 +22,4 @@ if (PHP_VERSION_ID < 50600) {
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit661a48a1090125ae9f8625adda34402d::getLoader();
return ComposerAutoloaderInit0a78a899db7d2a4fb6eee1e8ee40bcee::getLoader();

View File

@ -1561,6 +1561,7 @@ return array(
'Rector\\DeadCode\\NodeAnalyzer\\ExprUsedInNodeAnalyzer' => $baseDir . '/rules/DeadCode/NodeAnalyzer/ExprUsedInNodeAnalyzer.php',
'Rector\\DeadCode\\NodeAnalyzer\\IsClassMethodUsedAnalyzer' => $baseDir . '/rules/DeadCode/NodeAnalyzer/IsClassMethodUsedAnalyzer.php',
'Rector\\DeadCode\\NodeAnalyzer\\JustPropertyFetchVariableAssignMatcher' => $baseDir . '/rules/DeadCode/NodeAnalyzer/JustPropertyFetchVariableAssignMatcher.php',
'Rector\\DeadCode\\NodeAnalyzer\\PropertyWriteonlyAnalyzer' => $baseDir . '/rules/DeadCode/NodeAnalyzer/PropertyWriteonlyAnalyzer.php',
'Rector\\DeadCode\\NodeAnalyzer\\UsedVariableNameAnalyzer' => $baseDir . '/rules/DeadCode/NodeAnalyzer/UsedVariableNameAnalyzer.php',
'Rector\\DeadCode\\NodeCollector\\UnusedParameterResolver' => $baseDir . '/rules/DeadCode/NodeCollector/UnusedParameterResolver.php',
'Rector\\DeadCode\\NodeManipulator\\ControllerClassMethodManipulator' => $baseDir . '/rules/DeadCode/NodeManipulator/ControllerClassMethodManipulator.php',

View File

@ -2,7 +2,7 @@
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit661a48a1090125ae9f8625adda34402d
class ComposerAutoloaderInit0a78a899db7d2a4fb6eee1e8ee40bcee
{
private static $loader;
@ -22,17 +22,17 @@ class ComposerAutoloaderInit661a48a1090125ae9f8625adda34402d
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInit661a48a1090125ae9f8625adda34402d', 'loadClassLoader'), true, true);
spl_autoload_register(array('ComposerAutoloaderInit0a78a899db7d2a4fb6eee1e8ee40bcee', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInit661a48a1090125ae9f8625adda34402d', 'loadClassLoader'));
spl_autoload_unregister(array('ComposerAutoloaderInit0a78a899db7d2a4fb6eee1e8ee40bcee', 'loadClassLoader'));
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit661a48a1090125ae9f8625adda34402d::getInitializer($loader));
call_user_func(\Composer\Autoload\ComposerStaticInit0a78a899db7d2a4fb6eee1e8ee40bcee::getInitializer($loader));
$loader->setClassMapAuthoritative(true);
$loader->register(true);
$filesToLoad = \Composer\Autoload\ComposerStaticInit661a48a1090125ae9f8625adda34402d::$files;
$filesToLoad = \Composer\Autoload\ComposerStaticInit0a78a899db7d2a4fb6eee1e8ee40bcee::$files;
$requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;

View File

@ -4,7 +4,7 @@
namespace Composer\Autoload;
class ComposerStaticInit661a48a1090125ae9f8625adda34402d
class ComposerStaticInit0a78a899db7d2a4fb6eee1e8ee40bcee
{
public static $files = array (
'ad155f8f1cf0d418fe49e248db8c661b' => __DIR__ . '/..' . '/react/promise/src/functions_include.php',
@ -1803,6 +1803,7 @@ class ComposerStaticInit661a48a1090125ae9f8625adda34402d
'Rector\\DeadCode\\NodeAnalyzer\\ExprUsedInNodeAnalyzer' => __DIR__ . '/../..' . '/rules/DeadCode/NodeAnalyzer/ExprUsedInNodeAnalyzer.php',
'Rector\\DeadCode\\NodeAnalyzer\\IsClassMethodUsedAnalyzer' => __DIR__ . '/../..' . '/rules/DeadCode/NodeAnalyzer/IsClassMethodUsedAnalyzer.php',
'Rector\\DeadCode\\NodeAnalyzer\\JustPropertyFetchVariableAssignMatcher' => __DIR__ . '/../..' . '/rules/DeadCode/NodeAnalyzer/JustPropertyFetchVariableAssignMatcher.php',
'Rector\\DeadCode\\NodeAnalyzer\\PropertyWriteonlyAnalyzer' => __DIR__ . '/../..' . '/rules/DeadCode/NodeAnalyzer/PropertyWriteonlyAnalyzer.php',
'Rector\\DeadCode\\NodeAnalyzer\\UsedVariableNameAnalyzer' => __DIR__ . '/../..' . '/rules/DeadCode/NodeAnalyzer/UsedVariableNameAnalyzer.php',
'Rector\\DeadCode\\NodeCollector\\UnusedParameterResolver' => __DIR__ . '/../..' . '/rules/DeadCode/NodeCollector/UnusedParameterResolver.php',
'Rector\\DeadCode\\NodeManipulator\\ControllerClassMethodManipulator' => __DIR__ . '/../..' . '/rules/DeadCode/NodeManipulator/ControllerClassMethodManipulator.php',
@ -3048,9 +3049,9 @@ class ComposerStaticInit661a48a1090125ae9f8625adda34402d
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit661a48a1090125ae9f8625adda34402d::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit661a48a1090125ae9f8625adda34402d::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInit661a48a1090125ae9f8625adda34402d::$classMap;
$loader->prefixLengthsPsr4 = ComposerStaticInit0a78a899db7d2a4fb6eee1e8ee40bcee::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit0a78a899db7d2a4fb6eee1e8ee40bcee::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInit0a78a899db7d2a4fb6eee1e8ee40bcee::$classMap;
}, null, ClassLoader::class);
}