diff --git a/phpstan.neon b/phpstan.neon index dd0cbfa5d60..b1014b70deb 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -387,3 +387,4 @@ parameters: - '#Method Rector\\Core\\PhpParser\\Node\\BetterNodeFinder\:\:findPreviousAssignToExpr\(\) should return PhpParser\\Node\\Expr\\Assign\|null but returns PhpParser\\Node\|null#' - '#Method Rector\\Nette\\FormControlTypeResolver\\GetComponentMethodCallFormControlTypeResolver\:\:resolve\(\) should return array but returns array#' - '#Class with base "LexerFactory" name is already used in "PHPStan\\Parser\\LexerFactory", "Rector\\Core\\PhpParser\\Parser\\LexerFactory"\. Use unique name to make classes easy to recognize#' + - '#Parameter \#1 \$shortControlString of method Rector\\Nette\\Rector\\Assign\\MakeGetComponentAssignAnnotatedRector\:\:resolveTypeFromShortControlNameAndVariable\(\) expects PhpParser\\Node\\Scalar\\String_, PhpParser\\Node\\Expr\|null given#' diff --git a/rules/nette/src/Rector/Assign/MakeGetComponentAssignAnnotatedRector.php b/rules/nette/src/Rector/Assign/MakeGetComponentAssignAnnotatedRector.php index 99742378c55..786fbefa49a 100644 --- a/rules/nette/src/Rector/Assign/MakeGetComponentAssignAnnotatedRector.php +++ b/rules/nette/src/Rector/Assign/MakeGetComponentAssignAnnotatedRector.php @@ -5,6 +5,8 @@ declare(strict_types=1); namespace Rector\Nette\Rector\Assign; use PhpParser\Node; +use PhpParser\Node\Expr; +use PhpParser\Node\Expr\ArrayDimFetch; use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\Variable; @@ -19,6 +21,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeWithClassName; use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareFullyQualifiedIdentifierTypeNode; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; +use Rector\Core\Exception\ShouldNotHappenException; use Rector\Core\Rector\AbstractRector; use Rector\Core\RectorDefinition\CodeSample; use Rector\Core\RectorDefinition\RectorDefinition; @@ -103,7 +106,7 @@ PHP */ public function refactor(Node $node): ?Node { - if (! $this->isOnClassMethodCall($node->expr, 'Nette\Application\UI\Control', 'getComponent')) { + if (! $this->isGetComponentMethodCallOrArrayDimFetchOnControl($node->expr)) { return null; } @@ -121,10 +124,8 @@ PHP return null; } - /** @var MethodCall $methodCall */ - $methodCall = $node->expr; - $createComponentClassMethodReturnType = $this->resolveGetComponentReturnType($methodCall); - if (! $createComponentClassMethodReturnType instanceof TypeWithClassName) { + $controlType = $this->resolveControlType($node); + if ($controlType instanceof MixedType) { return null; } @@ -134,7 +135,7 @@ PHP } $attributeAwareFullyQualifiedIdentifierTypeNode = new AttributeAwareFullyQualifiedIdentifierTypeNode( - $createComponentClassMethodReturnType->getClassName() + $controlType->getClassName() ); $varTagValueNode = new VarTagValueNode( $attributeAwareFullyQualifiedIdentifierTypeNode, @@ -166,7 +167,7 @@ PHP return $phpDocInfo; } - private function resolveGetComponentReturnType(MethodCall $methodCall): Type + private function resolveCreateComponentMethodCallReturnType(MethodCall $methodCall): Type { /** @var Scope|null $scope */ $scope = $methodCall->getAttribute(AttributeKey::SCOPE); @@ -174,8 +175,6 @@ PHP return new MixedType(); } - $calledOnType = $scope->getType($methodCall->var); - if (count($methodCall->args) !== 1) { return new MixedType(); } @@ -185,9 +184,53 @@ PHP return new MixedType(); } - $componentName = $this->getValue($firstArgumentValue); + return $this->resolveTypeFromShortControlNameAndVariable($firstArgumentValue, $scope, $methodCall->var); + } + + private function isGetComponentMethodCallOrArrayDimFetchOnControl(Expr $expr): bool + { + if ($this->isOnClassMethodCall($expr, 'Nette\Application\UI\Control', 'getComponent')) { + return true; + } + + return $this->isArrayDimFetchStringOnControlVariable($expr); + } + + private function resolveArrayDimFetchControlType(ArrayDimFetch $arrayDimFetch): Type + { + /** @var Scope|null $scope */ + $scope = $arrayDimFetch->getAttribute(AttributeKey::SCOPE); + if ($scope === null) { + throw new ShouldNotHappenException(); + } + + return $this->resolveTypeFromShortControlNameAndVariable($arrayDimFetch->dim, $scope, $arrayDimFetch->var); + } + + private function isArrayDimFetchStringOnControlVariable(Expr $expr): bool + { + if (! $expr instanceof ArrayDimFetch) { + return false; + } + + if (! $expr->dim instanceof String_) { + return false; + } + + $varStaticType = $this->getStaticType($expr->var); + if (! $varStaticType instanceof TypeWithClassName) { + return false; + } + + return is_a($varStaticType->getClassName(), 'Nette\Application\UI\Control', true); + } + + private function resolveTypeFromShortControlNameAndVariable(String_ $shortControlString, Scope $scope, Expr $expr) + { + $componentName = $this->getValue($shortControlString); $methodName = sprintf('createComponent%s', ucfirst($componentName)); + $calledOnType = $scope->getType($expr); if (! $calledOnType instanceof ObjectType) { return new MixedType(); } @@ -201,4 +244,21 @@ PHP return ParametersAcceptorSelector::selectSingle($method->getVariants())->getReturnType(); } + + private function resolveControlType(Assign $assign): Type + { + if ($assign->expr instanceof MethodCall) { + /** @var MethodCall $methodCall */ + $methodCall = $assign->expr; + return $this->resolveCreateComponentMethodCallReturnType($methodCall); + } + + if ($assign->expr instanceof ArrayDimFetch) { + /** @var ArrayDimFetch $arrayDimFetch */ + $arrayDimFetch = $assign->expr; + return $this->resolveArrayDimFetchControlType($arrayDimFetch); + } + + return new MixedType(); + } } diff --git a/rules/nette/tests/Rector/Assign/MakeGetComponentAssignAnnotatedRector/Fixture/array_dim_access.php.inc b/rules/nette/tests/Rector/Assign/MakeGetComponentAssignAnnotatedRector/Fixture/array_dim_access.php.inc new file mode 100644 index 00000000000..f09264aed63 --- /dev/null +++ b/rules/nette/tests/Rector/Assign/MakeGetComponentAssignAnnotatedRector/Fixture/array_dim_access.php.inc @@ -0,0 +1,34 @@ + +----- +