From 687f5efeef709ffb301f72814ff50580bfc07ea1 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Tue, 6 Jun 2023 11:55:37 +0000 Subject: [PATCH] Updated Rector to commit d9cf3d4b9f427691349d4c1fbe2160241c1585b8 https://github.com/rectorphp/rector-src/commit/d9cf3d4b9f427691349d4c1fbe2160241c1585b8 Remove removeNode() from RemoveUnusedPrivatePropertyRector (#4092) --- .../NodeTypeResolver/Node/AttributeKey.php | 8 + .../NodeVisitor/AssignedToNodeVisitor.php | 5 + .../NodeVisitor/ByRefReturnNodeVisitor.php | 10 +- .../NodeVisitor/CallableNodeVisitor.php | 20 ++ .../PropertyWriteonlyAnalyzer.php | 52 +++++ .../RemoveUnusedPromotedPropertyRector.php | 38 ++-- .../RemoveUnusedPrivatePropertyRector.php | 127 +++++++---- .../Property/ReadOnlyPropertyRector.php | 17 ++ .../NodeManipulator/ComplexNodeRemover.php | 207 ------------------ src/Application/VersionResolver.php | 4 +- src/Kernel/RectorKernel.php | 2 +- src/NodeAnalyzer/PropertyFetchAnalyzer.php | 27 +-- src/NodeManipulator/PropertyManipulator.php | 47 ---- .../NodeFinder/PropertyFetchFinder.php | 22 +- vendor/autoload.php | 2 +- vendor/composer/autoload_classmap.php | 1 + vendor/composer/autoload_real.php | 10 +- vendor/composer/autoload_static.php | 9 +- 18 files changed, 246 insertions(+), 362 deletions(-) create mode 100644 rules/DeadCode/NodeAnalyzer/PropertyWriteonlyAnalyzer.php diff --git a/packages/NodeTypeResolver/Node/AttributeKey.php b/packages/NodeTypeResolver/Node/AttributeKey.php index acc61158737..cb25d366726 100644 --- a/packages/NodeTypeResolver/Node/AttributeKey.php +++ b/packages/NodeTypeResolver/Node/AttributeKey.php @@ -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'; } diff --git a/packages/NodeTypeResolver/PHPStan/Scope/NodeVisitor/AssignedToNodeVisitor.php b/packages/NodeTypeResolver/PHPStan/Scope/NodeVisitor/AssignedToNodeVisitor.php index a7036d0425f..96140a71d65 100644 --- a/packages/NodeTypeResolver/PHPStan/Scope/NodeVisitor/AssignedToNodeVisitor.php +++ b/packages/NodeTypeResolver/PHPStan/Scope/NodeVisitor/AssignedToNodeVisitor.php @@ -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; } } diff --git a/packages/NodeTypeResolver/PHPStan/Scope/NodeVisitor/ByRefReturnNodeVisitor.php b/packages/NodeTypeResolver/PHPStan/Scope/NodeVisitor/ByRefReturnNodeVisitor.php index d937ec33ec7..e1917ec753e 100644 --- a/packages/NodeTypeResolver/PHPStan/Scope/NodeVisitor/ByRefReturnNodeVisitor.php +++ b/packages/NodeTypeResolver/PHPStan/Scope/NodeVisitor/ByRefReturnNodeVisitor.php @@ -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; } diff --git a/packages/PhpDocParser/NodeVisitor/CallableNodeVisitor.php b/packages/PhpDocParser/NodeVisitor/CallableNodeVisitor.php index b90e0dec229..32e8f68cd70 100644 --- a/packages/PhpDocParser/NodeVisitor/CallableNodeVisitor.php +++ b/packages/PhpDocParser/NodeVisitor/CallableNodeVisitor.php @@ -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; + } } diff --git a/rules/DeadCode/NodeAnalyzer/PropertyWriteonlyAnalyzer.php b/rules/DeadCode/NodeAnalyzer/PropertyWriteonlyAnalyzer.php new file mode 100644 index 00000000000..31b0c56441f --- /dev/null +++ b/rules/DeadCode/NodeAnalyzer/PropertyWriteonlyAnalyzer.php @@ -0,0 +1,52 @@ +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 $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; + } +} diff --git a/rules/DeadCode/Rector/ClassMethod/RemoveUnusedPromotedPropertyRector.php b/rules/DeadCode/Rector/ClassMethod/RemoveUnusedPromotedPropertyRector.php index cacd438be0d..385ba1b0380 100644 --- a/rules/DeadCode/Rector/ClassMethod/RemoveUnusedPromotedPropertyRector.php +++ b/rules/DeadCode/Rector/ClassMethod/RemoveUnusedPromotedPropertyRector.php @@ -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; + } } diff --git a/rules/DeadCode/Rector/Property/RemoveUnusedPrivatePropertyRector.php b/rules/DeadCode/Rector/Property/RemoveUnusedPrivatePropertyRector.php index 747cd976cee..095f36a99d4 100644 --- a/rules/DeadCode/Rector/Property/RemoveUnusedPrivatePropertyRector.php +++ b/rules/DeadCode/Rector/Property/RemoveUnusedPrivatePropertyRector.php @@ -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> @@ -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; + }); } } diff --git a/rules/Php81/Rector/Property/ReadOnlyPropertyRector.php b/rules/Php81/Rector/Property/ReadOnlyPropertyRector.php index 7e1baadbb4f..2f949ca1b65 100644 --- a/rules/Php81/Rector/Property/ReadOnlyPropertyRector.php +++ b/rules/Php81/Rector/Property/ReadOnlyPropertyRector.php @@ -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'); + }); + } } diff --git a/rules/Removing/NodeManipulator/ComplexNodeRemover.php b/rules/Removing/NodeManipulator/ComplexNodeRemover.php index f3f08193d0c..ecee231dbd3 100644 --- a/rules/Removing/NodeManipulator/ComplexNodeRemover.php +++ b/rules/Removing/NodeManipulator/ComplexNodeRemover.php @@ -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; - } } diff --git a/src/Application/VersionResolver.php b/src/Application/VersionResolver.php index 979d84b04e7..ad1604a3625 100644 --- a/src/Application/VersionResolver.php +++ b/src/Application/VersionResolver.php @@ -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 */ diff --git a/src/Kernel/RectorKernel.php b/src/Kernel/RectorKernel.php index a959cd036e0..6a7e24d78fd 100644 --- a/src/Kernel/RectorKernel.php +++ b/src/Kernel/RectorKernel.php @@ -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 */ diff --git a/src/NodeAnalyzer/PropertyFetchAnalyzer.php b/src/NodeAnalyzer/PropertyFetchAnalyzer.php index 4fa85fef409..e708b8d1cca 100644 --- a/src/NodeAnalyzer/PropertyFetchAnalyzer.php +++ b/src/NodeAnalyzer/PropertyFetchAnalyzer.php @@ -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) { diff --git a/src/NodeManipulator/PropertyManipulator.php b/src/NodeManipulator/PropertyManipulator.php index b6b9483a2a6..efd7b026bc5 100644 --- a/src/NodeManipulator/PropertyManipulator.php +++ b/src/NodeManipulator/PropertyManipulator.php @@ -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[] */ 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[] - */ - 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) { diff --git a/src/PhpParser/NodeFinder/PropertyFetchFinder.php b/src/PhpParser/NodeFinder/PropertyFetchFinder.php index 0946373a9d7..35bcf2c9dc6 100644 --- a/src/PhpParser/NodeFinder/PropertyFetchFinder.php +++ b/src/PhpParser/NodeFinder/PropertyFetchFinder.php @@ -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 * @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; diff --git a/vendor/autoload.php b/vendor/autoload.php index 25fd15e8ca2..768b7303415 100644 --- a/vendor/autoload.php +++ b/vendor/autoload.php @@ -22,4 +22,4 @@ if (PHP_VERSION_ID < 50600) { require_once __DIR__ . '/composer/autoload_real.php'; -return ComposerAutoloaderInit661a48a1090125ae9f8625adda34402d::getLoader(); +return ComposerAutoloaderInit0a78a899db7d2a4fb6eee1e8ee40bcee::getLoader(); diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php index da645d4caf5..582e03743a7 100644 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -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', diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php index cd1676c68fc..5738a4e6d57 100644 --- a/vendor/composer/autoload_real.php +++ b/vendor/composer/autoload_real.php @@ -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; diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index ab7a4f8f5db..0cc7279729f 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -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); }