diff --git a/packages/CodeQuality/src/Rector/If_/RemoveAlwaysTrueConditionSetInConstructorRector.php b/packages/CodeQuality/src/Rector/If_/RemoveAlwaysTrueConditionSetInConstructorRector.php index e02c0715ecd6..1c35801fbde8 100644 --- a/packages/CodeQuality/src/Rector/If_/RemoveAlwaysTrueConditionSetInConstructorRector.php +++ b/packages/CodeQuality/src/Rector/If_/RemoveAlwaysTrueConditionSetInConstructorRector.php @@ -6,12 +6,10 @@ use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\Closure; use PhpParser\Node\Expr\PropertyFetch; -use PhpParser\Node\Stmt; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Expression; use PhpParser\Node\Stmt\If_; -use PhpParser\Node\Stmt\Property; use PHPStan\Type\Type; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\NodeTypeResolver\PHPStan\Type\StaticTypeAnalyzer; diff --git a/packages/CodingStyle/src/Rector/Class_/AddArrayDefaultToArrayPropertyRector.php b/packages/CodingStyle/src/Rector/Class_/AddArrayDefaultToArrayPropertyRector.php index d6abf78baf5b..8f9c1c56e951 100644 --- a/packages/CodingStyle/src/Rector/Class_/AddArrayDefaultToArrayPropertyRector.php +++ b/packages/CodingStyle/src/Rector/Class_/AddArrayDefaultToArrayPropertyRector.php @@ -17,6 +17,7 @@ use PHPStan\Type\IterableType; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator; +use Rector\PhpParser\Node\Manipulator\PropertyFetchManipulator; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\RectorDefinition; @@ -34,9 +35,17 @@ final class AddArrayDefaultToArrayPropertyRector extends AbstractRector */ private $docBlockManipulator; - public function __construct(DocBlockManipulator $docBlockManipulator) - { + /** + * @var PropertyFetchManipulator + */ + private $propertyFetchManipulator; + + public function __construct( + DocBlockManipulator $docBlockManipulator, + PropertyFetchManipulator $propertyFetchManipulator + ) { $this->docBlockManipulator = $docBlockManipulator; + $this->propertyFetchManipulator = $propertyFetchManipulator; } public function getDefinition(): RectorDefinition @@ -102,22 +111,6 @@ public function refactor(Node $node): ?Node return $node; } - /** - * @param string[] $changedProperties - */ - private function isLocalPropertyFetchOfNames(Expr $expr, array $changedProperties): bool - { - if (! $expr instanceof PropertyFetch) { - return false; - } - - if (! $this->isName($expr->var, 'this')) { - return false; - } - - return $this->isNames($expr->name, $changedProperties); - } - /** * @return string[] */ @@ -187,11 +180,15 @@ private function replaceNullComparisonOfArrayPropertiesWithArrayComparison( return null; } - if ($this->isLocalPropertyFetchOfNames($node->left, $propertyNames) && $this->isNull($node->right)) { + if ($this->propertyFetchManipulator->isLocalPropertyOfNames($node->left, $propertyNames) && $this->isNull( + $node->right + )) { $node->right = new Array_(); } - if ($this->isLocalPropertyFetchOfNames($node->right, $propertyNames) && $this->isNull($node->left)) { + if ($this->propertyFetchManipulator->isLocalPropertyOfNames($node->right, $propertyNames) && $this->isNull( + $node->left + )) { $node->left = new Array_(); } @@ -204,7 +201,7 @@ private function replaceNullComparisonOfArrayPropertiesWithArrayComparison( */ private function clearNotNullBeforeCount(Class_ $class, array $propertyNames): void { - $this->traverseNodesWithCallable($class, function (Node $node) use ($propertyNames) { + $this->traverseNodesWithCallable($class, function (Node $node) use ($propertyNames): ?Expr { if (! $node instanceof BooleanAnd) { return null; } @@ -254,11 +251,15 @@ private function isLocalPropertyOfNamesNotIdenticalToNull(Expr $expr, array $pro return false; } - if ($this->isLocalPropertyFetchOfNames($expr->left, $propertyNames) && $this->isNull($expr->right)) { + if ($this->propertyFetchManipulator->isLocalPropertyOfNames($expr->left, $propertyNames) && $this->isNull( + $expr->right + )) { return true; } - if ($this->isLocalPropertyFetchOfNames($expr->right, $propertyNames) && $this->isNull($expr->left)) { + if ($this->propertyFetchManipulator->isLocalPropertyOfNames($expr->right, $propertyNames) && $this->isNull( + $expr->left + )) { return true; } diff --git a/packages/DeadCode/src/Analyzer/SetterOnlyMethodAnalyzer.php b/packages/DeadCode/src/Analyzer/SetterOnlyMethodAnalyzer.php index 6097c5fff62d..df1682c5a213 100644 --- a/packages/DeadCode/src/Analyzer/SetterOnlyMethodAnalyzer.php +++ b/packages/DeadCode/src/Analyzer/SetterOnlyMethodAnalyzer.php @@ -3,13 +3,12 @@ namespace Rector\DeadCode\Analyzer; use PhpParser\Node; -use PhpParser\Node\Expr\Assign; -use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Expression; use Rector\DeadCode\Doctrine\DoctrineEntityManipulator; use Rector\NodeContainer\ParsedNodesByType; +use Rector\PhpParser\Node\Manipulator\AssignManipulator; use Rector\PhpParser\Node\Manipulator\ClassManipulator; use Rector\PhpParser\Node\Resolver\NameResolver; use Rector\PhpParser\NodeTraverser\CallableNodeTraverser; @@ -52,18 +51,25 @@ final class SetterOnlyMethodAnalyzer */ private $isSetterOnlyPropertiesAndMethodsByTypeAnalyzed = false; + /** + * @var AssignManipulator + */ + private $assignManipulator; + public function __construct( ParsedNodesByType $parsedNodesByType, ClassManipulator $classManipulator, NameResolver $nameResolver, CallableNodeTraverser $callableNodeTraverser, - DoctrineEntityManipulator $doctrineEntityManipulator + DoctrineEntityManipulator $doctrineEntityManipulator, + AssignManipulator $assignManipulator ) { $this->parsedNodesByType = $parsedNodesByType; $this->classManipulator = $classManipulator; $this->nameResolver = $nameResolver; $this->callableNodeTraverser = $callableNodeTraverser; $this->doctrineEntityManipulator = $doctrineEntityManipulator; + $this->assignManipulator = $assignManipulator; } /** @@ -158,29 +164,6 @@ private function isClassMethodWithSinglePropertyAssignOfNames(Node $node, array $onlyStmt = $onlyExpression->expr; - return $this->isPropertyAssignWithPropertyNames($onlyStmt, $propertyNames); - } - - /** - * Is: "$this->value = <$value>" - * - * @param string[] $propertyNames - */ - private function isPropertyAssignWithPropertyNames(Node $node, array $propertyNames): bool - { - if (! $node instanceof Assign) { - return false; - } - - if (! $node->var instanceof PropertyFetch) { - return false; - } - - $propertyFetch = $node->var; - if (! $this->nameResolver->isName($propertyFetch->var, 'this')) { - return false; - } - - return $this->nameResolver->isNames($propertyFetch->name, $propertyNames); + return $this->assignManipulator->isLocalPropertyAssignWithPropertyNames($onlyStmt, $propertyNames); } } diff --git a/packages/DeadCode/src/Rector/Assign/RemoveDoubleAssignRector.php b/packages/DeadCode/src/Rector/Assign/RemoveDoubleAssignRector.php index 66fc51dca5b6..b598fca0fd25 100644 --- a/packages/DeadCode/src/Rector/Assign/RemoveDoubleAssignRector.php +++ b/packages/DeadCode/src/Rector/Assign/RemoveDoubleAssignRector.php @@ -8,6 +8,7 @@ use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Expr\StaticCall; +use PhpParser\Node\Expr\StaticPropertyFetch; use PhpParser\Node\Expr\Variable; use PhpParser\Node\Stmt\Case_; use PhpParser\Node\Stmt\Catch_; @@ -68,7 +69,7 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - if (! $node->var instanceof Variable && ! $node->var instanceof PropertyFetch) { + if (! $node->var instanceof Variable && ! $node->var instanceof PropertyFetch && ! $node->var instanceof StaticPropertyFetch) { return null; } diff --git a/packages/DeadCode/src/Rector/Class_/RemoveSetterOnlyPropertyAndMethodCallRector.php b/packages/DeadCode/src/Rector/Class_/RemoveSetterOnlyPropertyAndMethodCallRector.php index bdd17faae813..f22d40bffcbd 100644 --- a/packages/DeadCode/src/Rector/Class_/RemoveSetterOnlyPropertyAndMethodCallRector.php +++ b/packages/DeadCode/src/Rector/Class_/RemoveSetterOnlyPropertyAndMethodCallRector.php @@ -6,11 +6,11 @@ use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\PropertyFetch; -use PhpParser\Node\Expr\Variable; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Property; use Rector\DeadCode\Analyzer\SetterOnlyMethodAnalyzer; use Rector\NodeTypeResolver\Node\AttributeKey; +use Rector\PhpParser\Node\Manipulator\AssignManipulator; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\RectorDefinition; @@ -27,9 +27,17 @@ final class RemoveSetterOnlyPropertyAndMethodCallRector extends AbstractRector */ private $setterOnlyMethodAnalyzer; - public function __construct(SetterOnlyMethodAnalyzer $setterOnlyMethodAnalyzer) - { + /** + * @var AssignManipulator + */ + private $assignManipulator; + + public function __construct( + SetterOnlyMethodAnalyzer $setterOnlyMethodAnalyzer, + AssignManipulator $assignManipulator + ) { $this->setterOnlyMethodAnalyzer = $setterOnlyMethodAnalyzer; + $this->assignManipulator = $assignManipulator; } public function getDefinition(): RectorDefinition @@ -149,7 +157,7 @@ private function processClassStmts(Node $node, array $setterOnlyPropertiesAndMet } // 2. remove class inner assigns - if ($this->isThisVariableAssign($node)) { + if ($this->assignManipulator->isLocalPropertyAssign($node)) { /** @var Assign $node */ $propertyFetch = $node->var; /** @var PropertyFetch $propertyFetch */ @@ -165,25 +173,4 @@ private function processClassStmts(Node $node, array $setterOnlyPropertiesAndMet } } } - - /** - * Checks: - * $this->x = y; - */ - private function isThisVariableAssign(Node $node): bool - { - if (! $node instanceof Assign) { - return false; - } - - if (! $node->var instanceof PropertyFetch) { - return false; - } - - if (! $node->var->var instanceof Variable) { - return false; - } - - return $this->isName($node->var->var, 'this'); - } } diff --git a/packages/MysqlToMysqli/src/Rector/Assign/MysqlAssignToMysqliRector.php b/packages/MysqlToMysqli/src/Rector/Assign/MysqlAssignToMysqliRector.php index 1dc8516809c0..c80adbc06827 100644 --- a/packages/MysqlToMysqli/src/Rector/Assign/MysqlAssignToMysqliRector.php +++ b/packages/MysqlToMysqli/src/Rector/Assign/MysqlAssignToMysqliRector.php @@ -9,6 +9,7 @@ use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\PostInc; use PhpParser\Node\Expr\PropertyFetch; +use PhpParser\Node\Expr\StaticPropertyFetch; use PhpParser\Node\Expr\Variable; use PhpParser\Node\Name; use PhpParser\Node\Scalar\LNumber; @@ -167,7 +168,7 @@ private function processFieldToFieldDirect(Assign $assign, FuncCall $funcCall): foreach ($this->fieldToFieldDirect as $funcName => $property) { if ($this->isName($funcCall, $funcName)) { $parentNode = $funcCall->getAttribute(AttributeKey::PARENT_NODE); - if ($parentNode instanceof PropertyFetch) { + if ($parentNode instanceof PropertyFetch || $parentNode instanceof StaticPropertyFetch) { continue; } diff --git a/packages/NodeTypeResolver/config/config.yaml b/packages/NodeTypeResolver/config/config.yaml index b23ead23d4b6..6fd748c9caf8 100644 --- a/packages/NodeTypeResolver/config/config.yaml +++ b/packages/NodeTypeResolver/config/config.yaml @@ -5,7 +5,8 @@ services: Rector\NodeTypeResolver\: resource: '../src' - exclude: '../src/{Contract,Php/*Info.php,PHPStanOverride/*}' + # "Type" is because the file is needed for PHPStan container only + exclude: '../src/{Contract,Php/*Info.php,PHPStanOverride/*,Type}' Rector\Php\TypeAnalyzer: ~ Rector\FileSystem\FilesFinder: ~ diff --git a/packages/NodeTypeResolver/config/phpstan/type-extensions.neon b/packages/NodeTypeResolver/config/phpstan/type-extensions.neon new file mode 100644 index 000000000000..f62eb1df9c8c --- /dev/null +++ b/packages/NodeTypeResolver/config/phpstan/type-extensions.neon @@ -0,0 +1,4 @@ +services: + - + class: Rector\NodeTypeResolver\Type\TypeExtension\StaticContainerGetDynamicMethodReturnTypeExtension + tags: [phpstan.broker.dynamicMethodReturnTypeExtension] diff --git a/packages/NodeTypeResolver/src/DependencyInjection/PHPStanServicesFactory.php b/packages/NodeTypeResolver/src/DependencyInjection/PHPStanServicesFactory.php index c42e2897f0e4..0faf88478609 100644 --- a/packages/NodeTypeResolver/src/DependencyInjection/PHPStanServicesFactory.php +++ b/packages/NodeTypeResolver/src/DependencyInjection/PHPStanServicesFactory.php @@ -27,6 +27,8 @@ public function __construct() $additionalConfigFiles[] = $phpstanPhpunitExtensionConfig; } + $additionalConfigFiles[] = __DIR__ . '/../../config/phpstan/type-extensions.neon'; + $this->container = $containerFactory->create(sys_get_temp_dir(), $additionalConfigFiles, []); } diff --git a/packages/NodeTypeResolver/src/NodeTypeResolver.php b/packages/NodeTypeResolver/src/NodeTypeResolver.php index 80dd0d110ad9..604c70fc59f4 100644 --- a/packages/NodeTypeResolver/src/NodeTypeResolver.php +++ b/packages/NodeTypeResolver/src/NodeTypeResolver.php @@ -15,6 +15,7 @@ use PhpParser\Node\Expr\New_; use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Expr\StaticCall; +use PhpParser\Node\Expr\StaticPropertyFetch; use PhpParser\Node\Expr\Variable; use PhpParser\Node\Param; use PhpParser\Node\Scalar\String_; @@ -252,7 +253,7 @@ public function isArrayType(Node $node): bool return true; } - if ($node instanceof PropertyFetch) { + if ($node instanceof PropertyFetch || $node instanceof StaticPropertyFetch) { // PHPStan false positive, when variable has type[] docblock, but default array is missing if ($this->isPropertyFetchWithArrayDefault($node) === false) { return false; @@ -527,7 +528,7 @@ private function isIntersectionArrayType(Type $nodeType): bool */ private function isPropertyFetchWithArrayDefault(Node $node): bool { - if (! $node instanceof PropertyFetch) { + if (! $node instanceof PropertyFetch && ! $node instanceof StaticPropertyFetch) { return false; } diff --git a/packages/NodeTypeResolver/src/NodeVisitor/ClassAndMethodNodeVisitor.php b/packages/NodeTypeResolver/src/NodeVisitor/ClassAndMethodNodeVisitor.php index 7bdb533660dd..8ad4d639a5ce 100644 --- a/packages/NodeTypeResolver/src/NodeVisitor/ClassAndMethodNodeVisitor.php +++ b/packages/NodeTypeResolver/src/NodeVisitor/ClassAndMethodNodeVisitor.php @@ -123,6 +123,6 @@ private function isClassAnonymous(Node $node): bool } // PHPStan polution - return Strings::startsWith($node->name->toString(), 'AnonymousClass'); + return (bool) Strings::match($node->name->toString(), '#^AnonymousClass\w+#'); } } diff --git a/packages/NodeTypeResolver/src/Type/TypeExtension/StaticContainerGetDynamicMethodReturnTypeExtension.php b/packages/NodeTypeResolver/src/Type/TypeExtension/StaticContainerGetDynamicMethodReturnTypeExtension.php new file mode 100644 index 000000000000..85533ae45957 --- /dev/null +++ b/packages/NodeTypeResolver/src/Type/TypeExtension/StaticContainerGetDynamicMethodReturnTypeExtension.php @@ -0,0 +1,40 @@ +getName() === 'get'; + } + + public function getTypeFromMethodCall( + MethodReflection $methodReflection, + MethodCall $methodCall, + Scope $scope + ): Type { + $valueType = $scope->getType($methodCall->args[0]->value); + + if ($valueType instanceof ConstantStringType) { + return new ObjectType($valueType->getValue()); + } + + throw new ShouldNotHappenException(); + } +} diff --git a/packages/PHPStan/src/Rector/Assign/PHPStormVarAnnotationRector.php b/packages/PHPStan/src/Rector/Assign/PHPStormVarAnnotationRector.php index 46b1120f7795..e1d9a306464e 100644 --- a/packages/PHPStan/src/Rector/Assign/PHPStormVarAnnotationRector.php +++ b/packages/PHPStan/src/Rector/Assign/PHPStormVarAnnotationRector.php @@ -56,6 +56,7 @@ public function refactor(Node $node): ?Node return null; } + /** @var Node|null $nextNode */ $nextNode = $expression->getAttribute(AttributeKey::NEXT_NODE); if ($nextNode === null) { return null; diff --git a/packages/PHPUnit/src/Rector/SpecificMethod/AssertIssetToSpecificMethodRector.php b/packages/PHPUnit/src/Rector/SpecificMethod/AssertIssetToSpecificMethodRector.php index 0a3c3a101f4e..08c8879310bb 100644 --- a/packages/PHPUnit/src/Rector/SpecificMethod/AssertIssetToSpecificMethodRector.php +++ b/packages/PHPUnit/src/Rector/SpecificMethod/AssertIssetToSpecificMethodRector.php @@ -85,10 +85,11 @@ public function refactor(Node $node): ?Node /** * @param MethodCall|StaticCall $node + * @param PropertyFetch $expr */ - private function refactorPropertyFetchNode(Node $node, PropertyFetch $propertyFetch): void + private function refactorPropertyFetchNode(Node $node, Node\Expr $expr): void { - $name = $this->getName($propertyFetch); + $name = $this->getName($expr); if ($name === null) { return; } @@ -101,7 +102,7 @@ private function refactorPropertyFetchNode(Node $node, PropertyFetch $propertyFe $oldArgs = $node->args; unset($oldArgs[0]); - $node->args = array_merge($this->createArgs([new String_($name), $propertyFetch->var]), $oldArgs); + $node->args = array_merge($this->createArgs([new String_($name), $expr->var]), $oldArgs); } /** diff --git a/packages/Php/src/Rector/Each/ListEachRector.php b/packages/Php/src/Rector/Each/ListEachRector.php index ecbcc5c2bc7f..9f0d7c000509 100644 --- a/packages/Php/src/Rector/Each/ListEachRector.php +++ b/packages/Php/src/Rector/Each/ListEachRector.php @@ -9,6 +9,7 @@ use PhpParser\Node\Stmt\Do_; use PhpParser\Node\Stmt\Expression; use Rector\NodeTypeResolver\Node\AttributeKey; +use Rector\PhpParser\Node\Manipulator\AssignManipulator; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\RectorDefinition; @@ -18,6 +19,16 @@ */ final class ListEachRector extends AbstractRector { + /** + * @var AssignManipulator + */ + private $assignManipulator; + + public function __construct(AssignManipulator $assignManipulator) + { + $this->assignManipulator = $assignManipulator; + } + public function getDefinition(): RectorDefinition { return new RectorDefinition( @@ -95,7 +106,7 @@ public function refactor(Node $node): ?Node private function shouldSkip(Assign $assign): bool { - if (! $this->isListToEachAssign($assign)) { + if (! $this->assignManipulator->isListToEachAssign($assign)) { return true; } @@ -130,13 +141,4 @@ private function isInsideDoWhile(Node $assignNode): bool return $parentParentNode instanceof Do_; } - - private function isListToEachAssign(Assign $assign): bool - { - if (! $assign->var instanceof List_) { - return false; - } - - return $assign->expr instanceof FuncCall && $this->isName($assign->expr, 'each'); - } } diff --git a/packages/Php/src/Rector/Each/WhileEachToForeachRector.php b/packages/Php/src/Rector/Each/WhileEachToForeachRector.php index 86708d687180..b96ec0ca182f 100644 --- a/packages/Php/src/Rector/Each/WhileEachToForeachRector.php +++ b/packages/Php/src/Rector/Each/WhileEachToForeachRector.php @@ -9,6 +9,7 @@ use PhpParser\Node\Expr\List_; use PhpParser\Node\Stmt\Foreach_; use PhpParser\Node\Stmt\While_; +use Rector\PhpParser\Node\Manipulator\AssignManipulator; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\RectorDefinition; @@ -18,6 +19,16 @@ */ final class WhileEachToForeachRector extends AbstractRector { + /** + * @var AssignManipulator + */ + private $assignManipulator; + + public function __construct(AssignManipulator $assignManipulator) + { + $this->assignManipulator = $assignManipulator; + } + public function getDefinition(): RectorDefinition { return new RectorDefinition( @@ -72,7 +83,7 @@ public function refactor(Node $node): ?Node /** @var Assign $assignNode */ $assignNode = $node->cond; - if (! $this->isListToEachAssign($assignNode)) { + if (! $this->assignManipulator->isListToEachAssign($assignNode)) { return null; } @@ -102,13 +113,4 @@ public function refactor(Node $node): ?Node return $foreachNode; } - - private function isListToEachAssign(Assign $assign): bool - { - if (! $assign->var instanceof List_) { - return false; - } - - return $assign->expr instanceof FuncCall && $this->isName($assign->expr, 'each'); - } } diff --git a/packages/TypeDeclaration/src/Rector/Property/PropertyTypeDeclarationRector.php b/packages/TypeDeclaration/src/Rector/Property/PropertyTypeDeclarationRector.php index aaa93accf5a8..c561de732535 100644 --- a/packages/TypeDeclaration/src/Rector/Property/PropertyTypeDeclarationRector.php +++ b/packages/TypeDeclaration/src/Rector/Property/PropertyTypeDeclarationRector.php @@ -69,6 +69,7 @@ public function refactor(Node $node): ?Node } $this->docBlockManipulator->changeVarTag($node, $type); + return $node; } } diff --git a/packages/TypeDeclaration/tests/Rector/Property/PropertyTypeDeclarationRector/Fixture/setter_type.php.inc b/packages/TypeDeclaration/tests/Rector/Property/PropertyTypeDeclarationRector/Fixture/setter_type.php.inc index e8fe51eb6833..e0474f192814 100644 --- a/packages/TypeDeclaration/tests/Rector/Property/PropertyTypeDeclarationRector/Fixture/setter_type.php.inc +++ b/packages/TypeDeclaration/tests/Rector/Property/PropertyTypeDeclarationRector/Fixture/setter_type.php.inc @@ -19,7 +19,7 @@ final class SetterType */ public function setName($name) { - return $this->name = $name; + $this->name = $name; } } @@ -52,7 +52,7 @@ final class SetterType */ public function setName($name) { - return $this->name = $name; + $this->name = $name; } } diff --git a/src/PhpParser/Node/BetterNodeFinder.php b/src/PhpParser/Node/BetterNodeFinder.php index 24565e17bb25..be9177de5b33 100644 --- a/src/PhpParser/Node/BetterNodeFinder.php +++ b/src/PhpParser/Node/BetterNodeFinder.php @@ -140,6 +140,7 @@ public function findClassLikes(array $nodes): array return false; } + // skip anonymous classes if ($node instanceof Class_ && $node->isAnonymous()) { return false; } diff --git a/src/PhpParser/Node/Manipulator/AssignManipulator.php b/src/PhpParser/Node/Manipulator/AssignManipulator.php new file mode 100644 index 000000000000..ac09a2447182 --- /dev/null +++ b/src/PhpParser/Node/Manipulator/AssignManipulator.php @@ -0,0 +1,102 @@ +nameResolver = $nameResolver; + } + + /** + * Checks: + * $this->x = y; + * $this->x[] = y; + */ + public function isLocalPropertyAssign(Node $node): bool + { + if (! $node instanceof Assign) { + return false; + } + + if ($node->var instanceof ArrayDimFetch) { + $potentialPropertyFetch = $node->var->var; + } else { + $potentialPropertyFetch = $node->var; + } + + return $potentialPropertyFetch instanceof PropertyFetch || $potentialPropertyFetch instanceof StaticPropertyFetch; + } + + /** + * Is: "$this->value = <$value>" + * + * @param string[] $propertyNames + */ + public function isLocalPropertyAssignWithPropertyNames(Node $node, array $propertyNames): bool + { + if (! $this->isLocalPropertyAssign($node)) { + return false; + } + + /** @var Assign $node */ + if ($node->var instanceof ArrayDimFetch) { + /** @var PropertyFetch|StaticPropertyFetch $propertyFetch */ + $propertyFetch = $node->var->var; + } else { + /** @var PropertyFetch|StaticPropertyFetch $propertyFetch */ + $propertyFetch = $node->var; + } + + return $this->nameResolver->isNames($propertyFetch, $propertyNames); + } + + /** + * Covers: + * - $this->propertyName = <$expr>; + * - self::$propertyName = <$expr>; + * - $this->propertyName[] = <$expr>; + * - self::$propertyName[] = <$expr>; + */ + public function matchPropertyAssignExpr(Assign $assign, string $propertyName): ?Expr + { + if (! $this->isLocalPropertyAssignWithPropertyNames($assign, [$propertyName])) { + return null; + } + + return $assign->expr; + } + + /** + * Matches: + * each() = [1, 2]; + */ + public function isListToEachAssign(Assign $assign): bool + { + if (! $assign->expr instanceof FuncCall) { + return false; + } + + if (! $assign->var instanceof List_) { + return false; + } + + return $this->nameResolver->isName($assign->expr, 'each'); + } +} diff --git a/src/PhpParser/Node/Manipulator/PropertyFetchManipulator.php b/src/PhpParser/Node/Manipulator/PropertyFetchManipulator.php index 3c5f8f65795a..e10ccc7a3ab6 100644 --- a/src/PhpParser/Node/Manipulator/PropertyFetchManipulator.php +++ b/src/PhpParser/Node/Manipulator/PropertyFetchManipulator.php @@ -7,7 +7,11 @@ use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Expr\Variable; +use PhpParser\Node\Param; use PhpParser\Node\Stmt\Class_; +use PhpParser\Node\Stmt\ClassMethod; +use PhpParser\NodeTraverser; +use PHPStan\Analyser\Scope; use PHPStan\Broker\Broker; use PHPStan\Type\ErrorType; use PHPStan\Type\MixedType; @@ -50,18 +54,25 @@ final class PropertyFetchManipulator */ private $callableNodeTraverser; + /** + * @var AssignManipulator + */ + private $assignManipulator; + public function __construct( NodeTypeResolver $nodeTypeResolver, Broker $broker, NameResolver $nameResolver, ClassManipulator $classManipulator, - CallableNodeTraverser $callableNodeTraverser + CallableNodeTraverser $callableNodeTraverser, + AssignManipulator $assignManipulator ) { $this->nodeTypeResolver = $nodeTypeResolver; $this->broker = $broker; $this->nameResolver = $nameResolver; $this->classManipulator = $classManipulator; $this->callableNodeTraverser = $callableNodeTraverser; + $this->assignManipulator = $assignManipulator; } public function isPropertyToSelf(PropertyFetch $propertyFetch): bool @@ -169,14 +180,11 @@ public function isVariableAssignToThisPropertyFetch(Node $node, string $variable */ public function isLocalPropertyOfNames(Expr $expr, array $propertyNames): bool { - if (! $expr instanceof PropertyFetch) { - return false; - } - if (! $this->isLocalProperty($expr)) { return false; } + /** @var PropertyFetch $expr */ return $this->nameResolver->isNames($expr->name, $propertyNames); } @@ -189,6 +197,105 @@ public function isLocalProperty(Node $node): bool return $this->nameResolver->isName($node->var, 'this'); } + public function getFirstVariableAssignedToPropertyOfName( + ClassMethod $classMethod, + string $propertyName + ): ?Variable { + $variable = null; + + $this->callableNodeTraverser->traverseNodesWithCallable((array) $classMethod->stmts, function (Node $node) use ( + $propertyName, + &$variable + ): ?int { + if (! $node instanceof Assign) { + return null; + } + + if (! $this->isLocalPropertyOfNames($node->var, [$propertyName])) { + return null; + } + + if (! $node->expr instanceof Variable) { + return null; + } + + $variable = $node->expr; + + return NodeTraverser::STOP_TRAVERSAL; + }); + + return $variable; + } + + /** + * @return Expr[] + */ + public function getExprsAssignedToPropertyName(ClassMethod $classMethod, string $propertyName): array + { + $assignedExprs = []; + + $this->callableNodeTraverser->traverseNodesWithCallable($classMethod, function (Node $node) use ( + $propertyName, + &$assignedExprs + ) { + if (! $this->assignManipulator->isLocalPropertyAssignWithPropertyNames($node, [$propertyName])) { + return null; + } + + /** @var Assign $node */ + $assignedExprs[] = $node->expr; + }); + + return $assignedExprs; + } + + /** + * In case the property name is different to param name: + * + * E.g.: + * (SomeType $anotherValue) + * $this->value = $anotherValue; + * ↓ + * (SomeType $anotherValue) + */ + public function resolveParamForPropertyFetch(ClassMethod $classMethod, string $propertyName): ?Param + { + $assignedParamName = null; + + $this->callableNodeTraverser->traverseNodesWithCallable((array) $classMethod->stmts, function (Node $node) use ( + $propertyName, + &$assignedParamName + ): ?int { + if (! $node instanceof Assign) { + return null; + } + + if (! $this->nameResolver->isName($node->var, $propertyName)) { + return null; + } + + $assignedParamName = $this->nameResolver->getName($node->expr); + + return NodeTraverser::STOP_TRAVERSAL; + }); + + /** @var string|null $assignedParamName */ + if ($assignedParamName === null) { + return null; + } + + /** @var Param $param */ + foreach ((array) $classMethod->params as $param) { + if (! $this->nameResolver->isName($param, $assignedParamName)) { + continue; + } + + return $param; + } + + return null; + } + private function hasPublicProperty(PropertyFetch $propertyFetch, string $propertyName): bool { $nodeScope = $propertyFetch->getAttribute(AttributeKey::SCOPE); diff --git a/src/PhpParser/Node/NodeFactory.php b/src/PhpParser/Node/NodeFactory.php index 2b56ced9b28e..6b919fdc3ae6 100644 --- a/src/PhpParser/Node/NodeFactory.php +++ b/src/PhpParser/Node/NodeFactory.php @@ -176,6 +176,10 @@ public function createMethodCall($variable, string $method, array $arguments): M $variable = new PropertyFetch($variable->var, $variable->name); } + if ($variable instanceof Expr\StaticPropertyFetch) { + $variable = new Expr\StaticPropertyFetch($variable->class, $variable->name); + } + $methodCallNode = $this->builderFactory->methodCall($variable, $method, $arguments); $variable->setAttribute(AttributeKey::PARENT_NODE, $methodCallNode); diff --git a/src/PhpParser/Parser/InlineCodeParser.php b/src/PhpParser/Parser/InlineCodeParser.php index 523576a53d5b..8fad329bcc28 100644 --- a/src/PhpParser/Parser/InlineCodeParser.php +++ b/src/PhpParser/Parser/InlineCodeParser.php @@ -81,7 +81,7 @@ public function stringify($content): string return $this->stringify($content->left) . $this->stringify($content->right); } - if ($content instanceof Variable || $content instanceof PropertyFetch) { + if ($content instanceof Variable || $content instanceof PropertyFetch || $content instanceof Node\Expr\StaticPropertyFetch) { return $this->betterStandardPrinter->print($content); } diff --git a/src/PhpParser/Printer/FormatPerservingPrinter.php b/src/PhpParser/Printer/FormatPerservingPrinter.php index 67d8068c2402..0a77c9c0a661 100644 --- a/src/PhpParser/Printer/FormatPerservingPrinter.php +++ b/src/PhpParser/Printer/FormatPerservingPrinter.php @@ -4,7 +4,6 @@ use Nette\Utils\FileSystem; use PhpParser\Node; -use PhpParser\PrettyPrinter\Standard; use Symplify\PackageBuilder\FileSystem\SmartFileInfo; /** @@ -13,7 +12,7 @@ final class FormatPerservingPrinter { /** - * @var BetterStandardPrinter|Standard + * @var BetterStandardPrinter */ private $betterStandardPrinter; diff --git a/src/Rector/Class_/RenameClassRector.php b/src/Rector/Class_/RenameClassRector.php index d72b9d09a7c5..81e701465141 100644 --- a/src/Rector/Class_/RenameClassRector.php +++ b/src/Rector/Class_/RenameClassRector.php @@ -275,6 +275,7 @@ private function refactorClassLikeNode(ClassLike $classLike): ?Node return null; } + /** @var string $name */ $this->alreadyProcessedClasses[] = $name; $newName = $this->oldToNewClasses[$name]; diff --git a/src/Rector/MagicDisclosure/GetAndSetToMethodCallRector.php b/src/Rector/MagicDisclosure/GetAndSetToMethodCallRector.php index 9b80e30d1ac8..7333e016b675 100644 --- a/src/Rector/MagicDisclosure/GetAndSetToMethodCallRector.php +++ b/src/Rector/MagicDisclosure/GetAndSetToMethodCallRector.php @@ -97,7 +97,7 @@ public function getNodeTypes(): array public function refactor(Node $node): ?Node { if ($node instanceof Assign) { - if ($node->var instanceof PropertyFetch) { + if ($node->var instanceof PropertyFetch || $node->var instanceof Node\Expr\StaticPropertyFetch) { return $this->processMagicSet($node); } diff --git a/src/Rector/MethodBody/NormalToFluentRector.php b/src/Rector/MethodBody/NormalToFluentRector.php index 64d63681b7cc..13689d5e0391 100644 --- a/src/Rector/MethodBody/NormalToFluentRector.php +++ b/src/Rector/MethodBody/NormalToFluentRector.php @@ -108,8 +108,13 @@ public function refactor(Node $node): ?Node } // add all matching fluent calls - $this->collectedMethodCalls[$i] = $stmt->expr; - $this->collectedMethodCalls[$i - 1] = $prevStmt->expr; + /** @var MethodCall $currentMethodCall */ + $currentMethodCall = $stmt->expr; + $this->collectedMethodCalls[$i] = $currentMethodCall; + + /** @var MethodCall $previousMethodCall */ + $previousMethodCall = $prevStmt->expr; + $this->collectedMethodCalls[$i - 1] = $previousMethodCall; } return $node;