diff --git a/packages/Php/src/Rector/FuncCall/CreateFunctionToAnonymousFunctionRector.php b/packages/Php/src/Rector/FuncCall/CreateFunctionToAnonymousFunctionRector.php index ff8d71b2fd3..3f64307448b 100644 --- a/packages/Php/src/Rector/FuncCall/CreateFunctionToAnonymousFunctionRector.php +++ b/packages/Php/src/Rector/FuncCall/CreateFunctionToAnonymousFunctionRector.php @@ -159,7 +159,6 @@ CODE_SAMPLE $paramNames[] = $this->getName($paramNode); } - /** @var Variable[] $variableNodes */ $variableNodes = $this->betterNodeFinder->findInstanceOf($nodes, Variable::class); $filteredVariables = []; @@ -169,11 +168,12 @@ CODE_SAMPLE continue; } - if (in_array($this->getName($variableNode), $paramNames, true)) { + $variableName = $this->getName($variableNode); + if (in_array($variableName, $paramNames, true)) { continue; } - $filteredVariables[$this->getName($variableNode)] = $variableNode; + $filteredVariables[$variableName] = $variableNode; } return $filteredVariables; diff --git a/packages/Php/src/Rector/FunctionLike/Php4ConstructorRector.php b/packages/Php/src/Rector/FunctionLike/Php4ConstructorRector.php index 87559a0c447..fffdb756cd4 100644 --- a/packages/Php/src/Rector/FunctionLike/Php4ConstructorRector.php +++ b/packages/Php/src/Rector/FunctionLike/Php4ConstructorRector.php @@ -12,7 +12,7 @@ use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Expression; use Rector\NodeTypeResolver\Node\Attribute; -use Rector\PhpParser\Node\BetterNodeFinder; +use Rector\PhpParser\Node\Maintainer\ClassMaintainer; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\RectorDefinition; @@ -23,13 +23,13 @@ use Rector\RectorDefinition\RectorDefinition; final class Php4ConstructorRector extends AbstractRector { /** - * @var BetterNodeFinder + * @var ClassMaintainer */ - private $betterNodeFinder; + private $classMaintainer; - public function __construct(BetterNodeFinder $betterNodeFinder) + public function __construct(ClassMaintainer $classMaintainer) { - $this->betterNodeFinder = $betterNodeFinder; + $this->classMaintainer = $classMaintainer; } public function getDefinition(): RectorDefinition @@ -73,9 +73,7 @@ CODE_SAMPLE */ public function refactor(Node $node): ?Node { - $namespace = $node->getAttribute(Attribute::NAMESPACE_NAME); - // catch only classes without namespace - if ($namespace) { + if ($this->shouldSkip($node)) { return null; } @@ -84,21 +82,16 @@ CODE_SAMPLE return null; } - // anonymous class → skip - if ($classNode->name === null || $node->isAbstract() || $node->isStatic()) { - return null; - } - // process parent call references first $this->processClassMethodStatementsForParentConstructorCalls($node); // not PSR-4 constructor - if (! $this->isNameInsensitive($classNode, (string) $node->name)) { + if (! $this->isNameInsensitive($classNode, $this->getName($node))) { return null; } // does it already have a __construct method? - if (! in_array('__construct', $this->getClassMethodNames($classNode), true)) { + if (! $this->classMaintainer->hasClassMethod($classNode, '__construct')) { $node->name = new Identifier('__construct'); } @@ -120,6 +113,26 @@ CODE_SAMPLE return $node; } + private function shouldSkip(ClassMethod $classMethod): bool + { + $namespace = $classMethod->getAttribute(Attribute::NAMESPACE_NAME); + // catch only classes without namespace + if ($namespace !== null) { + return true; + } + + if ($classMethod->isAbstract() || $classMethod->isStatic()) { + return true; + } + + $classNode = $classMethod->getAttribute(Attribute::CLASS_NODE); + if ($classNode instanceof Class_ && $classNode->name === null) { + return true; + } + + return false; + } + private function processClassMethodStatementsForParentConstructorCalls(ClassMethod $classMethodNode): void { if (! is_iterable($classMethodNode->stmts)) { @@ -140,22 +153,6 @@ CODE_SAMPLE } } - /** - * @return string[] - */ - private function getClassMethodNames(Class_ $classNode): array - { - $classMethodNames = []; - - /** @var ClassMethod[] $classMethodNodes */ - $classMethodNodes = $this->betterNodeFinder->findInstanceOf($classNode->stmts, ClassMethod::class); - foreach ($classMethodNodes as $classMethodNode) { - $classMethodNames[] = $this->getName($classMethodNode); - } - - return $classMethodNames; - } - private function isThisConstructCall(Node $node): bool { if (! $node instanceof MethodCall) { @@ -191,16 +188,16 @@ CODE_SAMPLE } // rename ParentClass - if ((string) $node->class === $parentClassName) { + if ($this->isName($node->class, $parentClassName)) { $node->class = new Name('parent'); } - if ((string) $node->class !== 'parent') { + if ($this->isName($node->class, 'parent') === false) { return; } // it's not a parent PHP 4 constructor call - if (! $this->isNameInsensitive($node, $parentClassName)) { + if ($this->isNameInsensitive($node, $parentClassName) === false) { return; } diff --git a/src/PhpParser/Node/Maintainer/ClassMaintainer.php b/src/PhpParser/Node/Maintainer/ClassMaintainer.php index 7db2efe0ce2..1bad73fa4ee 100644 --- a/src/PhpParser/Node/Maintainer/ClassMaintainer.php +++ b/src/PhpParser/Node/Maintainer/ClassMaintainer.php @@ -10,6 +10,7 @@ use PhpParser\Node\Stmt\Expression; use PhpParser\Node\Stmt\Nop; use PhpParser\Node\Stmt\Property; use PhpParser\Node\Stmt\TraitUse; +use Rector\PhpParser\Node\BetterNodeFinder; use Rector\PhpParser\Node\NodeFactory; use Rector\PhpParser\Node\Resolver\NameResolver; use Rector\PhpParser\Node\VariableInfo; @@ -32,14 +33,21 @@ final class ClassMaintainer */ private $childAndParentClassMaintainer; + /** + * @var BetterNodeFinder + */ + private $betterNodeFinder; + public function __construct( NameResolver $nameResolver, NodeFactory $nodeFactory, - ChildAndParentClassMaintainer $childAndParentClassMaintainer + ChildAndParentClassMaintainer $childAndParentClassMaintainer, + BetterNodeFinder $betterNodeFinder ) { $this->nodeFactory = $nodeFactory; $this->nameResolver = $nameResolver; $this->childAndParentClassMaintainer = $childAndParentClassMaintainer; + $this->betterNodeFinder = $betterNodeFinder; } public function addConstructorDependency(Class_ $classNode, VariableInfo $variableInfo): void @@ -208,6 +216,13 @@ final class ClassMaintainer return false; } + public function hasClassMethod(Class_ $classNode, string $methodName): bool + { + $methodNames = $this->getClassMethodNames($classNode); + + return in_array($methodName, $methodNames, true); + } + private function tryInsertBeforeFirstMethod(Class_ $classNode, Stmt $node): bool { foreach ($classNode->stmts as $key => $classElementNode) { @@ -283,6 +298,21 @@ final class ClassMaintainer $classMethodNode->stmts[] = new Expression($propertyAssignNode); } + /** + * @return string[] + */ + private function getClassMethodNames(Class_ $classNode): array + { + $classMethodNames = []; + + $classMethodNodes = $this->betterNodeFinder->findInstanceOf($classNode->stmts, ClassMethod::class); + foreach ($classMethodNodes as $classMethodNode) { + $classMethodNames[] = $this->nameResolver->resolve($classMethodNode); + } + + return $classMethodNames; + } + private function hasMethodParameter(ClassMethod $classMethodNode, VariableInfo $variableInfo): bool { foreach ($classMethodNode->params as $constructorParameter) { diff --git a/src/Rector/Architecture/DependencyInjection/ReplaceVariableByPropertyFetchRector.php b/src/Rector/Architecture/DependencyInjection/ReplaceVariableByPropertyFetchRector.php index 9d6b359769d..c480a9bcbf6 100644 --- a/src/Rector/Architecture/DependencyInjection/ReplaceVariableByPropertyFetchRector.php +++ b/src/Rector/Architecture/DependencyInjection/ReplaceVariableByPropertyFetchRector.php @@ -7,7 +7,6 @@ use PhpParser\Node; use PhpParser\Node\Expr\Variable; use PhpParser\Node\Stmt\ClassMethod; use Rector\Configuration\Rector\Architecture\DependencyInjection\VariablesToPropertyFetchCollection; -use Rector\Exception\ShouldNotHappenException; use Rector\NodeTypeResolver\Node\Attribute; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; @@ -107,11 +106,12 @@ CODE_SAMPLE return null; } - private function isInControllerActionMethod(Node $node): bool + private function isInControllerActionMethod(Variable $node): bool { $className = $node->getAttribute(Attribute::CLASS_NAME); + if ($className === null) { - throw new ShouldNotHappenException(); + return false; } if (! Strings::endsWith($className, 'Controller')) { diff --git a/src/Rector/Psr4/MultipleClassFileToPsr4ClassesRector.php b/src/Rector/Psr4/MultipleClassFileToPsr4ClassesRector.php index 7e8aaaa948c..351815254e5 100644 --- a/src/Rector/Psr4/MultipleClassFileToPsr4ClassesRector.php +++ b/src/Rector/Psr4/MultipleClassFileToPsr4ClassesRector.php @@ -122,7 +122,6 @@ CODE_SAMPLE $smartFileInfo->getRealPath() ); - /** @var Namespace_[] $namespaceNodes */ $namespaceNodes = $this->betterNodeFinder->findInstanceOf($newStmts, Namespace_::class); if ($this->shouldSkip($smartFileInfo, $newStmts, $namespaceNodes)) { diff --git a/utils/phpstan/config/phpstan-extensions.neon b/utils/phpstan/config/phpstan-extensions.neon index f46522c60d2..a347cbca42e 100644 --- a/utils/phpstan/config/phpstan-extensions.neon +++ b/utils/phpstan/config/phpstan-extensions.neon @@ -1,5 +1,8 @@ services: + - Rector\PHPStanExtensions\Utils\ValueResolver + - { class: Symplify\PHPStanExtensions\Type\SplFileInfoTolerantDynamicMethodReturnTypeExtension, tags: [phpstan.broker.dynamicMethodReturnTypeExtension] } + # $node->geAttribute($1) => Type|null by $1 - { class: Rector\PHPStanExtensions\Rector\Type\GetAttributeReturnTypeExtension, tags: [phpstan.broker.dynamicMethodReturnTypeExtension] } @@ -7,3 +10,6 @@ services: - { class: Rector\PHPStanExtensions\Rector\Type\NameResolverReturnTypeExtension, tags: [phpstan.broker.dynamicMethodReturnTypeExtension] } # $nameResolverTrait->getName() => in some cases always string - { class: Rector\PHPStanExtensions\Rector\Type\NameResolverTraitReturnTypeExtension, tags: [phpstan.broker.dynamicMethodReturnTypeExtension] } + + # $betterNodeFinder->findByInstance(..., $1) => $1[] + - { class: Rector\PHPStanExtensions\Rector\Type\BetterNodeFinderReturnTypeExtension, tags: [phpstan.broker.dynamicMethodReturnTypeExtension] } diff --git a/utils/phpstan/src/Rector/Type/BetterNodeFinderReturnTypeExtension.php b/utils/phpstan/src/Rector/Type/BetterNodeFinderReturnTypeExtension.php new file mode 100644 index 00000000000..d7d5f5016ef --- /dev/null +++ b/utils/phpstan/src/Rector/Type/BetterNodeFinderReturnTypeExtension.php @@ -0,0 +1,58 @@ +valueResolver = $valueResolver; + } + + public function getClass(): string + { + return BetterNodeFinder::class; + } + + public function isMethodSupported(MethodReflection $methodReflection): bool + { + return $methodReflection->getName() === 'findInstanceOf'; + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type + { + $returnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + + $secondArgumentNode = $methodCall->args[1]->value; + if (! $secondArgumentNode instanceof ClassConstFetch) { + return $returnType; + } + + if (! $secondArgumentNode->class instanceof Name) { + return $returnType; + } + + $class = $secondArgumentNode->class->toString(); + + return new ArrayType(new MixedType(), new ObjectType($class)); + } +} diff --git a/utils/phpstan/src/Rector/Type/GetAttributeReturnTypeExtension.php b/utils/phpstan/src/Rector/Type/GetAttributeReturnTypeExtension.php index 75324437742..991fd9c3d0b 100644 --- a/utils/phpstan/src/Rector/Type/GetAttributeReturnTypeExtension.php +++ b/utils/phpstan/src/Rector/Type/GetAttributeReturnTypeExtension.php @@ -26,6 +26,7 @@ use PHPStan\Type\Type; use PHPStan\Type\UnionType; use Rector\Php\PhpTypeSupport; use Rector\Php\TypeAnalyzer; +use Rector\PHPStanExtensions\Utils\ValueResolver; use Symplify\PackageBuilder\FileSystem\SmartFileInfo; final class GetAttributeReturnTypeExtension implements DynamicMethodReturnTypeExtension @@ -53,6 +54,15 @@ final class GetAttributeReturnTypeExtension implements DynamicMethodReturnTypeEx 'Rector\NodeTypeResolver\Node\Attribute::CLASS_NAME' => 'string', 'Rector\NodeTypeResolver\Node\Attribute::METHOD_NAME' => 'string', ]; + /** + * @var ValueResolver + */ + private $valueResolver; + + public function __construct(ValueResolver $valueResolver) + { + $this->valueResolver = $valueResolver; + } public function getClass(): string { @@ -92,22 +102,10 @@ final class GetAttributeReturnTypeExtension implements DynamicMethodReturnTypeEx private function resolveArgumentValue(Expr $node): ?string { - $value = null; - if ($node instanceof ClassConstFetch) { - if ($node->class instanceof Name) { - $value = $node->class->toString(); - } else { - return null; - } - - if ($node->name instanceof Identifier) { - $value .= '::' . $node->name->toString(); - } else { - return null; - } + return $this->valueResolver->resolveClassConstFetch($node); } - return $value; + return null; } } diff --git a/utils/phpstan/src/Utils/ValueResolver.php b/utils/phpstan/src/Utils/ValueResolver.php new file mode 100644 index 00000000000..3952e0773ff --- /dev/null +++ b/utils/phpstan/src/Utils/ValueResolver.php @@ -0,0 +1,29 @@ +class instanceof Name) { + $value = $classConstFetch->class->toString(); + } else { + return null; + } + + if ($classConstFetch->name instanceof Identifier) { + $value .= '::' . $classConstFetch->name->toString(); + } else { + return null; + } + + return $value; + } +}