diff --git a/.github/workflows/rector_ci.yaml b/.github/workflows/rector_ci.yaml index 476384bfd8c1..902bed29e079 100644 --- a/.github/workflows/rector_ci.yaml +++ b/.github/workflows/rector_ci.yaml @@ -21,7 +21,11 @@ jobs: fail-fast: false matrix: directories: - - rules + #- rules + - rules/naming + - rules/privatization + - rules/code-quality + - rules/php74 - packages - src - tests @@ -54,6 +58,7 @@ jobs: ## First run Rector - here can't be --dry-run !!! it would stop the job with it and not commit anything in the future - run: bin/rector rectify ${{ matrix.directories }} --ansi --no-progress-bar + - run: vendor/bin/ecs check --match-git-diff --fix --ansi # see https://github.com/EndBug/add-and-commit diff --git a/composer.json b/composer.json index f63132f91867..5091f785c333 100644 --- a/composer.json +++ b/composer.json @@ -37,7 +37,7 @@ "nette/utils": "^3.2", "nikic/php-parser": "^4.10.4", "phpstan/phpdoc-parser": "^0.4.9", - "phpstan/phpstan": "^0.12.76", + "phpstan/phpstan": "^0.12.79", "phpstan/phpstan-phpunit": "^0.12.17", "psr/simple-cache": "^1.0", "sebastian/diff": "^4.0.4", @@ -186,12 +186,11 @@ "rules/autodiscovery/tests/Rector/FileNode/MoveServicesBySuffixToDirectoryRector/Expected", "rules/cakephp/tests/Rector/FileWithoutNamespace/ImplicitShortClassNameUseStatementRector/Source", "rules/cakephp/tests/Rector/Namespace_/AppUsesStaticCallToUseStatementRector/Source", - "rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Source", "rules/renaming/tests/Rector/FileWithoutNamespace/PseudoNamespaceToNamespaceRector/Source", "rules/symfony4/tests/Rector/MethodCall/ContainerGetToConstructorInjectionRector/Source" ], "files": [ - "rules/type-declaration/tests/Rector/ClassMethod/ReturnTypeFromStrictTypedCallRector/Source/external_bool_function.php", + "vendor/nette/forms/src/Forms/Controls/SubmitButton.php", "rules/restoration/tests/Rector/Use_/RestoreFullyQualifiedNameRector/Source/ShortClassOnly.php", "rules/coding-style/tests/Rector/Namespace_/ImportFullyQualifiedNamesRector/Source/AnotherClass.php", "rules/coding-style/tests/Rector/Namespace_/ImportFullyQualifiedNamesRector/Source/Foo.php", diff --git a/packages/better-php-doc-parser/src/AnnotationReader/NodeAnnotationReader.php b/packages/better-php-doc-parser/src/AnnotationReader/NodeAnnotationReader.php index 74d1e8c22044..5905f795d525 100644 --- a/packages/better-php-doc-parser/src/AnnotationReader/NodeAnnotationReader.php +++ b/packages/better-php-doc-parser/src/AnnotationReader/NodeAnnotationReader.php @@ -10,11 +10,13 @@ use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Property; +use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\Php\PhpPropertyReflection; +use PHPStan\Reflection\ReflectionProvider; +use Rector\Core\Exception\ShouldNotHappenException; use Rector\DoctrineAnnotationGenerated\PhpDocNode\ConstantReferenceIdentifierRestorer; use Rector\NodeNameResolver\NodeNameResolver; -use Rector\NodeTypeResolver\ClassExistenceStaticHelper; use Rector\NodeTypeResolver\Node\AttributeKey; -use ReflectionClass; use ReflectionMethod; use ReflectionProperty; use Throwable; @@ -41,14 +43,21 @@ final class NodeAnnotationReader */ private $constantReferenceIdentifierRestorer; + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + public function __construct( ConstantReferenceIdentifierRestorer $constantReferenceIdentifierRestorer, NodeNameResolver $nodeNameResolver, - Reader $reader + Reader $reader, + ReflectionProvider $reflectionProvider ) { $this->reader = $reader; $this->nodeNameResolver = $nodeNameResolver; $this->constantReferenceIdentifierRestorer = $constantReferenceIdentifierRestorer; + $this->reflectionProvider = $reflectionProvider; } public function readAnnotation(Node $node, string $annotationClass): ?object @@ -71,12 +80,13 @@ public function readAnnotation(Node $node, string $annotationClass): ?object public function readClassAnnotation(Class_ $class, string $annotationClassName): ?object { $classReflection = $this->createClassReflectionFromNode($class); + $nativeClassReflection = $classReflection->getNativeReflection(); try { // covers cases like https://github.com/rectorphp/rector/issues/3046 /** @var object[] $classAnnotations */ - $classAnnotations = $this->reader->getClassAnnotations($classReflection); + $classAnnotations = $this->reader->getClassAnnotations($nativeClassReflection); return $this->matchNextAnnotation($classAnnotations, $annotationClassName, $class); } catch (AnnotationException $annotationException) { // unable to load @@ -86,16 +96,16 @@ public function readClassAnnotation(Class_ $class, string $annotationClassName): public function readPropertyAnnotation(Property $property, string $annotationClassName): ?object { - $propertyReflection = $this->createPropertyReflectionFromPropertyNode($property); - if (! $propertyReflection instanceof ReflectionProperty) { - return null; + $reflectionProperty = $this->getNativePropertyReflection($property); + if (! $reflectionProperty instanceof ReflectionProperty) { + throw new ShouldNotHappenException(); } try { // covers cases like https://github.com/rectorphp/rector/issues/3046 /** @var object[] $propertyAnnotations */ - $propertyAnnotations = $this->reader->getPropertyAnnotations($propertyReflection); + $propertyAnnotations = $this->reader->getPropertyAnnotations($reflectionProperty); return $this->matchNextAnnotation($propertyAnnotations, $annotationClassName, $property); } catch (AnnotationException $annotationException) { // unable to load @@ -111,13 +121,14 @@ private function readMethodAnnotation(ClassMethod $classMethod, string $annotati /** @var string $methodName */ $methodName = $this->nodeNameResolver->getName($classMethod); - $reflectionMethod = new ReflectionMethod($className, $methodName); + $reflectionMethod = $this->resolveNativeClassMethodReflection($className, $methodName); try { // covers cases like https://github.com/rectorphp/rector/issues/3046 /** @var object[] $methodAnnotations */ $methodAnnotations = $this->reader->getMethodAnnotations($reflectionMethod); + foreach ($methodAnnotations as $methodAnnotation) { if (! is_a($methodAnnotation, $annotationClassName, true)) { continue; @@ -141,14 +152,13 @@ private function readMethodAnnotation(ClassMethod $classMethod, string $annotati return null; } - private function createClassReflectionFromNode(Class_ $class): ReflectionClass + private function createClassReflectionFromNode(Class_ $class): ClassReflection { /** @var string $className */ $className = $this->nodeNameResolver->getName($class); // covers cases like https://github.com/rectorphp/rector/issues/3230#issuecomment-683317288 - - return new ReflectionClass($className); + return $this->reflectionProvider->getClass($className); } /** @@ -175,7 +185,7 @@ private function matchNextAnnotation(array $annotations, string $annotationClass return null; } - private function createPropertyReflectionFromPropertyNode(Property $property): ?ReflectionProperty + private function getNativePropertyReflection(Property $property): ?ReflectionProperty { /** @var string $propertyName */ $propertyName = $this->nodeNameResolver->getName($property); @@ -186,16 +196,35 @@ private function createPropertyReflectionFromPropertyNode(Property $property): ? // probably fresh node return null; } - if (! ClassExistenceStaticHelper::doesClassLikeExist($className)) { + + if (! $this->reflectionProvider->hasClass($className)) { // probably fresh node return null; } try { - return new ReflectionProperty($className, $propertyName); + $classReflection = $this->reflectionProvider->getClass($className); + $scope = $property->getAttribute(AttributeKey::SCOPE); + + $propertyReflection = $classReflection->getProperty($propertyName, $scope); + if ($propertyReflection instanceof PhpPropertyReflection) { + return $propertyReflection->getNativeReflection(); + } } catch (Throwable $throwable) { // in case of PHPUnit property or just-added property return null; } } + + private function resolveNativeClassMethodReflection(string $className, string $methodName): ReflectionMethod + { + if (! $this->reflectionProvider->hasClass($className)) { + throw new ShouldNotHappenException(); + } + + $classReflection = $this->reflectionProvider->getClass($className); + $reflectionClass = $classReflection->getNativeReflection(); + + return $reflectionClass->getMethod($methodName); + } } diff --git a/packages/better-php-doc-parser/src/PhpDocNodeFactory/AbstractPhpDocNodeFactory.php b/packages/better-php-doc-parser/src/PhpDocNodeFactory/AbstractPhpDocNodeFactory.php index 4c58fee45e9b..ca2ca09a1f19 100644 --- a/packages/better-php-doc-parser/src/PhpDocNodeFactory/AbstractPhpDocNodeFactory.php +++ b/packages/better-php-doc-parser/src/PhpDocNodeFactory/AbstractPhpDocNodeFactory.php @@ -7,6 +7,7 @@ use Nette\Utils\Strings; use PhpParser\Node; use PHPStan\PhpDocParser\Parser\TokenIterator; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\ObjectType; use Rector\BetterPhpDocParser\Annotation\AnnotationItemsResolver; use Rector\BetterPhpDocParser\AnnotationReader\NodeAnnotationReader; @@ -56,6 +57,11 @@ abstract class AbstractPhpDocNodeFactory */ private $objectTypeSpecifier; + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + /** * @required */ @@ -63,12 +69,14 @@ public function autowireAbstractPhpDocNodeFactory( NodeAnnotationReader $nodeAnnotationReader, AnnotationContentResolver $annotationContentResolver, AnnotationItemsResolver $annotationItemsResolver, - ObjectTypeSpecifier $objectTypeSpecifier + ObjectTypeSpecifier $objectTypeSpecifier, + ReflectionProvider $reflectionProvider ): void { $this->nodeAnnotationReader = $nodeAnnotationReader; $this->annotationContentResolver = $annotationContentResolver; $this->annotationItemsResolver = $annotationItemsResolver; $this->objectTypeSpecifier = $objectTypeSpecifier; + $this->reflectionProvider = $reflectionProvider; } protected function resolveContentFromTokenIterator(TokenIterator $tokenIterator): string @@ -79,12 +87,12 @@ protected function resolveContentFromTokenIterator(TokenIterator $tokenIterator) protected function resolveFqnTargetEntity(string $targetEntity, Node $node): string { $targetEntity = $this->getCleanedUpTargetEntity($targetEntity); - if (class_exists($targetEntity)) { + if ($this->reflectionProvider->hasClass($targetEntity)) { return $targetEntity; } $namespacedTargetEntity = $node->getAttribute(AttributeKey::NAMESPACE_NAME) . '\\' . $targetEntity; - if (class_exists($namespacedTargetEntity)) { + if ($this->reflectionProvider->hasClass($namespacedTargetEntity)) { return $namespacedTargetEntity; } diff --git a/packages/better-php-doc-parser/src/PhpDocParser/ClassAnnotationMatcher.php b/packages/better-php-doc-parser/src/PhpDocParser/ClassAnnotationMatcher.php index c6507da52bec..6591260ee256 100644 --- a/packages/better-php-doc-parser/src/PhpDocParser/ClassAnnotationMatcher.php +++ b/packages/better-php-doc-parser/src/PhpDocParser/ClassAnnotationMatcher.php @@ -8,6 +8,7 @@ use PhpParser\Node; use PhpParser\Node\Stmt\Use_; use PhpParser\Node\Stmt\UseUse; +use PHPStan\Reflection\ReflectionProvider; use Rector\NodeTypeResolver\Node\AttributeKey; /** @@ -20,6 +21,16 @@ final class ClassAnnotationMatcher */ private $fullyQualifiedNameByHash = []; + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + public function resolveTagFullyQualifiedName(string $tag, Node $node): string { $uniqueHash = $tag . spl_object_hash($node); @@ -48,7 +59,7 @@ private function resolveFullyQualifiedClass(array $uses, Node $node, string $tag $namespace = $node->getAttribute(AttributeKey::NAMESPACE_NAME); if ($namespace !== null) { $namespacedTag = $namespace . '\\' . $tag; - if (class_exists($namespacedTag)) { + if ($this->reflectionProvider->hasClass($namespacedTag)) { return $namespacedTag; } } diff --git a/packages/better-php-doc-parser/src/Printer/MultilineSpaceFormatPreserver.php b/packages/better-php-doc-parser/src/Printer/MultilineSpaceFormatPreserver.php index f70a7e760edb..23b51a170969 100644 --- a/packages/better-php-doc-parser/src/Printer/MultilineSpaceFormatPreserver.php +++ b/packages/better-php-doc-parser/src/Printer/MultilineSpaceFormatPreserver.php @@ -24,9 +24,7 @@ final class MultilineSpaceFormatPreserver public function resolveCurrentPhpDocNodeText(Node $node): ?string { - if ($node instanceof PhpDocTagNode && - property_exists($node->value, 'description') - ) { + if ($node instanceof PhpDocTagNode && property_exists($node->value, 'description')) { return $node->value->description; } diff --git a/packages/better-php-doc-parser/src/ValueObject/PhpDocNode/AbstractTagValueNode.php b/packages/better-php-doc-parser/src/ValueObject/PhpDocNode/AbstractTagValueNode.php index 369b3d20d572..98dd84c68f2b 100644 --- a/packages/better-php-doc-parser/src/ValueObject/PhpDocNode/AbstractTagValueNode.php +++ b/packages/better-php-doc-parser/src/ValueObject/PhpDocNode/AbstractTagValueNode.php @@ -32,14 +32,14 @@ abstract class AbstractTagValueNode implements AttributeAwareNodeInterface, PhpD protected $tagValueNodeConfiguration; /** - * @var ArrayPartPhpDocTagPrinter + * @var TagValueNodePrinter */ - protected $arrayPartPhpDocTagPrinter; + protected $tagValueNodePrinter; /** - * @var TagValueNodePrinter + * @var ArrayPartPhpDocTagPrinter */ - protected $tagValueNodePrinter; + private $arrayPartPhpDocTagPrinter; /** * @param array $items diff --git a/packages/better-php-doc-parser/src/ValueObject/PhpDocNode/Symfony/SymfonyRouteTagValueNode.php b/packages/better-php-doc-parser/src/ValueObject/PhpDocNode/Symfony/SymfonyRouteTagValueNode.php index fd3652687827..e13c3d0e4540 100644 --- a/packages/better-php-doc-parser/src/ValueObject/PhpDocNode/Symfony/SymfonyRouteTagValueNode.php +++ b/packages/better-php-doc-parser/src/ValueObject/PhpDocNode/Symfony/SymfonyRouteTagValueNode.php @@ -9,7 +9,6 @@ use Rector\BetterPhpDocParser\Contract\PhpDocNode\SilentKeyNodeInterface; use Rector\BetterPhpDocParser\ValueObject\PhpDocNode\AbstractTagValueNode; use Rector\PhpAttribute\Contract\PhpAttributableTagNodeInterface; -use Symfony\Component\Routing\Annotation\Route; /** * @see \Rector\BetterPhpDocParser\Tests\PhpDocParser\TagValueNodeReprint\TagValueNodeReprintTest @@ -19,7 +18,7 @@ final class SymfonyRouteTagValueNode extends AbstractTagValueNode implements Sho /** * @var string */ - public const CLASS_NAME = Route::class; + public const CLASS_NAME = 'Symfony\Component\Routing\Annotation\Route'; /** * @var string diff --git a/packages/better-php-doc-parser/tests/PhpDocParser/TagValueNodeReprint/Source/ApiFilter.php b/packages/better-php-doc-parser/tests/PhpDocParser/TagValueNodeReprint/Source/ApiFilter.php index 974860cbbc4f..d02c5dc732e4 100644 --- a/packages/better-php-doc-parser/tests/PhpDocParser/TagValueNodeReprint/Source/ApiFilter.php +++ b/packages/better-php-doc-parser/tests/PhpDocParser/TagValueNodeReprint/Source/ApiFilter.php @@ -14,8 +14,5 @@ class ApiFilter { public function __construct($options = []) { - if(! class_exists($options['value'])) { - throw new ShouldNotHappenException(); - } } } diff --git a/packages/family-tree/src/NodeAnalyzer/ClassChildAnalyzer.php b/packages/family-tree/src/NodeAnalyzer/ClassChildAnalyzer.php index 32c3d79f2e00..bb9876af78be 100644 --- a/packages/family-tree/src/NodeAnalyzer/ClassChildAnalyzer.php +++ b/packages/family-tree/src/NodeAnalyzer/ClassChildAnalyzer.php @@ -4,88 +4,63 @@ namespace Rector\FamilyTree\NodeAnalyzer; -use PhpParser\Node\Stmt\Class_; -use Rector\NodeTypeResolver\Node\AttributeKey; -use ReflectionClass; -use ReflectionMethod; +use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\Php\PhpMethodReflection; +use Rector\FamilyTree\Reflection\FamilyRelationsAnalyzer; final class ClassChildAnalyzer { - public function hasChildClassConstructor(Class_ $class): bool - { - $childClasses = $this->getChildClasses($class); - - foreach ($childClasses as $childClass) { - if (! class_exists($childClass)) { - continue; - } - - $reflectionClass = new ReflectionClass($childClass); - $constructorReflectionMethod = $reflectionClass->getConstructor(); - if (! $constructorReflectionMethod instanceof ReflectionMethod) { - continue; - } - - if ($constructorReflectionMethod->class !== $childClass) { - continue; - } - - return true; - } + /** + * @var FamilyRelationsAnalyzer + */ + private $familyRelationsAnalyzer; - return false; + public function __construct(FamilyRelationsAnalyzer $familyRelationsAnalyzer) + { + $this->familyRelationsAnalyzer = $familyRelationsAnalyzer; } - public function hasParentClassConstructor(Class_ $class): bool + public function hasChildClassMethod(ClassReflection $classReflection, string $methodName): bool { - $className = $class->getAttribute(AttributeKey::CLASS_NAME); - if ($className === null) { - return false; - } - - /** @var string[] $classParents */ - $classParents = (array) class_parents($className); + $childrenClassReflections = $this->familyRelationsAnalyzer->getChildrenOfClassReflection($classReflection); - foreach ($classParents as $classParent) { - $parentReflectionClass = new ReflectionClass($classParent); - $constructMethodReflection = $parentReflectionClass->getConstructor(); - if (! $constructMethodReflection instanceof ReflectionMethod) { + foreach ($childrenClassReflections as $childClassReflection) { + if (! $childClassReflection->hasMethod($methodName)) { continue; } - if ($constructMethodReflection->class !== $classParent) { + $constructorReflectionMethod = $childClassReflection->getNativeMethod($methodName); + if (! $constructorReflectionMethod instanceof PhpMethodReflection) { continue; } - return true; + $methodDeclaringClassReflection = $constructorReflectionMethod->getDeclaringClass(); + if ($methodDeclaringClassReflection->getName() === $childClassReflection->getName()) { + return true; + } } return false; } - /** - * @return class-string[] - */ - private function getChildClasses(Class_ $class): array + public function hasParentClassMethod(ClassReflection $classReflection, string $methodName): bool { - $className = $class->getAttribute(AttributeKey::CLASS_NAME); - if ($className === null) { - return []; - } - - $childClasses = []; - foreach (get_declared_classes() as $declaredClass) { - if (! is_a($declaredClass, $className, true)) { + foreach ($classReflection->getParents() as $parentClassReflections) { + if (! $parentClassReflections->hasMethod($methodName)) { continue; } - if ($declaredClass === $className) { + $constructMethodReflection = $parentClassReflections->getNativeMethod($methodName); + if (! $constructMethodReflection instanceof PhpMethodReflection) { continue; } - $childClasses[] = $declaredClass; + $methodDeclaringMethodClass = $constructMethodReflection->getDeclaringClass(); + if ($methodDeclaringMethodClass->getName() === $parentClassReflections->getName()) { + return true; + } } - return $childClasses; + return false; } } diff --git a/packages/family-tree/src/NodeAnalyzer/PropertyUsageAnalyzer.php b/packages/family-tree/src/NodeAnalyzer/PropertyUsageAnalyzer.php index ce90d84f3fff..add7a2a9a6c5 100644 --- a/packages/family-tree/src/NodeAnalyzer/PropertyUsageAnalyzer.php +++ b/packages/family-tree/src/NodeAnalyzer/PropertyUsageAnalyzer.php @@ -7,6 +7,9 @@ use PhpParser\Node; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\Property; +use PHPStan\Analyser\Scope; +use PHPStan\Reflection\ClassReflection; +use Rector\Core\Exception\ShouldNotHappenException; use Rector\Core\PhpParser\Node\BetterNodeFinder; use Rector\FamilyTree\Reflection\FamilyRelationsAnalyzer; use Rector\NodeCollector\NodeCollector\NodeRepository; @@ -54,16 +57,26 @@ public function isPropertyFetchedInChildClass(Property $property): bool return false; } - $classLike = $property->getAttribute(AttributeKey::CLASS_NODE); - if ($classLike instanceof Class_ && $classLike->isFinal()) { + $scope = $property->getAttribute(AttributeKey::SCOPE); + if (! $scope instanceof Scope) { + return false; + } + + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { + throw new ShouldNotHappenException(); + } + + if ($classReflection->isClass() && $classReflection->isFinal()) { return false; } $propertyName = $this->nodeNameResolver->getName($property); - $childrenClassNames = $this->familyRelationsAnalyzer->getChildrenOfClass($className); - foreach ($childrenClassNames as $childClassName) { - $childClass = $this->nodeRepository->findClass($childClassName); + $childrenClassReflections = $this->familyRelationsAnalyzer->getChildrenOfClassReflection($classReflection); + + foreach ($childrenClassReflections as $childClassReflection) { + $childClass = $this->nodeRepository->findClass($childClassReflection->getName()); if (! $childClass instanceof Class_) { continue; } diff --git a/packages/family-tree/src/Reflection/FamilyRelationsAnalyzer.php b/packages/family-tree/src/Reflection/FamilyRelationsAnalyzer.php index 558fc557e229..9c3df2998ea2 100644 --- a/packages/family-tree/src/Reflection/FamilyRelationsAnalyzer.php +++ b/packages/family-tree/src/Reflection/FamilyRelationsAnalyzer.php @@ -4,43 +4,46 @@ namespace Rector\FamilyTree\Reflection; +use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ReflectionProvider; +use Symplify\PackageBuilder\Reflection\PrivatesAccessor; + final class FamilyRelationsAnalyzer { /** - * @return class-string[] + * @var ReflectionProvider */ - public function getChildrenOfClass(string $parentClass): array - { - $childrenClasses = []; - foreach (get_declared_classes() as $declaredClass) { - if ($declaredClass === $parentClass) { - continue; - } + private $reflectionProvider; - if (! is_a($declaredClass, $parentClass, true)) { - continue; - } - - $childrenClasses[] = $declaredClass; - } + /** + * @var PrivatesAccessor + */ + private $privatesAccessor; - return $childrenClasses; + public function __construct(ReflectionProvider $reflectionProvider, PrivatesAccessor $privatesAccessor) + { + $this->reflectionProvider = $reflectionProvider; + $this->privatesAccessor = $privatesAccessor; } - public function isParentClass(string $class): bool + /** + * @return ClassReflection[] + */ + public function getChildrenOfClassReflection(ClassReflection $desiredClassReflection): array { - foreach (get_declared_classes() as $declaredClass) { - if ($declaredClass === $class) { - continue; - } + /** @var ClassReflection[] $classReflections */ + $classReflections = $this->privatesAccessor->getPrivateProperty($this->reflectionProvider, 'classes'); + + $childrenClassReflections = []; - if (! is_a($declaredClass, $class, true)) { + foreach ($classReflections as $classReflection) { + if (! $classReflection->isSubclassOf($desiredClassReflection->getName())) { continue; } - return true; + $childrenClassReflections[] = $classReflection; } - return false; + return $childrenClassReflections; } } diff --git a/packages/node-collector/src/NodeCollector/NodeRepository.php b/packages/node-collector/src/NodeCollector/NodeRepository.php index bcd49a6119eb..5bd54eeeac32 100644 --- a/packages/node-collector/src/NodeCollector/NodeRepository.php +++ b/packages/node-collector/src/NodeCollector/NodeRepository.php @@ -26,7 +26,9 @@ use PhpParser\Node\Stmt\Interface_; use PhpParser\Node\Stmt\Property; use PhpParser\Node\Stmt\Trait_; +use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\MethodReflection; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\MixedType; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; @@ -51,7 +53,7 @@ final class NodeRepository { /** - * @var array + * @var array */ private $classMethodsByType = []; @@ -66,13 +68,13 @@ final class NodeRepository private $funcCallsByName = []; /** - * @var array>> + * @var array>> */ private $callsByTypeAndMethod = []; /** * E.g. [$this, 'someLocalMethod'] - * @var ArrayCallable[][][] + * @var array> */ private $arrayCallablesByTypeAndMethod = []; @@ -121,13 +123,19 @@ final class NodeRepository */ private $names = []; + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + public function __construct( ArrayCallableMethodReferenceAnalyzer $arrayCallableMethodReferenceAnalyzer, ParsedPropertyFetchNodeCollector $parsedPropertyFetchNodeCollector, NodeNameResolver $nodeNameResolver, ParsedClassConstFetchNodeCollector $parsedClassConstFetchNodeCollector, ParsedNodeCollector $parsedNodeCollector, - TypeUnwrapper $typeUnwrapper + TypeUnwrapper $typeUnwrapper, + ReflectionProvider $reflectionProvider ) { $this->nodeNameResolver = $nodeNameResolver; $this->arrayCallableMethodReferenceAnalyzer = $arrayCallableMethodReferenceAnalyzer; @@ -135,6 +143,7 @@ public function __construct( $this->parsedClassConstFetchNodeCollector = $parsedClassConstFetchNodeCollector; $this->parsedNodeCollector = $parsedNodeCollector; $this->typeUnwrapper = $typeUnwrapper; + $this->reflectionProvider = $reflectionProvider; } /** @@ -200,7 +209,7 @@ public function findFunctionByFuncCall(FuncCall $funcCall): ?Function_ } /** - * @return MethodCall[][]|StaticCall[][] + * @return array */ public function findMethodCallsOnClass(string $className): array { @@ -226,7 +235,12 @@ public function findClassMethodByTypeAndMethod(string $desiredType, string $desi $classMethods = []; foreach ($this->classMethodsByType as $className => $classMethodByMethodName) { - if (! is_a($className, $desiredType, true)) { + if (! $this->reflectionProvider->hasClass($className)) { + continue; + } + + $classReflection = $this->reflectionProvider->getClass($className); + if (! $classReflection->isSubclassOf($desiredType)) { continue; } @@ -271,15 +285,14 @@ public function findClassMethod(string $className, string $methodName): ?ClassMe return $this->classMethodsByType[$className][$methodName]; } - $parentClass = $className; - - if (! class_exists($parentClass)) { + if (! $this->reflectionProvider->hasClass($className)) { return null; } - while ($parentClass = get_parent_class($parentClass)) { - if (isset($this->classMethodsByType[$parentClass][$methodName])) { - return $this->classMethodsByType[$parentClass][$methodName]; + $classReflection = $this->reflectionProvider->getClass($className); + foreach ($classReflection->getParents() as $parentClassReflection) { + if (isset($this->classMethodsByType[$parentClassReflection->getName()][$methodName])) { + return $this->classMethodsByType[$parentClassReflection->getName()][$methodName]; } } @@ -366,36 +379,46 @@ public function findCallsByClassMethod(ClassMethod $classMethod): array } /** - * @return string[] + * @return ClassReflection[] */ - public function findDirectClassConstantFetches(string $desiredClassName, string $desiredConstantName): array + public function findDirectClassConstantFetches(ClassReflection $classReflection, string $desiredConstantName): array { $classConstantFetchByClassAndName = $this->parsedClassConstFetchNodeCollector->getClassConstantFetchByClassAndName(); - return $classConstantFetchByClassAndName[$desiredClassName][$desiredConstantName] ?? []; + $classTypes = $classConstantFetchByClassAndName[$classReflection->getName()][$desiredConstantName] ?? []; + + return $this->resolveClassReflectionsFromClassTypes($classTypes); } /** - * @return string[] + * @return ClassReflection[] */ - public function findIndirectClassConstantFetches(string $desiredClassName, string $desiredConstantName): array - { + public function findIndirectClassConstantFetches( + ClassReflection $classReflection, + string $desiredConstantName + ): array { $classConstantFetchByClassAndName = $this->parsedClassConstFetchNodeCollector->getClassConstantFetchByClassAndName(); foreach ($classConstantFetchByClassAndName as $className => $classesByConstantName) { + if (! $this->reflectionProvider->hasClass($className)) { + return []; + } + + $currentClassReflection = $this->reflectionProvider->getClass($className); if (! isset($classesByConstantName[$desiredConstantName])) { continue; } - // include child usages and parent usages - if (! is_a($className, $desiredClassName, true) && ! is_a($desiredClassName, $className, true)) { + if (! $classReflection->isSubclassOf($currentClassReflection->getName()) && + ! $currentClassReflection->isSubclassOf($classReflection->getName())) { continue; } - if ($desiredClassName === $className) { + // include child usages and parent usages + if ($currentClassReflection->getName() === $classReflection->getName()) { continue; } - return $classesByConstantName[$desiredConstantName]; + return $this->resolveClassReflectionsFromClassTypes($classesByConstantName[$desiredConstantName]); } return []; @@ -638,6 +661,17 @@ public function getClassConstFetches(): array return $this->parsedNodeCollector->getClassConstFetches(); } + public function resolveCallerClassName(MethodCall $methodCall): ?string + { + $callerType = $this->nodeTypeResolver->getStaticType($methodCall->var); + $callerObjectType = $this->typeUnwrapper->unwrapFirstObjectTypeFromUnionType($callerType); + if (! $callerObjectType instanceof TypeWithClassName) { + return null; + } + + return $callerObjectType->getClassName(); + } + private function collectArray(Array_ $array): void { $arrayCallable = $this->arrayCallableMethodReferenceAnalyzer->match($array); @@ -645,7 +679,16 @@ private function collectArray(Array_ $array): void return; } - if (! $arrayCallable->isExistingMethod()) { + if (! $this->reflectionProvider->hasClass($arrayCallable->getClass())) { + return; + } + + $classReflection = $this->reflectionProvider->getClass($arrayCallable->getClass()); + if (! $classReflection->isClass()) { + return; + } + + if (! $classReflection->hasMethod($arrayCallable->getMethod())) { return; } @@ -655,6 +698,7 @@ private function collectArray(Array_ $array): void private function addMethod(ClassMethod $classMethod): void { $className = $classMethod->getAttribute(AttributeKey::CLASS_NAME); + // anonymous if ($className === null) { return; @@ -704,10 +748,20 @@ private function isChildOrEqualClassLike(string $desiredClass, ?string $currentC return false; } - if (! is_a($currentClassName, $desiredClass, true)) { + if (! $this->reflectionProvider->hasClass($desiredClass)) { + return false; + } + + if (! $this->reflectionProvider->hasClass($currentClassName)) { return false; } + $desiredClassReflection = $this->reflectionProvider->getClass($desiredClass); + $currentClassReflection = $this->reflectionProvider->getClass($currentClassName); + + if (! $currentClassReflection->isSubclassOf($desiredClassReflection->getName())) { + return false; + } return $currentClassName !== $desiredClass; } @@ -731,23 +785,11 @@ private function findImplementersOfInterface(string $interface): array return $implementerInterfaces; } - private function resolveCallerClassName(MethodCall $methodCall): ?string - { - $callerType = $this->nodeTypeResolver->getStaticType($methodCall->var); - $callerObjectType = $this->typeUnwrapper->unwrapFirstObjectTypeFromUnionType($callerType); - if (! $callerObjectType instanceof TypeWithClassName) { - return null; - } - - return $callerObjectType->getClassName(); - } - private function resolveNodeClassTypes(Node $node): Type { if ($node instanceof MethodCall && $node->var instanceof Variable && $node->var->name === 'this') { /** @var string|null $className */ $className = $node->getAttribute(AttributeKey::CLASS_NAME); - if ($className) { return new ObjectType($className); } @@ -781,4 +823,22 @@ private function addCallByType(Node $node, Type $classType, string $methodName): } } } + + /** + * @param class-string[] $classTypes + * @return ClassReflection[] + */ + private function resolveClassReflectionsFromClassTypes(array $classTypes): array + { + $classReflections = []; + foreach ($classTypes as $classType) { + if (! $this->reflectionProvider->hasClass($classType)) { + continue; + } + + $classReflections[] = $this->reflectionProvider->getClass($classType); + } + + return $classReflections; + } } diff --git a/packages/node-collector/src/NodeCollector/ParsedClassConstFetchNodeCollector.php b/packages/node-collector/src/NodeCollector/ParsedClassConstFetchNodeCollector.php index 6b59d859dc8c..57abde0f4145 100644 --- a/packages/node-collector/src/NodeCollector/ParsedClassConstFetchNodeCollector.php +++ b/packages/node-collector/src/NodeCollector/ParsedClassConstFetchNodeCollector.php @@ -6,6 +6,7 @@ use PhpParser\Node; use PhpParser\Node\Expr\ClassConstFetch; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; use PHPStan\Type\TypeUtils; @@ -13,12 +14,11 @@ use Rector\NodeNameResolver\NodeNameResolver; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\NodeTypeResolver\NodeTypeResolver; -use ReflectionClass; final class ParsedClassConstFetchNodeCollector { /** - * @var string[][][] + * @var array> */ private $classConstantFetchByClassAndName = []; @@ -32,9 +32,15 @@ final class ParsedClassConstFetchNodeCollector */ private $nodeTypeResolver; - public function __construct(NodeNameResolver $nodeNameResolver) + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct(NodeNameResolver $nodeNameResolver, ReflectionProvider $reflectionProvider) { $this->nodeNameResolver = $nodeNameResolver; + $this->reflectionProvider = $reflectionProvider; } /** @@ -70,6 +76,7 @@ public function collect(Node $node): void } // current class + /** @var string $classOfUse */ $classOfUse = $node->getAttribute(AttributeKey::CLASS_NAME); $this->classConstantFetchByClassAndName[$className][$constantName][] = $classOfUse; @@ -80,7 +87,7 @@ public function collect(Node $node): void } /** - * @return string[][][] + * @return array> */ public function getClassConstantFetchByClassAndName(): array { @@ -137,14 +144,17 @@ private function matchClassTypeThatContainsConstant(Type $type, string $constant */ private function getConstantsDefinedInClass(string $className): array { - $reflectionClass = new ReflectionClass($className); + if (! $this->reflectionProvider->hasClass($className)) { + return []; + } + + $classReflection = $this->reflectionProvider->getClass($className); + $reflectionClass = $classReflection->getNativeReflection(); $constants = $reflectionClass->getConstants(); $currentClassConstants = array_keys($constants); - $parentClassReflection = $reflectionClass->getParentClass(); - - if (! $parentClassReflection) { + if ($classReflection->getParentClass() !== false) { return $currentClassConstants; } diff --git a/packages/node-collector/src/Reflection/MethodReflectionProvider.php b/packages/node-collector/src/Reflection/MethodReflectionProvider.php index 85658da4aa1c..deef7266f92d 100644 --- a/packages/node-collector/src/Reflection/MethodReflectionProvider.php +++ b/packages/node-collector/src/Reflection/MethodReflectionProvider.php @@ -9,6 +9,7 @@ use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Stmt\ClassMethod; use PHPStan\Analyser\Scope; +use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\Native\NativeMethodReflection; use PHPStan\Reflection\ParameterReflection; @@ -21,7 +22,6 @@ use Rector\NodeNameResolver\NodeNameResolver; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\NodeTypeResolver\NodeTypeResolver; -use ReflectionMethod; final class MethodReflectionProvider { @@ -70,7 +70,7 @@ public function provideParameterTypesFromMethodReflection(MethodReflection $meth return $parameterTypes; } - public function provideByMethodCall(MethodCall $methodCall): ?ReflectionMethod + public function provideByMethodCall(MethodCall $methodCall): ?MethodReflection { $className = $methodCall->getAttribute(AttributeKey::CLASS_NAME); if (! is_string($className)) { @@ -82,21 +82,16 @@ public function provideByMethodCall(MethodCall $methodCall): ?ReflectionMethod return null; } - if (! method_exists($className, $methodName)) { + if (! $this->reflectionProvider->hasClass($className)) { return null; } - return new ReflectionMethod($className, $methodName); - } - - public function provideByClassAndMethodName(string $class, string $method, Scope $scope): ?MethodReflection - { - $classReflection = $this->reflectionProvider->getClass($class); - if (! $classReflection->hasMethod($method)) { + $classReflection = $this->reflectionProvider->getClass($className); + if (! $classReflection->hasMethod($methodName)) { return null; } - return $classReflection->getMethod($method, $scope); + return $classReflection->getNativeMethod($methodName); } /** @@ -155,7 +150,12 @@ public function provideByClassMethod(ClassMethod $classMethod): ?MethodReflectio return null; } - return $this->provideByClassAndMethodName($class, $method, $scope); + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { + return null; + } + + return $classReflection->getMethod($method, $scope); } /** @@ -163,9 +163,8 @@ public function provideByClassMethod(ClassMethod $classMethod): ?MethodReflectio */ public function getParameterReflectionsFromMethodReflection(MethodReflection $methodReflection): array { - $methodReflectionVariant = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants()); - - return $methodReflectionVariant->getParameters(); + $parametersAcceptor = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants()); + return $parametersAcceptor->getParameters(); } /** @@ -174,18 +173,27 @@ public function getParameterReflectionsFromMethodReflection(MethodReflection $me public function provideParameterNamesByNew(New_ $new): array { $objectType = $this->nodeTypeResolver->resolve($new->class); + $classes = TypeUtils::getDirectClassNames($objectType); $parameterNames = []; foreach ($classes as $class) { - if (! method_exists($class, MethodName::CONSTRUCT)) { + if (! $this->reflectionProvider->hasClass($class)) { + continue; + } + + $classReflection = $this->reflectionProvider->getClass($class); + + if (! $classReflection->hasMethod(MethodName::CONSTRUCT)) { continue; } - $methodReflection = new ReflectionMethod($class, MethodName::CONSTRUCT); + $nativeClassReflection = $classReflection->getNativeReflection(); + $methodReflection = $nativeClassReflection->getMethod(MethodName::CONSTRUCT); + foreach ($methodReflection->getParameters() as $reflectionParameter) { - $parameterNames[] = $reflectionParameter->name; + $parameterNames[] = $reflectionParameter->getName(); } } @@ -207,7 +215,9 @@ private function provideByClassNamesAndMethodName( } foreach ($classes as $class) { - $methodReflection = $this->provideByClassAndMethodName($class, $methodName, $scope); + $classReflection = $this->reflectionProvider->getClass($class); + $methodReflection = $classReflection->getMethod($methodName, $scope); + if ($methodReflection instanceof MethodReflection) { return $methodReflection; } diff --git a/packages/node-collector/src/StaticAnalyzer.php b/packages/node-collector/src/StaticAnalyzer.php index d9863259ab24..84fe77073759 100644 --- a/packages/node-collector/src/StaticAnalyzer.php +++ b/packages/node-collector/src/StaticAnalyzer.php @@ -5,9 +5,10 @@ namespace Rector\NodeCollector; use Nette\Utils\Strings; +use PHPStan\PhpDoc\ResolvedPhpDocBlock; +use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ReflectionProvider; use Rector\NodeCollector\NodeCollector\NodeRepository; -use Rector\NodeTypeResolver\ClassExistenceStaticHelper; -use ReflectionClass; final class StaticAnalyzer { @@ -16,9 +17,15 @@ final class StaticAnalyzer */ private $nodeRepository; - public function __construct(NodeRepository $nodeRepository) + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct(NodeRepository $nodeRepository, ReflectionProvider $reflectionProvider) { $this->nodeRepository = $nodeRepository; + $this->reflectionProvider = $reflectionProvider; } public function isStaticMethod(string $methodName, string $className): bool @@ -30,29 +37,33 @@ public function isStaticMethod(string $methodName, string $className): bool // could be static in doc type magic // @see https://regex101.com/r/tlvfTB/1 - if (! ClassExistenceStaticHelper::doesClassLikeExist($className)) { + if (! $this->reflectionProvider->hasClass($className)) { return false; } - $reflectionClass = new ReflectionClass($className); - if ($this->hasStaticAnnotation($methodName, $reflectionClass)) { + $classReflection = $this->reflectionProvider->getClass($className); + if ($this->hasStaticAnnotation($methodName, $classReflection)) { return true; } // probably magic method → we don't know - if (! method_exists($className, $methodName)) { + if (! $classReflection->hasMethod($methodName)) { return false; } - $methodReflection = $reflectionClass->getMethod($methodName); - + $methodReflection = $classReflection->getNativeMethod($methodName); return $methodReflection->isStatic(); } - private function hasStaticAnnotation(string $methodName, ReflectionClass $reflectionClass): bool + private function hasStaticAnnotation(string $methodName, ClassReflection $classReflection): bool { + $resolvedPhpDocBlock = $classReflection->getResolvedPhpDoc(); + if (! $resolvedPhpDocBlock instanceof ResolvedPhpDocBlock) { + return false; + } + return (bool) Strings::match( - (string) $reflectionClass->getDocComment(), + $resolvedPhpDocBlock->getPhpDocString(), '#@method\s*static\s*(.*?)\b' . $methodName . '\b#' ); } diff --git a/packages/node-collector/src/ValueObject/ArrayCallable.php b/packages/node-collector/src/ValueObject/ArrayCallable.php index 1c7a0bc99f1e..e503e0740334 100644 --- a/packages/node-collector/src/ValueObject/ArrayCallable.php +++ b/packages/node-collector/src/ValueObject/ArrayCallable.php @@ -4,8 +4,6 @@ namespace Rector\NodeCollector\ValueObject; -use ReflectionMethod; - final class ArrayCallable { /** @@ -33,14 +31,4 @@ public function getMethod(): string { return $this->method; } - - public function isExistingMethod(): bool - { - return method_exists($this->class, $this->method); - } - - public function getReflectionMethod(): ReflectionMethod - { - return new ReflectionMethod($this->class, $this->method); - } } diff --git a/packages/node-name-resolver/src/NodeNameResolver.php b/packages/node-name-resolver/src/NodeNameResolver.php index 82c96a45fcea..ba13b6f916c5 100644 --- a/packages/node-name-resolver/src/NodeNameResolver.php +++ b/packages/node-name-resolver/src/NodeNameResolver.php @@ -17,6 +17,7 @@ use PhpParser\Node\Identifier; use PhpParser\Node\Name; use PhpParser\Node\Stmt\ClassLike; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\ObjectType; use Rector\CodingStyle\Naming\ClassNaming; use Rector\Core\Contract\Rector\RectorInterface; @@ -61,6 +62,11 @@ final class NodeNameResolver */ private $betterStandardPrinter; + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + /** * @param NodeNameResolverInterface[] $nodeNameResolvers */ @@ -69,6 +75,7 @@ public function __construct( BetterStandardPrinter $betterStandardPrinter, CurrentFileInfoProvider $currentFileInfoProvider, ClassNaming $classNaming, + ReflectionProvider $reflectionProvider, array $nodeNameResolvers = [] ) { $this->regexPatternDetector = $regexPatternDetector; @@ -76,6 +83,7 @@ public function __construct( $this->currentFileInfoProvider = $currentFileInfoProvider; $this->betterStandardPrinter = $betterStandardPrinter; $this->classNaming = $classNaming; + $this->reflectionProvider = $reflectionProvider; } /** @@ -331,17 +339,17 @@ public function isVariableName(Node $node, string $name): bool } /** - * @param string[] $desiredClassNames + * @param ObjectType[] $desiredObjectTypes */ - public function isInClassNames(Node $node, array $desiredClassNames): bool + public function isInClassNames(Node $node, array $desiredObjectTypes): bool { - $className = $node->getAttribute(AttributeKey::CLASS_NAME); - if ($className === null) { + $classNode = $node->getAttribute(AttributeKey::CLASS_NODE); + if ($classNode === null) { return false; } - foreach ($desiredClassNames as $desiredClassName) { - if (is_a($className, $desiredClassName, true)) { + foreach ($desiredObjectTypes as $desiredObjectType) { + if ($this->isInClassNamed($classNode, $desiredObjectType)) { return true; } } @@ -356,7 +364,16 @@ public function isInClassNamed(Node $node, ObjectType $objectType): bool return false; } - return is_a($className, $objectType->getClassName(), true); + if (! $this->reflectionProvider->hasClass($className)) { + return false; + } + + $classReflection = $this->reflectionProvider->getClass($className); + if ($classReflection->getName() === $objectType->getClassName()) { + return true; + } + + return $classReflection->isSubclassOf($objectType->getClassName()); } /** diff --git a/packages/node-name-resolver/src/NodeNameResolver/FuncCallNameResolver.php b/packages/node-name-resolver/src/NodeNameResolver/FuncCallNameResolver.php index e7b96b502e2c..d2faabc454cd 100644 --- a/packages/node-name-resolver/src/NodeNameResolver/FuncCallNameResolver.php +++ b/packages/node-name-resolver/src/NodeNameResolver/FuncCallNameResolver.php @@ -9,11 +9,22 @@ use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Name; use PhpParser\Node\Name\FullyQualified; +use PHPStan\Reflection\ReflectionProvider; use Rector\NodeNameResolver\Contract\NodeNameResolverInterface; use Rector\NodeTypeResolver\Node\AttributeKey; final class FuncCallNameResolver implements NodeNameResolverInterface { + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + public function getNode(): string { return FuncCall::class; @@ -39,7 +50,8 @@ public function resolve(Node $node): ?string $namespaceName = $functionName->getAttribute(AttributeKey::NAMESPACED_NAME); if ($namespaceName instanceof FullyQualified) { $functionFqnName = $namespaceName->toString(); - if (function_exists($functionFqnName)) { + + if ($this->reflectionProvider->hasFunction($namespaceName, null)) { return $functionFqnName; } } diff --git a/packages/node-type-resolver/config/config.php b/packages/node-type-resolver/config/config.php index 7dc10e311ddd..62cf538326af 100644 --- a/packages/node-type-resolver/config/config.php +++ b/packages/node-type-resolver/config/config.php @@ -13,6 +13,8 @@ use Rector\Core\PhpParser\Node\BetterNodeFinder; use Rector\Core\PhpParser\Printer\BetterStandardPrinter; use Rector\NodeTypeResolver\DependencyInjection\PHPStanServicesFactory; +use Rector\NodeTypeResolver\Reflection\BetterReflection\SourceLocator\IntermediateSourceLocator; +use Rector\NodeTypeResolver\Reflection\BetterReflection\SourceLocatorProvider\DynamicSourceLocatorProvider; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; use function Symfony\Component\DependencyInjection\Loader\Configurator\service; @@ -29,7 +31,9 @@ ->autoconfigure(); $services->load('Rector\NodeTypeResolver\\', __DIR__ . '/../src') - ->exclude([__DIR__ . '/../src/Contract']); + ->exclude([__DIR__ . '/../src/Contract', __DIR__ . '/../src/Reflection/BetterReflection']); + + $services->set(IntermediateSourceLocator::class); $services->set(TypeAnalyzer::class); $services->set(FilesFinder::class); @@ -48,5 +52,8 @@ $services->set(TypeNodeResolver::class) ->factory([service(PHPStanServicesFactory::class), 'createTypeNodeResolver']); + $services->set(DynamicSourceLocatorProvider::class) + ->factory([service(PHPStanServicesFactory::class), 'createDynamicSourceLocatorProvider']); + $services->set(NodeConnectingVisitor::class); }; diff --git a/packages/node-type-resolver/config/phpstan/static-reflection.neon b/packages/node-type-resolver/config/phpstan/static-reflection.neon new file mode 100644 index 000000000000..740ca36b7dd1 --- /dev/null +++ b/packages/node-type-resolver/config/phpstan/static-reflection.neon @@ -0,0 +1,15 @@ +parameters: + # see https://github.com/rectorphp/rector/issues/3490#issue-634342324 + featureToggles: + disableRuntimeReflectionProvider: false + +services: + - Rector\NodeTypeResolver\Reflection\BetterReflection\RectorBetterReflectionSourceLocatorFactory + - Rector\NodeTypeResolver\Reflection\BetterReflection\SourceLocator\IntermediateSourceLocator + - Rector\NodeTypeResolver\Reflection\BetterReflection\SourceLocatorProvider\DynamicSourceLocatorProvider + + # basically decorates native PHPStan source locator with a dynamic source locator that is also available in Rector DI + betterReflectionSourceLocator: + class: PHPStan\BetterReflection\SourceLocator\Type\SourceLocator + factory: ['@Rector\NodeTypeResolver\Reflection\BetterReflection\RectorBetterReflectionSourceLocatorFactory', 'create'] + autowired: false diff --git a/packages/node-type-resolver/src/ClassExistenceStaticHelper.php b/packages/node-type-resolver/src/ClassExistenceStaticHelper.php deleted file mode 100644 index 62049aae59ee..000000000000 --- a/packages/node-type-resolver/src/ClassExistenceStaticHelper.php +++ /dev/null @@ -1,21 +0,0 @@ -provideStringParameter(Option::PHPSTAN_FOR_RECTOR_PATH); + $additionalConfigFiles[] = __DIR__ . '/../../config/phpstan/static-reflection.neon'; + $additionalConfigFiles[] = __DIR__ . '/../../config/phpstan/better-infer.neon'; $existingAdditionalConfigFiles = array_filter($additionalConfigFiles, 'file_exists'); @@ -103,4 +106,12 @@ public function createTypeNodeResolver(): TypeNodeResolver { return $this->container->getByType(TypeNodeResolver::class); } + + /** + * @api + */ + public function createDynamicSourceLocatorProvider(): DynamicSourceLocatorProvider + { + return $this->container->getByType(DynamicSourceLocatorProvider::class); + } } diff --git a/packages/node-type-resolver/src/NodeTypeCorrector/GenericClassStringTypeCorrector.php b/packages/node-type-resolver/src/NodeTypeCorrector/GenericClassStringTypeCorrector.php index 92d63871714a..54a4a1438068 100644 --- a/packages/node-type-resolver/src/NodeTypeCorrector/GenericClassStringTypeCorrector.php +++ b/packages/node-type-resolver/src/NodeTypeCorrector/GenericClassStringTypeCorrector.php @@ -4,23 +4,23 @@ namespace Rector\NodeTypeResolver\NodeTypeCorrector; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Generic\GenericClassStringType; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; use PHPStan\Type\TypeTraverser; -use Symplify\PackageBuilder\Reflection\ClassLikeExistenceChecker; final class GenericClassStringTypeCorrector { /** - * @var ClassLikeExistenceChecker + * @var ReflectionProvider */ - private $classLikeExistenceChecker; + private $reflectionProvider; - public function __construct(ClassLikeExistenceChecker $classLikeExistenceChecker) + public function __construct(ReflectionProvider $reflectionProvider) { - $this->classLikeExistenceChecker = $classLikeExistenceChecker; + $this->reflectionProvider = $reflectionProvider; } public function correct(Type $mainType): Type @@ -31,7 +31,7 @@ public function correct(Type $mainType): Type return $traverse($type); } - if (! $this->classLikeExistenceChecker->doesClassLikeExist($type->getValue())) { + if (! $this->reflectionProvider->hasClass($type->getValue())) { return $traverse($type); } diff --git a/packages/node-type-resolver/src/NodeTypeCorrector/ParentClassLikeTypeCorrector.php b/packages/node-type-resolver/src/NodeTypeCorrector/ParentClassLikeTypeCorrector.php index e3f6f1521292..34fed1ba362f 100644 --- a/packages/node-type-resolver/src/NodeTypeCorrector/ParentClassLikeTypeCorrector.php +++ b/packages/node-type-resolver/src/NodeTypeCorrector/ParentClassLikeTypeCorrector.php @@ -8,7 +8,6 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeUtils; use PHPStan\Type\TypeWithClassName; -use Rector\NodeTypeResolver\ClassExistenceStaticHelper; use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory; use Rector\NodeTypeResolver\Reflection\ClassReflectionTypesResolver; @@ -42,7 +41,7 @@ public function __construct( public function correct(Type $type): Type { if ($type instanceof TypeWithClassName) { - if (! ClassExistenceStaticHelper::doesClassLikeExist($type->getClassName())) { + if (! $this->reflectionProvider->hasClass($type->getClassName())) { return $type; } @@ -54,7 +53,7 @@ public function correct(Type $type): Type $allTypes = []; foreach ($classNames as $className) { - if (! ClassExistenceStaticHelper::doesClassLikeExist($className)) { + if (! $this->reflectionProvider->hasClass($className)) { continue; } diff --git a/packages/node-type-resolver/src/NodeTypeResolver.php b/packages/node-type-resolver/src/NodeTypeResolver.php index f0e3fd3687b8..dc61d381ef93 100644 --- a/packages/node-type-resolver/src/NodeTypeResolver.php +++ b/packages/node-type-resolver/src/NodeTypeResolver.php @@ -5,7 +5,6 @@ namespace Rector\NodeTypeResolver; use PhpParser\Node; -use PhpParser\Node\Arg; use PhpParser\Node\Expr; use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\New_; @@ -34,7 +33,6 @@ use PHPStan\Type\UnionType; use Rector\Core\Exception\ShouldNotHappenException; use Rector\Core\NodeAnalyzer\ClassAnalyzer; -use Rector\Core\Util\StaticInstanceOf; use Rector\NodeTypeResolver\Contract\NodeTypeResolverInterface; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\NodeTypeResolver\NodeTypeCorrector\GenericClassStringTypeCorrector; @@ -144,7 +142,6 @@ public function isObjectType(Node $node, ObjectType $requiredObjectType): bool // this should also work with ObjectType and UnionType with ObjectType // use PHPStan types here - if ($resolvedType->equals($requiredObjectType)) { return true; } @@ -155,6 +152,15 @@ public function isObjectType(Node $node, ObjectType $requiredObjectType): bool public function resolve(Node $node): Type { $type = $this->resolveFirstType($node); + + if ($type instanceof IntersectionType) { + foreach ($type->getTypes() as $intersectionedType) { + if ($intersectionedType instanceof TypeWithClassName) { + return $this->parentClassLikeTypeCorrector->correct($intersectionedType); + } + } + } + if (! $type instanceof TypeWithClassName) { return $type; } @@ -187,24 +193,24 @@ public function getNativeType(Expr $expr): Type */ public function getStaticType(Node $node): Type { - if ($this->isArrayExpr($node)) { - /** @var Expr $node */ - return $this->resolveArrayType($node); + if ($node instanceof Param) { + return $this->resolve($node); } - if ($node instanceof Arg) { - throw new ShouldNotHappenException('Arg cannot have type, use $arg->value instead'); + if (! $node instanceof Expr) { + return new MixedType(); } - if (StaticInstanceOf::isOneOf($node, [Param::class, Scalar::class])) { - return $this->resolve($node); + if ($this->arrayTypeAnalyzer->isArrayType($node)) { + return $this->resolveArrayType($node); } - $nodeScope = $node->getAttribute(AttributeKey::SCOPE); - if (! $node instanceof Expr) { - return new MixedType(); + if ($node instanceof Scalar) { + return $this->resolve($node); } - if (! $nodeScope instanceof Scope) { + + $scope = $node->getAttribute(AttributeKey::SCOPE); + if (! $scope instanceof Scope) { return new MixedType(); } @@ -215,7 +221,7 @@ public function getStaticType(Node $node): Type } } - $staticType = $nodeScope->getType($node); + $staticType = $scope->getType($node); if (! $staticType instanceof ObjectType) { return $staticType; } @@ -228,9 +234,13 @@ public function isNumberType(Node $node): bool if ($this->isStaticType($node, IntegerType::class)) { return true; } + return $this->isStaticType($node, FloatType::class); } + /** + * @param class-string $staticTypeClass + */ public function isStaticType(Node $node, string $staticTypeClass): bool { if (! is_a($staticTypeClass, Type::class, true)) { @@ -389,7 +399,9 @@ private function isMatchingUnionType(ObjectType $requiredObjectType, Type $resol continue; } - return true; + if ($unionedType->equals($requiredObjectType)) { + return true; + } } return false; @@ -432,14 +444,6 @@ private function resolveFirstType(Node $node): Type return $this->resolveFirstType($node->var); } - private function isArrayExpr(Node $node): bool - { - if (! $node instanceof Expr) { - return false; - } - return $this->arrayTypeAnalyzer->isArrayType($node); - } - private function resolveArrayType(Expr $expr): Type { /** @var Scope|null $scope */ diff --git a/packages/node-type-resolver/src/NodeTypeResolver/ArrayDimFetchTypeResolver.php b/packages/node-type-resolver/src/NodeTypeResolver/ArrayDimFetchTypeResolver.php index 590c919ddc4f..725c52c8ecef 100644 --- a/packages/node-type-resolver/src/NodeTypeResolver/ArrayDimFetchTypeResolver.php +++ b/packages/node-type-resolver/src/NodeTypeResolver/ArrayDimFetchTypeResolver.php @@ -27,7 +27,7 @@ public function autowireArrayDimFetchTypeResolver(NodeTypeResolver $nodeTypeReso } /** - * @return string[] + * @return array> */ public function getNodeClasses(): array { diff --git a/packages/node-type-resolver/src/NodeTypeResolver/CastTypeResolver.php b/packages/node-type-resolver/src/NodeTypeResolver/CastTypeResolver.php index 5339b710374b..51298786b5c2 100644 --- a/packages/node-type-resolver/src/NodeTypeResolver/CastTypeResolver.php +++ b/packages/node-type-resolver/src/NodeTypeResolver/CastTypeResolver.php @@ -26,7 +26,7 @@ public function autowireCastTypeResolver(NodeTypeResolver $nodeTypeResolver): vo } /** - * @return string[] + * @return array> */ public function getNodeClasses(): array { diff --git a/packages/node-type-resolver/src/NodeTypeResolver/ClassAndInterfaceTypeResolver.php b/packages/node-type-resolver/src/NodeTypeResolver/ClassAndInterfaceTypeResolver.php index 332cfe52487b..302062d4af7a 100644 --- a/packages/node-type-resolver/src/NodeTypeResolver/ClassAndInterfaceTypeResolver.php +++ b/packages/node-type-resolver/src/NodeTypeResolver/ClassAndInterfaceTypeResolver.php @@ -35,7 +35,7 @@ public function __construct(ClassReflectionTypesResolver $classReflectionTypesRe } /** - * @return string[] + * @return array> */ public function getNodeClasses(): array { diff --git a/packages/node-type-resolver/src/NodeTypeResolver/ClassConstFetchTypeResolver.php b/packages/node-type-resolver/src/NodeTypeResolver/ClassConstFetchTypeResolver.php index cd6a6786c6e6..23c5b6135c9c 100644 --- a/packages/node-type-resolver/src/NodeTypeResolver/ClassConstFetchTypeResolver.php +++ b/packages/node-type-resolver/src/NodeTypeResolver/ClassConstFetchTypeResolver.php @@ -26,7 +26,7 @@ public function autowireClassConstFetchTypeResolver(NodeTypeResolver $nodeTypeRe } /** - * @return string[] + * @return array> */ public function getNodeClasses(): array { diff --git a/packages/node-type-resolver/src/NodeTypeResolver/ClassMethodOrClassConstTypeResolver.php b/packages/node-type-resolver/src/NodeTypeResolver/ClassMethodOrClassConstTypeResolver.php index b63db658d675..c9bc8c9add3f 100644 --- a/packages/node-type-resolver/src/NodeTypeResolver/ClassMethodOrClassConstTypeResolver.php +++ b/packages/node-type-resolver/src/NodeTypeResolver/ClassMethodOrClassConstTypeResolver.php @@ -31,7 +31,7 @@ public function autowireClassMethodOrClassConstTypeResolver(NodeTypeResolver $no } /** - * @return string[] + * @return array> */ public function getNodeClasses(): array { diff --git a/packages/node-type-resolver/src/NodeTypeResolver/NameTypeResolver.php b/packages/node-type-resolver/src/NodeTypeResolver/NameTypeResolver.php index 90fb50aca9b2..4e8a52da9b9d 100644 --- a/packages/node-type-resolver/src/NodeTypeResolver/NameTypeResolver.php +++ b/packages/node-type-resolver/src/NodeTypeResolver/NameTypeResolver.php @@ -7,6 +7,7 @@ use PhpParser\Node; use PhpParser\Node\Name; use PhpParser\Node\Name\FullyQualified; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\MixedType; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; @@ -20,7 +21,17 @@ final class NameTypeResolver implements NodeTypeResolverInterface { /** - * @return string[] + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + + /** + * @return array> */ public function getNodeClasses(): array { @@ -45,22 +56,31 @@ public function resolve(Node $node): Type */ private function resolveParent(Name $name): Type { - /** @var string|null $parentClassName */ - $parentClassName = $name->getAttribute(AttributeKey::PARENT_CLASS_NAME); + $className = $name->getAttribute(AttributeKey::CLASS_NAME); + if ($className === null) { + return new MixedType(); + } - // missing parent class, probably unused parent:: call - if ($parentClassName === null) { + if (! $this->reflectionProvider->hasClass($className)) { return new MixedType(); } - $type = new ObjectType($parentClassName); + $classReflection = $this->reflectionProvider->getClass($className); + + $parentClassObjectTypes = []; + foreach ($classReflection->getParents() as $parentClassReflection) { + $parentClassObjectTypes[] = new ObjectType($parentClassReflection->getName()); + } + + if ($parentClassObjectTypes === []) { + return new MixedType(); + } - $parentParentClass = get_parent_class($parentClassName); - if ($parentParentClass) { - $type = new UnionType([$type, new ObjectType($parentParentClass)]); + if (count($parentClassObjectTypes) === 1) { + return $parentClassObjectTypes[0]; } - return $type; + return new UnionType($parentClassObjectTypes); } private function resolveFullyQualifiedName(Name $name): string diff --git a/packages/node-type-resolver/src/NodeTypeResolver/ParamTypeResolver.php b/packages/node-type-resolver/src/NodeTypeResolver/ParamTypeResolver.php index e256035e9c4b..e53f14f1e1f1 100644 --- a/packages/node-type-resolver/src/NodeTypeResolver/ParamTypeResolver.php +++ b/packages/node-type-resolver/src/NodeTypeResolver/ParamTypeResolver.php @@ -75,7 +75,7 @@ public function autowireParamTypeResolver( } /** - * @return string[] + * @return array> */ public function getNodeClasses(): array { diff --git a/packages/node-type-resolver/src/NodeTypeResolver/PropertyFetchTypeResolver.php b/packages/node-type-resolver/src/NodeTypeResolver/PropertyFetchTypeResolver.php index 8d509c45eaf2..84c807e1ef44 100644 --- a/packages/node-type-resolver/src/NodeTypeResolver/PropertyFetchTypeResolver.php +++ b/packages/node-type-resolver/src/NodeTypeResolver/PropertyFetchTypeResolver.php @@ -5,32 +5,19 @@ namespace Rector\NodeTypeResolver\NodeTypeResolver; use PhpParser\Node; -use PhpParser\Node\Expr\Property; use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Stmt\ClassLike; -use PhpParser\Node\Stmt\Nop; -use PhpParser\Node\Stmt\PropertyProperty; use PhpParser\Node\Stmt\Trait_; -use PhpParser\Node\VarLikeIdentifier; -use PhpParser\Parser; use PHPStan\Analyser\Scope; -use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode; -use PHPStan\PhpDocParser\Ast\Type\TypeNode; -use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\MixedType; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; -use PHPStan\Type\TypeWithClassName; -use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; -use Rector\Core\PhpParser\Node\BetterNodeFinder; -use Rector\NodeCollector\NodeCollector\NodeRepository; use Rector\NodeNameResolver\NodeNameResolver; use Rector\NodeTypeResolver\Contract\NodeTypeResolverInterface; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\NodeTypeResolver\NodeTypeResolver; use Rector\NodeTypeResolver\PHPStan\Collector\TraitNodeScopeCollector; -use Rector\StaticTypeMapper\StaticTypeMapper; -use Symplify\SmartFileSystem\SmartFileSystem; /** * @see \Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\PropertyFetchTypeResolver\PropertyFetchTypeResolverTest @@ -47,52 +34,24 @@ final class PropertyFetchTypeResolver implements NodeTypeResolverInterface */ private $nodeNameResolver; - /** - * @var StaticTypeMapper - */ - private $staticTypeMapper; - /** * @var TraitNodeScopeCollector */ private $traitNodeScopeCollector; /** - * @var SmartFileSystem - */ - private $smartFileSystem; - - /** - * @var BetterNodeFinder + * @var ReflectionProvider */ - private $betterNodeFinder; - - /** - * @var Parser - */ - private $parser; - - /** - * @var NodeRepository - */ - private $nodeRepository; + private $reflectionProvider; public function __construct( NodeNameResolver $nodeNameResolver, - StaticTypeMapper $staticTypeMapper, TraitNodeScopeCollector $traitNodeScopeCollector, - SmartFileSystem $smartFileSystem, - BetterNodeFinder $betterNodeFinder, - Parser $parser, - NodeRepository $nodeRepository + ReflectionProvider $reflectionProvider ) { $this->nodeNameResolver = $nodeNameResolver; - $this->staticTypeMapper = $staticTypeMapper; $this->traitNodeScopeCollector = $traitNodeScopeCollector; - $this->betterNodeFinder = $betterNodeFinder; - $this->smartFileSystem = $smartFileSystem; - $this->parser = $parser; - $this->nodeRepository = $nodeRepository; + $this->reflectionProvider = $reflectionProvider; } /** @@ -104,7 +63,7 @@ public function autowirePropertyFetchTypeResolver(NodeTypeResolver $nodeTypeReso } /** - * @return string[] + * @return array> */ public function getNodeClasses(): array { @@ -152,79 +111,29 @@ public function resolve(Node $node): Type private function getVendorPropertyFetchType(PropertyFetch $propertyFetch): Type { - $varObjectType = $this->nodeTypeResolver->resolve($propertyFetch->var); - if (! $varObjectType instanceof TypeWithClassName) { - return new MixedType(); - } - - $class = $this->nodeRepository->findClass($varObjectType->getClassName()); - if ($class !== null) { - return new MixedType(); - } - // 3rd party code $propertyName = $this->nodeNameResolver->getName($propertyFetch->name); if ($propertyName === null) { return new MixedType(); } - if (! property_exists($varObjectType->getClassName(), $propertyName)) { + $varType = $this->nodeTypeResolver->resolve($propertyFetch->var); + if (! $varType instanceof ObjectType) { return new MixedType(); } - $phpDocInfo = $propertyFetch->getAttribute(AttributeKey::PHP_DOC_INFO); - if (! $phpDocInfo instanceof PhpDocInfo && $varObjectType instanceof ObjectType) { - return $this->getPropertyPropertyResolution($varObjectType, $propertyName); - } - - return $this->getTypeFromPhpDocInfo($phpDocInfo); - } - - private function getPropertyPropertyResolution(ObjectType $varObjectType, string $propertyName): Type - { - $classReflection = $varObjectType->getClassReflection(); - if (! $classReflection instanceof ClassReflection) { - return $varObjectType; - } - - if ($classReflection->isBuiltIn()) { - return $varObjectType; - } - - $nodes = $this->parser->parse($this->smartFileSystem->readFile((string) $classReflection->getFileName())); - $propertyProperty = $this->betterNodeFinder->findFirst($nodes, function (Node $node) use ( - $propertyName - ): bool { - if (! $node instanceof PropertyProperty) { - return false; - } - - if (! $node->name instanceof VarLikeIdentifier) { - return false; - } - - return $node->name->toString() === $propertyName; - }); - - if ($propertyProperty instanceof PropertyProperty) { - return $this->nodeTypeResolver->resolve($propertyProperty); - } - - return new MixedType(); - } - - private function getTypeFromPhpDocInfo(PhpDocInfo $phpDocInfo): Type - { - $tagValueNode = $phpDocInfo->getVarTagValueNode(); - if (! $tagValueNode instanceof VarTagValueNode) { + if (! $this->reflectionProvider->hasClass($varType->getClassName())) { return new MixedType(); } - $typeNode = $tagValueNode->type; - if (! $typeNode instanceof TypeNode) { + $classReflection = $this->reflectionProvider->getClass($varType->getClassName()); + if (! $classReflection->hasProperty($propertyName)) { return new MixedType(); } - return $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($typeNode, new Nop()); + $propertyFetchScope = $propertyFetch->getAttribute(AttributeKey::SCOPE); + $propertyReflection = $classReflection->getProperty($propertyName, $propertyFetchScope); + + return $propertyReflection->getReadableType(); } } diff --git a/packages/node-type-resolver/src/NodeTypeResolver/PropertyTypeResolver.php b/packages/node-type-resolver/src/NodeTypeResolver/PropertyTypeResolver.php index 36a50cdf14d0..c5083363b54e 100644 --- a/packages/node-type-resolver/src/NodeTypeResolver/PropertyTypeResolver.php +++ b/packages/node-type-resolver/src/NodeTypeResolver/PropertyTypeResolver.php @@ -32,7 +32,7 @@ public function autowirePropertyTypeResolver(NodeTypeResolver $nodeTypeResolver) } /** - * @return string[] + * @return array> */ public function getNodeClasses(): array { diff --git a/packages/node-type-resolver/src/NodeTypeResolver/StaticCallTypeResolver.php b/packages/node-type-resolver/src/NodeTypeResolver/StaticCallTypeResolver.php index 1a02a01c05eb..05393d1900d9 100644 --- a/packages/node-type-resolver/src/NodeTypeResolver/StaticCallTypeResolver.php +++ b/packages/node-type-resolver/src/NodeTypeResolver/StaticCallTypeResolver.php @@ -7,8 +7,10 @@ use PhpParser\Node; use PhpParser\Node\Expr\StaticCall; use PHPStan\Analyser\Scope; +use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ReflectionProvider; +use PHPStan\Type\ObjectType; use PHPStan\Type\Type; -use PHPStan\Type\TypeUtils; use Rector\NodeNameResolver\NodeNameResolver; use Rector\NodeTypeResolver\Contract\NodeTypeResolverInterface; use Rector\NodeTypeResolver\Node\AttributeKey; @@ -26,9 +28,15 @@ final class StaticCallTypeResolver implements NodeTypeResolverInterface */ private $nodeNameResolver; - public function __construct(NodeNameResolver $nodeNameResolver) + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct(NodeNameResolver $nodeNameResolver, ReflectionProvider $reflectionProvider) { $this->nodeNameResolver = $nodeNameResolver; + $this->reflectionProvider = $reflectionProvider; } /** @@ -40,7 +48,7 @@ public function autowireStaticCallTypeResolver(NodeTypeResolver $nodeTypeResolve } /** - * @return string[] + * @return array> */ public function getNodeClasses(): array { @@ -60,15 +68,26 @@ public function resolve(Node $node): Type return $classType; } - $classNames = TypeUtils::getDirectClassNames($classType); + if (! $classType instanceof ObjectType) { + return $classType; + } + + if (! $this->reflectionProvider->hasClass($classType->getClassName())) { + return $classType; + } + + $classReflection = $this->reflectionProvider->getClass($classType->getClassName()); $scope = $node->getAttribute(AttributeKey::SCOPE); if (! $scope instanceof Scope) { return $classType; } - foreach ($classNames as $className) { - if (! method_exists($className, $methodName)) { + /** @var ClassReflection[] $currentAndParentClassReflections */ + $currentAndParentClassReflections = array_merge([$classReflection], $classReflection->getParents()); + + foreach ($currentAndParentClassReflections as $currentAndParentClassReflection) { + if (! $currentAndParentClassReflection->hasMethod($methodName)) { continue; } diff --git a/packages/node-type-resolver/src/NodeTypeResolver/TraitTypeResolver.php b/packages/node-type-resolver/src/NodeTypeResolver/TraitTypeResolver.php index f0e10ce044a4..9b9b99304ecc 100644 --- a/packages/node-type-resolver/src/NodeTypeResolver/TraitTypeResolver.php +++ b/packages/node-type-resolver/src/NodeTypeResolver/TraitTypeResolver.php @@ -6,12 +6,12 @@ use PhpParser\Node; use PhpParser\Node\Stmt\Trait_; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\MixedType; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; use PHPStan\Type\UnionType; use Rector\NodeTypeResolver\Contract\NodeTypeResolverInterface; -use ReflectionClass; /** * @see \Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\TraitTypeResolver\TraitTypeResolverTest @@ -19,7 +19,17 @@ final class TraitTypeResolver implements NodeTypeResolverInterface { /** - * @return string[] + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + + /** + * @return array> */ public function getNodeClasses(): array { @@ -31,12 +41,17 @@ public function getNodeClasses(): array */ public function resolve(Node $traitNode): Type { - $reflectionClass = new ReflectionClass((string) $traitNode->namespacedName); + $traitName = (string) $traitNode->namespacedName; + if (! $this->reflectionProvider->hasClass($traitName)) { + return new MixedType(); + } + + $classReflection = $this->reflectionProvider->getClass($traitName); $types = []; - $types[] = new ObjectType($reflectionClass->getName()); + $types[] = new ObjectType($traitName); - foreach ($reflectionClass->getTraits() as $usedTraitReflection) { + foreach ($classReflection->getTraits() as $usedTraitReflection) { $types[] = new ObjectType($usedTraitReflection->getName()); } diff --git a/packages/node-type-resolver/src/NodeTypeResolver/VariableTypeResolver.php b/packages/node-type-resolver/src/NodeTypeResolver/VariableTypeResolver.php index 70a127d7e5a4..7b6cf21972bf 100644 --- a/packages/node-type-resolver/src/NodeTypeResolver/VariableTypeResolver.php +++ b/packages/node-type-resolver/src/NodeTypeResolver/VariableTypeResolver.php @@ -59,7 +59,7 @@ public function __construct( } /** - * @return string[] + * @return array> */ public function getNodeClasses(): array { diff --git a/packages/node-type-resolver/src/PHPStan/TypeHasher.php b/packages/node-type-resolver/src/PHPStan/TypeHasher.php index 2458132848a6..7a50cd31b5f9 100644 --- a/packages/node-type-resolver/src/PHPStan/TypeHasher.php +++ b/packages/node-type-resolver/src/PHPStan/TypeHasher.php @@ -4,7 +4,6 @@ namespace Rector\NodeTypeResolver\PHPStan; -use PHPStan\ShouldNotHappenException; use PHPStan\Type\ArrayType; use PHPStan\Type\ConstantType; use PHPStan\Type\Generic\GenericObjectType; @@ -54,11 +53,7 @@ private function createTypeHash(Type $type): string } if ($type instanceof ConstantType) { - if (method_exists($type, 'getValue')) { - return get_class($type) . $type->getValue(); - } - - throw new ShouldNotHappenException(); + return get_class($type) . $type->getValue(); } if ($type instanceof UnionType) { diff --git a/packages/node-type-resolver/src/Reflection/BetterReflection/RectorBetterReflectionSourceLocatorFactory.php b/packages/node-type-resolver/src/Reflection/BetterReflection/RectorBetterReflectionSourceLocatorFactory.php new file mode 100644 index 000000000000..4ce347b80995 --- /dev/null +++ b/packages/node-type-resolver/src/Reflection/BetterReflection/RectorBetterReflectionSourceLocatorFactory.php @@ -0,0 +1,37 @@ +betterReflectionSourceLocatorFactory = $betterReflectionSourceLocatorFactory; + $this->intermediateSourceLocator = $intermediateSourceLocator; + } + + public function create(): SourceLocator + { + $phpStanSourceLocator = $this->betterReflectionSourceLocatorFactory->create(); + return new AggregateSourceLocator([$this->intermediateSourceLocator, $phpStanSourceLocator]); + } +} diff --git a/packages/node-type-resolver/src/Reflection/BetterReflection/SourceLocator/IntermediateSourceLocator.php b/packages/node-type-resolver/src/Reflection/BetterReflection/SourceLocator/IntermediateSourceLocator.php new file mode 100644 index 000000000000..66b6defd2974 --- /dev/null +++ b/packages/node-type-resolver/src/Reflection/BetterReflection/SourceLocator/IntermediateSourceLocator.php @@ -0,0 +1,60 @@ +sourceLocatorProviders = $sourceLocatorProviders; + } + + public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection + { + foreach ($this->sourceLocatorProviders as $sourceLocatorProvider) { + $sourceLocator = $sourceLocatorProvider->provide(); + + $reflection = $sourceLocator->locateIdentifier($reflector, $identifier); + if ($reflection instanceof Reflection) { + return $reflection; + } + } + + return null; + } + + /** + * Find all identifiers of a type + * @return Reflection[] + */ + public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array + { + foreach ($this->sourceLocatorProviders as $sourceLocatorProvider) { + $sourceLocator = $sourceLocatorProvider->provide(); + + $reflections = $sourceLocator->locateIdentifiersByType($reflector, $identifierType); + if ($reflections !== []) { + return $reflections; + } + } + + return []; + } +} diff --git a/packages/node-type-resolver/src/Reflection/BetterReflection/SourceLocatorProvider/DynamicSourceLocatorProvider.php b/packages/node-type-resolver/src/Reflection/BetterReflection/SourceLocatorProvider/DynamicSourceLocatorProvider.php new file mode 100644 index 000000000000..4d5bf8c167a0 --- /dev/null +++ b/packages/node-type-resolver/src/Reflection/BetterReflection/SourceLocatorProvider/DynamicSourceLocatorProvider.php @@ -0,0 +1,53 @@ +fileNodesFetcher = $fileNodesFetcher; + } + + public function setFileInfo(SmartFileInfo $fileInfo): void + { + $this->fileInfos = [$fileInfo]; + } + + /** + * @param SmartFileInfo[] $fileInfos + */ + public function addFileInfos(array $fileInfos): void + { + $this->fileInfos = array_merge($this->fileInfos, $fileInfos); + } + + public function provide(): SourceLocator + { + $sourceLocators = []; + foreach ($this->fileInfos as $fileInfo) { + $sourceLocators[] = new OptimizedSingleFileSourceLocator($this->fileNodesFetcher, $fileInfo->getRealPath()); + } + + return new AggregateSourceLocator($sourceLocators); + } +} diff --git a/packages/php-attribute/src/AnnotationToAttributeConverter.php b/packages/php-attribute/src/AnnotationToAttributeConverter.php index 6886743ed001..1346572e3409 100644 --- a/packages/php-attribute/src/AnnotationToAttributeConverter.php +++ b/packages/php-attribute/src/AnnotationToAttributeConverter.php @@ -12,6 +12,7 @@ use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Function_; use PhpParser\Node\Stmt\Property; +use PHPStan\Reflection\ReflectionProvider; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTagRemover; use Rector\PhpAttribute\Contract\PhpAttributableTagNodeInterface; @@ -35,14 +36,21 @@ final class AnnotationToAttributeConverter */ private $phpDocTagRemover; + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + public function __construct( PhpAttributeGroupFactory $phpAttributeGroupFactory, PhpDocInfoFactory $phpDocInfoFactory, - PhpDocTagRemover $phpDocTagRemover + PhpDocTagRemover $phpDocTagRemover, + ReflectionProvider $reflectionProvider ) { $this->phpAttributeGroupFactory = $phpAttributeGroupFactory; $this->phpDocInfoFactory = $phpDocInfoFactory; $this->phpDocTagRemover = $phpDocTagRemover; + $this->reflectionProvider = $reflectionProvider; } /** @@ -96,7 +104,7 @@ private function filterOnlyExistingAttributes(array $phpAttributableTagNodes): a return array_filter( $phpAttributableTagNodes, function (PhpAttributableTagNodeInterface $phpAttributableTagNode): bool { - return class_exists($phpAttributableTagNode->getAttributeClassName()); + return $this->reflectionProvider->hasClass($phpAttributableTagNode->getAttributeClassName()); } ); } diff --git a/packages/phpstan-static-type-mapper/src/TypeAnalyzer/UnionTypeCommonTypeNarrower.php b/packages/phpstan-static-type-mapper/src/TypeAnalyzer/UnionTypeCommonTypeNarrower.php index 07cc734a609f..1484a140a499 100644 --- a/packages/phpstan-static-type-mapper/src/TypeAnalyzer/UnionTypeCommonTypeNarrower.php +++ b/packages/phpstan-static-type-mapper/src/TypeAnalyzer/UnionTypeCommonTypeNarrower.php @@ -4,16 +4,17 @@ namespace Rector\PHPStanStaticTypeMapper\TypeAnalyzer; -use Nette\Utils\Strings; use PhpParser\Node; use PhpParser\Node\Expr; use PhpParser\Node\Expr\BinaryOp; use PhpParser\Node\Stmt; +use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode; +use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Generic\GenericClassStringType; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; -use PHPStan\Type\TypeWithClassName; use PHPStan\Type\UnionType; use Rector\NodeTypeResolver\NodeTypeCorrector\GenericClassStringTypeCorrector; @@ -29,10 +30,7 @@ final class UnionTypeCommonTypeNarrower BinaryOp::class => [BinaryOp::class, Expr::class], Expr::class => [Node::class, Expr::class], Stmt::class => [Node::class, Stmt::class], - 'PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode' => [ - 'PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode', - 'PHPStan\PhpDocParser\Ast\Node', - ], + PhpDocTagValueNode::class => [PhpDocTagValueNode::class, \PHPStan\PhpDocParser\Ast\Node::class], ]; /** @@ -40,9 +38,17 @@ final class UnionTypeCommonTypeNarrower */ private $genericClassStringTypeCorrector; - public function __construct(GenericClassStringTypeCorrector $genericClassStringTypeCorrector) - { + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct( + GenericClassStringTypeCorrector $genericClassStringTypeCorrector, + ReflectionProvider $reflectionProvider + ) { $this->genericClassStringTypeCorrector = $genericClassStringTypeCorrector; + $this->reflectionProvider = $reflectionProvider; } public function narrowToSharedObjectType(UnionType $unionType): ?ObjectType @@ -79,9 +85,15 @@ public function narrowToGenericClassStringType(UnionType $unionType): Type return $unionType; } - if ($unionedType->getGenericType() instanceof TypeWithClassName) { - $availableTypes[] = $this->resolveClassParentClassesAndInterfaces($unionedType->getGenericType()); + $genericClassStrings = []; + if ($unionedType->getGenericType() instanceof ObjectType) { + $parentClassReflections = $this->resolveClassParentClassesAndInterfaces($unionedType->getGenericType()); + foreach ($parentClassReflections as $classReflection) { + $genericClassStrings[] = $classReflection->getName(); + } } + + $availableTypes[] = $genericClassStrings; } $genericClassStringType = $this->createGenericClassStringType($availableTypes); @@ -100,57 +112,43 @@ private function narrowToSharedTypes(UnionType $unionType): array $availableTypes = []; foreach ($unionType->getTypes() as $unionedType) { - if (! $unionedType instanceof TypeWithClassName) { + if (! $unionedType instanceof ObjectType) { return []; } - $availableTypes[] = $this->resolveClassParentClassesAndInterfaces($unionedType); + $typeClassReflections = $this->resolveClassParentClassesAndInterfaces($unionedType); + $typeClassNames = []; + foreach ($typeClassReflections as $classReflection) { + $typeClassNames[] = $classReflection->getName(); + } + + $availableTypes[] = $typeClassNames; } return $this->narrowAvailableTypes($availableTypes); } /** - * @return string[] + * @return ClassReflection[] */ - private function resolveClassParentClassesAndInterfaces(TypeWithClassName $typeWithClassName): array + private function resolveClassParentClassesAndInterfaces(ObjectType $objectType): array { - $parentClasses = class_parents($typeWithClassName->getClassName()); - if ($parentClasses === false) { - $parentClasses = []; - } - - $implementedInterfaces = class_implements($typeWithClassName->getClassName()); - if ($implementedInterfaces === false) { - $implementedInterfaces = []; + if (! $this->reflectionProvider->hasClass($objectType->getClassName())) { + return []; } - $implementedInterfaces = $this->filterOutNativeInterfaces($implementedInterfaces); + $classReflection = $this->reflectionProvider->getClass($objectType->getClassName()); // put earliest interfaces first - $implementedInterfaces = array_reverse($implementedInterfaces); + $implementedInterfaceClassReflections = array_reverse($classReflection->getInterfaces()); - $classParentClassesAndInterfaces = array_merge($implementedInterfaces, $parentClasses); + /** @var ClassReflection[] $parentClassAndInterfaceReflections */ + $parentClassAndInterfaceReflections = array_merge( + $implementedInterfaceClassReflections, + $classReflection->getParents() + ); - return array_unique($classParentClassesAndInterfaces); - } - - /** - * @param class-string[] $interfaces - * @return class-string[] - */ - private function filterOutNativeInterfaces(array $interfaces): array - { - foreach ($interfaces as $key => $implementedInterface) { - // remove native interfaces - if (Strings::contains($implementedInterface, '\\')) { - continue; - } - - unset($interfaces[$key]); - } - - return $interfaces; + return $this->filterOutNativeClassReflections($parentClassAndInterfaceReflections); } /** @@ -185,4 +183,15 @@ private function createGenericClassStringType(array $availableTypes): ?GenericCl return null; } + + /** + * @param ClassReflection[] $classReflections + * @return ClassReflection[] + */ + private function filterOutNativeClassReflections(array $classReflections): array + { + return array_filter($classReflections, function (ClassReflection $classReflection): bool { + return ! $classReflection->isBuiltin(); + }); + } } diff --git a/packages/phpstan-static-type-mapper/src/TypeMapper/ArrayTypeMapper.php b/packages/phpstan-static-type-mapper/src/TypeMapper/ArrayTypeMapper.php index ed929c7ee8a2..7f0ec3fb0a6d 100644 --- a/packages/phpstan-static-type-mapper/src/TypeMapper/ArrayTypeMapper.php +++ b/packages/phpstan-static-type-mapper/src/TypeMapper/ArrayTypeMapper.php @@ -8,6 +8,7 @@ use PhpParser\Node\Name; use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantIntegerType; @@ -27,7 +28,6 @@ use Rector\PHPStanStaticTypeMapper\PHPStanStaticTypeMapper; use Rector\PHPStanStaticTypeMapper\TypeAnalyzer\UnionTypeCommonTypeNarrower; use Rector\TypeDeclaration\TypeNormalizer; -use Symplify\PackageBuilder\Reflection\ClassLikeExistenceChecker; /** * @see \Rector\PHPStanStaticTypeMapper\Tests\TypeMapper\ArrayTypeMapperTest @@ -55,23 +55,24 @@ final class ArrayTypeMapper implements TypeMapperInterface private $unionTypeCommonTypeNarrower; /** - * @var ClassLikeExistenceChecker + * @var ReflectionProvider */ - private $classLikeExistenceChecker; + private $reflectionProvider; /** + * To avoid circular dependency * @required */ public function autowireArrayTypeMapper( PHPStanStaticTypeMapper $phpStanStaticTypeMapper, TypeNormalizer $typeNormalizer, UnionTypeCommonTypeNarrower $unionTypeCommonTypeNarrower, - ClassLikeExistenceChecker $classLikeExistenceChecker + ReflectionProvider $reflectionProvider ): void { $this->phpStanStaticTypeMapper = $phpStanStaticTypeMapper; $this->typeNormalizer = $typeNormalizer; $this->unionTypeCommonTypeNarrower = $unionTypeCommonTypeNarrower; - $this->classLikeExistenceChecker = $classLikeExistenceChecker; + $this->reflectionProvider = $reflectionProvider; } public function getNodeClass(): string @@ -254,9 +255,7 @@ private function createTypeNodeFromGenericClassStringType( GenericClassStringType $genericClassStringType ): AttributeAwareNodeInterface { $genericType = $genericClassStringType->getGenericType(); - if ($genericType instanceof ObjectType && ! $this->classLikeExistenceChecker->doesClassLikeExist( - $genericType->getClassName() - )) { + if ($genericType instanceof ObjectType && ! $this->reflectionProvider->hasClass($genericType->getClassName())) { return new AttributeAwareIdentifierTypeNode($genericType->getClassName()); } diff --git a/packages/phpstan-static-type-mapper/src/TypeMapper/ObjectTypeMapper.php b/packages/phpstan-static-type-mapper/src/TypeMapper/ObjectTypeMapper.php index 3fb4ae320872..b9368c3a86a4 100644 --- a/packages/phpstan-static-type-mapper/src/TypeMapper/ObjectTypeMapper.php +++ b/packages/phpstan-static-type-mapper/src/TypeMapper/ObjectTypeMapper.php @@ -10,13 +10,13 @@ use PhpParser\Node\Name\FullyQualified; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; use PHPStan\Type\VerbosityLevel; use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareGenericTypeNode; use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareIdentifierTypeNode; -use Rector\NodeTypeResolver\ClassExistenceStaticHelper; use Rector\PHPStanStaticTypeMapper\Contract\PHPStanStaticTypeMapperAwareInterface; use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface; use Rector\PHPStanStaticTypeMapper\PHPStanStaticTypeMapper; @@ -32,6 +32,16 @@ final class ObjectTypeMapper implements TypeMapperInterface, PHPStanStaticTypeMa */ private $phpStanStaticTypeMapper; + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + public function getNodeClass(): string { return ObjectType::class; @@ -120,7 +130,7 @@ public function mapToDocString(Type $type, ?Type $parentType = null): string return '\\' . $type->getClassName(); } - if (ClassExistenceStaticHelper::doesClassLikeExist($type->getClassName())) { + if ($this->reflectionProvider->hasClass($type->getClassName())) { // FQN by default return '\\' . $type->describe(VerbosityLevel::typeOnly()); } diff --git a/packages/post-rector/src/NodeAnalyzer/NetteInjectDetector.php b/packages/post-rector/src/NodeAnalyzer/NetteInjectDetector.php index de0daebb00d9..840c07ace728 100644 --- a/packages/post-rector/src/NodeAnalyzer/NetteInjectDetector.php +++ b/packages/post-rector/src/NodeAnalyzer/NetteInjectDetector.php @@ -5,12 +5,11 @@ namespace Rector\PostRector\NodeAnalyzer; use PhpParser\Node\Stmt\Class_; +use PHPStan\Reflection\ReflectionProvider; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\BetterPhpDocParser\ValueObject\PhpDocNode\Nette\NetteInjectTagNode; use Rector\Core\ValueObject\MethodName; use Rector\NodeNameResolver\NodeNameResolver; -use ReflectionClass; -use ReflectionMethod; final class NetteInjectDetector { @@ -24,10 +23,19 @@ final class NetteInjectDetector */ private $phpDocInfoFactory; - public function __construct(NodeNameResolver $nodeNameResolver, PhpDocInfoFactory $phpDocInfoFactory) - { + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct( + NodeNameResolver $nodeNameResolver, + PhpDocInfoFactory $phpDocInfoFactory, + ReflectionProvider $reflectionProvider + ) { $this->nodeNameResolver = $nodeNameResolver; $this->phpDocInfoFactory = $phpDocInfoFactory; + $this->reflectionProvider = $reflectionProvider; } public function isNetteInjectPreferred(Class_ $class): bool @@ -62,11 +70,16 @@ private function hasParentClassConstructor(Class_ $class): bool return false; } - if (! is_a($className, 'Nette\Application\IPresenter', true)) { + if (! $this->reflectionProvider->hasClass($className)) { return false; } - // has parent class + $classReflection = $this->reflectionProvider->getClass($className); + if (! $classReflection->isSubclassOf('Nette\Application\IPresenter')) { + return false; + } + + // has no parent class if ($class->extends === null) { return false; } @@ -78,18 +91,19 @@ private function hasParentClassConstructor(Class_ $class): bool } // prefer local constructor - $classReflection = new ReflectionClass($className); + $classReflection = $this->reflectionProvider->getClass($className); + if ($classReflection->hasMethod(MethodName::CONSTRUCT)) { - /** @var ReflectionMethod $constructorReflectionMethod */ $constructorReflectionMethod = $classReflection->getConstructor(); + $declaringClass = $constructorReflectionMethod->getDeclaringClass(); // be sure its local constructor - if ($constructorReflectionMethod->class === $className) { + if ($declaringClass->getName() === $className) { return false; } } - $classReflection = new ReflectionClass($parentClass); + $classReflection = $this->reflectionProvider->getClass($parentClass); return $classReflection->hasMethod(MethodName::CONSTRUCT); } } diff --git a/packages/post-rector/src/Rector/NameImportingPostRector.php b/packages/post-rector/src/Rector/NameImportingPostRector.php index 15411c4cddc8..210918e08d35 100644 --- a/packages/post-rector/src/Rector/NameImportingPostRector.php +++ b/packages/post-rector/src/Rector/NameImportingPostRector.php @@ -7,6 +7,7 @@ use PhpParser\Node; use PhpParser\Node\Name; use PhpParser\NodeVisitorAbstract; +use PHPStan\Reflection\ReflectionProvider; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\CodingStyle\ClassNameImport\ClassNameImportSkipper; use Rector\CodingStyle\Node\NameImporter; @@ -48,13 +49,19 @@ final class NameImportingPostRector extends NodeVisitorAbstract implements PostR */ private $nodeNameResolver; + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + public function __construct( ParameterProvider $parameterProvider, NameImporter $nameImporter, DocBlockNameImporter $docBlockNameImporter, ClassNameImportSkipper $classNameImportSkipper, PhpDocInfoFactory $phpDocInfoFactory, - NodeNameResolver $nodeNameResolver + NodeNameResolver $nodeNameResolver, + ReflectionProvider $reflectionProvider ) { $this->parameterProvider = $parameterProvider; $this->nameImporter = $nameImporter; @@ -62,6 +69,7 @@ public function __construct( $this->classNameImportSkipper = $classNameImportSkipper; $this->phpDocInfoFactory = $phpDocInfoFactory; $this->nodeNameResolver = $nodeNameResolver; + $this->reflectionProvider = $reflectionProvider; } public function enterNode(Node $node): ?Node @@ -102,15 +110,19 @@ private function processNodeName(Name $name): ?Node if (! is_callable($importName)) { return $this->nameImporter->importName($name); } + if (substr_count($name->toCodeString(), '\\') <= 1) { return $this->nameImporter->importName($name); } + if (! $this->classNameImportSkipper->isFoundInUse($name)) { return $this->nameImporter->importName($name); } - if (function_exists($name->getLast())) { + + if ($this->reflectionProvider->hasFunction(new Name($name->getLast()), null)) { return $this->nameImporter->importName($name); } + return null; } } diff --git a/packages/post-rector/src/Rector/UseAddingPostRector.php b/packages/post-rector/src/Rector/UseAddingPostRector.php index 8091d5985e6a..152eb2f4844c 100644 --- a/packages/post-rector/src/Rector/UseAddingPostRector.php +++ b/packages/post-rector/src/Rector/UseAddingPostRector.php @@ -92,12 +92,12 @@ public function beforeTraverse(array $nodes): array $this->useNodesToAddCollector->clear($smartFileInfo); // A. has namespace? add under it - $namespace = $this->betterNodeFinder->findFirstInstanceOf($nodes, Namespace_::class); - if ($namespace instanceof Namespace_) { + $foundNode = $this->betterNodeFinder->findFirstInstanceOf($nodes, Namespace_::class); + if ($foundNode instanceof Namespace_) { // first clean - $this->useImportsRemover->removeImportsFromNamespace($namespace, $removedShortUses); + $this->useImportsRemover->removeImportsFromNamespace($foundNode, $removedShortUses); // then add, to prevent adding + removing false positive of same short use - $this->useImportsAdder->addImportsToNamespace($namespace, $useImportTypes, $functionUseImportTypes); + $this->useImportsAdder->addImportsToNamespace($foundNode, $useImportTypes, $functionUseImportTypes); return $nodes; } diff --git a/packages/read-write/src/Guard/VariableToConstantGuard.php b/packages/read-write/src/Guard/VariableToConstantGuard.php index 6ca18011acc5..9ebab9473b62 100644 --- a/packages/read-write/src/Guard/VariableToConstantGuard.php +++ b/packages/read-write/src/Guard/VariableToConstantGuard.php @@ -6,10 +6,15 @@ use PhpParser\Node\Arg; use PhpParser\Node\Expr\FuncCall; +use PhpParser\Node\Name; +use PHPStan\Reflection\FunctionReflection; +use PHPStan\Reflection\Native\NativeFunctionReflection; +use PHPStan\Reflection\ReflectionProvider; use Rector\Core\Exception\ShouldNotHappenException; use Rector\NodeNameResolver\NodeNameResolver; use Rector\NodeTypeResolver\Node\AttributeKey; use ReflectionFunction; +use Symplify\PackageBuilder\Reflection\PrivatesAccessor; final class VariableToConstantGuard { @@ -23,9 +28,24 @@ final class VariableToConstantGuard */ private $nodeNameResolver; - public function __construct(NodeNameResolver $nodeNameResolver) - { + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + /** + * @var PrivatesAccessor + */ + private $privatesAccessor; + + public function __construct( + NodeNameResolver $nodeNameResolver, + ReflectionProvider $reflectionProvider, + PrivatesAccessor $privatesAccessor + ) { $this->nodeNameResolver = $nodeNameResolver; + $this->reflectionProvider = $reflectionProvider; + $this->privatesAccessor = $privatesAccessor; } public function isReadArg(Arg $arg): bool @@ -35,40 +55,50 @@ public function isReadArg(Arg $arg): bool return true; } - $functionName = $this->nodeNameResolver->getName($parentParent); - if ($functionName === null) { + $functionNameString = $this->nodeNameResolver->getName($parentParent); + if ($functionNameString === null) { return true; } - if (! function_exists($functionName)) { + $functionName = new Name($functionNameString); + $argScope = $arg->getAttribute(AttributeKey::SCOPE); + + if (! $this->reflectionProvider->hasFunction($functionName, $argScope)) { // we don't know return true; } - $referenceParametersPositions = $this->resolveFunctionReferencePositions($functionName); + $functionReflection = $this->reflectionProvider->getFunction($functionName, $argScope); + + $referenceParametersPositions = $this->resolveFunctionReferencePositions($functionReflection); if ($referenceParametersPositions === []) { // no reference always only write return true; } $argumentPosition = $this->getArgumentPosition($parentParent, $arg); - return ! in_array($argumentPosition, $referenceParametersPositions, true); } /** * @return int[] */ - private function resolveFunctionReferencePositions(string $functionName): array + private function resolveFunctionReferencePositions(FunctionReflection $functionReflection): array { - if (isset($this->referencePositionsByFunctionName[$functionName])) { - return $this->referencePositionsByFunctionName[$functionName]; + if (isset($this->referencePositionsByFunctionName[$functionReflection->getName()])) { + return $this->referencePositionsByFunctionName[$functionReflection->getName()]; } - $referencePositions = []; + // this is needed, as native function reflection does not have access to referenced parameters + if ($functionReflection instanceof NativeFunctionReflection) { + $nativeFunctionReflection = new ReflectionFunction($functionReflection->getName()); + } else { + $nativeFunctionReflection = $this->privatesAccessor->getPrivateProperty($functionReflection, 'reflection'); + } - $reflectionFunction = new ReflectionFunction($functionName); - foreach ($reflectionFunction->getParameters() as $position => $reflectionParameter) { + $referencePositions = []; + /** @var int $position */ + foreach ($nativeFunctionReflection->getParameters() as $position => $reflectionParameter) { if (! $reflectionParameter->isPassedByReference()) { continue; } @@ -76,7 +106,7 @@ private function resolveFunctionReferencePositions(string $functionName): array $referencePositions[] = $position; } - $this->referencePositionsByFunctionName[$functionName] = $referencePositions; + $this->referencePositionsByFunctionName[$functionReflection->getName()] = $referencePositions; return $referencePositions; } diff --git a/packages/rector-generator/src/Provider/NodeTypesProvider.php b/packages/rector-generator/src/Provider/NodeTypesProvider.php index 83060c1eb9f1..82d8590816cd 100644 --- a/packages/rector-generator/src/Provider/NodeTypesProvider.php +++ b/packages/rector-generator/src/Provider/NodeTypesProvider.php @@ -29,8 +29,7 @@ final class NodeTypesProvider public function provide(): array { $finder = new Finder(); - $finder = $finder - ->files() + $finder = $finder->files() ->in(self::PHP_PARSER_NODES_PATH); $fileInfos = iterator_to_array($finder->getIterator()); @@ -44,6 +43,7 @@ public function provide(): array if ($reflectionClass->isAbstract()) { continue; } + if ($reflectionClass->isInterface()) { continue; } diff --git a/packages/static-type-mapper/src/Mapper/PhpParserNodeMapper.php b/packages/static-type-mapper/src/Mapper/PhpParserNodeMapper.php index 440a10050297..479868437931 100644 --- a/packages/static-type-mapper/src/Mapper/PhpParserNodeMapper.php +++ b/packages/static-type-mapper/src/Mapper/PhpParserNodeMapper.php @@ -32,15 +32,16 @@ public function mapToPHPStanType(Node $node): Type if (! is_a($node, $phpParserNodeMapper->getNodeType())) { continue; } + // do not let Expr collect all the types // note: can be solve later with priorities on mapper interface, making this last if ($phpParserNodeMapper->getNodeType() !== Expr::class) { return $phpParserNodeMapper->mapToPHPStan($node); } - if (! is_a($node, String_::class)) { + + if (! $node instanceof String_) { return $phpParserNodeMapper->mapToPHPStan($node); } - continue; } throw new NotImplementedYetException(get_class($node)); diff --git a/packages/static-type-mapper/src/PhpParser/NameNodeMapper.php b/packages/static-type-mapper/src/PhpParser/NameNodeMapper.php index 1cbf7967acb2..7952c281d1ae 100644 --- a/packages/static-type-mapper/src/PhpParser/NameNodeMapper.php +++ b/packages/static-type-mapper/src/PhpParser/NameNodeMapper.php @@ -6,6 +6,7 @@ use PhpParser\Node; use PhpParser\Node\Name; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\ArrayType; use PHPStan\Type\BooleanType; use PHPStan\Type\FloatType; @@ -15,7 +16,6 @@ use PHPStan\Type\StringType; use PHPStan\Type\ThisType; use PHPStan\Type\Type; -use Rector\NodeTypeResolver\ClassExistenceStaticHelper; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\PSR4\Collector\RenamedClassesCollector; use Rector\StaticTypeMapper\Contract\PhpParser\PhpParserNodeMapperInterface; @@ -29,9 +29,17 @@ final class NameNodeMapper implements PhpParserNodeMapperInterface */ private $renamedClassesCollector; - public function __construct(RenamedClassesCollector $renamedClassesCollector) - { + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct( + RenamedClassesCollector $renamedClassesCollector, + ReflectionProvider $reflectionProvider + ) { $this->renamedClassesCollector = $renamedClassesCollector; + $this->reflectionProvider = $reflectionProvider; } public function getNodeType(): string @@ -58,7 +66,7 @@ public function mapToPHPStan(Node $node): Type private function isExistingClass(string $name): bool { - if (ClassExistenceStaticHelper::doesClassLikeExist($name)) { + if ($this->reflectionProvider->hasClass($name)) { return true; } @@ -79,6 +87,11 @@ private function createClassReferenceType(Name $name, string $reference): Type return new StaticType($className); } + if ($this->reflectionProvider->hasClass($className)) { + $classReflection = $this->reflectionProvider->getClass($className); + return new ThisType($classReflection); + } + return new ThisType($className); } diff --git a/packages/testing/src/PHPUnit/AbstractRectorTestCase.php b/packages/testing/src/PHPUnit/AbstractRectorTestCase.php index 60fe2761cbc5..3b05c7731694 100644 --- a/packages/testing/src/PHPUnit/AbstractRectorTestCase.php +++ b/packages/testing/src/PHPUnit/AbstractRectorTestCase.php @@ -20,6 +20,7 @@ use Rector\Core\PhpParser\Printer\BetterStandardPrinter; use Rector\Core\Stubs\StubLoader; use Rector\Core\ValueObject\StaticNonPhpFileSuffixes; +use Rector\NodeTypeResolver\Reflection\BetterReflection\SourceLocatorProvider\DynamicSourceLocatorProvider; use Rector\Testing\Application\EnabledRectorClassProvider; use Rector\Testing\Configuration\AllRectorConfigFactory; use Rector\Testing\Contract\RunnableInterface; @@ -87,11 +88,6 @@ abstract class AbstractRectorTestCase extends AbstractKernelTestCase */ private static $runnableRectorFactory; - /** - * @var bool - */ - private $autoloadTestFixture = true; - /** * @var RectorConfigsResolver */ @@ -154,13 +150,6 @@ protected function yieldFilesFromDirectory(string $directory, string $suffix = ' return StaticFixtureFinder::yieldDirectory($directory, $suffix); } - protected function doTestFileInfoWithoutAutoload(SmartFileInfo $fileInfo): void - { - $this->autoloadTestFixture = false; - $this->doTestFileInfo($fileInfo); - $this->autoloadTestFixture = true; - } - /** * @param SmartFileInfo[] $extraFileInfos */ @@ -170,7 +159,7 @@ protected function doTestFileInfo(SmartFileInfo $fixtureFileInfo, array $extraFi $inputFileInfoAndExpectedFileInfo = StaticFixtureSplitter::splitFileInfoToLocalInputAndExpectedFileInfos( $fixtureFileInfo, - $this->autoloadTestFixture + false ); $inputFileInfo = $inputFileInfoAndExpectedFileInfo->getInputFileInfo(); @@ -180,6 +169,10 @@ protected function doTestFileInfo(SmartFileInfo $fixtureFileInfo, array $extraFi $nodeScopeResolver = $this->getService(NodeScopeResolver::class); $nodeScopeResolver->setAnalysedFiles([$inputFileInfo->getRealPath()]); + /** @var DynamicSourceLocatorProvider $dynamicDirectoryLocatorProvider */ + $dynamicDirectoryLocatorProvider = $this->getService(DynamicSourceLocatorProvider::class); + $dynamicDirectoryLocatorProvider->setFileInfo($inputFileInfo); + $expectedFileInfo = $inputFileInfoAndExpectedFileInfo->getExpectedFileInfo(); $this->doTestFileMatchesExpectedContent($inputFileInfo, $expectedFileInfo, $fixtureFileInfo, $extraFileInfos); diff --git a/packages/vendor-locker/src/NodeVendorLocker/AbstractNodeVendorLockResolver.php b/packages/vendor-locker/src/NodeVendorLocker/AbstractNodeVendorLockResolver.php index 4f5507cb11b2..42d35170edbe 100644 --- a/packages/vendor-locker/src/NodeVendorLocker/AbstractNodeVendorLockResolver.php +++ b/packages/vendor-locker/src/NodeVendorLocker/AbstractNodeVendorLockResolver.php @@ -4,22 +4,13 @@ namespace Rector\VendorLocker\NodeVendorLocker; -use PhpParser\Node\Stmt\Class_; -use PhpParser\Node\Stmt\ClassLike; -use PhpParser\Node\Stmt\Interface_; -use Rector\Core\NodeManipulator\ClassManipulator; +use PHPStan\Reflection\ClassReflection; use Rector\FamilyTree\Reflection\FamilyRelationsAnalyzer; use Rector\NodeCollector\NodeCollector\NodeRepository; use Rector\NodeNameResolver\NodeNameResolver; -use Rector\NodeTypeResolver\Node\AttributeKey; abstract class AbstractNodeVendorLockResolver { - /** - * @var ClassManipulator - */ - protected $classManipulator; - /** * @var NodeNameResolver */ @@ -33,80 +24,53 @@ abstract class AbstractNodeVendorLockResolver /** * @var FamilyRelationsAnalyzer */ - private $familyRelationsAnalyzer; + protected $familyRelationsAnalyzer; /** * @required */ public function autowireAbstractNodeVendorLockResolver( NodeRepository $nodeRepository, - ClassManipulator $classManipulator, NodeNameResolver $nodeNameResolver, FamilyRelationsAnalyzer $familyRelationsAnalyzer ): void { $this->nodeRepository = $nodeRepository; - $this->classManipulator = $classManipulator; $this->nodeNameResolver = $nodeNameResolver; $this->familyRelationsAnalyzer = $familyRelationsAnalyzer; } - protected function hasParentClassChildrenClassesOrImplementsInterface(ClassLike $classLike): bool + protected function hasParentClassChildrenClassesOrImplementsInterface(ClassReflection $classReflection): bool { - if (($classLike instanceof Class_ || $classLike instanceof Interface_) && $classLike->extends) { - return true; - } - - if ($classLike instanceof Class_) { - if ((bool) $classLike->implements) { + if ($classReflection->isClass()) { + // has at least interface + if ($classReflection->getInterfaces() !== []) { return true; } - /** @var Class_ $classLike */ - return $this->getChildrenClassesByClass($classLike) !== []; - } - - return false; - } - - /** - * @param Class_|Interface_ $classLike - */ - protected function isMethodVendorLockedByInterface(ClassLike $classLike, string $methodName): bool - { - $interfaceNames = $this->classManipulator->getClassLikeNodeParentInterfaceNames($classLike); - - foreach ($interfaceNames as $interfaceName) { - if (! $this->hasInterfaceMethod($methodName, $interfaceName)) { - continue; + // has at least one parent class + if ($classReflection->getParents() !== []) { + return true; } - return true; + $childrenClassReflections = $this->familyRelationsAnalyzer->getChildrenOfClassReflection($classReflection); + return $childrenClassReflections !== []; } - return false; - } - - /** - * @return class-string[] - */ - protected function getChildrenClassesByClass(Class_ $class): array - { - $desiredClassName = $class->getAttribute(AttributeKey::CLASS_NAME); - if ($desiredClassName === null) { - return []; + if ($classReflection->isInterface()) { + return $classReflection->getInterfaces() !== []; } - return $this->familyRelationsAnalyzer->getChildrenOfClass($desiredClassName); + return false; } - private function hasInterfaceMethod(string $methodName, string $interfaceName): bool + protected function isMethodVendorLockedByInterface(ClassReflection $classReflection, string $methodName): bool { - if (! interface_exists($interfaceName)) { - return false; + foreach ($classReflection->getInterfaces() as $interfaceReflection) { + if ($interfaceReflection->hasMethod($methodName)) { + return true; + } } - $interfaceMethods = get_class_methods($interfaceName); - - return in_array($methodName, $interfaceMethods, true); + return false; } } diff --git a/packages/vendor-locker/src/NodeVendorLocker/ClassMethodParamVendorLockResolver.php b/packages/vendor-locker/src/NodeVendorLocker/ClassMethodParamVendorLockResolver.php index b4d2262f8166..27c8712896d0 100644 --- a/packages/vendor-locker/src/NodeVendorLocker/ClassMethodParamVendorLockResolver.php +++ b/packages/vendor-locker/src/NodeVendorLocker/ClassMethodParamVendorLockResolver.php @@ -4,49 +4,59 @@ namespace Rector\VendorLocker\NodeVendorLocker; -use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; -use PhpParser\Node\Stmt\Interface_; +use PHPStan\Analyser\Scope; +use PHPStan\Reflection\ClassReflection; use Rector\NodeTypeResolver\Node\AttributeKey; final class ClassMethodParamVendorLockResolver extends AbstractNodeVendorLockResolver { public function isVendorLocked(ClassMethod $classMethod, int $paramPosition): bool { - $classNode = $classMethod->getAttribute(AttributeKey::CLASS_NODE); - if (! $classNode instanceof Class_) { + $scope = $classMethod->getAttribute(AttributeKey::SCOPE); + if (! $scope instanceof Scope) { return false; } - if (! $this->hasParentClassChildrenClassesOrImplementsInterface($classNode)) { + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { + return false; + } + + if (! $this->hasParentClassChildrenClassesOrImplementsInterface($classReflection)) { return false; } - /** @var string $methodName */ $methodName = $this->nodeNameResolver->getName($classMethod); - /** @var string|null $parentClassName */ - $parentClassName = $classMethod->getAttribute(AttributeKey::PARENT_CLASS_NAME); - if ($parentClassName !== null) { - $vendorLock = $this->isParentClassVendorLocking($paramPosition, $parentClassName, $methodName); + if ($classReflection->getParentClass() !== false) { + $vendorLock = $this->isParentClassVendorLocking( + $classReflection->getParentClass(), + $paramPosition, + $methodName + ); if ($vendorLock !== null) { return $vendorLock; } } - $classNode = $classMethod->getAttribute(AttributeKey::CLASS_NODE); - if ($classNode instanceof Class_) { - return $this->isMethodVendorLockedByInterface($classNode, $methodName); + if ($classReflection->isClass()) { + return $this->isMethodVendorLockedByInterface($classReflection, $methodName); } - if ($classNode instanceof Interface_) { - return $this->isMethodVendorLockedByInterface($classNode, $methodName); + + if ($classReflection->isInterface()) { + return $this->isMethodVendorLockedByInterface($classReflection, $methodName); } + return false; } - private function isParentClassVendorLocking(int $paramPosition, string $parentClassName, string $methodName): ?bool - { - $parentClass = $this->nodeRepository->findClass($parentClassName); + private function isParentClassVendorLocking( + ClassReflection $parentClassReflection, + int $paramPosition, + string $methodName + ): ?bool { + $parentClass = $this->nodeRepository->findClass($parentClassReflection->getName()); if ($parentClass !== null) { $parentClassMethod = $parentClass->getMethod($methodName); // parent class method in local scope → it's ok @@ -58,7 +68,8 @@ private function isParentClassVendorLocking(int $paramPosition, string $parentCl return $parentClassMethod->params[$paramPosition]->type === null; } } - if (method_exists($parentClassName, $methodName)) { + + if ($parentClassReflection->hasMethod($methodName)) { // parent class method in external scope → it's not ok // if not, look for it's parent parent return true; diff --git a/packages/vendor-locker/src/NodeVendorLocker/ClassMethodReturnTypeOverrideGuard.php b/packages/vendor-locker/src/NodeVendorLocker/ClassMethodReturnTypeOverrideGuard.php index a006fb13ee2c..9bfb154df866 100644 --- a/packages/vendor-locker/src/NodeVendorLocker/ClassMethodReturnTypeOverrideGuard.php +++ b/packages/vendor-locker/src/NodeVendorLocker/ClassMethodReturnTypeOverrideGuard.php @@ -6,17 +6,20 @@ use PhpParser\Node; use PhpParser\Node\Expr; -use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; +//use PHPStan\Analyser\Scope; use PhpParser\Node\Stmt\Return_; -use PhpParser\NodeVisitor; +use PHPStan\Analyser\Scope; +use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\ArrayType; use PHPStan\Type\MixedType; use PHPStan\Type\Type; use PHPStan\Type\TypeWithClassName; use PHPStan\Type\UnionType; +use Rector\Core\Exception\ShouldNotHappenException; use Rector\Core\PhpParser\Node\BetterNodeFinder; -use Rector\NodeCollector\NodeCollector\NodeRepository; +use Rector\FamilyTree\Reflection\FamilyRelationsAnalyzer; use Rector\NodeNameResolver\NodeNameResolver; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\NodeTypeResolver\NodeTypeResolver; @@ -24,10 +27,10 @@ final class ClassMethodReturnTypeOverrideGuard { /** - * @var array> + * @var array> */ private const CHAOTIC_CLASS_METHOD_NAMES = [ - NodeVisitor::class => ['enterNode', 'leaveNode', 'beforeTraverse', 'afterTraverse'], + 'PhpParser\NodeVisitor' => ['enterNode', 'leaveNode', 'beforeTraverse', 'afterTraverse'], ]; /** @@ -41,9 +44,14 @@ final class ClassMethodReturnTypeOverrideGuard private $nodeTypeResolver; /** - * @var NodeRepository + * @var ReflectionProvider */ - private $nodeRepository; + private $reflectionProvider; + + /** + * @var FamilyRelationsAnalyzer + */ + private $familyRelationsAnalyzer; /** * @var BetterNodeFinder @@ -53,19 +61,21 @@ final class ClassMethodReturnTypeOverrideGuard public function __construct( NodeNameResolver $nodeNameResolver, NodeTypeResolver $nodeTypeResolver, - NodeRepository $nodeRepository, + ReflectionProvider $reflectionProvider, + FamilyRelationsAnalyzer $familyRelationsAnalyzer, BetterNodeFinder $betterNodeFinder ) { $this->nodeNameResolver = $nodeNameResolver; $this->nodeTypeResolver = $nodeTypeResolver; - $this->nodeRepository = $nodeRepository; + $this->reflectionProvider = $reflectionProvider; + $this->familyRelationsAnalyzer = $familyRelationsAnalyzer; $this->betterNodeFinder = $betterNodeFinder; } public function shouldSkipClassMethod(ClassMethod $classMethod): bool { // 1. skip magic methods - if ($this->nodeNameResolver->isName($classMethod->name, '__*')) { + if ($classMethod->isMagic()) { return true; } @@ -74,24 +84,22 @@ public function shouldSkipClassMethod(ClassMethod $classMethod): bool return true; } - // 3. skip has children and current has no return - $class = $classMethod->getAttribute(AttributeKey::PARENT_NODE); - $hasChildren = $class instanceof Class_ && $this->nodeRepository->hasClassChildren($class); - if (! $hasChildren) { + $scope = $classMethod->getAttribute(AttributeKey::SCOPE); + if (! $scope instanceof Scope) { return false; } - $hasReturn = (bool) $this->betterNodeFinder->findFirst( - (array) $classMethod->stmts, - function (Node $node): bool { - if (! $node instanceof Return_) { - return false; - } - return $node->expr instanceof Expr; - } - ); + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { + throw new ShouldNotHappenException(); + } - if ($hasReturn) { + $childrenClassReflections = $this->familyRelationsAnalyzer->getChildrenOfClassReflection($classReflection); + if ($childrenClassReflections === []) { + return false; + } + + if ($this->hasClassMethodExprReturn($classMethod)) { return false; } @@ -119,15 +127,27 @@ private function shouldSkipChaoticClassMethods(ClassMethod $classMethod): bool return false; } - /** @var string $methodName */ - $methodName = $this->nodeNameResolver->getName($classMethod); + $scope = $classMethod->getAttribute(AttributeKey::SCOPE); + if (! $scope instanceof Scope) { + return false; + } + + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { + return false; + } foreach (self::CHAOTIC_CLASS_METHOD_NAMES as $chaoticClass => $chaoticMethodNames) { - if (! is_a($className, $chaoticClass, true)) { + if (! $this->reflectionProvider->hasClass($chaoticClass)) { + continue; + } + + $chaoticClassReflection = $this->reflectionProvider->getClass($chaoticClass); + if (! $classReflection->isSubclassOf($chaoticClassReflection->getName())) { continue; } - return in_array($methodName, $chaoticMethodNames, true); + return $this->nodeNameResolver->isNames($classMethod, $chaoticMethodNames); } return false; @@ -172,4 +192,15 @@ private function isArrayMutualType(Type $newType, Type $oldType): bool return $isMatchingClassTypes; } + + private function hasClassMethodExprReturn(ClassMethod $classMethod): bool + { + return (bool) $this->betterNodeFinder->findFirst($classMethod->getStmts(), function (Node $node): bool { + if (! $node instanceof Return_) { + return false; + } + + return $node->expr instanceof Expr; + }); + } } diff --git a/packages/vendor-locker/src/NodeVendorLocker/ClassMethodReturnVendorLockResolver.php b/packages/vendor-locker/src/NodeVendorLocker/ClassMethodReturnVendorLockResolver.php index 6864cc3245c1..6a2ec7a3d840 100644 --- a/packages/vendor-locker/src/NodeVendorLocker/ClassMethodReturnVendorLockResolver.php +++ b/packages/vendor-locker/src/NodeVendorLocker/ClassMethodReturnVendorLockResolver.php @@ -4,60 +4,60 @@ namespace Rector\VendorLocker\NodeVendorLocker; -use PhpParser\Node\Stmt\Class_; -use PhpParser\Node\Stmt\ClassLike; use PhpParser\Node\Stmt\ClassMethod; -use PhpParser\Node\Stmt\Interface_; +use PHPStan\Analyser\Scope; +use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\FunctionVariantWithPhpDocs; +use PHPStan\Type\MixedType; use Rector\NodeTypeResolver\Node\AttributeKey; final class ClassMethodReturnVendorLockResolver extends AbstractNodeVendorLockResolver { public function isVendorLocked(ClassMethod $classMethod): bool { - $classNode = $classMethod->getAttribute(AttributeKey::CLASS_NODE); - if (! $classNode instanceof ClassLike) { + $scope = $classMethod->getAttribute(AttributeKey::SCOPE); + if (! $scope instanceof Scope) { return false; } - if (! $this->hasParentClassChildrenClassesOrImplementsInterface($classNode)) { + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { return false; } - /** @var string $methodName */ - $methodName = $this->nodeNameResolver->getName($classMethod); - - /** @var string|null $parentClassName */ - $parentClassName = $classMethod->getAttribute(AttributeKey::PARENT_CLASS_NAME); - if ($parentClassName !== null) { - return $this->isVendorLockedByParentClass($parentClassName, $methodName); + if (! $this->hasParentClassChildrenClassesOrImplementsInterface($classReflection)) { + return false; } - $classNode = $classMethod->getAttribute(AttributeKey::CLASS_NODE); - if ($classNode instanceof Class_) { - return $this->isMethodVendorLockedByInterface($classNode, $methodName); + $methodName = $this->nodeNameResolver->getName($classMethod); + if ($this->isVendorLockedByParentClass($classReflection, $methodName)) { + return true; } - if ($classNode instanceof Interface_) { - return $this->isMethodVendorLockedByInterface($classNode, $methodName); + + if ($classReflection->isTrait()) { + return false; } - return false; + + return $this->isMethodVendorLockedByInterface($classReflection, $methodName); } - private function isVendorLockedByParentClass(string $parentClassName, string $methodName): bool + private function isVendorLockedByParentClass(ClassReflection $classReflection, string $methodName): bool { - $parentClass = $this->nodeRepository->findClass($parentClassName); - if ($parentClass !== null) { - $parentClassMethod = $parentClass->getMethod($methodName); - // validate type is conflicting - // parent class method in local scope → it's ok - if ($parentClassMethod !== null) { - return $parentClassMethod->returnType !== null; + foreach ($classReflection->getParents() as $parentClassReflections) { + if (! $parentClassReflections->hasMethod($methodName)) { + continue; } - // if not, look for it's parent parent + $parentClassMethodReflection = $parentClassReflections->getNativeMethod($methodName); + $parametersAcceptor = $parentClassMethodReflection->getVariants()[0]; + if (! $parametersAcceptor instanceof FunctionVariantWithPhpDocs) { + continue; + } + + // here we count only on strict types, not on docs + return ! $parametersAcceptor->getNativeReturnType() instanceof MixedType; } - // validate type is conflicting - // parent class method in external scope → it's not ok - return method_exists($parentClassName, $methodName); + return false; } } diff --git a/packages/vendor-locker/src/NodeVendorLocker/ClassMethodVendorLockResolver.php b/packages/vendor-locker/src/NodeVendorLocker/ClassMethodVendorLockResolver.php index e9e9a6a00fbd..2783389a4889 100644 --- a/packages/vendor-locker/src/NodeVendorLocker/ClassMethodVendorLockResolver.php +++ b/packages/vendor-locker/src/NodeVendorLocker/ClassMethodVendorLockResolver.php @@ -4,11 +4,11 @@ namespace Rector\VendorLocker\NodeVendorLocker; -use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; -use PhpParser\Node\Stmt\Interface_; +use PHPStan\Analyser\Scope; +use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\Php\PhpMethodReflection; use Rector\NodeTypeResolver\Node\AttributeKey; -use ReflectionClass; final class ClassMethodVendorLockResolver extends AbstractNodeVendorLockResolver { @@ -22,45 +22,33 @@ final class ClassMethodVendorLockResolver extends AbstractNodeVendorLockResolver */ public function isRemovalVendorLocked(ClassMethod $classMethod): bool { - /** @var string $classMethodName */ $classMethodName = $this->nodeNameResolver->getName($classMethod); - /** @var Class_|Interface_|null $classLike */ - $classLike = $classMethod->getAttribute(AttributeKey::CLASS_NODE); - if ($classLike === null) { + /** @var Scope $scope */ + $scope = $classMethod->getAttribute(AttributeKey::SCOPE); + + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { return false; } - if ($this->isMethodVendorLockedByInterface($classLike, $classMethodName)) { + if ($this->isMethodVendorLockedByInterface($classReflection, $classMethodName)) { return true; } - if ($classLike->extends === null) { - return false; - } - - /** @var string $className */ - $className = $classMethod->getAttribute(AttributeKey::CLASS_NAME); - - /** @var string[] $classParents */ - $classParents = (array) class_parents($className); - - foreach ($classParents as $classParent) { - if (! class_exists($classParent)) { - continue; - } - - $parentClassReflection = new ReflectionClass($classParent); + foreach ($classReflection->getParents() as $parentClassReflection) { if (! $parentClassReflection->hasMethod($classMethodName)) { continue; } - $methodReflection = $parentClassReflection->getMethod($classMethodName); - if (! $methodReflection->isAbstract()) { + $methodReflection = $parentClassReflection->getNativeMethod($classMethodName); + if (! $methodReflection instanceof PhpMethodReflection) { continue; } - return true; + if ($methodReflection->isAbstract()) { + return true; + } } return false; diff --git a/packages/vendor-locker/src/NodeVendorLocker/ClassMethodVisibilityVendorLockResolver.php b/packages/vendor-locker/src/NodeVendorLocker/ClassMethodVisibilityVendorLockResolver.php index 2fb0696c2fcf..9f20dc82176c 100644 --- a/packages/vendor-locker/src/NodeVendorLocker/ClassMethodVisibilityVendorLockResolver.php +++ b/packages/vendor-locker/src/NodeVendorLocker/ClassMethodVisibilityVendorLockResolver.php @@ -5,6 +5,8 @@ namespace Rector\VendorLocker\NodeVendorLocker; use PhpParser\Node\Stmt\ClassMethod; +use PHPStan\Analyser\Scope; +use PHPStan\Reflection\ClassReflection; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\Privatization\VisibilityGuard\ClassMethodVisibilityGuard; @@ -25,85 +27,47 @@ final class ClassMethodVisibilityVendorLockResolver extends AbstractNodeVendorLo */ public function isParentLockedMethod(ClassMethod $classMethod): bool { - /** @var string $className */ - $className = $classMethod->getAttribute(AttributeKey::CLASS_NAME); + /** @var Scope $scope */ + $scope = $classMethod->getAttribute(AttributeKey::SCOPE); - if ($this->isInterfaceMethod($classMethod, $className)) { - return true; + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { + return false; } - /** @var string $methodName */ $methodName = $this->nodeNameResolver->getName($classMethod); - return $this->hasParentMethod($className, $methodName); - } - - public function isChildLockedMethod(ClassMethod $classMethod): bool - { - /** @var string $className */ - $className = $classMethod->getAttribute(AttributeKey::CLASS_NAME); - - /** @var string $methodName */ - $methodName = $this->nodeNameResolver->getName($classMethod); - - return $this->hasChildMethod($className, $methodName); - } + /** @var ClassReflection[] $parentClassReflections */ + $parentClassReflections = array_merge($classReflection->getParents(), $classReflection->getInterfaces()); - private function isInterfaceMethod(ClassMethod $classMethod, string $className): bool - { - $interfaceMethodNames = $this->getInterfaceMethodNames($className); - return $this->nodeNameResolver->isNames($classMethod, $interfaceMethodNames); - } - - private function hasParentMethod(string $className, string $methodName): bool - { - /** @var string[] $parentClasses */ - $parentClasses = (array) class_parents($className); - - foreach ($parentClasses as $parentClass) { - if (! method_exists($parentClass, $methodName)) { - continue; + foreach ($parentClassReflections as $parentClassReflection) { + if ($parentClassReflection->hasMethod($methodName)) { + return true; } - - return true; } return false; } - private function hasChildMethod(string $desiredClassName, string $methodName): bool + public function isChildLockedMethod(ClassMethod $classMethod): bool { - foreach (get_declared_classes() as $className) { - if ($className === $desiredClassName) { - continue; - } + /** @var Scope $scope */ + $scope = $classMethod->getAttribute(AttributeKey::SCOPE); - if (! is_a($className, $desiredClassName, true)) { - continue; - } + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { + return false; + } - if (method_exists($className, $methodName)) { + $methodName = $this->nodeNameResolver->getName($classMethod); + + $childrenClassReflections = $this->familyRelationsAnalyzer->getChildrenOfClassReflection($classReflection); + foreach ($childrenClassReflections as $childClassReflection) { + if ($childClassReflection->hasMethod($methodName)) { return true; } } return false; } - - /** - * @return string[] - */ - private function getInterfaceMethodNames(string $className): array - { - /** @var string[] $interfaces */ - $interfaces = (array) class_implements($className); - - $interfaceMethods = []; - foreach ($interfaces as $interface) { - $currentInterfaceMethods = get_class_methods($interface); - $interfaceMethods = array_merge($interfaceMethods, $currentInterfaceMethods); - } - - return $interfaceMethods; - } } diff --git a/packages/vendor-locker/src/NodeVendorLocker/PropertyTypeVendorLockResolver.php b/packages/vendor-locker/src/NodeVendorLocker/PropertyTypeVendorLockResolver.php index 183bae687cb0..cfc1e4cb804e 100644 --- a/packages/vendor-locker/src/NodeVendorLocker/PropertyTypeVendorLockResolver.php +++ b/packages/vendor-locker/src/NodeVendorLocker/PropertyTypeVendorLockResolver.php @@ -4,94 +4,79 @@ namespace Rector\VendorLocker\NodeVendorLocker; -use PhpParser\Node\Stmt\Class_; -use PhpParser\Node\Stmt\ClassLike; -use PhpParser\Node\Stmt\Interface_; use PhpParser\Node\Stmt\Property; +use PHPStan\Analyser\Scope; +use PHPStan\Reflection\ClassReflection; use Rector\NodeTypeResolver\Node\AttributeKey; -use ReflectionProperty; final class PropertyTypeVendorLockResolver extends AbstractNodeVendorLockResolver { public function isVendorLocked(Property $property): bool { - $classLike = $property->getAttribute(AttributeKey::CLASS_NODE); - if (! $classLike instanceof Class_) { + /** @var Scope $scope */ + $scope = $property->getAttribute(AttributeKey::SCOPE); + + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { return false; } - /** @var Class_|Interface_ $classLike */ - if (! $this->hasParentClassChildrenClassesOrImplementsInterface($classLike)) { + if (! $this->hasParentClassChildrenClassesOrImplementsInterface($classReflection)) { return false; } /** @var string $propertyName */ $propertyName = $this->nodeNameResolver->getName($property); - if ($this->isParentClassLocked($classLike, $propertyName)) { + if ($this->isParentClassLocked($classReflection, $propertyName)) { return true; } - return $this->isChildClassLocked($property, $classLike, $propertyName); + return $this->isChildClassLocked($property, $classReflection, $propertyName); } - /** - * @param Class_|Interface_ $classLike - */ - private function isParentClassLocked(ClassLike $classLike, string $propertyName): bool + private function isParentClassLocked(ClassReflection $classReflection, string $propertyName): bool { - if (! $classLike instanceof Class_) { - return false; - } - // extract to some "inherited parent method" service - /** @var string|null $parentClassName */ - $parentClassName = $classLike->getAttribute(AttributeKey::PARENT_CLASS_NAME); - if ($parentClassName === null) { - return false; - } - - // if not, look for it's parent parent - recursion - - if (property_exists($parentClassName, $propertyName)) { - // validate type is conflicting - // parent class property in external scope → it's not ok - return true; - - // if not, look for it's parent parent + foreach ($classReflection->getParents() as $parentClassReflection) { + if ($parentClassReflection->hasProperty($propertyName)) { + // validate type is conflicting + // parent class property in external scope → it's not ok + return true; + } } return false; } - /** - * @param Class_|Interface_ $classLike - */ - private function isChildClassLocked(Property $property, ClassLike $classLike, string $propertyName): bool - { - if (! $classLike instanceof Class_) { + private function isChildClassLocked( + Property $property, + ClassReflection $classReflection, + string $propertyName + ): bool { + if (! $classReflection->isClass()) { return false; } - // is child class locker + // is child class locked? if ($property->isPrivate()) { return false; } - $childrenClassNames = $this->getChildrenClassesByClass($classLike); + $childClassReflections = $this->familyRelationsAnalyzer->getChildrenOfClassReflection($classReflection); - foreach ($childrenClassNames as $childClassName) { - if (! property_exists($childClassName, $propertyName)) { + foreach ($childClassReflections as $childClassReflection) { + if (! $childClassReflection->hasProperty($propertyName)) { continue; } + $propertyReflection = $childClassReflection->getNativeProperty($propertyName); + // ensure the property is not in the parent class - $reflectionProperty = new ReflectionProperty($childClassName, $propertyName); - if ($reflectionProperty->class !== $childClassName) { - continue; + $propertyReflectionDeclaringClass = $propertyReflection->getDeclaringClass(); + if ($propertyReflectionDeclaringClass->getName() === $childClassReflection->getName()) { + return true; } - - return true; } return false; diff --git a/packages/vendor-locker/src/NodeVendorLocker/PropertyVisibilityVendorLockResolver.php b/packages/vendor-locker/src/NodeVendorLocker/PropertyVisibilityVendorLockResolver.php index 47fda3af7c76..35bbcbb12447 100644 --- a/packages/vendor-locker/src/NodeVendorLocker/PropertyVisibilityVendorLockResolver.php +++ b/packages/vendor-locker/src/NodeVendorLocker/PropertyVisibilityVendorLockResolver.php @@ -4,7 +4,10 @@ namespace Rector\VendorLocker\NodeVendorLocker; +use PhpParser\Node; use PhpParser\Node\Stmt\Property; +use PHPStan\Analyser\Scope; +use PHPStan\Reflection\ClassReflection; use Rector\NodeTypeResolver\Node\AttributeKey; final class PropertyVisibilityVendorLockResolver extends AbstractNodeVendorLockResolver @@ -18,58 +21,52 @@ final class PropertyVisibilityVendorLockResolver extends AbstractNodeVendorLockR */ public function isParentLockedProperty(Property $property): bool { - /** @var string $className */ - $className = $property->getAttribute(AttributeKey::CLASS_NAME); + $classReflection = $this->resolveClassReflection($property); + if (! $classReflection instanceof ClassReflection) { + return false; + } - /** @var string $propertyName */ $propertyName = $this->nodeNameResolver->getName($property); - return $this->hasParentProperty($className, $propertyName); + foreach ($classReflection->getParents() as $parentClassReflection) { + if ($parentClassReflection->hasProperty($propertyName)) { + return true; + } + } + + return false; } public function isChildLockedProperty(Property $property): bool { - /** @var string $className */ - $className = $property->getAttribute(AttributeKey::CLASS_NAME); + $classReflection = $this->resolveClassReflection($property); + if (! $classReflection instanceof ClassReflection) { + return false; + } - /** @var string $propertyName */ $propertyName = $this->nodeNameResolver->getName($property); - return $this->hasChildProperty($className, $propertyName); - } - - private function hasParentProperty(string $className, string $propertyName): bool - { - /** @var string[] $parentClasses */ - $parentClasses = (array) class_parents($className); - - foreach ($parentClasses as $parentClass) { - if (! property_exists($parentClass, $propertyName)) { + $childrenClassReflections = $this->familyRelationsAnalyzer->getChildrenOfClassReflection($classReflection); + foreach ($childrenClassReflections as $childClassReflection) { + if ($childClassReflection === $classReflection) { continue; } - return true; + if ($childClassReflection->hasProperty($propertyName)) { + return true; + } } return false; } - private function hasChildProperty(string $desiredClassName, string $propertyName): bool + private function resolveClassReflection(Node $node): ?ClassReflection { - foreach (get_declared_classes() as $className) { - if ($className === $desiredClassName) { - continue; - } - - if (! is_a($className, $desiredClassName, true)) { - continue; - } - - if (property_exists($className, $propertyName)) { - return true; - } + $scope = $node->getAttribute(AttributeKey::SCOPE); + if (! $scope instanceof Scope) { + return null; } - return false; + return $scope->getClassReflection(); } } diff --git a/phpstan-for-rector.neon b/phpstan-for-rector.neon index d399aaf6bebf..0371c6d676be 100644 --- a/phpstan-for-rector.neon +++ b/phpstan-for-rector.neon @@ -2,6 +2,9 @@ parameters: inferPrivatePropertyTypeFromConstructor: true + scanDirectories: + - stubs + includes: - utils/phpstan-extensions/config/phpstan-extensions.neon - vendor/phpstan/phpstan-nette/extension.neon diff --git a/phpstan.neon b/phpstan.neon index e100447c6d5b..ceae8fe01f4c 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -121,10 +121,11 @@ parameters: - '#Cognitive complexity for "Rector\\Php80\\NodeResolver\\SwitchExprsResolver\:\:resolve\(\)" is \d+, keep it under 9#' + - message: "#^Cognitive complexity for \"Rector\\\\PhpSpecToPHPUnit\\\\Rector\\\\MethodCall\\\\PhpSpecPromisesToPHPUnitAssertRector\\:\\:refactor\\(\\)\" is 13, keep it under 9$#" - count: 1 path: rules/php-spec-to-phpunit/src/Rector/MethodCall/PhpSpecPromisesToPHPUnitAssertRector.php + - message: '#Class cognitive complexity is \d+, keep it under \d+#' paths: @@ -133,6 +134,7 @@ parameters: - packages/node-type-resolver/src/NodeTypeResolver.php - rules/code-quality-strict/src/Rector/Variable/MoveVariableDeclarationNearReferenceRector.php - rules/php80/src/Rector/If_/NullsafeOperatorRector.php + - "#^Cognitive complexity for \"Rector\\\\Php70\\\\EregToPcreTransformer\\:\\:(.*?)\" is (.*?), keep it under 9$#" - '#Cognitive complexity for "Rector\\CodeQuality\\Rector\\If_\\SimplifyIfIssetToNullCoalescingRector\:\:shouldSkip\(\)" is 10, keep it under 9#' - '#Cognitive complexity for "Rector\\TypeDeclaration\\PHPStan\\Type\\ObjectTypeSpecifier\:\:matchShortenedObjectType\(\)" is 10, keep it under 9#' @@ -158,8 +160,6 @@ parameters: message: '#Class "Rector\\RectorGenerator\\Rector\\Closure\\AddNewServiceToSymfonyPhpConfigRector" is missing @see annotation with test case class reference#' path: 'packages/rector-generator/src/Rector/Closure/AddNewServiceToSymfonyPhpConfigRector.php' - - '#Class Rector\\Renaming\\Tests\\Rector\\MethodCall\\RenameMethodRector\\Fixture\\SkipSelfMethodRename not found#' - - '#Parameter \#1 \$variable of class Rector\\Php70\\ValueObject\\VariableAssignPair constructor expects PhpParser\\Node\\Expr\\ArrayDimFetch\|PhpParser\\Node\\Expr\\PropertyFetch\|PhpParser\\Node\\Expr\\StaticPropertyFetch\|PhpParser\\Node\\Expr\\Variable, PhpParser\\Node\\Expr given#' # is nested expr @@ -319,8 +319,6 @@ parameters: ################################################### - - '#should return ReflectionClassConstant\|null but returns ReflectionClassConstant\|false#' - - '#Method "evaluateBinaryToVersionCompareCondition\(\)" returns bool type, so the name should start with is/has/was#' # soo many false positive naming - fix later with Recto rule @@ -404,12 +402,6 @@ parameters: - rules/naming/src/Guard/PropertyConflictingNameGuard/AbstractPropertyConflictingNameGuard.php - rules/naming/src/PropertyRenamer/AbstractPropertyRenamer.php - - - message: '#".*" regex need to use string named capture group instead of numeric#' - paths: - - packages/better-php-doc-parser/src/PhpDocParser/BetterPhpDocParser.php #268 - - rules/php70/src/EregToPcreTransformer.php #277 - - message: '#There should be no empty class#' paths: @@ -512,7 +504,6 @@ parameters: # known values - '#Method Rector\\Testing\\Finder\\RectorsFinder\:\:findClassesInDirectoriesByName\(\) should return array but returns array#' - '#Property PhpParser\\Node\\Param\:\:\$type \(PhpParser\\Node\\Identifier\|PhpParser\\Node\\Name\|PhpParser\\Node\\NullableType\|PhpParser\\Node\\UnionType\|null\) does not accept PhpParser\\Node#' - - '#Parameter \#1 \$interfaces of method Rector\\PHPStanStaticTypeMapper\\TypeAnalyzer\\UnionTypeCommonTypeNarrower\:\:filterOutNativeInterfaces\(\) expects array, array given#' - '#Content of method "getFunctionLikePhpDocInfo\(\)" is duplicated with method "getFunctionLikePhpDocInfo\(\)" in "Rector\\TypeDeclaration\\TypeInferer\\ParamTypeInferer\\PHPUnitDataProviderParamTypeInferer" class\. Use unique content or abstract service instead#' - @@ -528,3 +519,81 @@ parameters: - '#Property Rector\\Core\\PhpParser\\Node\\AssignAndBinaryMap\:\:\$binaryOpToAssignClasses \(array, class\-string\>\) does not accept array#' - '#Content of method "configure\(\)" is duplicated with method "configure\(\)" in "Rector\\RemovingStatic\\Rector\\Class_\\NewUniqueObjectToEntityFactoryRector" class\. Use unique content or abstract service instead#' - '#Content of method "configure\(\)" is duplicated with method "configure\(\)" in "Rector\\RemovingStatic\\Rector\\Class_\\PassFactoryToUniqueObjectRector" class\. Use unique content or abstract service instead#' + + - + message: '#Function "class_exists\(\)" cannot be used/left in the code#' + paths: + - utils/phpstan-type-mapper-checker/src/Finder/PHPStanTypeClassFinder.php + - packages/testing/src/Finder/RectorsFinder.php + - packages/better-php-doc-parser/src/AnnotationReader/AnnotationReaderFactory.php + + - + message: '#Function "property_exists\(\)" cannot be used/left in the code#' + paths: + # on PhpParser Nodes + - src/NodeManipulator/ClassMethodAssignManipulator.php + - rules/php80/src/Rector/If_/NullsafeOperatorRector.php + - packages/node-type-resolver/src/NodeVisitor/FunctionMethodAndClassNodeVisitor.php + - packages/node-name-resolver/src/NodeNameResolver.php + - packages/node-type-resolver/src/PHPStan/Scope/PHPStanNodeScopeResolver.php + - packages/node-name-resolver/src/NodeNameResolver/ClassNameResolver.php + - packages/node-type-resolver/src/NodeVisitor/StatementNodeVisitor.php + - packages/better-php-doc-parser/src/Printer/PhpDocInfoPrinter.php + - packages/better-php-doc-parser/src/Printer/MultilineSpaceFormatPreserver.php + + - + message: '#Function "class_implements\(\)" cannot be used/left in the code#' + paths: + - rules/symfony/src/ValueObject/ServiceMap/ServiceMap.php + + - + message: '#Instead of "ReflectionClass" class/interface use "PHPStan\\Reflection\\ClassReflection"#' + paths: + # own classes + - packages/rector-generator/src/Provider/SetsListProvider.php + - packages/testing/src/Finder/RectorsFinder.php + - packages/static-type-mapper/src/TypeFactory/UnionTypeFactory.php + - packages/set/src/RectorSetProvider.php + - packages/rector-generator/src/Provider/NodeTypesProvider.php + + - '#Method Rector\\TypeDeclaration\\PhpParserTypeAnalyzer\:\:unwrapNullableAndToString\(\) should return string but returns string\|null#' + + # @todo resolve in next PR!!! (Feb 2021) + - '#Function "array_filter\(\)" cannot be used/left in the code#' + - '#Cognitive complexity for "Rector\\CodeQuality\\Rector\\Isset_\\IssetOnPropertyObjectToPropertyExistsRector\:\:refactor\(\)" is 11, keep it under 9#' + - '#Cognitive complexity for "Rector\\DeadCode\\UnusedNodeResolver\\ClassUnusedPrivateClassMethodResolver\:\:filterOutParentAbstractMethods\(\)" is 10, keep it under 9#' + + # known types + - '#Call to an undefined method PHPStan\\Type\\ConstantType\:\:getValue\(\)#' + - '#Method Rector\\BetterPhpDocParser\\AnnotationReader\\NodeAnnotationReader\:\:getNativePropertyReflection\(\) should return ReflectionProperty\|null but return statement is missing#' + - '#Parameter \#1 \$node of method Rector\\NetteKdyby\\Naming\\VariableNaming\:\:resolveFromMethodCall\(\) expects PhpParser\\Node\\Expr\\MethodCall\|PhpParser\\Node\\Expr\\NullsafeMethodCall\|PhpParser\\Node\\Expr\\StaticCall, PhpParser\\Node given#' + - '#Parameter \#1 \$stmts of method Rector\\EarlyReturn\\Rector\\Return_\\PreparedValueToEarlyReturnRector\:\:collectIfs\(\) expects array, array given#' + - '#Access to an undefined property PhpParser\\Node\\FunctionLike\|PhpParser\\Node\\Stmt\\If_\:\:\$stmts#' + - '#Method Rector\\NetteKdyby\\DataProvider\\EventAndListenerTreeProvider\:\:getListeningClassMethodsInEventSubscriberByClass\(\) should return array\> but returns array\>#' + + - + message: '#Function "get_declared_classes\(\)" cannot be used/left in the code#' + paths: + - rules/restoration/src/ClassMap/ExistingClassesProvider.php + + # false positives + - + message: '#Empty array passed to foreach#' + paths: + - rules/transform/src/Rector/New_/NewToConstructorInjectionRector.php + - + message: '#Parameter \#1 \$types of method Rector\\Defluent\\NodeAnalyzer\\FluentCallStaticTypeResolver\:\:filterOutAlreadyPresentParentClasses\(\) expects array, array given#' + paths: + - rules/defluent/src/NodeAnalyzer/FluentCallStaticTypeResolver.php + + - '#Cognitive complexity for "Rector\\CodeQuality\\Rector\\Isset_\\IssetOnPropertyObjectToPropertyExistsRector\:\:refactor\(\)" is \d+, keep it under 9#' + + # needed for native reflection parameter reference + - + message: '#Instead of "ReflectionFunction" class/interface use "PHPStan\\Reflection\\FunctionReflection"#' + paths: + - packages/read-write/src/Guard/VariableToConstantGuard.php + + - '#Cognitive complexity for "Rector\\NetteCodeQuality\\FormControlTypeResolver\\MagicNetteFactoryInterfaceFormControlTypeResolver\:\:resolve\(\)" is 11, keep it under 9#' + - '#Cognitive complexity for "Rector\\NetteCodeQuality\\FormControlTypeResolver\\MagicNetteFactoryInterfaceFormControlTypeResolver\:\:resolve\(\)" is 12, keep it under 9#' + diff --git a/rector.php b/rector.php index d5f9649cd6dc..1d48ad216313 100644 --- a/rector.php +++ b/rector.php @@ -8,8 +8,8 @@ use Rector\Core\Configuration\Option; use Rector\Core\Rector\AbstractRector; use Rector\Core\ValueObject\PhpVersion; -use Rector\DeadCode\Rector\ClassConst\RemoveUnusedClassConstantRector; use Rector\Php55\Rector\String_\StringClassNameToClassConstantRector; +use Rector\Privatization\Rector\Property\PrivatizeLocalPropertyToPrivatePropertyRector; use Rector\Restoration\Rector\ClassMethod\InferParamFromClassMethodReturnRector; use Rector\Restoration\ValueObject\InferParamFromClassMethodReturn; use Rector\Set\ValueObject\SetList; @@ -74,8 +74,10 @@ $parameters->set(Option::SKIP, [ StringClassNameToClassConstantRector::class, SplitStringClassConstantToClassConstFetchRector::class, - // false positives on constants used in rector.php - RemoveUnusedClassConstantRector::class, + + PrivatizeLocalPropertyToPrivatePropertyRector::class => [ + __DIR__ . '/src/Rector/AbstractTemporaryRector.php', + ], // test paths '*/Fixture/*', diff --git a/rules/cakephp/src/Naming/CakePHPFullyQualifiedClassNameResolver.php b/rules/cakephp/src/Naming/CakePHPFullyQualifiedClassNameResolver.php index 3e1141ca85c8..b131a74929be 100644 --- a/rules/cakephp/src/Naming/CakePHPFullyQualifiedClassNameResolver.php +++ b/rules/cakephp/src/Naming/CakePHPFullyQualifiedClassNameResolver.php @@ -5,6 +5,7 @@ namespace Rector\CakePHP\Naming; use Nette\Utils\Strings; +use PHPStan\Reflection\ReflectionProvider; use Rector\CakePHP\ImplicitNameResolver; /** @@ -35,9 +36,15 @@ final class CakePHPFullyQualifiedClassNameResolver */ private $implicitNameResolver; - public function __construct(ImplicitNameResolver $implicitNameResolver) + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct(ImplicitNameResolver $implicitNameResolver, ReflectionProvider $reflectionProvider) { $this->implicitNameResolver = $implicitNameResolver; + $this->reflectionProvider = $reflectionProvider; } /** @@ -63,10 +70,7 @@ public function resolveFromPseudoNamespaceAndShortClassName(string $pseudoNamesp // B. is Cake native class? $cakePhpVersion = 'Cake\\' . $pseudoNamespace . '\\' . $shortClass; - if (class_exists($cakePhpVersion)) { - return $cakePhpVersion; - } - if (interface_exists($cakePhpVersion)) { + if ($this->reflectionProvider->hasClass($cakePhpVersion)) { return $cakePhpVersion; } diff --git a/rules/code-quality-strict/src/Rector/If_/MoveOutMethodCallInsideIfConditionRector.php b/rules/code-quality-strict/src/Rector/If_/MoveOutMethodCallInsideIfConditionRector.php index 5909993fe9e4..e35b23e019d1 100644 --- a/rules/code-quality-strict/src/Rector/If_/MoveOutMethodCallInsideIfConditionRector.php +++ b/rules/code-quality-strict/src/Rector/If_/MoveOutMethodCallInsideIfConditionRector.php @@ -89,28 +89,15 @@ public function refactor(Node $node): ?Node private function shouldSkipMethodCall(MethodCall $methodCall): bool { - $methodCallVar = $methodCall->var; - - $scope = $methodCallVar->getAttribute(Scope::class); - if ($scope === null) { - return true; - } - - $type = $scope->getType($methodCallVar); + $variableType = $this->getStaticType($methodCall->var); // From PropertyFetch → skip - if ($type instanceof ThisType) { - return true; - } - - // Is Boolean return → skip - $scope = $methodCall->getAttribute(Scope::class); - if ($scope === null) { + if ($variableType instanceof ThisType) { return true; } - $type = $scope->getType($methodCall); - if ($type instanceof BooleanType) { + $methodCallReturnType = $this->getStaticType($methodCall); + if ($methodCallReturnType instanceof BooleanType) { return true; } diff --git a/rules/code-quality-strict/tests/Rector/If_/MoveOutMethodCallInsideIfConditionRector/Fixture/skip_if_condition_method_call_return_bool.php.inc b/rules/code-quality-strict/tests/Rector/If_/MoveOutMethodCallInsideIfConditionRector/Fixture/skip_if_condition_method_call_return_bool.php.inc index 8a2699d5a280..6ce12eead534 100644 --- a/rules/code-quality-strict/tests/Rector/If_/MoveOutMethodCallInsideIfConditionRector/Fixture/skip_if_condition_method_call_return_bool.php.inc +++ b/rules/code-quality-strict/tests/Rector/If_/MoveOutMethodCallInsideIfConditionRector/Fixture/skip_if_condition_method_call_return_bool.php.inc @@ -2,21 +2,15 @@ namespace Rector\CodeQualityStrict\Tests\Rector\If_\MoveOutMethodCallInsideIfConditionRector\Fixture; -class SkipIfConditionMethodCallReturnBool +use Rector\CodeQualityStrict\Tests\Rector\If_\MoveOutMethodCallInsideIfConditionRector\Source\ClassMethodWithCall; + +final class SkipIfConditionMethodCallReturnBool { - public function run($arg) + public function run(ClassMethodWithCall $classMethodWithCall, $arg) { - $obj = new self(); - - if ($obj->condition($arg)) { + if ($classMethodWithCall->condition($arg)) { } } - - public function condition($arg): bool - { - - } } -?> diff --git a/rules/code-quality-strict/tests/Rector/If_/MoveOutMethodCallInsideIfConditionRector/Source/ClassMethodWithCall.php b/rules/code-quality-strict/tests/Rector/If_/MoveOutMethodCallInsideIfConditionRector/Source/ClassMethodWithCall.php new file mode 100644 index 000000000000..3a01ede2a3fa --- /dev/null +++ b/rules/code-quality-strict/tests/Rector/If_/MoveOutMethodCallInsideIfConditionRector/Source/ClassMethodWithCall.php @@ -0,0 +1,13 @@ +valueResolver = $valueResolver; $this->nodeTypeResolver = $nodeTypeResolver; - $this->nodeRepository = $nodeRepository; $this->nodeNameResolver = $nodeNameResolver; + $this->reflectionProvider = $reflectionProvider; } /** * @param Variable|PropertyFetch $objectExpr */ - public function match(Expr $objectExpr, String_ $string): ?ClassMethod + public function match(Expr $objectExpr, String_ $string): ?PhpMethodReflection { $methodName = $this->valueResolver->getValue($string); if (! is_string($methodName)) { @@ -67,25 +67,29 @@ public function match(Expr $objectExpr, String_ $string): ?ClassMethod $objectType = $this->popFirstObjectType($objectType); if ($objectType instanceof ObjectType) { - $class = $this->nodeRepository->findClass($objectType->getClassName()); + if (! $this->reflectionProvider->hasClass($objectType->getClassName())) { + return null; + } - if (! $class instanceof Class_) { + $classReflection = $this->reflectionProvider->getClass($objectType->getClassName()); + if (! $classReflection->hasMethod($methodName)) { return null; } - $classMethod = $class->getMethod($methodName); + $stringScope = $string->getAttribute(AttributeKey::SCOPE); - if (! $classMethod instanceof ClassMethod) { + $methodReflection = $classReflection->getMethod($methodName, $stringScope); + if (! $methodReflection instanceof PhpMethodReflection) { return null; } if ($this->nodeNameResolver->isName($objectExpr, 'this')) { - return $classMethod; + return $methodReflection; } // is public method of another service - if ($classMethod->isPublic()) { - return $classMethod; + if ($methodReflection->isPublic()) { + return $methodReflection; } } diff --git a/rules/code-quality/src/NodeFactory/AnonymousFunctionFactory.php b/rules/code-quality/src/NodeFactory/AnonymousFunctionFactory.php index f9daf9871b0c..fe264e970132 100644 --- a/rules/code-quality/src/NodeFactory/AnonymousFunctionFactory.php +++ b/rules/code-quality/src/NodeFactory/AnonymousFunctionFactory.php @@ -11,21 +11,19 @@ use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Expr\Variable; use PhpParser\Node\Param; -use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Expression; use PhpParser\Node\Stmt\Return_; -use Rector\Core\PhpParser\Node\BetterNodeFinder; +use PHPStan\Reflection\FunctionVariantWithPhpDocs; +use PHPStan\Reflection\ParameterReflection; +use PHPStan\Reflection\Php\PhpMethodReflection; +use PHPStan\Type\MixedType; +use PHPStan\Type\VoidType; use Rector\Core\PhpParser\Node\NodeFactory; use Rector\NodeNameResolver\NodeNameResolver; -use Rector\NodeTypeResolver\Node\AttributeKey; +use Rector\StaticTypeMapper\StaticTypeMapper; final class AnonymousFunctionFactory { - /** - * @var BetterNodeFinder - */ - private $betterNodeFinder; - /** * @var NodeFactory */ @@ -36,40 +34,47 @@ final class AnonymousFunctionFactory */ private $nodeNameResolver; + /** + * @var StaticTypeMapper + */ + private $staticTypeMapper; + public function __construct( - BetterNodeFinder $betterNodeFinder, NodeFactory $nodeFactory, - NodeNameResolver $nodeNameResolver + NodeNameResolver $nodeNameResolver, + StaticTypeMapper $staticTypeMapper ) { - $this->betterNodeFinder = $betterNodeFinder; $this->nodeFactory = $nodeFactory; $this->nodeNameResolver = $nodeNameResolver; + $this->staticTypeMapper = $staticTypeMapper; } /** * @param Variable|PropertyFetch $node */ - public function create(ClassMethod $classMethod, Node $node): Closure + public function create(PhpMethodReflection $phpMethodReflection, Node $node): Closure { - /** @var Return_[] $classMethodReturns */ - $classMethodReturns = $this->betterNodeFinder->findInstanceOf((array) $classMethod->stmts, Return_::class); + /** @var FunctionVariantWithPhpDocs $functionVariantWithPhpDoc */ + $functionVariantWithPhpDoc = $phpMethodReflection->getVariants()[0]; $anonymousFunction = new Closure(); - $newParams = $this->copyParams($classMethod->params); + $newParams = $this->createParams($functionVariantWithPhpDoc->getParameters()); $anonymousFunction->params = $newParams; - $innerMethodCall = new MethodCall($node, $classMethod->name); + $innerMethodCall = new MethodCall($node, $phpMethodReflection->getName()); $innerMethodCall->args = $this->nodeFactory->createArgsFromParams($newParams); - if ($classMethod->returnType !== null) { - $newReturnType = $classMethod->returnType; - $newReturnType->setAttribute(AttributeKey::ORIGINAL_NODE, null); - $anonymousFunction->returnType = $newReturnType; + if (! $functionVariantWithPhpDoc->getReturnType() instanceof MixedType) { + $returnType = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode( + $functionVariantWithPhpDoc->getReturnType() + ); + $anonymousFunction->returnType = $returnType; } // does method return something? - if ($this->hasClassMethodReturn($classMethodReturns)) { + + if (! $functionVariantWithPhpDoc->getReturnType() instanceof VoidType) { $anonymousFunction->stmts[] = new Return_($innerMethodCall); } else { $anonymousFunction->stmts[] = new Expression($innerMethodCall); @@ -83,32 +88,23 @@ public function create(ClassMethod $classMethod, Node $node): Closure } /** - * @param Param[] $params + * @param ParameterReflection[] $parameterReflections * @return Param[] */ - private function copyParams(array $params): array + private function createParams(array $parameterReflections): array { - $newParams = []; - foreach ($params as $param) { - $newParam = clone $param; - $newParam->setAttribute(AttributeKey::ORIGINAL_NODE, null); - $newParam->var->setAttribute(AttributeKey::ORIGINAL_NODE, null); - $newParams[] = $newParam; - } + $params = []; + foreach ($parameterReflections as $parameterReflection) { + $param = new Param(new Variable($parameterReflection->getName())); - return $newParams; - } - - /** - * @param Return_[] $nodes - */ - private function hasClassMethodReturn(array $nodes): bool - { - foreach ($nodes as $node) { - if ($node->expr !== null) { - return true; + if (! $parameterReflection->getType() instanceof MixedType) { + $paramType = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($parameterReflection->getType()); + $param->type = $paramType; } + + $params[] = $param; } - return false; + + return $params; } } diff --git a/rules/code-quality/src/Rector/Array_/ArrayThisCallToThisMethodCallRector.php b/rules/code-quality/src/Rector/Array_/ArrayThisCallToThisMethodCallRector.php index d86885f01b1a..c50ad4bb6719 100644 --- a/rules/code-quality/src/Rector/Array_/ArrayThisCallToThisMethodCallRector.php +++ b/rules/code-quality/src/Rector/Array_/ArrayThisCallToThisMethodCallRector.php @@ -14,6 +14,7 @@ use PhpParser\Node\Expr\Variable; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Property; +use PHPStan\Reflection\ReflectionProvider; use Rector\Core\Rector\AbstractRector; use Rector\NodeCollector\NodeAnalyzer\ArrayCallableMethodReferenceAnalyzer; use Rector\NodeCollector\ValueObject\ArrayCallable; @@ -32,9 +33,17 @@ final class ArrayThisCallToThisMethodCallRector extends AbstractRector */ private $arrayCallableMethodReferenceAnalyzer; - public function __construct(ArrayCallableMethodReferenceAnalyzer $arrayCallableMethodReferenceAnalyzer) - { + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct( + ArrayCallableMethodReferenceAnalyzer $arrayCallableMethodReferenceAnalyzer, + ReflectionProvider $reflectionProvider + ) { $this->arrayCallableMethodReferenceAnalyzer = $arrayCallableMethodReferenceAnalyzer; + $this->reflectionProvider = $reflectionProvider; } public function getRuleDefinition(): RuleDefinition @@ -94,6 +103,7 @@ public function refactor(Node $node): ?Node if (! $arrayCallable instanceof ArrayCallable) { return null; } + if ($this->isAssignedToNetteMagicOnProperty($node)) { return null; } @@ -107,12 +117,18 @@ public function refactor(Node $node): ?Node return null; } - if (! $arrayCallable->isExistingMethod()) { + if (! $this->reflectionProvider->hasClass($arrayCallable->getClass())) { + return null; + } + + $classReflection = $this->reflectionProvider->getClass($arrayCallable->getClass()); + if (! $classReflection->hasMethod($arrayCallable->getMethod())) { return null; } - $reflectionMethod = $arrayCallable->getReflectionMethod(); + $nativeReflectionClass = $classReflection->getNativeReflection(); + $reflectionMethod = $nativeReflectionClass->getMethod($arrayCallable->getMethod()); $this->privatizeClassMethod($reflectionMethod); if ($reflectionMethod->getNumberOfParameters() > 0) { diff --git a/rules/code-quality/src/Rector/Array_/CallableThisArrayToAnonymousFunctionRector.php b/rules/code-quality/src/Rector/Array_/CallableThisArrayToAnonymousFunctionRector.php index fb5c70da020d..525ac7bd52e1 100644 --- a/rules/code-quality/src/Rector/Array_/CallableThisArrayToAnonymousFunctionRector.php +++ b/rules/code-quality/src/Rector/Array_/CallableThisArrayToAnonymousFunctionRector.php @@ -11,9 +11,8 @@ use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Expr\Variable; -use PhpParser\Node\Param; use PhpParser\Node\Scalar\String_; -use PhpParser\Node\Stmt\ClassMethod; +use PHPStan\Reflection\Php\PhpMethodReflection; use Rector\CodeQuality\NodeAnalyzer\CallableClassMethodMatcher; use Rector\CodeQuality\NodeFactory\AnonymousFunctionFactory; use Rector\Core\Rector\AbstractRector; @@ -133,12 +132,12 @@ public function refactor(Node $node): ?Node return null; } - $classMethod = $this->callableClassMethodMatcher->match($objectVariable, $methodName); - if (! $classMethod instanceof ClassMethod) { + $phpMethodReflection = $this->callableClassMethodMatcher->match($objectVariable, $methodName); + if (! $phpMethodReflection instanceof PhpMethodReflection) { return null; } - return $this->anonymousFunctionFactory->create($classMethod, $objectVariable); + return $this->anonymousFunctionFactory->create($phpMethodReflection, $objectVariable); } private function shouldSkipArray(Array_ $array): bool diff --git a/rules/code-quality/src/Rector/Class_/CompleteDynamicPropertiesRector.php b/rules/code-quality/src/Rector/Class_/CompleteDynamicPropertiesRector.php index bf3b5129c9eb..54cce775b9c5 100644 --- a/rules/code-quality/src/Rector/Class_/CompleteDynamicPropertiesRector.php +++ b/rules/code-quality/src/Rector/Class_/CompleteDynamicPropertiesRector.php @@ -6,6 +6,8 @@ use PhpParser\Node; use PhpParser\Node\Stmt\Class_; +use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\Type; use Rector\CodeQuality\NodeAnalyzer\ClassLikeAnalyzer; use Rector\CodeQuality\NodeAnalyzer\LocalPropertyAnalyzer; @@ -38,14 +40,21 @@ final class CompleteDynamicPropertiesRector extends AbstractRector */ private $classLikeAnalyzer; + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + public function __construct( MissingPropertiesFactory $missingPropertiesFactory, LocalPropertyAnalyzer $localPropertyAnalyzer, - ClassLikeAnalyzer $classLikeAnalyzer + ClassLikeAnalyzer $classLikeAnalyzer, + ReflectionProvider $reflectionProvider ) { $this->missingPropertiesFactory = $missingPropertiesFactory; $this->localPropertyAnalyzer = $localPropertyAnalyzer; $this->classLikeAnalyzer = $classLikeAnalyzer; + $this->reflectionProvider = $reflectionProvider; } public function getRuleDefinition(): RuleDefinition @@ -96,6 +105,17 @@ public function refactor(Node $node): ?Node return null; } + $className = $this->getName($node); + if ($className === null) { + return null; + } + + if (! $this->reflectionProvider->hasClass($className)) { + return null; + } + + $classReflection = $this->reflectionProvider->getClass($className); + // special case for Laravel Collection macro magic $fetchedLocalPropertyNameToTypes = $this->localPropertyAnalyzer->resolveFetchedPropertiesToTypesFromClass( $node @@ -106,10 +126,7 @@ public function refactor(Node $node): ?Node return null; } - /** @var string $className */ - $className = $this->getName($node); - - $propertiesToComplete = $this->filterOutExistingProperties($className, $propertiesToComplete); + $propertiesToComplete = $this->filterOutExistingProperties($classReflection, $propertiesToComplete); $newProperties = $this->missingPropertiesFactory->create( $fetchedLocalPropertyNameToTypes, @@ -127,17 +144,23 @@ private function shouldSkipClass(Class_ $class): bool return true; } - $className = $this->getName($class); + $className = $this->nodeNameResolver->getName($class); if ($className === null) { return true; } + if (! $this->reflectionProvider->hasClass($className)) { + return true; + } + + $classReflection = $this->reflectionProvider->getClass($className); + // properties are accessed via magic, nothing we can do - if (method_exists($className, '__set')) { + if ($classReflection->hasMethod('__set')) { return true; } - return method_exists($className, '__get'); + return $classReflection->hasMethod('__get'); } /** @@ -158,14 +181,13 @@ private function resolvePropertiesToComplete(Class_ $class, array $fetchedLocalP * @param string[] $propertiesToComplete * @return string[] */ - private function filterOutExistingProperties(string $className, array $propertiesToComplete): array + private function filterOutExistingProperties(ClassReflection $classReflection, array $propertiesToComplete): array { $missingPropertyNames = []; // remove other properties that are accessible from this scope foreach ($propertiesToComplete as $propertyToComplete) { - /** @var string $propertyToComplete */ - if (property_exists($className, $propertyToComplete)) { + if ($classReflection->hasProperty($propertyToComplete)) { continue; } diff --git a/rules/code-quality/src/Rector/FuncCall/RemoveSoleValueSprintfRector.php b/rules/code-quality/src/Rector/FuncCall/RemoveSoleValueSprintfRector.php index 80ef863ada27..568fe31c8c2f 100644 --- a/rules/code-quality/src/Rector/FuncCall/RemoveSoleValueSprintfRector.php +++ b/rules/code-quality/src/Rector/FuncCall/RemoveSoleValueSprintfRector.php @@ -81,7 +81,7 @@ public function refactor(Node $node): ?Node } $valueArgument = $node->args[1]->value; - if (! $this->isStaticType($valueArgument, StringType::class)) { + if (! $this->nodeTypeResolver->isStaticType($valueArgument, StringType::class)) { return null; } diff --git a/rules/code-quality/src/Rector/Identical/BooleanNotIdenticalToNotIdenticalRector.php b/rules/code-quality/src/Rector/Identical/BooleanNotIdenticalToNotIdenticalRector.php index 630d910b8f4a..522abf03c506 100644 --- a/rules/code-quality/src/Rector/Identical/BooleanNotIdenticalToNotIdenticalRector.php +++ b/rules/code-quality/src/Rector/Identical/BooleanNotIdenticalToNotIdenticalRector.php @@ -78,11 +78,11 @@ public function refactor(Node $node): ?Node if ($node->expr instanceof Identical) { $identical = $node->expr; - if (! $this->isStaticType($identical->left, BooleanType::class)) { + if (! $this->nodeTypeResolver->isStaticType($identical->left, BooleanType::class)) { return null; } - if (! $this->isStaticType($identical->right, BooleanType::class)) { + if (! $this->nodeTypeResolver->isStaticType($identical->right, BooleanType::class)) { return null; } @@ -94,11 +94,11 @@ public function refactor(Node $node): ?Node private function processIdentical(Identical $identical): ?NotIdentical { - if (! $this->isStaticType($identical->left, BooleanType::class)) { + if (! $this->nodeTypeResolver->isStaticType($identical->left, BooleanType::class)) { return null; } - if (! $this->isStaticType($identical->right, BooleanType::class)) { + if (! $this->nodeTypeResolver->isStaticType($identical->right, BooleanType::class)) { return null; } diff --git a/rules/code-quality/src/Rector/Identical/FlipTypeControlToUseExclusiveTypeRector.php b/rules/code-quality/src/Rector/Identical/FlipTypeControlToUseExclusiveTypeRector.php index 09b86b109248..5db7f089c4cb 100644 --- a/rules/code-quality/src/Rector/Identical/FlipTypeControlToUseExclusiveTypeRector.php +++ b/rules/code-quality/src/Rector/Identical/FlipTypeControlToUseExclusiveTypeRector.php @@ -184,9 +184,9 @@ private function processConvertToExclusiveType(array $types, Expr $expr, PhpDocI return null; } - $tagValueNode = $phpDocInfo->getVarTagValueNode(); - if ($tagValueNode instanceof VarTagValueNode) { - $this->phpDocTagRemover->removeTagValueFromNode($phpDocInfo, $tagValueNode); + $varTagValueNode = $phpDocInfo->getVarTagValueNode(); + if ($varTagValueNode instanceof VarTagValueNode) { + $this->phpDocTagRemover->removeTagValueFromNode($phpDocInfo, $varTagValueNode); } return new BooleanNot(new Instanceof_($expr, new FullyQualified($type->getClassName()))); diff --git a/rules/code-quality/src/Rector/Identical/SimplifyBoolIdenticalTrueRector.php b/rules/code-quality/src/Rector/Identical/SimplifyBoolIdenticalTrueRector.php index 344271dd68bd..4742360e9e92 100644 --- a/rules/code-quality/src/Rector/Identical/SimplifyBoolIdenticalTrueRector.php +++ b/rules/code-quality/src/Rector/Identical/SimplifyBoolIdenticalTrueRector.php @@ -64,12 +64,13 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - if ($this->isStaticType($node->left, BooleanType::class) && ! $this->valueResolver->isTrueOrFalse( - $node->left - )) { + if ($this->nodeTypeResolver->isStaticType( + $node->left, + BooleanType::class + ) && ! $this->valueResolver->isTrueOrFalse($node->left)) { return $this->processBoolTypeToNotBool($node, $node->left, $node->right); } - if (! $this->isStaticType($node->right, BooleanType::class)) { + if (! $this->nodeTypeResolver->isStaticType($node->right, BooleanType::class)) { return null; } if ($this->valueResolver->isTrueOrFalse($node->right)) { diff --git a/rules/code-quality/src/Rector/If_/ExplicitBoolCompareRector.php b/rules/code-quality/src/Rector/If_/ExplicitBoolCompareRector.php index ee0193281c75..3b3042ada0bd 100644 --- a/rules/code-quality/src/Rector/If_/ExplicitBoolCompareRector.php +++ b/rules/code-quality/src/Rector/If_/ExplicitBoolCompareRector.php @@ -144,11 +144,11 @@ private function resolveNewConditionNode(Expr $expr, bool $isNegated): ?BinaryOp return $this->resolveString($isNegated, $expr); } - if ($this->isStaticType($expr, IntegerType::class)) { + if ($this->nodeTypeResolver->isStaticType($expr, IntegerType::class)) { return $this->resolveInteger($isNegated, $expr); } - if ($this->isStaticType($expr, FloatType::class)) { + if ($this->nodeTypeResolver->isStaticType($expr, FloatType::class)) { return $this->resolveFloat($isNegated, $expr); } diff --git a/rules/code-quality/src/Rector/Isset_/IssetOnPropertyObjectToPropertyExistsRector.php b/rules/code-quality/src/Rector/Isset_/IssetOnPropertyObjectToPropertyExistsRector.php index e65f332eda5c..6c0879d35a38 100644 --- a/rules/code-quality/src/Rector/Isset_/IssetOnPropertyObjectToPropertyExistsRector.php +++ b/rules/code-quality/src/Rector/Isset_/IssetOnPropertyObjectToPropertyExistsRector.php @@ -13,6 +13,7 @@ use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Scalar\String_; use PhpParser\Node\Stmt\Property; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\TypeWithClassName; use Rector\Core\Rector\AbstractRector; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; @@ -24,6 +25,16 @@ */ final class IssetOnPropertyObjectToPropertyExistsRector extends AbstractRector { + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + public function getRuleDefinition(): RuleDefinition { return new RuleDefinition('Change isset on property object to property_exists() and not null check', [ @@ -86,15 +97,28 @@ public function refactor(Node $node): ?Node } $propertyFetchVarType = $this->getObjectType($issetVar->var); - if ($propertyFetchVarType instanceof TypeWithClassName && property_exists( - $propertyFetchVarType->getClassName(), - $propertyFetchName - )) { - $newNodes[] = $this->createNotIdenticalToNull($issetVar); - continue; + if ($propertyFetchVarType instanceof TypeWithClassName) { + if (! $this->reflectionProvider->hasClass($propertyFetchVarType->getClassName())) { + continue; + } + + $classReflection = $this->reflectionProvider->getClass($propertyFetchVarType->getClassName()); + if (! $classReflection->hasProperty($propertyFetchName)) { + $newNodes[] = $this->replaceToPropertyExistsWithNullCheck( + $issetVar->var, + $propertyFetchName, + $issetVar + ); + } else { + $newNodes[] = $this->createNotIdenticalToNull($issetVar); + } + } else { + $newNodes[] = $this->replaceToPropertyExistsWithNullCheck( + $issetVar->var, + $propertyFetchName, + $issetVar + ); } - - $newNodes[] = $this->replaceToPropertyExistsWithNullCheck($issetVar->var, $propertyFetchName, $issetVar); } return $this->nodeFactory->createReturnBooleanAnd($newNodes); diff --git a/rules/code-quality/src/Rector/Ternary/SimplifyDuplicatedTernaryRector.php b/rules/code-quality/src/Rector/Ternary/SimplifyDuplicatedTernaryRector.php index a206d678710a..bbaa045b8b7b 100644 --- a/rules/code-quality/src/Rector/Ternary/SimplifyDuplicatedTernaryRector.php +++ b/rules/code-quality/src/Rector/Ternary/SimplifyDuplicatedTernaryRector.php @@ -61,7 +61,7 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - if (! $this->isStaticType($node->cond, BooleanType::class)) { + if (! $this->nodeTypeResolver->isStaticType($node->cond, BooleanType::class)) { return null; } diff --git a/rules/code-quality/src/Rector/Ternary/UnnecessaryTernaryExpressionRector.php b/rules/code-quality/src/Rector/Ternary/UnnecessaryTernaryExpressionRector.php index a9f2be0ef580..53978c0d24ba 100644 --- a/rules/code-quality/src/Rector/Ternary/UnnecessaryTernaryExpressionRector.php +++ b/rules/code-quality/src/Rector/Ternary/UnnecessaryTernaryExpressionRector.php @@ -97,7 +97,7 @@ public function refactor(Node $node): ?Node private function processNonBinaryCondition(Expr $ifExpression, Expr $elseExpression, Expr $condition): ?Node { if ($this->valueResolver->isTrue($ifExpression) && $this->valueResolver->isFalse($elseExpression)) { - if ($this->isStaticType($condition, BooleanType::class)) { + if ($this->nodeTypeResolver->isStaticType($condition, BooleanType::class)) { return $condition; } @@ -105,7 +105,7 @@ private function processNonBinaryCondition(Expr $ifExpression, Expr $elseExpress } if ($this->valueResolver->isFalse($ifExpression) && $this->valueResolver->isTrue($elseExpression)) { - if ($this->isStaticType($condition, BooleanType::class)) { + if ($this->nodeTypeResolver->isStaticType($condition, BooleanType::class)) { return new BooleanNot($condition); } diff --git a/rules/code-quality/tests/Rector/Array_/CallableThisArrayToAnonymousFunctionRector/Fixture/another_class.php.inc b/rules/code-quality/tests/Rector/Array_/CallableThisArrayToAnonymousFunctionRector/Fixture/another_class.php.inc index 4e085b10be35..be4933531ced 100644 --- a/rules/code-quality/tests/Rector/Array_/CallableThisArrayToAnonymousFunctionRector/Fixture/another_class.php.inc +++ b/rules/code-quality/tests/Rector/Array_/CallableThisArrayToAnonymousFunctionRector/Fixture/another_class.php.inc @@ -2,6 +2,8 @@ namespace Rector\CodeQuality\Tests\Rector\Array_\CallableThisArrayToAnonymousFunctionRector\Fixture; +use Rector\CodeQuality\Tests\Rector\Array_\CallableThisArrayToAnonymousFunctionRector\Source\SortingClass; + class AnotherClass { /** @@ -24,30 +26,14 @@ class AnotherClass } } -final class SortingClass -{ - public function publicSort($a, $b) - { - return $a <=> $b; - } - - protected function protectedSort($a, $b) - { - return $a <=> $b; - } - - private function privateSort($a, $b) - { - return $a <=> $b; - } -} - ?> ----- $b; - } - - protected function protectedSort($a, $b) - { - return $a <=> $b; - } - - private function privateSort($a, $b) - { - return $a <=> $b; - } -} - ?> diff --git a/rules/code-quality/tests/Rector/Array_/CallableThisArrayToAnonymousFunctionRector/Source/SortingClass.php b/rules/code-quality/tests/Rector/Array_/CallableThisArrayToAnonymousFunctionRector/Source/SortingClass.php new file mode 100644 index 000000000000..547e378f0b9b --- /dev/null +++ b/rules/code-quality/tests/Rector/Array_/CallableThisArrayToAnonymousFunctionRector/Source/SortingClass.php @@ -0,0 +1,23 @@ + $b; + } + + protected function protectedSort($a, $b) + { + return $a <=> $b; + } + + private function privateSort($a, $b) + { + return $a <=> $b; + } +} diff --git a/rules/code-quality/tests/Rector/Isset_/IssetOnPropertyObjectToPropertyExistsRector/Fixture/property_after_instantiation_if_always_exists.php.inc b/rules/code-quality/tests/Rector/Isset_/IssetOnPropertyObjectToPropertyExistsRector/Fixture/property_after_instantiation_if_always_exists.php.inc index 44aeccc2872c..a9692a3c281c 100644 --- a/rules/code-quality/tests/Rector/Isset_/IssetOnPropertyObjectToPropertyExistsRector/Fixture/property_after_instantiation_if_always_exists.php.inc +++ b/rules/code-quality/tests/Rector/Isset_/IssetOnPropertyObjectToPropertyExistsRector/Fixture/property_after_instantiation_if_always_exists.php.inc @@ -2,7 +2,7 @@ namespace Rector\CodeQuality\Tests\Rector\Isset_\IssetOnPropertyObjectToPropertyExistsRector\Fixture; -class PropertyAfterInstantiationIfAlwaysExists +final class PropertyAfterInstantiationIfAlwaysExists { public $x; public $y; @@ -22,7 +22,7 @@ class PropertyAfterInstantiationIfAlwaysExists namespace Rector\CodeQuality\Tests\Rector\Isset_\IssetOnPropertyObjectToPropertyExistsRector\Fixture; -class PropertyAfterInstantiationIfAlwaysExists +final class PropertyAfterInstantiationIfAlwaysExists { public $x; public $y; diff --git a/rules/code-quality/tests/Rector/Isset_/IssetOnPropertyObjectToPropertyExistsRector/Fixture/property_maybe_exists_after_instantiation.php.inc b/rules/code-quality/tests/Rector/Isset_/IssetOnPropertyObjectToPropertyExistsRector/Fixture/property_maybe_exists_after_instantiation.php.inc index 70109767dc3b..c7a1f6130f8f 100644 --- a/rules/code-quality/tests/Rector/Isset_/IssetOnPropertyObjectToPropertyExistsRector/Fixture/property_maybe_exists_after_instantiation.php.inc +++ b/rules/code-quality/tests/Rector/Isset_/IssetOnPropertyObjectToPropertyExistsRector/Fixture/property_maybe_exists_after_instantiation.php.inc @@ -2,12 +2,11 @@ namespace Rector\CodeQuality\Tests\Rector\Isset_\IssetOnPropertyObjectToPropertyExistsRector\Fixture; -class PropertyMaybeExistsAfterInstantiation +final class PropertyMaybeExistsAfterInstantiation { public function init() { $this->x = 'a'; - $this->y = 'b'; } public function run() @@ -16,8 +15,6 @@ class PropertyMaybeExistsAfterInstantiation $obj->init(); isset($obj->x); - isset($obj->y); - isset($obj->x) && isset($obj->y); } } @@ -27,12 +24,11 @@ class PropertyMaybeExistsAfterInstantiation namespace Rector\CodeQuality\Tests\Rector\Isset_\IssetOnPropertyObjectToPropertyExistsRector\Fixture; -class PropertyMaybeExistsAfterInstantiation +final class PropertyMaybeExistsAfterInstantiation { public function init() { $this->x = 'a'; - $this->y = 'b'; } public function run() @@ -41,8 +37,6 @@ class PropertyMaybeExistsAfterInstantiation $obj->init(); property_exists($obj, 'x') && $obj->x !== null; - property_exists($obj, 'y') && $obj->y !== null; - property_exists($obj, 'x') && $obj->x !== null && (property_exists($obj, 'y') && $obj->y !== null); } } diff --git a/rules/coding-style/src/Node/NameImporter.php b/rules/coding-style/src/Node/NameImporter.php index 788e7ca085be..589b47ec50c2 100644 --- a/rules/coding-style/src/Node/NameImporter.php +++ b/rules/coding-style/src/Node/NameImporter.php @@ -12,11 +12,11 @@ use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Stmt\Namespace_; use PhpParser\Node\Stmt\UseUse; +use PHPStan\Reflection\ReflectionProvider; use Rector\CodingStyle\ClassNameImport\AliasUsesResolver; use Rector\CodingStyle\ClassNameImport\ClassNameImportSkipper; use Rector\Core\Configuration\Option; use Rector\NodeNameResolver\NodeNameResolver; -use Rector\NodeTypeResolver\ClassExistenceStaticHelper; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\PostRector\Collector\UseNodesToAddCollector; use Rector\PSR4\Collector\RenamedClassesCollector; @@ -66,6 +66,11 @@ final class NameImporter */ private $renamedClassesCollector; + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + public function __construct( AliasUsesResolver $aliasUsesResolver, ClassNameImportSkipper $classNameImportSkipper, @@ -73,7 +78,8 @@ public function __construct( ParameterProvider $parameterProvider, RenamedClassesCollector $renamedClassesCollector, StaticTypeMapper $staticTypeMapper, - UseNodesToAddCollector $useNodesToAddCollector + UseNodesToAddCollector $useNodesToAddCollector, + ReflectionProvider $reflectionProvider ) { $this->staticTypeMapper = $staticTypeMapper; $this->aliasUsesResolver = $aliasUsesResolver; @@ -82,6 +88,7 @@ public function __construct( $this->parameterProvider = $parameterProvider; $this->useNodesToAddCollector = $useNodesToAddCollector; $this->renamedClassesCollector = $renamedClassesCollector; + $this->reflectionProvider = $reflectionProvider; } public function importName(Name $name): ?Name @@ -198,15 +205,17 @@ private function isFunctionOrConstantImportWithSingleName(Name $name): bool if ($autoImportNames && ! $parentNode instanceof Node && ! Strings::contains( $fullName, '\\' - ) && function_exists($fullName)) { + ) && $this->reflectionProvider->hasFunction(new Name($fullName), null)) { return true; } if ($parentNode instanceof ConstFetch) { return count($name->parts) === 1; } + if ($parentNode instanceof FuncCall) { return count($name->parts) === 1; } + return false; } @@ -225,11 +234,12 @@ private function isNonExistingClassLikeAndFunctionFullyQualifiedName(Name $name) } // skip-non existing class-likes and functions - if (ClassExistenceStaticHelper::doesClassLikeExist($classOrFunctionName)) { + if ($this->reflectionProvider->hasClass($classOrFunctionName)) { return false; } - return ! function_exists($classOrFunctionName); + $parent = $name->getAttribute(AttributeKey::PARENT_NODE); + return ! $parent instanceof FuncCall; } private function addUseImport(Name $name, FullyQualifiedObjectType $fullyQualifiedObjectType): void diff --git a/rules/coding-style/src/Rector/ClassMethod/MakeInheritedMethodVisibilitySameAsParentRector.php b/rules/coding-style/src/Rector/ClassMethod/MakeInheritedMethodVisibilitySameAsParentRector.php index 7b70f1e1d411..92776aba124f 100644 --- a/rules/coding-style/src/Rector/ClassMethod/MakeInheritedMethodVisibilitySameAsParentRector.php +++ b/rules/coding-style/src/Rector/ClassMethod/MakeInheritedMethodVisibilitySameAsParentRector.php @@ -92,12 +92,14 @@ public function refactor(Node $node): ?Node /** @var string $methodName */ $methodName = $this->getName($node->name); - foreach ($classReflection->getParentClassesNames() as $parentClassName) { - if (! method_exists($parentClassName, $methodName)) { + foreach ($classReflection->getParents() as $parentClassReflection) { + if (! $parentClassReflection->hasMethod($methodName)) { continue; } - $parentReflectionMethod = new ReflectionMethod($parentClassName, $methodName); + $nativeClassReflection = $parentClassReflection->getNativeReflection(); + + $parentReflectionMethod = $nativeClassReflection->getMethod($methodName); if ($this->isClassMethodCompatibleWithParentReflectionMethod($node, $parentReflectionMethod)) { return null; } diff --git a/rules/coding-style/src/Rector/ClassMethod/YieldClassMethodToArrayClassMethodRector.php b/rules/coding-style/src/Rector/ClassMethod/YieldClassMethodToArrayClassMethodRector.php index 4cd2c83ccb17..3d1fde49d373 100644 --- a/rules/coding-style/src/Rector/ClassMethod/YieldClassMethodToArrayClassMethodRector.php +++ b/rules/coding-style/src/Rector/ClassMethod/YieldClassMethodToArrayClassMethodRector.php @@ -28,10 +28,10 @@ final class YieldClassMethodToArrayClassMethodRector extends AbstractRector impl /** * @var string */ - public const METHODS_BY_TYPE = '$methodsByType'; + public const METHODS_BY_TYPE = 'methods_by_type'; /** - * @var string[][] + * @var array */ private $methodsByType = []; @@ -41,7 +41,7 @@ final class YieldClassMethodToArrayClassMethodRector extends AbstractRector impl private $nodeTransformer; /** - * @param string[][] $methodsByType + * @param array $methodsByType */ public function __construct(NodeTransformer $nodeTransformer, array $methodsByType = []) { diff --git a/rules/coding-style/src/Rector/MethodCall/PreferThisOrSelfMethodCallRector.php b/rules/coding-style/src/Rector/MethodCall/PreferThisOrSelfMethodCallRector.php index 4c75b24bfd1a..a11e9876fd5f 100644 --- a/rules/coding-style/src/Rector/MethodCall/PreferThisOrSelfMethodCallRector.php +++ b/rules/coding-style/src/Rector/MethodCall/PreferThisOrSelfMethodCallRector.php @@ -49,7 +49,7 @@ final class PreferThisOrSelfMethodCallRector extends AbstractRector implements C private const SELF = 'self'; /** - * @var array + * @var array */ private $typeToPreference = []; @@ -79,7 +79,7 @@ public function run() , [ self::TYPE_TO_PREFERENCE => [ - '\PHPUnit\Framework\TestCase' => self::PREFER_SELF, + 'PHPUnit\Framework\TestCase' => self::PREFER_SELF, ], ] ), diff --git a/rules/coding-style/src/Rector/String_/SplitStringClassConstantToClassConstFetchRector.php b/rules/coding-style/src/Rector/String_/SplitStringClassConstantToClassConstFetchRector.php index d1aa15cbb539..1d418f6093b5 100644 --- a/rules/coding-style/src/Rector/String_/SplitStringClassConstantToClassConstFetchRector.php +++ b/rules/coding-style/src/Rector/String_/SplitStringClassConstantToClassConstFetchRector.php @@ -9,6 +9,7 @@ use PhpParser\Node\Expr\ClassConstFetch; use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Scalar\String_; +use PHPStan\Reflection\ReflectionProvider; use Rector\Core\Rector\AbstractRector; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -18,6 +19,16 @@ */ final class SplitStringClassConstantToClassConstFetchRector extends AbstractRector { + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + public function getRuleDefinition(): RuleDefinition { return new RuleDefinition( @@ -78,7 +89,7 @@ public function refactor(Node $node): ?Node // a possible constant reference [$possibleClass, $secondPart] = explode('::', $node->value); - if (! class_exists($possibleClass)) { + if (! $this->reflectionProvider->hasClass($possibleClass)) { return null; } diff --git a/rules/coding-style/src/Rector/String_/UseClassKeywordForClassNameResolutionRector.php b/rules/coding-style/src/Rector/String_/UseClassKeywordForClassNameResolutionRector.php index b14a0a0fa4b4..50c33514ad74 100644 --- a/rules/coding-style/src/Rector/String_/UseClassKeywordForClassNameResolutionRector.php +++ b/rules/coding-style/src/Rector/String_/UseClassKeywordForClassNameResolutionRector.php @@ -9,6 +9,7 @@ use PhpParser\Node\Expr\ClassConstFetch; use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Scalar\String_; +use PHPStan\Reflection\ReflectionProvider; use Rector\Core\Rector\AbstractRector; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -24,6 +25,16 @@ final class UseClassKeywordForClassNameResolutionRector extends AbstractRector */ private const CLASS_BEFORE_STATIC_ACCESS_REGEX = '#(?[\\\\a-zA-Z0-9_\\x80-\\xff]*)::#'; + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + public function getRuleDefinition(): RuleDefinition { return new RuleDefinition( @@ -83,7 +94,7 @@ public function getExistingClasses(String_ $string): array $classNames = []; foreach ($matches['class_name'] as $matchedClassName) { - if (! class_exists($matchedClassName)) { + if (! $this->reflectionProvider->hasClass($matchedClassName)) { continue; } @@ -119,7 +130,7 @@ private function createExpressionsToConcat(array $parts): array { $exprsToConcat = []; foreach ($parts as $part) { - if (class_exists($part)) { + if ($this->reflectionProvider->hasClass($part)) { $exprsToConcat[] = new ClassConstFetch(new FullyQualified(ltrim($part, '\\')), 'class'); } else { $exprsToConcat[] = new String_($part); diff --git a/rules/coding-style/tests/Rector/ClassConst/VarConstantCommentRector/Fixture/skip_class_string.php.inc b/rules/coding-style/tests/Rector/ClassConst/VarConstantCommentRector/Fixture/skip_class_string.php.inc deleted file mode 100644 index acbdc1c70153..000000000000 --- a/rules/coding-style/tests/Rector/ClassConst/VarConstantCommentRector/Fixture/skip_class_string.php.inc +++ /dev/null @@ -1,11 +0,0 @@ -getData()) === 0; - 0 === count($self->getData()); - count($self->getData()) > 0; - 0 < count($self->getData()); + count($this->getData()) === 0; + 0 === count($this->getData()); + count($this->getData()) > 0; + 0 < count($this->getData()); } public function getData(): array @@ -25,15 +24,14 @@ class FixtureFromMethodCall namespace Rector\CodingStyle\Tests\Rector\FuncCall\CountArrayToEmptyArrayComparisonRector\Fixture; -class FixtureFromMethodCall +final class FixtureFromMethodCall { public function run() { - $self = new self(); - $self->getData() === []; - [] === $self->getData(); - $self->getData() !== []; - [] !== $self->getData(); + $this->getData() === []; + [] === $this->getData(); + $this->getData() !== []; + [] !== $this->getData(); } public function getData(): array diff --git a/rules/coding-style/tests/Rector/Namespace_/ImportFullyQualifiedNamesRector/ImportFullyQualifiedNamesRectorTest.php b/rules/coding-style/tests/Rector/Namespace_/ImportFullyQualifiedNamesRector/ImportFullyQualifiedNamesRectorTest.php index bf4a25c4eb2d..e39dd2f18302 100644 --- a/rules/coding-style/tests/Rector/Namespace_/ImportFullyQualifiedNamesRector/ImportFullyQualifiedNamesRectorTest.php +++ b/rules/coding-style/tests/Rector/Namespace_/ImportFullyQualifiedNamesRector/ImportFullyQualifiedNamesRectorTest.php @@ -23,16 +23,25 @@ public function test(SmartFileInfo $fileInfo): void $this->doTestFileInfo($fileInfo); } + /** + * @return Iterator + */ public function provideData(): Iterator { return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); } + /** + * @return Iterator + */ public function provideDataFunction(): Iterator { return $this->yieldFilesFromDirectory(__DIR__ . '/FixtureFunction'); } + /** + * @return Iterator + */ public function provideDataGeneric(): Iterator { return $this->yieldFilesFromDirectory(__DIR__ . '/FixtureGeneric'); diff --git a/rules/dead-code/src/Comparator/CurrentAndParentClassMethodComparator.php b/rules/dead-code/src/Comparator/CurrentAndParentClassMethodComparator.php index 4796cfa691a3..f6268bb32c02 100644 --- a/rules/dead-code/src/Comparator/CurrentAndParentClassMethodComparator.php +++ b/rules/dead-code/src/Comparator/CurrentAndParentClassMethodComparator.php @@ -9,12 +9,12 @@ use PhpParser\Node\Param; use PhpParser\Node\Stmt\ClassMethod; use PHPStan\Analyser\Scope; +use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\MethodReflection; use Rector\Core\Exception\ShouldNotHappenException; use Rector\Core\PhpParser\Comparing\NodeComparator; use Rector\DeadCode\Comparator\Parameter\ParameterDefaultsComparator; use Rector\DeadCode\Comparator\Parameter\ParameterTypeComparator; -use Rector\NodeCollector\NodeCollector\NodeRepository; use Rector\NodeCollector\Reflection\MethodReflectionProvider; use Rector\NodeNameResolver\NodeNameResolver; use Rector\NodeTypeResolver\Node\AttributeKey; @@ -31,11 +31,6 @@ final class CurrentAndParentClassMethodComparator */ private $nodeComparator; - /** - * @var NodeRepository - */ - private $nodeRepository; - /** * @var MethodReflectionProvider */ @@ -53,14 +48,12 @@ final class CurrentAndParentClassMethodComparator public function __construct( NodeNameResolver $nodeNameResolver, - NodeRepository $nodeRepository, MethodReflectionProvider $methodReflectionProvider, ParameterDefaultsComparator $parameterDefaultsComparator, ParameterTypeComparator $parameterTypeComparator, NodeComparator $nodeComparator ) { $this->nodeNameResolver = $nodeNameResolver; - $this->nodeRepository = $nodeRepository; $this->methodReflectionProvider = $methodReflectionProvider; $this->parameterDefaultsComparator = $parameterDefaultsComparator; $this->parameterTypeComparator = $parameterTypeComparator; @@ -126,46 +119,54 @@ private function isParentClassMethodVisibilityOrDefaultOverride( ClassMethod $classMethod, StaticCall $staticCall ): bool { - /** @var string $className */ - $className = $staticCall->getAttribute(AttributeKey::CLASS_NAME); + $scope = $classMethod->getAttribute(AttributeKey::SCOPE); + if (! $scope instanceof Scope) { + return false; + } - $parentClassName = get_parent_class($className); - if (! $parentClassName) { - throw new ShouldNotHappenException(); + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { + return false; } - /** @var string $methodName */ $methodName = $this->nodeNameResolver->getName($staticCall->name); - - $parentClassMethod = $this->nodeRepository->findClassMethod($parentClassName, $methodName); - if (! $parentClassMethod instanceof ClassMethod) { - return $this->checkOverrideUsingReflection($classMethod, $parentClassName, $methodName); - } - if (! $parentClassMethod->isProtected()) { - return $this->checkOverrideUsingReflection($classMethod, $parentClassName, $methodName); + if ($methodName === null) { + return false; } - if (! $classMethod->isPublic()) { - return $this->checkOverrideUsingReflection($classMethod, $parentClassName, $methodName); + + foreach ($classReflection->getParents() as $parentClassReflection) { + if (! $parentClassReflection->hasMethod($methodName)) { + continue; + } + + $nativeParentClassReflection = $parentClassReflection->getNativeReflection(); + $nativeParentClassMethodReflection = $nativeParentClassReflection->getMethod($methodName); + + if (! $nativeParentClassMethodReflection->isProtected()) { + return $this->checkOverrideUsingReflection($classMethod, $parentClassReflection, $methodName); + } + + if (! $nativeParentClassMethodReflection->isPublic()) { + return $this->checkOverrideUsingReflection($classMethod, $parentClassReflection, $methodName); + } + + return true; } - return true; + + return false; } private function checkOverrideUsingReflection( ClassMethod $classMethod, - string $parentClassName, + ClassReflection $classReflection, string $methodName ): bool { - // @todo use phpstan reflecoitn $scope = $classMethod->getAttribute(AttributeKey::SCOPE); if (! $scope instanceof Scope) { throw new ShouldNotHappenException(); } - $parentMethodReflection = $this->methodReflectionProvider->provideByClassAndMethodName( - $parentClassName, - $methodName, - $scope - ); + $parentMethodReflection = $classReflection->getMethod($methodName, $scope); // 3rd party code if ($parentMethodReflection !== null) { diff --git a/rules/dead-code/src/NodeManipulator/CallDefaultParamValuesResolver.php b/rules/dead-code/src/NodeManipulator/CallDefaultParamValuesResolver.php index 87460acc54c7..379d69f26af9 100644 --- a/rules/dead-code/src/NodeManipulator/CallDefaultParamValuesResolver.php +++ b/rules/dead-code/src/NodeManipulator/CallDefaultParamValuesResolver.php @@ -11,12 +11,15 @@ use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\FunctionLike; +use PhpParser\Node\Name; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Function_; +use PHPStan\Reflection\ReflectionProvider; use Rector\NodeCollector\NodeCollector\NodeRepository; use Rector\NodeNameResolver\NodeNameResolver; use Rector\NodeTypeResolver\Node\AttributeKey; -use ReflectionFunction; +use ReflectionParameter; +use Symplify\PackageBuilder\Reflection\PrivatesAccessor; final class CallDefaultParamValuesResolver { @@ -30,10 +33,26 @@ final class CallDefaultParamValuesResolver */ private $nodeNameResolver; - public function __construct(NodeRepository $nodeRepository, NodeNameResolver $nodeNameResolver) - { + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + /** + * @var PrivatesAccessor + */ + private $privatesAccessor; + + public function __construct( + NodeRepository $nodeRepository, + NodeNameResolver $nodeNameResolver, + ReflectionProvider $reflectionProvider, + PrivatesAccessor $privatesAccessor + ) { $this->nodeRepository = $nodeRepository; $this->nodeNameResolver = $nodeNameResolver; + $this->reflectionProvider = $reflectionProvider; + $this->privatesAccessor = $privatesAccessor; } /** @@ -89,29 +108,39 @@ public function resolveFromCall(Node $node): array */ private function resolveFromFunctionName(string $functionName): array { - $functionNode = $this->nodeRepository->findFunction($functionName); - if ($functionNode !== null) { - return $this->resolveFromFunctionLike($functionNode); + $function = $this->nodeRepository->findFunction($functionName); + if ($function instanceof Function_) { + return $this->resolveFromFunctionLike($function); } // non existing function - if (! function_exists($functionName)) { + $functionNameNode = new Name($functionName); + if (! $this->reflectionProvider->hasFunction($functionNameNode, null)) { + return []; + } + + $functionReflection = $this->reflectionProvider->getFunction($functionNameNode, null); + if ($functionReflection->isBuiltin()) { return []; } - $reflectionFunction = new ReflectionFunction($functionName); - if ($reflectionFunction->isUserDefined()) { - $defaultValues = []; + $defaultValues = []; + + $parametersAcceptor = $functionReflection->getVariants()[0]; - foreach ($reflectionFunction->getParameters() as $key => $reflectionParameter) { - if ($reflectionParameter->isDefaultValueAvailable()) { - $defaultValues[$key] = BuilderHelpers::normalizeValue($reflectionParameter->getDefaultValue()); - } + foreach ($parametersAcceptor->getParameters() as $key => $reflectionParameter) { + /** @var ReflectionParameter $nativeReflectionParameter */ + $nativeReflectionParameter = $this->privatesAccessor->getPrivateProperty( + $reflectionParameter, + 'reflection' + ); + if (! $nativeReflectionParameter->isDefaultValueAvailable()) { + continue; } - return $defaultValues; + $defaultValues[$key] = BuilderHelpers::normalizeValue($nativeReflectionParameter->getDefaultValue()); } - return []; + return $defaultValues; } } diff --git a/rules/dead-code/src/Rector/ClassConst/RemoveUnusedClassConstantRector.php b/rules/dead-code/src/Rector/ClassConst/RemoveUnusedClassConstantRector.php index f33dd81de976..bed541801236 100644 --- a/rules/dead-code/src/Rector/ClassConst/RemoveUnusedClassConstantRector.php +++ b/rules/dead-code/src/Rector/ClassConst/RemoveUnusedClassConstantRector.php @@ -7,6 +7,9 @@ use PhpParser\Node; use PhpParser\Node\Stmt\ClassConst; use PhpParser\Node\Stmt\ClassLike; +use PHPStan\Analyser\Scope; +use PHPStan\Reflection\ClassReflection; +use PHPStan\ShouldNotHappenException; use Rector\BetterPhpDocParser\ValueObject\PhpDocNode\ApiPhpDocTagNode; use Rector\Caching\Contract\Rector\ZeroCacheRectorInterface; use Rector\Core\NodeManipulator\ClassConstManipulator; @@ -74,12 +77,15 @@ public function refactor(Node $node): ?Node return null; } - /** @var string|null $class */ - $class = $node->getAttribute(AttributeKey::CLASS_NAME); - if ($class === null) { - return null; + /** @var Scope $scope */ + $scope = $node->getAttribute(AttributeKey::SCOPE); + + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { + throw new ShouldNotHappenException(); } - $nodeRepositoryFindInterface = $this->nodeRepository->findInterface($class); + + $nodeRepositoryFindInterface = $this->nodeRepository->findInterface($classReflection->getName()); // 0. constants declared in interfaces have to be public if ($nodeRepositoryFindInterface !== null) { @@ -90,13 +96,18 @@ public function refactor(Node $node): ?Node /** @var string $constant */ $constant = $this->getName($node); - $directUseClasses = $this->nodeRepository->findDirectClassConstantFetches($class, $constant); - if ($directUseClasses !== []) { - return null; - } + $directUsingClassReflections = $this->nodeRepository->findDirectClassConstantFetches( + $classReflection, + $constant + ); + + $indirectUsingClassReflections = $this->nodeRepository->findIndirectClassConstantFetches( + $classReflection, + $constant + ); - $indirectUseClasses = $this->nodeRepository->findIndirectClassConstantFetches($class, $constant); - if ($indirectUseClasses !== []) { + $usingClassReflections = array_merge($directUsingClassReflections, $indirectUsingClassReflections); + if ($usingClassReflections !== []) { return null; } @@ -125,6 +136,7 @@ private function shouldSkip(ClassConst $classConst): bool } $classLike = $classConst->getAttribute(AttributeKey::CLASS_NODE); + if ($classLike instanceof ClassLike) { $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($classLike); return $phpDocInfo->hasByType(ApiPhpDocTagNode::class); diff --git a/rules/dead-code/src/Rector/ClassMethod/RemoveEmptyClassMethodRector.php b/rules/dead-code/src/Rector/ClassMethod/RemoveEmptyClassMethodRector.php index ffc58fea6942..408920779651 100644 --- a/rules/dead-code/src/Rector/ClassMethod/RemoveEmptyClassMethodRector.php +++ b/rules/dead-code/src/Rector/ClassMethod/RemoveEmptyClassMethodRector.php @@ -111,7 +111,7 @@ private function shouldSkipNonFinalNonPrivateClassMethod(Class_ $class, ClassMet return false; } - if ($this->isName($classMethod, '__*')) { + if ($classMethod->isMagic()) { return false; } diff --git a/rules/dead-code/src/Rector/ClassMethod/RemoveUnusedPrivateMethodRector.php b/rules/dead-code/src/Rector/ClassMethod/RemoveUnusedPrivateMethodRector.php index 842a8878c3a7..8487bb11ea64 100644 --- a/rules/dead-code/src/Rector/ClassMethod/RemoveUnusedPrivateMethodRector.php +++ b/rules/dead-code/src/Rector/ClassMethod/RemoveUnusedPrivateMethodRector.php @@ -5,12 +5,10 @@ namespace Rector\DeadCode\Rector\ClassMethod; use PhpParser\Node; -use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; -use PhpParser\Node\Stmt\Interface_; -use PhpParser\Node\Stmt\Trait_; +use PHPStan\Analyser\Scope; +use PHPStan\Reflection\ClassReflection; use Rector\Core\Rector\AbstractRector; -use Rector\Core\Util\StaticInstanceOf; use Rector\NodeTypeResolver\Node\AttributeKey; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -81,18 +79,26 @@ public function refactor(Node $node): ?Node private function shouldSkip(ClassMethod $classMethod): bool { - /** @var Class_|Interface_|Trait_|null $classLike */ - $classLike = $classMethod->getAttribute(AttributeKey::CLASS_NODE); - if ($classLike === null) { + $scope = $classMethod->getAttribute(AttributeKey::SCOPE); + if (! $scope instanceof Scope) { + return true; + } + + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { return true; } // unreliable to detect trait, interface doesn't make sense - if (StaticInstanceOf::isOneOf($classLike, [Trait_::class, Interface_::class])) { + if ($classReflection->isTrait()) { + return true; + } + + if ($classReflection->isInterface()) { return true; } - if ($this->classNodeAnalyzer->isAnonymousClass($classLike)) { + if ($classReflection->isAnonymous()) { return true; } @@ -102,6 +108,6 @@ private function shouldSkip(ClassMethod $classMethod): bool } // skip magic methods - @see https://www.php.net/manual/en/language.oop5.magic.php - return $this->isName($classMethod, '__*'); + return $classMethod->isMagic(); } } diff --git a/rules/dead-code/src/Rector/ClassMethod/RemoveUnusedPublicMethodRector.php b/rules/dead-code/src/Rector/ClassMethod/RemoveUnusedPublicMethodRector.php index 435ee71a0033..7ae332c4e16a 100644 --- a/rules/dead-code/src/Rector/ClassMethod/RemoveUnusedPublicMethodRector.php +++ b/rules/dead-code/src/Rector/ClassMethod/RemoveUnusedPublicMethodRector.php @@ -9,7 +9,6 @@ use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Stmt\ClassMethod; use Rector\Caching\Contract\Rector\ZeroCacheRectorInterface; -use Rector\CodingStyle\ValueObject\ObjectMagicMethods; use Rector\Core\Rector\AbstractRector; use Rector\NodeCollector\ValueObject\ArrayCallable; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; @@ -83,15 +82,7 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - if ($this->isOpenSourceProjectType()) { - return null; - } - - if (! $node->isPublic()) { - return null; - } - - if ($this->isNames($node, ObjectMagicMethods::METHOD_NAMES)) { + if ($this->shouldSkip($node)) { return null; } @@ -114,4 +105,25 @@ public function refactor(Node $node): ?Node $this->removeNode($node); return $node; } + + private function shouldSkip(ClassMethod $classMethod): bool + { + if ($this->isOpenSourceProjectType()) { + return true; + } + + if (! $classMethod->isPublic()) { + return true; + } + + if ($classMethod->isMagic()) { + return true; + } + + if ($this->isNames($classMethod, ['test'])) { + return true; + } + + return false; + } } diff --git a/rules/dead-code/src/Rector/MethodCall/RemoveDefaultArgumentValueRector.php b/rules/dead-code/src/Rector/MethodCall/RemoveDefaultArgumentValueRector.php index d0b6dd8f111a..487af0abb1a0 100644 --- a/rules/dead-code/src/Rector/MethodCall/RemoveDefaultArgumentValueRector.php +++ b/rules/dead-code/src/Rector/MethodCall/RemoveDefaultArgumentValueRector.php @@ -10,9 +10,9 @@ use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Name; +use PHPStan\Reflection\ReflectionProvider; use Rector\Core\Rector\AbstractRector; use Rector\DeadCode\NodeManipulator\CallDefaultParamValuesResolver; -use ReflectionFunction; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -26,18 +26,24 @@ final class RemoveDefaultArgumentValueRector extends AbstractRector */ private $callDefaultParamValuesResolver; - public function __construct(CallDefaultParamValuesResolver $callDefaultParamValuesResolver) - { + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct( + CallDefaultParamValuesResolver $callDefaultParamValuesResolver, + ReflectionProvider $reflectionProvider + ) { $this->callDefaultParamValuesResolver = $callDefaultParamValuesResolver; + $this->reflectionProvider = $reflectionProvider; } public function getRuleDefinition(): RuleDefinition { - return new RuleDefinition( - 'Remove argument value, if it is the same as default value', - [ - new CodeSample( - <<<'CODE_SAMPLE' + return new RuleDefinition('Remove argument value, if it is the same as default value', [ + new CodeSample( + <<<'CODE_SAMPLE' class SomeClass { public function run() @@ -79,8 +85,7 @@ public function runStaticWithDefault($cards = []) } CODE_SAMPLE ), - - ]); + ]); } /** @@ -136,14 +141,15 @@ private function shouldSkip(Node $node): bool return false; } - if (! function_exists($functionName)) { + $functionNameNode = new Name($functionName); + if (! $this->reflectionProvider->hasFunction($functionNameNode, null)) { return false; } - $reflectionFunction = new ReflectionFunction($functionName); + $reflectionFunction = $this->reflectionProvider->getFunction($functionNameNode, null); // skip native functions, hard to analyze without stubs (stubs would make working with IDE non-practical) - return $reflectionFunction->isInternal(); + return $reflectionFunction->isBuiltin(); } /** diff --git a/rules/dead-code/src/Rector/Property/RemoveSetterOnlyPropertyAndMethodCallRector.php b/rules/dead-code/src/Rector/Property/RemoveSetterOnlyPropertyAndMethodCallRector.php index ef39d6acd3bb..b1f9967a2813 100644 --- a/rules/dead-code/src/Rector/Property/RemoveSetterOnlyPropertyAndMethodCallRector.php +++ b/rules/dead-code/src/Rector/Property/RemoveSetterOnlyPropertyAndMethodCallRector.php @@ -125,7 +125,6 @@ public function refactor(Node $node): ?Node $propertyFetches = $this->propertyFetchFinder->findPrivatePropertyFetches($node); $classMethodsToCheck = $this->collectClassMethodsToCheck($propertyFetches); - $vendorLockedClassMethodNames = $this->getVendorLockedClassMethodNames($classMethodsToCheck); $this->complexNodeRemover->removePropertyAndUsages($node, $vendorLockedClassMethodNames); @@ -201,7 +200,7 @@ private function collectClassMethodsToCheck(array $propertyFetches): array } /** - * @param ClassMethod[] $methodsToCheck + * @param array $methodsToCheck * @return string[] */ private function getVendorLockedClassMethodNames(array $methodsToCheck): array diff --git a/rules/dead-code/src/UnusedNodeResolver/ClassUnusedPrivateClassMethodResolver.php b/rules/dead-code/src/UnusedNodeResolver/ClassUnusedPrivateClassMethodResolver.php index e34ba4ad160e..5c9f8803162f 100644 --- a/rules/dead-code/src/UnusedNodeResolver/ClassUnusedPrivateClassMethodResolver.php +++ b/rules/dead-code/src/UnusedNodeResolver/ClassUnusedPrivateClassMethodResolver.php @@ -6,10 +6,11 @@ use Nette\Utils\Strings; use PhpParser\Node\Stmt\Class_; +use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ReflectionProvider; use Rector\Core\NodeManipulator\ClassManipulator; use Rector\NodeCollector\NodeCollector\NodeRepository; use Rector\NodeNameResolver\NodeNameResolver; -use ReflectionMethod; final class ClassUnusedPrivateClassMethodResolver { @@ -28,14 +29,21 @@ final class ClassUnusedPrivateClassMethodResolver */ private $nodeRepository; + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + public function __construct( ClassManipulator $classManipulator, NodeNameResolver $nodeNameResolver, - NodeRepository $nodeRepository + NodeRepository $nodeRepository, + ReflectionProvider $reflectionProvider ) { $this->nodeNameResolver = $nodeNameResolver; $this->classManipulator = $classManipulator; $this->nodeRepository = $nodeRepository; + $this->reflectionProvider = $reflectionProvider; } /** @@ -98,14 +106,14 @@ private function filterOutInterfaceRequiredMethods(Class_ $class, array $unusedM { /** @var string $className */ $className = $this->nodeNameResolver->getName($class); - - /** @var string[] $interfaces */ - $interfaces = (array) class_implements($className); + $classReflection = $this->reflectionProvider->getClass($className); $interfaceMethods = []; - foreach ($interfaces as $interface) { - $currentInterfaceMethods = get_class_methods($interface); - $interfaceMethods = array_merge($interfaceMethods, $currentInterfaceMethods); + foreach ($classReflection->getInterfaces() as $interfaceClassReflection) { + $nativeInterfaceClassReflection = $interfaceClassReflection->getNativeReflection(); + foreach ($nativeInterfaceClassReflection->getMethods() as $reflectionMethod) { + $interfaceMethods[] = $reflectionMethod->getName(); + } } return array_diff($unusedMethods, $interfaceMethods); @@ -121,18 +129,26 @@ private function filterOutParentAbstractMethods(Class_ $class, array $unusedMeth return $unusedMethods; } - /** @var string[] $parentClasses */ - $parentClasses = (array) class_parents($class); + $className = $this->nodeNameResolver->getName($class); + if ($className === null) { + return []; + } + + if (! $this->reflectionProvider->hasClass($className)) { + return []; + } + + $classReflection = $this->reflectionProvider->getClass($className); $parentAbstractMethods = []; - foreach ($parentClasses as $parentClass) { + foreach ($classReflection->getParents() as $parentClassReflection) { foreach ($unusedMethods as $unusedMethod) { if (in_array($unusedMethod, $parentAbstractMethods, true)) { continue; } - if ($this->isMethodAbstract($parentClass, $unusedMethod)) { + if ($this->isMethodAbstract($parentClassReflection, $unusedMethod)) { $parentAbstractMethods[] = $unusedMethod; } } @@ -141,12 +157,14 @@ private function filterOutParentAbstractMethods(Class_ $class, array $unusedMeth return array_diff($unusedMethods, $parentAbstractMethods); } - private function isMethodAbstract(string $class, string $method): bool + private function isMethodAbstract(ClassReflection $classReflection, string $method): bool { - if (! method_exists($class, $method)) { + if (! $classReflection->hasMethod($method)) { return false; } - $reflectionMethod = new ReflectionMethod($class, $method); + + $nativeClassReflection = $classReflection->getNativeReflection(); + $reflectionMethod = $nativeClassReflection->getMethod($method); return $reflectionMethod->isAbstract(); } diff --git a/rules/dead-code/tests/Rector/ClassConst/RemoveUnusedClassConstantRector/Fixture/skip_child_used.php.inc b/rules/dead-code/tests/Rector/ClassConst/RemoveUnusedClassConstantRector/Fixture/skip_child_used.php.inc deleted file mode 100644 index f3343b5653d0..000000000000 --- a/rules/dead-code/tests/Rector/ClassConst/RemoveUnusedClassConstantRector/Fixture/skip_child_used.php.inc +++ /dev/null @@ -1,16 +0,0 @@ - diff --git a/rules/dead-code/tests/Rector/ClassMethod/RemoveUnusedPublicMethodRector/Fixture/skip_tests.php.inc b/rules/dead-code/tests/Rector/ClassMethod/RemoveUnusedPublicMethodRector/Fixture/skip_tests.php.inc new file mode 100644 index 000000000000..dc936fcd87f1 --- /dev/null +++ b/rules/dead-code/tests/Rector/ClassMethod/RemoveUnusedPublicMethodRector/Fixture/skip_tests.php.inc @@ -0,0 +1,12 @@ +assertTrue('yes'); + } +} diff --git a/rules/dead-code/tests/Rector/Class_/RemoveUnusedDoctrineEntityMethodAndPropertyRector/Fixture/skip_trait_complex.php.inc b/rules/dead-code/tests/Rector/Class_/RemoveUnusedDoctrineEntityMethodAndPropertyRector/Fixture/skip_trait_complex.php.inc deleted file mode 100644 index 74a6943b323d..000000000000 --- a/rules/dead-code/tests/Rector/Class_/RemoveUnusedDoctrineEntityMethodAndPropertyRector/Fixture/skip_trait_complex.php.inc +++ /dev/null @@ -1,57 +0,0 @@ -provideMultiple([]); - - foreach ($entities as $entity) { - $entity->getStatus(); - } - } - - public static function getProvider(): SomeStatusEntityProvider - { - return new SomeStatusEntityProvider; - } -} - -/** - * @ORM\Entity - */ -class SomeStatusEntity -{ - /** - * @var string - */ - private $status; - - public function getStatus(): string - { - return $this->status; - } -} - -class SomeStatusEntityProvider -{ - /** - * @return SomeStatusEntity[] - */ - public function provideMultiple($items): array - { - return $items; - } -} \ No newline at end of file diff --git a/rules/dead-code/tests/Rector/Property/RemoveSetterOnlyPropertyAndMethodCallRector/Fixture/skip_abstract_class_required.php.inc b/rules/dead-code/tests/Rector/Property/RemoveSetterOnlyPropertyAndMethodCallRector/Fixture/skip_abstract_class_required.php.inc index 69497850bf6c..f440111d2fee 100644 --- a/rules/dead-code/tests/Rector/Property/RemoveSetterOnlyPropertyAndMethodCallRector/Fixture/skip_abstract_class_required.php.inc +++ b/rules/dead-code/tests/Rector/Property/RemoveSetterOnlyPropertyAndMethodCallRector/Fixture/skip_abstract_class_required.php.inc @@ -2,7 +2,9 @@ namespace Rector\DeadCode\Tests\Rector\Property\RemoveSetterOnlyPropertyAndMethodCallRector\Fixture; -class SkipAbstractClassRequired extends AbstractClassRequiring +use Rector\DeadCode\Tests\Rector\Property\RemoveSetterOnlyPropertyAndMethodCallRector\Source\AbstractClassRequiringAbstractMethod; + +class SkipAbstractClassRequired extends AbstractClassRequiringAbstractMethod { private $name; public function setName(string $name): void @@ -10,8 +12,3 @@ class SkipAbstractClassRequired extends AbstractClassRequiring $this->name = $name; } } - -abstract class AbstractClassRequiring -{ - protected abstract function setName(string $name); -} diff --git a/rules/dead-code/tests/Rector/Property/RemoveSetterOnlyPropertyAndMethodCallRector/Source/AbstractClassRequiringAbstractMethod.php b/rules/dead-code/tests/Rector/Property/RemoveSetterOnlyPropertyAndMethodCallRector/Source/AbstractClassRequiringAbstractMethod.php new file mode 100644 index 000000000000..629abc8c2ef4 --- /dev/null +++ b/rules/dead-code/tests/Rector/Property/RemoveSetterOnlyPropertyAndMethodCallRector/Source/AbstractClassRequiringAbstractMethod.php @@ -0,0 +1,10 @@ +reflectionProvider = $reflectionProvider; + public function __construct(NodeRepository $nodeRepository, NodeNameResolver $nodeNameResolver) + { $this->nodeRepository = $nodeRepository; $this->nodeNameResolver = $nodeNameResolver; } @@ -59,25 +50,21 @@ public function isReturnTypeChangeAllowed(ClassMethod $classMethod): bool private function getParentClassMethod(ClassMethod $classMethod): ?MethodReflection { - /** @var string $methodName */ - $methodName = $this->nodeNameResolver->getName($classMethod); - - $parentClassName = $classMethod->getAttribute(AttributeKey::PARENT_CLASS_NAME); - if ($parentClassName === null) { + $scope = $classMethod->getAttribute(AttributeKey::SCOPE); + if (! $scope instanceof Scope) { return null; } - if (! $this->reflectionProvider->hasClass($parentClassName)) { + /** @var string $methodName */ + $methodName = $this->nodeNameResolver->getName($classMethod); + + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { return null; } - $parentClassReflection = $this->reflectionProvider->getClass($parentClassName); - - /** @var ClassReflection[] $parentClassesReflections */ - $parentClassesReflections = array_merge([$parentClassReflection], $parentClassReflection->getParents()); - - foreach ($parentClassesReflections as $parentClassesReflection) { - if (! $parentClassesReflection->hasMethod($methodName)) { + foreach ($classReflection->getParents() as $parentClassReflection) { + if (! $parentClassReflection->hasMethod($methodName)) { continue; } diff --git a/rules/defluent/src/NodeAnalyzer/FluentCallStaticTypeResolver.php b/rules/defluent/src/NodeAnalyzer/FluentCallStaticTypeResolver.php index edd06e8fbc5f..e06e1b6562bd 100644 --- a/rules/defluent/src/NodeAnalyzer/FluentCallStaticTypeResolver.php +++ b/rules/defluent/src/NodeAnalyzer/FluentCallStaticTypeResolver.php @@ -5,6 +5,7 @@ namespace Rector\Defluent\NodeAnalyzer; use PhpParser\Node\Expr\MethodCall; +use PHPStan\Reflection\ReflectionProvider; final class FluentCallStaticTypeResolver { @@ -13,9 +14,15 @@ final class FluentCallStaticTypeResolver */ private $exprStringTypeResolver; - public function __construct(ExprStringTypeResolver $exprStringTypeResolver) + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct(ExprStringTypeResolver $exprStringTypeResolver, ReflectionProvider $reflectionProvider) { $this->exprStringTypeResolver = $exprStringTypeResolver; + $this->reflectionProvider = $reflectionProvider; } /** @@ -58,8 +65,8 @@ public function resolveCalleeUniqueTypes(array $chainMethodCalls): array /** * If a child class is with the parent class in the list, count them as 1 * - * @param string[] $types - * @return string[] + * @param class-string[] $types + * @return class-string[] */ private function filterOutAlreadyPresentParentClasses(array $types): array { @@ -71,7 +78,12 @@ private function filterOutAlreadyPresentParentClasses(array $types): array continue; } - if (is_a($type, $secondType, true)) { + if (! $this->reflectionProvider->hasClass($type)) { + continue; + } + + $firstClassReflection = $this->reflectionProvider->getClass($type); + if ($firstClassReflection->isSubclassOf($secondType)) { unset($types[$key]); continue 2; } diff --git a/rules/defluent/src/NodeAnalyzer/FluentChainMethodCallNodeAnalyzer.php b/rules/defluent/src/NodeAnalyzer/FluentChainMethodCallNodeAnalyzer.php index ae796d95a674..6e45212b99e0 100644 --- a/rules/defluent/src/NodeAnalyzer/FluentChainMethodCallNodeAnalyzer.php +++ b/rules/defluent/src/NodeAnalyzer/FluentChainMethodCallNodeAnalyzer.php @@ -14,8 +14,8 @@ use PhpParser\NodeFinder; use PHPStan\Analyser\MutatingScope; use PHPStan\Type\MixedType; +use PHPStan\Type\ObjectType; use PHPStan\Type\Type; -use PHPStan\Type\TypeWithClassName; use Rector\Core\Util\StaticInstanceOf; use Rector\Defluent\Reflection\MethodCallToClassMethodParser; use Rector\NodeNameResolver\NodeNameResolver; @@ -98,9 +98,9 @@ public function isFluentClassMethodOfMethodCall(MethodCall $methodCall): bool return false; } - if ($calleeStaticType instanceof TypeWithClassName) { - foreach (self::KNOWN_FACTORY_FLUENT_TYPES as $knownFactoryFluentTypes) { - if (is_a($calleeStaticType->getClassName(), $knownFactoryFluentTypes, true)) { + if ($calleeStaticType instanceof ObjectType) { + foreach (self::KNOWN_FACTORY_FLUENT_TYPES as $knownFactoryFluentType) { + if ($calleeStaticType->isInstanceOf($knownFactoryFluentType)->yes()) { return false; } } diff --git a/rules/defluent/src/NodeAnalyzer/SameClassMethodCallAnalyzer.php b/rules/defluent/src/NodeAnalyzer/SameClassMethodCallAnalyzer.php index 02f2ff55d57f..e3ba551e5997 100644 --- a/rules/defluent/src/NodeAnalyzer/SameClassMethodCallAnalyzer.php +++ b/rules/defluent/src/NodeAnalyzer/SameClassMethodCallAnalyzer.php @@ -31,7 +31,6 @@ public function haveSingleClass(array $chainMethodCalls): bool $classOfClassMethod = []; foreach ($chainMethodCalls as $chainMethodCall) { $classMethod = $this->nodeRepository->findClassMethodByMethodCall($chainMethodCall); - if ($classMethod instanceof ClassMethod) { $classOfClassMethod[] = $classMethod->getAttribute(AttributeKey::CLASS_NAME); } else { diff --git a/rules/defluent/src/Rector/AbstractFluentChainMethodCallRector.php b/rules/defluent/src/Rector/AbstractFluentChainMethodCallRector.php index 46afdef5634e..fbf5aaa87a99 100644 --- a/rules/defluent/src/Rector/AbstractFluentChainMethodCallRector.php +++ b/rules/defluent/src/Rector/AbstractFluentChainMethodCallRector.php @@ -33,19 +33,19 @@ abstract class AbstractFluentChainMethodCallRector extends AbstractRector protected $nonFluentChainMethodCallFactory; /** - * @var FluentChainMethodCallRootExtractor + * @var FluentMethodCallSkipper */ - protected $fluentChainMethodCallRootExtractor; + protected $fluentMethodCallSkipper; /** - * @var SameClassMethodCallAnalyzer + * @var FluentChainMethodCallRootExtractor */ - protected $sameClassMethodCallAnalyzer; + private $fluentChainMethodCallRootExtractor; /** - * @var FluentMethodCallSkipper + * @var SameClassMethodCallAnalyzer */ - protected $fluentMethodCallSkipper; + private $sameClassMethodCallAnalyzer; /** * @required diff --git a/rules/defluent/src/Rector/MethodCall/FluentChainMethodCallToNormalMethodCallRector.php b/rules/defluent/src/Rector/MethodCall/FluentChainMethodCallToNormalMethodCallRector.php index 3f1c8ccc0181..3d69a3d95833 100644 --- a/rules/defluent/src/Rector/MethodCall/FluentChainMethodCallToNormalMethodCallRector.php +++ b/rules/defluent/src/Rector/MethodCall/FluentChainMethodCallToNormalMethodCallRector.php @@ -13,7 +13,6 @@ use Rector\Defluent\ValueObject\AssignAndRootExprAndNodesToAdd; use Rector\Defluent\ValueObject\FluentCallsKind; use Rector\NodeTypeResolver\Node\AttributeKey; -use Symplify\PackageBuilder\Php\TypeChecker; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -25,16 +24,6 @@ */ final class FluentChainMethodCallToNormalMethodCallRector extends AbstractFluentChainMethodCallRector { - /** - * @var TypeChecker - */ - private $typeChecker; - - public function __construct(TypeChecker $typeChecker) - { - $this->typeChecker = $typeChecker; - } - public function getRuleDefinition(): RuleDefinition { return new RuleDefinition( @@ -101,6 +90,10 @@ public function refactor(Node $node): ?Node private function isHandledByAnotherRule(MethodCall $methodCall): bool { $parent = $methodCall->getAttribute(AttributeKey::PARENT_NODE); - return $this->typeChecker->isInstanceOf($parent, [Return_::class, Arg::class]); + if ($parent instanceof Return_) { + return true; + } + + return $parent instanceof Arg; } } diff --git a/rules/defluent/src/Rector/Return_/ReturnFluentChainMethodCallToNormalMethodCallRector.php b/rules/defluent/src/Rector/Return_/ReturnFluentChainMethodCallToNormalMethodCallRector.php index dec0f560ba8c..55369bd9cfc0 100644 --- a/rules/defluent/src/Rector/Return_/ReturnFluentChainMethodCallToNormalMethodCallRector.php +++ b/rules/defluent/src/Rector/Return_/ReturnFluentChainMethodCallToNormalMethodCallRector.php @@ -51,25 +51,24 @@ public function __construct( public function getRuleDefinition(): RuleDefinition { - return new RuleDefinition( - 'Turns fluent interface calls to classic ones.', - [ - new CodeSample( - <<<'CODE_SAMPLE' + return new RuleDefinition('Turns fluent interface calls to classic ones.', [ + new CodeSample( + <<<'CODE_SAMPLE' $someClass = new SomeClass(); + return $someClass->someFunction() - ->otherFunction(); + ->otherFunction(); CODE_SAMPLE - , - <<<'CODE_SAMPLE' + , + <<<'CODE_SAMPLE' $someClass = new SomeClass(); $someClass->someFunction(); $someClass->otherFunction(); + return $someClass; CODE_SAMPLE - ), - - ]); + ), + ]); } /** diff --git a/rules/defluent/src/Skipper/FluentMethodCallSkipper.php b/rules/defluent/src/Skipper/FluentMethodCallSkipper.php index ecf4bcb9ca2b..fdfff2d942e9 100644 --- a/rules/defluent/src/Skipper/FluentMethodCallSkipper.php +++ b/rules/defluent/src/Skipper/FluentMethodCallSkipper.php @@ -105,7 +105,6 @@ public function shouldSkipFirstAssignFluentCall(FirstAssignFluentCall $firstAssi public function shouldSkipMethodCalls(AssignAndRootExpr $assignAndRootExpr, array $fluentMethodCalls): bool { $calleeUniqueTypes = $this->fluentCallStaticTypeResolver->resolveCalleeUniqueTypes($fluentMethodCalls); - if (! $this->sameClassMethodCallAnalyzer->isCorrectTypeCount($calleeUniqueTypes, $assignAndRootExpr)) { return true; } diff --git a/rules/defluent/src/ValueObjectFactory/FluentMethodCallsFactory.php b/rules/defluent/src/ValueObjectFactory/FluentMethodCallsFactory.php index 48a937cb1a6c..24484d683b71 100644 --- a/rules/defluent/src/ValueObjectFactory/FluentMethodCallsFactory.php +++ b/rules/defluent/src/ValueObjectFactory/FluentMethodCallsFactory.php @@ -42,6 +42,7 @@ public function createFromLastMethodCall(MethodCall $lastMethodCall): ?FluentMet } $rootMethodCall = $this->resolveRootMethodCall($chainMethodCalls); + return new FluentMethodCalls($rootMethodCall, $chainMethodCalls, $lastMethodCall); } diff --git a/rules/dependency-injection/src/NodeAnalyzer/NetteInjectPropertyAnalyzer.php b/rules/dependency-injection/src/NodeAnalyzer/NetteInjectPropertyAnalyzer.php index a1a09e64b8a9..51aa84be3523 100644 --- a/rules/dependency-injection/src/NodeAnalyzer/NetteInjectPropertyAnalyzer.php +++ b/rules/dependency-injection/src/NodeAnalyzer/NetteInjectPropertyAnalyzer.php @@ -4,10 +4,12 @@ namespace Rector\DependencyInjection\NodeAnalyzer; -use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\Property; +use PHPStan\Analyser\Scope; +use PHPStan\Reflection\ClassReflection; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; use Rector\BetterPhpDocParser\ValueObject\PhpDocNode\Nette\NetteInjectTagNode; +use Rector\Core\ValueObject\MethodName; use Rector\FamilyTree\NodeAnalyzer\ClassChildAnalyzer; use Rector\NodeTypeResolver\Node\AttributeKey; @@ -29,20 +31,26 @@ public function detect(Property $property, PhpDocInfo $phpDocInfo): bool return false; } - $classLike = $property->getAttribute(AttributeKey::CLASS_NODE); - if (! $classLike instanceof Class_) { + /** @var Scope $scope */ + $scope = $property->getAttribute(AttributeKey::SCOPE); + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { return false; } - if ($classLike->isAbstract()) { + if ($classReflection->isAbstract()) { return false; } - if ($this->classChildAnalyzer->hasChildClassConstructor($classLike)) { + if ($classReflection->isAnonymous()) { return false; } - if ($this->classChildAnalyzer->hasParentClassConstructor($classLike)) { + if ($this->classChildAnalyzer->hasChildClassMethod($classReflection, MethodName::CONSTRUCT)) { + return false; + } + + if ($this->classChildAnalyzer->hasParentClassMethod($classReflection, MethodName::CONSTRUCT)) { return false; } diff --git a/rules/dependency-injection/src/TypeAnalyzer/JMSDITypeResolver.php b/rules/dependency-injection/src/TypeAnalyzer/JMSDITypeResolver.php index 83b7af2161de..066081f8daf2 100644 --- a/rules/dependency-injection/src/TypeAnalyzer/JMSDITypeResolver.php +++ b/rules/dependency-injection/src/TypeAnalyzer/JMSDITypeResolver.php @@ -5,6 +5,7 @@ namespace Rector\DependencyInjection\TypeAnalyzer; use PhpParser\Node\Stmt\Property; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\MixedType; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; @@ -32,14 +33,21 @@ final class JMSDITypeResolver */ private $phpDocInfoFactory; + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + public function __construct( ErrorAndDiffCollector $errorAndDiffCollector, ServiceMapProvider $serviceMapProvider, - PhpDocInfoFactory $phpDocInfoFactory + PhpDocInfoFactory $phpDocInfoFactory, + ReflectionProvider $reflectionProvider ) { $this->errorAndDiffCollector = $errorAndDiffCollector; $this->serviceMapProvider = $serviceMapProvider; $this->phpDocInfoFactory = $phpDocInfoFactory; + $this->reflectionProvider = $reflectionProvider; } public function resolve(Property $property, JMSInjectTagValueNode $jmsInjectTagValueNode): Type @@ -48,7 +56,7 @@ public function resolve(Property $property, JMSInjectTagValueNode $jmsInjectTagV $serviceName = $jmsInjectTagValueNode->getServiceName(); if ($serviceName) { - if (class_exists($serviceName)) { + if ($this->reflectionProvider->hasClass($serviceName)) { // single class service return new ObjectType($serviceName); } diff --git a/rules/dependency-injection/tests/Rector/Property/AnnotatedPropertyInjectToConstructorInjectionRector/Fixture/child_property_call_protected.php.inc b/rules/dependency-injection/tests/Rector/Property/AnnotatedPropertyInjectToConstructorInjectionRector/Fixture/child_property_call_protected.php.inc deleted file mode 100644 index 1699d4d8ae80..000000000000 --- a/rules/dependency-injection/tests/Rector/Property/AnnotatedPropertyInjectToConstructorInjectionRector/Fixture/child_property_call_protected.php.inc +++ /dev/null @@ -1,47 +0,0 @@ -someProductWith; - } -} - -class SomeParentWithInject -{ - /** - * @inject - * @var SomeProductWithInterface - */ - public $someProductWith; -} - -?> ------ -someProductWith; - } -} - -class SomeParentWithInject -{ - public function __construct(protected \Rector\DependencyInjection\Tests\Rector\Property\AnnotatedPropertyInjectToConstructorInjectionRector\Source\SomeProductWithInterface $someProductWith) - { - } -} - -?> diff --git a/rules/dependency-injection/tests/Rector/Property/AnnotatedPropertyInjectToConstructorInjectionRector/Fixture/skip_if_has_children.php.inc b/rules/dependency-injection/tests/Rector/Property/AnnotatedPropertyInjectToConstructorInjectionRector/Fixture/skip_if_has_children.php.inc deleted file mode 100644 index edc95323a65e..000000000000 --- a/rules/dependency-injection/tests/Rector/Property/AnnotatedPropertyInjectToConstructorInjectionRector/Fixture/skip_if_has_children.php.inc +++ /dev/null @@ -1,21 +0,0 @@ -isObjectType($classLike, new ObjectType('Doctrine\ORM\EntityRepository'))) { return true; } + if (! $this->isObjectType($methodCall->var, new ObjectType('Doctrine\ORM\EntityRepository'))) { return true; } diff --git a/rules/doctrine-gedmo-to-knplabs/src/Rector/Class_/TranslationBehaviorRector.php b/rules/doctrine-gedmo-to-knplabs/src/Rector/Class_/TranslationBehaviorRector.php index a51644bf6ad7..2b7406de1d44 100644 --- a/rules/doctrine-gedmo-to-knplabs/src/Rector/Class_/TranslationBehaviorRector.php +++ b/rules/doctrine-gedmo-to-knplabs/src/Rector/Class_/TranslationBehaviorRector.php @@ -8,6 +8,7 @@ use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\Namespace_; +use PHPStan\Type\ObjectType; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; use Rector\BetterPhpDocParser\ValueObject\PhpDocNode\Gedmo\LocaleTagValueNode; use Rector\BetterPhpDocParser\ValueObject\PhpDocNode\Gedmo\TranslatableTagValueNode; @@ -166,7 +167,7 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - if (! $this->classManipulator->hasInterface($node, 'Gedmo\Translatable\Translatable')) { + if (! $this->classManipulator->hasInterface($node, new ObjectType('Gedmo\Translatable\Translatable'))) { return null; } diff --git a/rules/doctrine/src/NodeFactory/EntityUuidNodeFactory.php b/rules/doctrine/src/NodeFactory/EntityUuidNodeFactory.php index e5386f657094..28d621f66ebd 100644 --- a/rules/doctrine/src/NodeFactory/EntityUuidNodeFactory.php +++ b/rules/doctrine/src/NodeFactory/EntityUuidNodeFactory.php @@ -79,7 +79,7 @@ public function createTemporaryUuidProperty(): Property public function createUuidPropertyDefaultValueAssign(string $uuidVariableName): Expression { $thisUuidPropertyFetch = new PropertyFetch(new Variable('this'), $uuidVariableName); - $uuid4StaticCall = $this->nodeFactory->createStaticCall(Uuid::class, 'uuid4'); + $uuid4StaticCall = $this->nodeFactory->createStaticCall('Ramsey\Uuid\Uuid', 'uuid4'); $assign = new Assign($thisUuidPropertyFetch, $uuid4StaticCall); diff --git a/rules/doctrine/src/PhpDocParser/DoctrineDocBlockResolver.php b/rules/doctrine/src/PhpDocParser/DoctrineDocBlockResolver.php index 8a571fce0823..649652c3477b 100644 --- a/rules/doctrine/src/PhpDocParser/DoctrineDocBlockResolver.php +++ b/rules/doctrine/src/PhpDocParser/DoctrineDocBlockResolver.php @@ -8,6 +8,8 @@ use PhpParser\Node; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\Property; +use PHPStan\PhpDoc\ResolvedPhpDocBlock; +use PHPStan\Reflection\ReflectionProvider; use Rector\BetterPhpDocParser\Contract\Doctrine\DoctrineRelationTagValueNodeInterface; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\BetterPhpDocParser\ValueObject\PhpDocNode\Doctrine\Class_\EmbeddableTagValueNode; @@ -15,9 +17,7 @@ use Rector\BetterPhpDocParser\ValueObject\PhpDocNode\Doctrine\Property_\IdTagValueNode; use Rector\Core\Exception\ShouldNotHappenException; use Rector\NodeCollector\NodeCollector\NodeRepository; -use Rector\NodeTypeResolver\ClassExistenceStaticHelper; use Rector\NodeTypeResolver\Node\AttributeKey; -use ReflectionClass; final class DoctrineDocBlockResolver { @@ -37,10 +37,19 @@ final class DoctrineDocBlockResolver */ private $nodeRepository; - public function __construct(NodeRepository $nodeRepository, PhpDocInfoFactory $phpDocInfoFactory) - { + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct( + NodeRepository $nodeRepository, + PhpDocInfoFactory $phpDocInfoFactory, + ReflectionProvider $reflectionProvider + ) { $this->phpDocInfoFactory = $phpDocInfoFactory; $this->nodeRepository = $nodeRepository; + $this->reflectionProvider = $reflectionProvider; } /** @@ -105,7 +114,7 @@ private function isDoctrineEntityClassNode(Class_ $class): bool private function isStringClassEntity(string $class): bool { - if (! ClassExistenceStaticHelper::doesClassLikeExist($class)) { + if (! $this->reflectionProvider->hasClass($class)) { return false; } @@ -114,11 +123,16 @@ private function isStringClassEntity(string $class): bool return $this->isDoctrineEntityClass($classNode); } - $reflectionClass = new ReflectionClass($class); + $classReflection = $this->reflectionProvider->getClass($class); + $resolvedPhpDocBlock = $classReflection->getResolvedPhpDoc(); + if (! $resolvedPhpDocBlock instanceof ResolvedPhpDocBlock) { + return false; + } // dummy check of 3rd party code without running it - $docCommentContent = (string) $reflectionClass->getDocComment(); - - return (bool) Strings::match($docCommentContent, self::ORM_ENTITY_EMBEDDABLE_SHORT_ANNOTATION_REGEX); + return (bool) Strings::match( + $resolvedPhpDocBlock->getPhpDocString(), + self::ORM_ENTITY_EMBEDDABLE_SHORT_ANNOTATION_REGEX + ); } } diff --git a/rules/doctrine/src/Rector/ClassMethod/ChangeGetIdTypeToUuidRector.php b/rules/doctrine/src/Rector/ClassMethod/ChangeGetIdTypeToUuidRector.php index f7ff3721efbb..112330b36e2f 100644 --- a/rules/doctrine/src/Rector/ClassMethod/ChangeGetIdTypeToUuidRector.php +++ b/rules/doctrine/src/Rector/ClassMethod/ChangeGetIdTypeToUuidRector.php @@ -7,7 +7,6 @@ use PhpParser\Node; use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Stmt\ClassMethod; -use Ramsey\Uuid\UuidInterface; use Rector\Core\Rector\AbstractRector; use Rector\Doctrine\PhpDocParser\DoctrineDocBlockResolver; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; @@ -106,6 +105,6 @@ private function hasUuidReturnType(ClassMethod $classMethod): bool return false; } - return $this->isName($classMethod->returnType, UuidInterface::class); + return $this->isName($classMethod->returnType, 'Ramsey\Uuid\UuidInterface'); } } diff --git a/rules/doctrine/src/Rector/ClassMethod/ChangeSetIdTypeToUuidRector.php b/rules/doctrine/src/Rector/ClassMethod/ChangeSetIdTypeToUuidRector.php index 3f4437c04960..1c6b47e87f1d 100644 --- a/rules/doctrine/src/Rector/ClassMethod/ChangeSetIdTypeToUuidRector.php +++ b/rules/doctrine/src/Rector/ClassMethod/ChangeSetIdTypeToUuidRector.php @@ -8,7 +8,6 @@ use PhpParser\Node\Identifier; use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Stmt\ClassMethod; -use Ramsey\Uuid\UuidInterface; use Rector\Core\Rector\AbstractRector; use Rector\Doctrine\PhpDocParser\DoctrineDocBlockResolver; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; @@ -101,7 +100,7 @@ public function refactor(Node $node): ?Node // is already set? if ($node->params[0]->type !== null) { $currentType = $this->getName($node->params[0]->type); - if ($currentType === UuidInterface::class) { + if ($currentType === 'Ramsey\Uuid\UuidInterface') { return null; } } diff --git a/rules/doctrine/src/Rector/ClassMethod/ServiceEntityRepositoryParentCallToDIRector.php b/rules/doctrine/src/Rector/ClassMethod/ServiceEntityRepositoryParentCallToDIRector.php index 3cb66a7398d9..7b9d4c505dee 100644 --- a/rules/doctrine/src/Rector/ClassMethod/ServiceEntityRepositoryParentCallToDIRector.php +++ b/rules/doctrine/src/Rector/ClassMethod/ServiceEntityRepositoryParentCallToDIRector.php @@ -23,16 +23,12 @@ * @sponsor Thanks https://www.luzanky.cz/ for sponsoring this rule * * @see https://tomasvotruba.com/blog/2017/10/16/how-to-use-repository-with-doctrine-as-service-in-symfony/ + * @see https://getrector.org/blog/2021/02/08/how-to-instantly-decouple-symfony-doctrine-repository-inheritance-to-clean-composition * * @see \Rector\Doctrine\Tests\Rector\ClassMethod\ServiceEntityRepositoryParentCallToDIRector\ServiceEntityRepositoryParentCallToDIRectorTest */ final class ServiceEntityRepositoryParentCallToDIRector extends AbstractRector { - /** - * @var string - */ - private const SERVICE_ENTITY_REPOSITORY_CLASS = 'Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository'; - /** * @var RepositoryNodeFactory */ @@ -155,13 +151,20 @@ private function shouldSkipClassMethod(ClassMethod $classMethod): bool return true; } - /** @var string|null $parentClassName */ - $parentClassName = $classMethod->getAttribute(AttributeKey::PARENT_CLASS_NAME); - if ($parentClassName === null) { + $scope = $classMethod->getAttribute(AttributeKey::SCOPE); + if ($scope === null) { + // fresh node? + return true; + } + + $classReflection = $scope->getClassReflection(); + + if ($classReflection === null) { + // possibly trait/interface return true; } - return $parentClassName !== self::SERVICE_ENTITY_REPOSITORY_CLASS; + return ! $classReflection->isSubclassOf('Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository'); } private function removeParentConstructAndCollectEntityReference(ClassMethod $classMethod): Expr diff --git a/rules/doctrine/src/Rector/Class_/AddUuidMirrorForRelationPropertyRector.php b/rules/doctrine/src/Rector/Class_/AddUuidMirrorForRelationPropertyRector.php index e8a73be4b14e..64f92336a1df 100644 --- a/rules/doctrine/src/Rector/Class_/AddUuidMirrorForRelationPropertyRector.php +++ b/rules/doctrine/src/Rector/Class_/AddUuidMirrorForRelationPropertyRector.php @@ -9,6 +9,7 @@ use PhpParser\Node\Stmt\Property; use PhpParser\Node\Stmt\PropertyProperty; use PhpParser\Node\VarLikeIdentifier; +use PHPStan\Reflection\ReflectionProvider; use Rector\BetterPhpDocParser\Contract\Doctrine\DoctrineRelationTagValueNodeInterface; use Rector\BetterPhpDocParser\Contract\Doctrine\ToManyTagNodeInterface; use Rector\BetterPhpDocParser\Contract\Doctrine\ToOneTagNodeInterface; @@ -51,16 +52,23 @@ final class AddUuidMirrorForRelationPropertyRector extends AbstractRector */ private $joinColumnTagValueNodeFactory; + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + public function __construct( PhpDocTagNodeFactory $phpDocTagNodeFactory, UuidMigrationDataCollector $uuidMigrationDataCollector, DoctrineDocBlockResolver $doctrineDocBlockResolver, - JoinColumnTagValueNodeFactory $joinColumnTagValueNodeFactory + JoinColumnTagValueNodeFactory $joinColumnTagValueNodeFactory, + ReflectionProvider $reflectionProvider ) { $this->phpDocTagNodeFactory = $phpDocTagNodeFactory; $this->uuidMigrationDataCollector = $uuidMigrationDataCollector; $this->doctrineDocBlockResolver = $doctrineDocBlockResolver; $this->joinColumnTagValueNodeFactory = $joinColumnTagValueNodeFactory; + $this->reflectionProvider = $reflectionProvider; } public function getRuleDefinition(): RuleDefinition @@ -189,7 +197,12 @@ private function shouldSkipProperty(Class_ $class, Property $property): bool return true; } - if (! property_exists($targetEntity, 'uuid')) { + if (! $this->reflectionProvider->hasClass($targetEntity)) { + return true; + } + + $classReflection = $this->reflectionProvider->getClass($targetEntity); + if (! $classReflection->hasProperty('uuid')) { return true; } diff --git a/rules/doctrine/src/Rector/Identical/ChangeIdenticalUuidToEqualsMethodCallRector.php b/rules/doctrine/src/Rector/Identical/ChangeIdenticalUuidToEqualsMethodCallRector.php index 539301008489..4d3ad69ab262 100644 --- a/rules/doctrine/src/Rector/Identical/ChangeIdenticalUuidToEqualsMethodCallRector.php +++ b/rules/doctrine/src/Rector/Identical/ChangeIdenticalUuidToEqualsMethodCallRector.php @@ -8,8 +8,6 @@ use PhpParser\Node\Expr; use PhpParser\Node\Expr\BinaryOp\Identical; use PHPStan\Type\ObjectType; -use Ramsey\Uuid\Uuid; -use Ramsey\Uuid\UuidInterface; use Rector\Core\Rector\AbstractRector; use Rector\DeadCode\Doctrine\DoctrineEntityManipulator; use Rector\Php71\ValueObject\TwoNodeMatch; @@ -86,7 +84,7 @@ public function refactor(Node $node): ?Node $entityMethodCall = $twoNodeMatch->getFirstExpr(); $comparedVariable = $twoNodeMatch->getSecondExpr(); - $staticCall = $this->nodeFactory->createStaticCall(Uuid::class, 'fromString', [$comparedVariable]); + $staticCall = $this->nodeFactory->createStaticCall('Ramsey\Uuid\Uuid', 'fromString', [$comparedVariable]); return $this->nodeFactory->createMethodCall($entityMethodCall, 'equals', [$staticCall]); } @@ -119,6 +117,6 @@ private function isAlreadyUuidType(Expr $expr): bool return false; } - return $comparedValueObjectType->getClassName() === UuidInterface::class; + return $comparedValueObjectType->getClassName() === 'Ramsey\Uuid\UuidInterface'; } } diff --git a/rules/doctrine/src/Rector/MethodCall/ChangeSetIdToUuidValueRector.php b/rules/doctrine/src/Rector/MethodCall/ChangeSetIdToUuidValueRector.php index 0762937596e2..ee23de59b344 100644 --- a/rules/doctrine/src/Rector/MethodCall/ChangeSetIdToUuidValueRector.php +++ b/rules/doctrine/src/Rector/MethodCall/ChangeSetIdToUuidValueRector.php @@ -130,13 +130,21 @@ public function refactor(Node $node): ?Node // update constant value $classConst->consts[0]->value = $this->createUuidStringNode(); - $node->args[0]->value = $this->nodeFactory->createStaticCall(Uuid::class, 'fromString', [$argumentValue]); + $node->args[0]->value = $this->nodeFactory->createStaticCall( + 'Ramsey\Uuid\Uuid', + 'fromString', + [$argumentValue] + ); return $node; } // C. set uuid from string with generated string - $value = $this->nodeFactory->createStaticCall(Uuid::class, 'fromString', [$this->createUuidStringNode()]); + $value = $this->nodeFactory->createStaticCall( + 'Ramsey\Uuid\Uuid', + 'fromString', + [$this->createUuidStringNode()] + ); $node->args[0]->value = $value; return $node; @@ -218,6 +226,6 @@ private function isUuidType(Expr $expr): bool return false; } - return $argumentStaticType->getClassName() === Uuid::class; + return $argumentStaticType->getClassName() === 'Ramsey\Uuid\Uuid'; } } diff --git a/rules/doctrine/src/Rector/Property/AddUuidAnnotationsToIdPropertyRector.php b/rules/doctrine/src/Rector/Property/AddUuidAnnotationsToIdPropertyRector.php index 6cb8571e321a..269aec84ba8a 100644 --- a/rules/doctrine/src/Rector/Property/AddUuidAnnotationsToIdPropertyRector.php +++ b/rules/doctrine/src/Rector/Property/AddUuidAnnotationsToIdPropertyRector.php @@ -6,7 +6,6 @@ use PhpParser\Node; use PhpParser\Node\Stmt\Property; -use Ramsey\Uuid\UuidInterface; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger; use Rector\BetterPhpDocParser\ValueObject\PhpDocNode\Doctrine\AbstractDoctrineTagValueNode; @@ -103,7 +102,7 @@ public function refactor(Node $node): ?Node $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node); - $fullyQualifiedObjectType = new FullyQualifiedObjectType(UuidInterface::class); + $fullyQualifiedObjectType = new FullyQualifiedObjectType('Ramsey\Uuid\UuidInterface'); $this->phpDocTypeChanger->changeVarType($phpDocInfo, $fullyQualifiedObjectType); diff --git a/rules/downgrade-php72/src/Rector/ClassMethod/DowngradeParameterTypeWideningRector.php b/rules/downgrade-php72/src/Rector/ClassMethod/DowngradeParameterTypeWideningRector.php index 6069fe50e13e..1ef5dda18b72 100644 --- a/rules/downgrade-php72/src/Rector/ClassMethod/DowngradeParameterTypeWideningRector.php +++ b/rules/downgrade-php72/src/Rector/ClassMethod/DowngradeParameterTypeWideningRector.php @@ -17,7 +17,6 @@ use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger; use Rector\Core\Rector\AbstractRector; use Rector\NodeTypeResolver\Node\AttributeKey; -use ReflectionMethod; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -138,7 +137,6 @@ private function refactorParamForAncestorsAndSiblings(Param $param, FunctionLike // Obtain the list of the ancestors classes and implemented interfaces // with a different signature - /** @var ClassLike[] $ancestorAndInterfaces */ $ancestorAndInterfaces = array_merge( $this->getClassesWithDifferentSignature($classReflection, $methodName, $paramName), @@ -181,16 +179,16 @@ private function getClassesWithDifferentSignature( string $paramName ): array { // 1. All ancestor classes with different signature - $ancestorClassNames = array_filter( - $classReflection->getParentClassesNames(), - function (string $ancestorClassName) use ($methodName, $paramName): bool { - return $this->hasMethodWithTypedParam($ancestorClassName, $methodName, $paramName); + $ancestorClassReflections = array_filter( + $classReflection->getParents(), + function (ClassReflection $classReflection) use ($methodName, $paramName): bool { + return $this->hasMethodWithTypedParam($classReflection, $methodName, $paramName); } ); $classes = []; - foreach ($ancestorClassNames as $ancestorClassName) { - $class = $this->nodeRepository->findClass($ancestorClassName); + foreach ($ancestorClassReflections as $ancestorClassReflection) { + $class = $this->nodeRepository->findClass($ancestorClassReflection->getName()); if (! $class instanceof Class_) { continue; } @@ -210,22 +208,16 @@ private function getInterfacesWithDifferentSignature( string $methodName, string $paramName ): array { - $interfaceClassNames = array_map( - function (ClassReflection $interfaceReflection): string { - return $interfaceReflection->getName(); - }, - $classReflection->getInterfaces() - ); - $refactorableInterfaceClassNames = array_filter( - $interfaceClassNames, - function (string $interfaceClassName) use ($methodName, $paramName): bool { - return $this->hasMethodWithTypedParam($interfaceClassName, $methodName, $paramName); + $interfaceClassReflections = array_filter( + $classReflection->getInterfaces(), + function (ClassReflection $interfaceReflection) use ($methodName, $paramName): bool { + return $this->hasMethodWithTypedParam($interfaceReflection, $methodName, $paramName); } ); $interfaces = []; - foreach ($refactorableInterfaceClassNames as $refactorableInterfaceClassName) { - $interface = $this->nodeRepository->findInterface($refactorableInterfaceClassName); + foreach ($interfaceClassReflections as $interfaceClassReflection) { + $interface = $this->nodeRepository->findInterface($interfaceClassReflection->getName()); if (! $interface instanceof Interface_) { continue; } @@ -284,23 +276,26 @@ private function removeParamTypeFromMethodForChildren( } } - private function hasMethodWithTypedParam(string $parentClassName, string $methodName, string $paramName): bool - { - if (! method_exists($parentClassName, $methodName)) { + private function hasMethodWithTypedParam( + ClassReflection $classReflection, + string $methodName, + string $paramName + ): bool { + if (! $classReflection->hasMethod($methodName)) { return false; } - $parentReflectionMethod = new ReflectionMethod($parentClassName, $methodName); + $nativeClassReflection = $classReflection->getNativeReflection(); + $reflectionMethodReflection = $nativeClassReflection->getMethod($methodName); - $parentReflectionMethodParams = $parentReflectionMethod->getParameters(); - foreach ($parentReflectionMethodParams as $reflectionParameter) { - if ($reflectionParameter->name !== $paramName) { + foreach ($reflectionMethodReflection->getParameters() as $reflectionParameter) { + if ($reflectionParameter->getName() !== $paramName) { continue; } - if ($reflectionParameter->getType() === null) { - continue; + + if ($reflectionParameter->getType() !== null) { + return true; } - return true; } return false; diff --git a/rules/downgrade-php72/tests/Rector/ClassMethod/DowngradeParameterTypeWideningRector/Fixture/skip_nothing_happens.php.inc b/rules/downgrade-php72/tests/Rector/ClassMethod/DowngradeParameterTypeWideningRector/Fixture/skip_nothing_happens.php.inc index 8df02a0e8285..223c29d5f11e 100644 --- a/rules/downgrade-php72/tests/Rector/ClassMethod/DowngradeParameterTypeWideningRector/Fixture/skip_nothing_happens.php.inc +++ b/rules/downgrade-php72/tests/Rector/ClassMethod/DowngradeParameterTypeWideningRector/Fixture/skip_nothing_happens.php.inc @@ -2,17 +2,14 @@ namespace Rector\DowngradePhp72\Tests\Rector\ClassMethod\DowngradeParameterTypeWideningRector\Fixture; -interface NothingHappensInterface +interface SkipNothingHappens { public function test(array $input); } -class SkipNothingHappens implements NothingHappensInterface +final class SkipNothingHappensClass implements SkipNothingHappens { public function test(array $input) { - /* ... */ } } - -?> diff --git a/rules/downgrade-php72/tests/Rector/ClassMethod/DowngradeParameterTypeWideningRector/Fixture/with_class_inheritance.php.inc b/rules/downgrade-php72/tests/Rector/ClassMethod/DowngradeParameterTypeWideningRector/Fixture/with_class_inheritance.php.inc index 958dfdc96af9..a60331dd0adb 100644 --- a/rules/downgrade-php72/tests/Rector/ClassMethod/DowngradeParameterTypeWideningRector/Fixture/with_class_inheritance.php.inc +++ b/rules/downgrade-php72/tests/Rector/ClassMethod/DowngradeParameterTypeWideningRector/Fixture/with_class_inheritance.php.inc @@ -18,14 +18,6 @@ class ChildClass extends AncestorClass } } -class AffectedChildClass extends AncestorClass -{ - public function test(array $input) - { - /* ... */ - } -} - ?> ----- diff --git a/rules/downgrade-php74/src/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector.php b/rules/downgrade-php74/src/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector.php index 80b06c8db903..8a4907e8d9a8 100644 --- a/rules/downgrade-php74/src/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector.php +++ b/rules/downgrade-php74/src/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector.php @@ -113,14 +113,15 @@ private function getDifferentParamTypeFromAncestorClass(Param $param, FunctionLi // If it is the NullableType, extract the name from its inner type /** @var Node $paramType */ $paramType = $param->type; - $isNullableType = $param->type instanceof NullableType; - if ($isNullableType) { + + if ($param->type instanceof NullableType) { /** @var NullableType $nullableType */ $nullableType = $paramType; $paramTypeName = $this->getName($nullableType->type); } else { $paramTypeName = $this->getName($paramType); } + if ($paramTypeName === null) { return null; } @@ -128,29 +129,26 @@ private function getDifferentParamTypeFromAncestorClass(Param $param, FunctionLi /** @var string $methodName */ $methodName = $this->getName($functionLike); - // Either Ancestor classes or implemented interfaces - $interfaceNames = array_map( - function (ClassReflection $interfaceReflection): string { - return $interfaceReflection->getName(); - }, - $classReflection->getInterfaces() - ); - - $parentClassesNames = $classReflection->getParentClassesNames(); - $parentClassLikes = array_merge($parentClassesNames, $interfaceNames); + // parent classes or implemented interfaces + /** @var ClassReflection[] $parentClassReflections */ + $parentClassReflections = array_merge($classReflection->getParents(), $classReflection->getInterfaces()); - foreach ($parentClassLikes as $parentClassLike) { - if (! method_exists($parentClassLike, $methodName)) { + foreach ($parentClassReflections as $parentClassReflection) { + if (! $parentClassReflection->hasMethod($methodName)) { continue; } + $nativeClassReflection = $parentClassReflection->getNativeReflection(); + // Find the param we're looking for - $parentReflectionMethod = new ReflectionMethod($parentClassLike, $methodName); + $parentReflectionMethod = $nativeClassReflection->getMethod($methodName); + $differentAncestorParamTypeName = $this->getDifferentParamTypeFromReflectionMethod( $parentReflectionMethod, $paramName, $paramTypeName ); + if ($differentAncestorParamTypeName !== null) { return $differentAncestorParamTypeName; } @@ -160,19 +158,21 @@ function (ClassReflection $interfaceReflection): string { } private function getDifferentParamTypeFromReflectionMethod( - ReflectionMethod $parentReflectionMethod, + ReflectionMethod $reflectionMethod, string $paramName, string $paramTypeName ): ?string { /** @var ReflectionParameter[] $parentReflectionMethodParams */ - $parentReflectionMethodParams = $parentReflectionMethod->getParameters(); + $parentReflectionMethodParams = $reflectionMethod->getParameters(); + foreach ($parentReflectionMethodParams as $reflectionParameter) { - if ($reflectionParameter->name === $paramName) { + if ($reflectionParameter->getName() === $paramName) { /** * Getting a ReflectionNamedType works from PHP 7.1 onwards * @see https://www.php.net/manual/en/reflectionparameter.gettype.php#125334 */ $reflectionParamType = $reflectionParameter->getType(); + /** * If the type is null, we don't have enough information * to check if they are different. Then do nothing @@ -180,6 +180,7 @@ private function getDifferentParamTypeFromReflectionMethod( if (! $reflectionParamType instanceof ReflectionNamedType) { continue; } + if ($reflectionParamType->getName() !== $paramTypeName) { // We found it: a different param type in some ancestor return $reflectionParamType->getName(); diff --git a/rules/downgrade-php74/src/Rector/ClassMethod/DowngradeCovariantReturnTypeRector.php b/rules/downgrade-php74/src/Rector/ClassMethod/DowngradeCovariantReturnTypeRector.php index 154e55487c25..911a0d3f8d17 100644 --- a/rules/downgrade-php74/src/Rector/ClassMethod/DowngradeCovariantReturnTypeRector.php +++ b/rules/downgrade-php74/src/Rector/ClassMethod/DowngradeCovariantReturnTypeRector.php @@ -6,19 +6,18 @@ use PhpParser\Node; use PhpParser\Node\Name; -use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\NullableType; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\UnionType; use PHPStan\Analyser\Scope; use PHPStan\Reflection\ClassReflection; +use PHPStan\Type\MixedType; +use PHPStan\Type\Type; use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger; use Rector\Core\Rector\AbstractRector; -use Rector\NodeTypeResolver\ClassExistenceStaticHelper; +use Rector\DeadDocBlock\TagRemover\ReturnTagRemover; use Rector\NodeTypeResolver\Node\AttributeKey; -use ReflectionMethod; -use ReflectionNamedType; -use ReflectionType; +use Symplify\PackageBuilder\Reflection\PrivatesCaller; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -34,9 +33,24 @@ final class DowngradeCovariantReturnTypeRector extends AbstractRector */ private $phpDocTypeChanger; - public function __construct(PhpDocTypeChanger $phpDocTypeChanger) - { + /** + * @var PrivatesCaller + */ + private $privatesCaller; + + /** + * @var ReturnTagRemover + */ + private $returnTagRemover; + + public function __construct( + PhpDocTypeChanger $phpDocTypeChanger, + PrivatesCaller $privatesCaller, + ReturnTagRemover $returnTagRemover + ) { $this->phpDocTypeChanger = $phpDocTypeChanger; + $this->privatesCaller = $privatesCaller; + $this->returnTagRemover = $returnTagRemover; } public function getRuleDefinition(): RuleDefinition @@ -50,13 +64,15 @@ class ChildType extends ParentType {} class A { public function covariantReturnTypes(): ParentType - { /* … */ } + { + } } class B extends A { public function covariantReturnTypes(): ChildType - { /* … */ } + { + } } CODE_SAMPLE , @@ -67,7 +83,8 @@ class ChildType extends ParentType {} class A { public function covariantReturnTypes(): ParentType - { /* … */ } + { + } } class B extends A @@ -76,7 +93,8 @@ class B extends A * @return ChildType */ public function covariantReturnTypes(): ParentType - { /* … */ } + { + } } CODE_SAMPLE ), @@ -96,108 +114,87 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - if (! $this->shouldRefactor($node)) { + if ($node->returnType === null) { + return null; + } + + $parentReturnType = $this->resolveDifferentAncestorReturnType($node, $node->returnType); + if ($parentReturnType instanceof MixedType) { return null; } - /** @var string $parentReflectionMethodName */ - $parentReflectionMethodName = $this->getDifferentReturnTypeNameFromAncestorClass($node); // The return type name could either be a classname, without the leading "\", // or one among the reserved identifiers ("static", "self", "iterable", etc) // To find out which is the case, check if this name exists as a class - $newType = ClassExistenceStaticHelper::doesClassLikeExist($parentReflectionMethodName) ? new FullyQualified( - $parentReflectionMethodName - ) : new Name($parentReflectionMethodName); + $parentReturnTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($parentReturnType); + if ($parentReturnTypeNode === null) { + return null; + } // Make it nullable? - if ($node->returnType instanceof NullableType) { - $newType = new NullableType($newType); + if ($node->returnType instanceof NullableType && ! $parentReturnTypeNode instanceof NullableType && ! $parentReturnTypeNode instanceof UnionType) { + $parentReturnTypeNode = new NullableType($parentReturnTypeNode); } // Add the docblock before changing the type $this->addDocBlockReturn($node); - - $node->returnType = $newType; + $node->returnType = $parentReturnTypeNode; return $node; } - private function shouldRefactor(ClassMethod $classMethod): bool - { - return $this->getDifferentReturnTypeNameFromAncestorClass($classMethod) !== null; - } - - private function getDifferentReturnTypeNameFromAncestorClass(ClassMethod $classMethod): ?string + /** + * @param UnionType|NullableType|Name|Node\Identifier $returnTypeNode + */ + private function resolveDifferentAncestorReturnType(ClassMethod $classMethod, Node $returnTypeNode): Type { $scope = $classMethod->getAttribute(AttributeKey::SCOPE); if (! $scope instanceof Scope) { // possibly trait - return null; + return new MixedType(); } $classReflection = $scope->getClassReflection(); if (! $classReflection instanceof ClassReflection) { - return null; + return new MixedType(); } - $nodeReturnType = $classMethod->returnType; - if ($nodeReturnType === null) { - return null; - } - if ($nodeReturnType instanceof UnionType) { - return null; + if ($returnTypeNode instanceof UnionType) { + return new MixedType(); } - $nodeReturnTypeName = $this->getName( - $nodeReturnType instanceof NullableType ? $nodeReturnType->type : $nodeReturnType - ); - /** @var string $methodName */ - $methodName = $this->getName($classMethod->name); + $bareReturnType = $returnTypeNode instanceof NullableType ? $returnTypeNode->type : $returnTypeNode; - // Either Ancestor classes or implemented interfaces - $interfaceName = array_map( - function (ClassReflection $interfaceReflection): string { - return $interfaceReflection->getName(); - }, - $classReflection->getInterfaces() - ); + $returnType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($bareReturnType); + $methodName = $this->getName($classMethod); - $parentClassesNames = $classReflection->getParentClassesNames(); + /** @var ClassReflection[] $parentClassesAndInterfaces */ + $parentClassesAndInterfaces = array_merge($classReflection->getParents(), $classReflection->getInterfaces()); - $parentClassLikes = array_merge($parentClassesNames, $interfaceName); - - foreach ($parentClassLikes as $parentClassLike) { - if (! method_exists($parentClassLike, $methodName)) { + foreach ($parentClassesAndInterfaces as $parentClassesOrInterface) { + if (! $parentClassesOrInterface->hasMethod($methodName)) { continue; } - $parentReflectionMethod = new ReflectionMethod($parentClassLike, $methodName); - $parentReflectionMethodReturnType = $parentReflectionMethod->getReturnType(); + $classMethodScope = $classMethod->getAttribute(AttributeKey::SCOPE); + $parameterMethodReflection = $parentClassesOrInterface->getMethod($methodName, $classMethodScope); + + /** @var Type $parentReturnType */ + $parentReturnType = $this->privatesCaller->callPrivateMethod( + $parameterMethodReflection, + 'getReturnType', + [] + ); - if ($this->isNotReflectionNamedTypeOrNotEqualsToNodeReturnTypeName( - $parentReflectionMethodReturnType, - $nodeReturnTypeName - )) { + if ($parentReturnType->equals($returnType)) { continue; } // This is an ancestor class with a different return type - /** @var ReflectionNamedType $parentReflectionMethodReturnType */ - return $parentReflectionMethodReturnType->getName(); - } - - return null; - } - - private function isNotReflectionNamedTypeOrNotEqualsToNodeReturnTypeName( - ?ReflectionType $reflectionType, - ?string $nodeReturnTypeName - ): bool { - if (! $reflectionType instanceof ReflectionNamedType) { - return true; + return $parentReturnType; } - return $reflectionType->getName() === $nodeReturnTypeName; + return new MixedType(); } private function addDocBlockReturn(ClassMethod $classMethod): void @@ -209,5 +206,6 @@ private function addDocBlockReturn(ClassMethod $classMethod): void $type = $this->staticTypeMapper->mapPhpParserNodePHPStanType($returnType); $this->phpDocTypeChanger->changeReturnType($phpDocInfo, $type); + $this->returnTagRemover->removeReturnTagIfUseless($phpDocInfo, $classMethod); } } diff --git a/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Fixture/fixture.php.inc b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Fixture/fixture.php.inc index 0c702dff41e2..9a8880b57ad4 100644 --- a/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Fixture/fixture.php.inc +++ b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Fixture/fixture.php.inc @@ -34,9 +34,6 @@ class A class B extends A { - /** - * @return \Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeCovariantReturnTypeRector\Fixture\ChildType - */ public function covariantReturnTypes(): \Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeCovariantReturnTypeRector\Fixture\ParentType { /* … */ } } diff --git a/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Fixture/gap_level_ancestor.php.inc b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Fixture/gap_level_ancestor.php.inc index b862f6a1c3e6..a2efaa9347e5 100644 --- a/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Fixture/gap_level_ancestor.php.inc +++ b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Fixture/gap_level_ancestor.php.inc @@ -46,9 +46,6 @@ class GapLevelB extends GapLevelA class GapLevelC extends GapLevelB { - /** - * @return \Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeCovariantReturnTypeRector\Fixture\GapLevelChildType - */ public function covariantReturnTypes(): \Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeCovariantReturnTypeRector\Fixture\GapLevelParentType { /* … */ } } diff --git a/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Fixture/nullable_type.php.inc b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Fixture/nullable_type.php.inc index 57c9fd92f8cb..85b7d43efd4b 100644 --- a/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Fixture/nullable_type.php.inc +++ b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Fixture/nullable_type.php.inc @@ -34,9 +34,6 @@ class NullableA class NullableB extends NullableA { - /** - * @return \Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeCovariantReturnTypeRector\Fixture\NullableChildType|null - */ public function covariantReturnTypes(): ?\Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeCovariantReturnTypeRector\Fixture\NullableParentType { /* … */ } } diff --git a/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Fixture/self.php.inc b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Fixture/self.php.inc index e48c5608b886..63ea6e17651d 100644 --- a/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Fixture/self.php.inc +++ b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Fixture/self.php.inc @@ -28,10 +28,7 @@ class SelfA class SelfB extends SelfA { - /** - * @return \Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeCovariantReturnTypeRector\Fixture\SelfB - */ - public function covariantReturnTypes(): self + public function covariantReturnTypes(): \Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeCovariantReturnTypeRector\Fixture\SelfA { /* … */ } } diff --git a/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Fixture/skip_nullable_scalar.php.inc b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Fixture/skip_nullable_scalar.php.inc index 78cf8004583d..ed9f62a7f714 100644 --- a/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Fixture/skip_nullable_scalar.php.inc +++ b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Fixture/skip_nullable_scalar.php.inc @@ -2,19 +2,16 @@ namespace Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeCovariantReturnTypeRector\Fixture; -class NullableScalarParentType {} -class NullableScalarChildType extends NullableScalarParentType {} - class NullableScalarA { public function covariantReturnTypes(): ?string - { /* … */ } + { + } } class NullableScalarB extends NullableScalarA { public function covariantReturnTypes(): ?string - { /* … */ } + { + } } - -?> diff --git a/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Fixture/two_level_ancestor.php.inc b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Fixture/two_level_ancestor.php.inc index fc5a04603c06..adaf4c164ce5 100644 --- a/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Fixture/two_level_ancestor.php.inc +++ b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Fixture/two_level_ancestor.php.inc @@ -40,18 +40,12 @@ class TwoLevelA class TwoLevelB extends TwoLevelA { - /** - * @return \Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeCovariantReturnTypeRector\Fixture\TwoLevelChildType - */ public function covariantReturnTypes(): \Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeCovariantReturnTypeRector\Fixture\TwoLevelParentType { /* … */ } } class TwoLevelC extends TwoLevelB { - /** - * @return \Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeCovariantReturnTypeRector\Fixture\TwoLevelChildType - */ public function covariantReturnTypes(): \Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeCovariantReturnTypeRector\Fixture\TwoLevelParentType { /* … */ } } diff --git a/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Fixture/two_level_interface.php.inc b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Fixture/two_level_interface.php.inc index 8fe9d8cd0a70..68011d62fef3 100644 --- a/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Fixture/two_level_interface.php.inc +++ b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Fixture/two_level_interface.php.inc @@ -39,9 +39,6 @@ interface TwoLevelInterfaceB extends TwoLevelInterfaceA interface TwoLevelInterfaceC extends TwoLevelInterfaceB { - /** - * @return \Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeCovariantReturnTypeRector\Fixture\TwoLevelInterfaceChildType - */ public function covariantReturnTypes(): \Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeCovariantReturnTypeRector\Fixture\TwoLevelInterfaceParentType; } diff --git a/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Fixture/with_interface.php.inc b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Fixture/with_interface.php.inc index 44b6fcad6ca5..9e8818b8aad8 100644 --- a/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Fixture/with_interface.php.inc +++ b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Fixture/with_interface.php.inc @@ -2,8 +2,8 @@ namespace Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeCovariantReturnTypeRector\Fixture; -class WithInterfaceParentType {} -class WithInterfaceChildType extends WithInterfaceParentType {} +use Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeCovariantReturnTypeRector\Source\WithInterfaceParentType; +use Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeCovariantReturnTypeRector\Source\WithInterfaceChildType; interface WithInterfaceA { @@ -21,8 +21,8 @@ interface WithInterfaceB extends WithInterfaceA namespace Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeCovariantReturnTypeRector\Fixture; -class WithInterfaceParentType {} -class WithInterfaceChildType extends WithInterfaceParentType {} +use Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeCovariantReturnTypeRector\Source\WithInterfaceParentType; +use Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeCovariantReturnTypeRector\Source\WithInterfaceChildType; interface WithInterfaceA { @@ -31,10 +31,7 @@ interface WithInterfaceA interface WithInterfaceB extends WithInterfaceA { - /** - * @return \Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeCovariantReturnTypeRector\Fixture\WithInterfaceChildType - */ - public function covariantReturnTypes(): \Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeCovariantReturnTypeRector\Fixture\WithInterfaceParentType; + public function covariantReturnTypes(): \Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeCovariantReturnTypeRector\Source\WithInterfaceParentType; } ?> diff --git a/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Source/WithInterfaceChildType.php b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Source/WithInterfaceChildType.php new file mode 100644 index 000000000000..8bff4194ebb4 --- /dev/null +++ b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeCovariantReturnTypeRector/Source/WithInterfaceChildType.php @@ -0,0 +1,10 @@ +processReplaceIfs($node, $conditions, $ifNextReturnClone); } - if (property_exists($ifNextReturn, 'expr') && $ifNextReturn->expr instanceof Expr) { + if (! $ifNextReturn instanceof Expression) { + return null; + } + + if ($ifNextReturn->expr instanceof Expr) { $this->addNodeAfterNode(new Return_(), $node); } @@ -186,7 +192,7 @@ private function shouldSkip(If_ $if): bool private function getBooleanAndConditions(BooleanAnd $booleanAnd): array { $ifs = []; - while (property_exists($booleanAnd, 'left')) { + while ($booleanAnd instanceof BinaryOp) { $ifs[] = $booleanAnd->right; $booleanAnd = $booleanAnd->left; diff --git a/rules/early-return/src/Rector/Return_/PreparedValueToEarlyReturnRector.php b/rules/early-return/src/Rector/Return_/PreparedValueToEarlyReturnRector.php index 109f0719b250..a76e6dd4cd43 100644 --- a/rules/early-return/src/Rector/Return_/PreparedValueToEarlyReturnRector.php +++ b/rules/early-return/src/Rector/Return_/PreparedValueToEarlyReturnRector.php @@ -103,8 +103,10 @@ public function refactor(Node $node): ?Node /** @var Expr $returnExpr */ $returnExpr = $node->expr; + /** @var Expression $previousFirstExpression */ $previousFirstExpression = $this->getPreviousIfLinearEquals($ifsBefore[0], $returnExpr); + /** @var Assign $previousAssign */ $previousAssign = $previousFirstExpression->expr; @@ -222,11 +224,12 @@ private function getIfsBefore(Return_ $return): array return []; } - if (! (property_exists($parent, 'stmts') && $parent->stmts !== null)) { + if ($parent->stmts === []) { return []; } - if ($parent->stmts[(is_countable($parent->stmts) ? count($parent->stmts) : 0) - 1] !== $return) { + $firstItemPosition = array_key_last($parent->stmts); + if ($parent->stmts[$firstItemPosition] !== $return) { return []; } diff --git a/rules/laravel/src/Rector/StaticCall/Redirect301ToPermanentRedirectRector.php b/rules/laravel/src/Rector/StaticCall/Redirect301ToPermanentRedirectRector.php index b8aff33b36be..924c92e4c788 100644 --- a/rules/laravel/src/Rector/StaticCall/Redirect301ToPermanentRedirectRector.php +++ b/rules/laravel/src/Rector/StaticCall/Redirect301ToPermanentRedirectRector.php @@ -74,7 +74,7 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - if (! $this->nodeTypeResolver->isObjectTypes($node, $this->routerObjectTypes)) { + if (! $this->nodeTypeResolver->isObjectTypes($node->class, $this->routerObjectTypes)) { return null; } diff --git a/rules/laravel/src/Rector/StaticCall/RequestStaticValidateToInjectRector.php b/rules/laravel/src/Rector/StaticCall/RequestStaticValidateToInjectRector.php index 2083d0a14a4b..00dc83c2f353 100644 --- a/rules/laravel/src/Rector/StaticCall/RequestStaticValidateToInjectRector.php +++ b/rules/laravel/src/Rector/StaticCall/RequestStaticValidateToInjectRector.php @@ -25,19 +25,18 @@ final class RequestStaticValidateToInjectRector extends AbstractRector { /** - * @var ObjectType[] + * @var ClassMethodManipulator */ - private $requestObjectTypes = []; + private $classMethodManipulator; /** - * @var ClassMethodManipulator + * @var ObjectType[] */ - private $classMethodManipulator; + private $requestObjectTypes = []; public function __construct(ClassMethodManipulator $classMethodManipulator) { $this->classMethodManipulator = $classMethodManipulator; - $this->requestObjectTypes = [new ObjectType('Illuminate\Http\Request'), new ObjectType('Request')]; } @@ -94,7 +93,7 @@ public function refactor(Node $node): ?Node $requestName = $this->classMethodManipulator->addMethodParameterIfMissing( $node, - 'Illuminate\Http\Request', + new ObjectType('Illuminate\Http\Request'), ['request', 'httpRequest'] ); diff --git a/rules/legacy/src/Naming/FullyQualifiedNameResolver.php b/rules/legacy/src/Naming/FullyQualifiedNameResolver.php index 7aeb59444fdc..1eba7c51d21e 100644 --- a/rules/legacy/src/Naming/FullyQualifiedNameResolver.php +++ b/rules/legacy/src/Naming/FullyQualifiedNameResolver.php @@ -32,12 +32,12 @@ public function __construct(BetterNodeFinder $betterNodeFinder, NodeNameResolver */ public function resolveFullyQualifiedName(array $nodes, string $shortClassName): string { - $namespace = $this->betterNodeFinder->findFirstInstanceOf($nodes, Namespace_::class); - if (! $namespace instanceof Namespace_) { + $foundNode = $this->betterNodeFinder->findFirstInstanceOf($nodes, Namespace_::class); + if (! $foundNode instanceof Namespace_) { return $shortClassName; } - $namespaceName = $this->nodeNameResolver->getName($namespace); + $namespaceName = $this->nodeNameResolver->getName($foundNode); if ($namespaceName === null) { return $shortClassName; } diff --git a/rules/mockista-to-mockery/src/Rector/ClassMethod/MockistaMockToMockeryMockRector.php b/rules/mockista-to-mockery/src/Rector/ClassMethod/MockistaMockToMockeryMockRector.php index 71965cd7bacc..f8d4132b5a4b 100644 --- a/rules/mockista-to-mockery/src/Rector/ClassMethod/MockistaMockToMockeryMockRector.php +++ b/rules/mockista-to-mockery/src/Rector/ClassMethod/MockistaMockToMockeryMockRector.php @@ -12,12 +12,11 @@ use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Expr\Variable; use PhpParser\Node\Stmt\ClassMethod; +use PHPStan\Reflection\ReflectionProvider; use Rector\Core\Rector\AbstractRector; use Rector\MockeryToProphecy\Collector\MockVariableCollector; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer; -use ReflectionMethod; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -32,7 +31,7 @@ final class MockistaMockToMockeryMockRector extends AbstractRector private const METHODS_TO_REMOVE = ['freeze', 'assertExpectations']; /** - * @var string[] + * @var array */ private $mockVariableTypesByNames = []; @@ -46,10 +45,19 @@ final class MockistaMockToMockeryMockRector extends AbstractRector */ private $testsNodeAnalyzer; - public function __construct(MockVariableCollector $mockVariableCollector, TestsNodeAnalyzer $testsNodeAnalyzer) - { + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct( + MockVariableCollector $mockVariableCollector, + TestsNodeAnalyzer $testsNodeAnalyzer, + ReflectionProvider $reflectionProvider + ) { $this->mockVariableCollector = $mockVariableCollector; $this->testsNodeAnalyzer = $testsNodeAnalyzer; + $this->reflectionProvider = $reflectionProvider; } public function getRuleDefinition(): RuleDefinition @@ -270,11 +278,17 @@ private function decorateWithAnyArgs(MethodCall $originalMethodCall, MethodCall return $expectsMethodCall; } - if (! method_exists($mockVariableType, $methodName)) { + if (! $this->reflectionProvider->hasClass($mockVariableType)) { return $expectsMethodCall; } - $reflectionMethod = new ReflectionMethod($mockVariableType, $methodName); + $classReflection = $this->reflectionProvider->getClass($mockVariableType); + if (! $classReflection->hasMethod($methodName)) { + return $expectsMethodCall; + } + + $nativeReflectionClass = $classReflection->getNativeReflection(); + $reflectionMethod = $nativeReflectionClass->getMethod($methodName); if ($reflectionMethod->getNumberOfRequiredParameters() === 0) { return $expectsMethodCall; } @@ -298,11 +312,16 @@ private function isPropertyFetchDisguisedAsMethodCall(Node $node): bool } $mockVariableType = $this->mockVariableTypesByNames[$variableName]; + if (! $this->reflectionProvider->hasClass($mockVariableType)) { + return false; + } + + $classReflection = $this->reflectionProvider->getClass($mockVariableType); $propertyName = $this->getName($node->name); if ($propertyName === null) { return false; } - return method_exists($mockVariableType, $propertyName); + return $classReflection->hasMethod($propertyName); } } diff --git a/rules/naming/src/Guard/HasMagicGetSetGuard.php b/rules/naming/src/Guard/HasMagicGetSetGuard.php index 060d219752e5..2d02169e3e84 100644 --- a/rules/naming/src/Guard/HasMagicGetSetGuard.php +++ b/rules/naming/src/Guard/HasMagicGetSetGuard.php @@ -4,20 +4,37 @@ namespace Rector\Naming\Guard; +use PHPStan\Reflection\ReflectionProvider; use Rector\Naming\Contract\Guard\ConflictingGuardInterface; use Rector\Naming\Contract\RenameValueObjectInterface; use Rector\Naming\ValueObject\PropertyRename; final class HasMagicGetSetGuard implements ConflictingGuardInterface { + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + /** * @param PropertyRename $renameValueObject */ public function check(RenameValueObjectInterface $renameValueObject): bool { - return method_exists($renameValueObject->getClassLikeName(), '__set') || method_exists( - $renameValueObject->getClassLikeName(), - '__get' - ); + if (! $this->reflectionProvider->hasClass($renameValueObject->getClassLikeName())) { + return false; + } + + $classReflection = $this->reflectionProvider->getClass($renameValueObject->getClassLikeName()); + if ($classReflection->hasMethod('__set')) { + return true; + } + + return $classReflection->hasMethod('__get'); } } diff --git a/rules/naming/src/Naming/PropertyNaming.php b/rules/naming/src/Naming/PropertyNaming.php index 08d9a040e43d..436b32ef7409 100644 --- a/rules/naming/src/Naming/PropertyNaming.php +++ b/rules/naming/src/Naming/PropertyNaming.php @@ -11,6 +11,7 @@ use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Property; use PhpParser\Node\Stmt\Return_; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\ObjectType; use PHPStan\Type\StaticType; use PHPStan\Type\Type; @@ -86,18 +87,25 @@ final class PropertyNaming */ private $nodeTypeResolver; + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + public function __construct( TypeUnwrapper $typeUnwrapper, RectorNamingInflector $rectorNamingInflector, BetterNodeFinder $betterNodeFinder, NodeNameResolver $nodeNameResolver, - NodeTypeResolver $nodeTypeResolver + NodeTypeResolver $nodeTypeResolver, + ReflectionProvider $reflectionProvider ) { $this->typeUnwrapper = $typeUnwrapper; $this->rectorNamingInflector = $rectorNamingInflector; $this->betterNodeFinder = $betterNodeFinder; $this->nodeNameResolver = $nodeNameResolver; $this->nodeTypeResolver = $nodeTypeResolver; + $this->reflectionProvider = $reflectionProvider; } public function getExpectedNameFromMethodName(string $methodName): ?ExpectedName @@ -278,7 +286,7 @@ private function fqnToShortName(string $fqn): string private function removeInterfaceSuffixPrefix(string $className, string $shortName): string { // remove interface prefix/suffix - if (! interface_exists($className)) { + if (! $this->reflectionProvider->hasClass($className)) { return $shortName; } diff --git a/rules/naming/src/PropertyRenamer/AbstractPropertyRenamer.php b/rules/naming/src/PropertyRenamer/AbstractPropertyRenamer.php index 6b62b9f0afd7..371c1759c016 100644 --- a/rules/naming/src/PropertyRenamer/AbstractPropertyRenamer.php +++ b/rules/naming/src/PropertyRenamer/AbstractPropertyRenamer.php @@ -22,11 +22,6 @@ abstract class AbstractPropertyRenamer implements RenamerInterface { - /** - * @var RenameGuardInterface - */ - protected $propertyRenameGuard; - /** * @var ConflictingGuardInterface */ @@ -35,7 +30,7 @@ abstract class AbstractPropertyRenamer implements RenamerInterface /** * @var PropertyFetchRenamer */ - protected $propertyFetchRenamer; + private $propertyFetchRenamer; /** * @var NotPrivatePropertyGuard @@ -52,6 +47,11 @@ abstract class AbstractPropertyRenamer implements RenamerInterface */ private $dateTimeAtNamingConventionGuard; + /** + * @var RenameGuardInterface + */ + private $propertyRenameGuard; + /** * @var HasMagicGetSetGuard */ diff --git a/rules/naming/src/Rector/Assign/RenameVariableToMatchMethodCallReturnTypeRector.php b/rules/naming/src/Rector/Assign/RenameVariableToMatchMethodCallReturnTypeRector.php index 14fc5feefc58..68d5e8840258 100644 --- a/rules/naming/src/Rector/Assign/RenameVariableToMatchMethodCallReturnTypeRector.php +++ b/rules/naming/src/Rector/Assign/RenameVariableToMatchMethodCallReturnTypeRector.php @@ -11,7 +11,8 @@ use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Stmt\ClassLike; -use PHPStan\Type\TypeWithClassName; +use PHPStan\Reflection\ReflectionProvider; +use PHPStan\Type\ObjectType; use Rector\Core\Rector\AbstractRector; use Rector\FamilyTree\Reflection\FamilyRelationsAnalyzer; use Rector\Naming\Guard\BreakingVariableRenameGuard; @@ -46,11 +47,6 @@ final class RenameVariableToMatchMethodCallReturnTypeRector extends AbstractRect */ private $breakingVariableRenameGuard; - /** - * @var FamilyRelationsAnalyzer - */ - private $familyRelationsAnalyzer; - /** * @var VariableAndCallAssignMatcher */ @@ -71,24 +67,36 @@ final class RenameVariableToMatchMethodCallReturnTypeRector extends AbstractRect */ private $typeUnwrapper; + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + /** + * @var FamilyRelationsAnalyzer + */ + private $familyRelationsAnalyzer; + public function __construct( BreakingVariableRenameGuard $breakingVariableRenameGuard, ExpectedNameResolver $expectedNameResolver, - FamilyRelationsAnalyzer $familyRelationsAnalyzer, NamingConventionAnalyzer $namingConventionAnalyzer, VarTagValueNodeRenamer $varTagValueNodeRenamer, VariableAndCallAssignMatcher $variableAndCallAssignMatcher, VariableRenamer $variableRenamer, - TypeUnwrapper $typeUnwrapper + TypeUnwrapper $typeUnwrapper, + ReflectionProvider $reflectionProvider, + FamilyRelationsAnalyzer $familyRelationsAnalyzer ) { $this->expectedNameResolver = $expectedNameResolver; $this->variableRenamer = $variableRenamer; $this->breakingVariableRenameGuard = $breakingVariableRenameGuard; - $this->familyRelationsAnalyzer = $familyRelationsAnalyzer; $this->variableAndCallAssignMatcher = $variableAndCallAssignMatcher; $this->namingConventionAnalyzer = $namingConventionAnalyzer; $this->varTagValueNodeRenamer = $varTagValueNodeRenamer; $this->typeUnwrapper = $typeUnwrapper; + $this->reflectionProvider = $reflectionProvider; + $this->familyRelationsAnalyzer = $familyRelationsAnalyzer; } public function getRuleDefinition(): RuleDefinition @@ -174,6 +182,7 @@ public function refactor(Node $node): ?Node private function isMultipleCall(Node $callNode): bool { $parentNode = $callNode->getAttribute(AttributeKey::PARENT_NODE); + while ($parentNode) { $usedNodes = $this->betterNodeFinder->find($parentNode, function (Node $node) use ($callNode): bool { if (get_class($callNode) !== get_class($node)) { @@ -254,7 +263,7 @@ private function isClassTypeWithChildren(Expr $expr): bool $callStaticType = $this->getStaticType($expr); $callStaticType = $this->typeUnwrapper->unwrapNullableType($callStaticType); - if (! $callStaticType instanceof TypeWithClassName) { + if (! $callStaticType instanceof ObjectType) { return false; } @@ -262,6 +271,13 @@ private function isClassTypeWithChildren(Expr $expr): bool return false; } - return $this->familyRelationsAnalyzer->isParentClass($callStaticType->getClassName()); + if (! $this->reflectionProvider->hasClass($callStaticType->getClassName())) { + return false; + } + + $classReflection = $this->reflectionProvider->getClass($callStaticType->getClassName()); + $childrenClassReflections = $this->familyRelationsAnalyzer->getChildrenOfClassReflection($classReflection); + + return $childrenClassReflections !== []; } } diff --git a/rules/naming/src/Rector/Foreach_/RenameForeachValueVariableToMatchExprVariableRector.php b/rules/naming/src/Rector/Foreach_/RenameForeachValueVariableToMatchExprVariableRector.php index 8f61df6df375..43476cdb0037 100644 --- a/rules/naming/src/Rector/Foreach_/RenameForeachValueVariableToMatchExprVariableRector.php +++ b/rules/naming/src/Rector/Foreach_/RenameForeachValueVariableToMatchExprVariableRector.php @@ -30,40 +30,36 @@ public function __construct(Inflector $inflector) public function getRuleDefinition(): RuleDefinition { - return new RuleDefinition( - 'Renames value variable name in foreach loop to match expression variable', - [ - new CodeSample( - <<<'CODE_SAMPLE' + return new RuleDefinition('Renames value variable name in foreach loop to match expression variable', [ + new CodeSample( + <<<'CODE_SAMPLE' class SomeClass { - public function run() - { - $array = []; - foreach ($variables as $foo) { - $array[] = $property; - } +public function run() +{ + $array = []; + foreach ($variables as $foo) { + $array[] = $property; } } +} CODE_SAMPLE - , - <<<'CODE_SAMPLE' + , + <<<'CODE_SAMPLE' class SomeClass { - public function run() - { - $array = []; - foreach ($variables as $variable) { - $array[] = $variable; - } +public function run() +{ + $array = []; + foreach ($variables as $variable) { + $array[] = $variable; } } +} CODE_SAMPLE - - ), - - ]); + ), + ]); } /** diff --git a/rules/naming/tests/Rector/Assign/RenameVariableToMatchMethodCallReturnTypeRector/Fixture/static_call.php.inc b/rules/naming/tests/Rector/Assign/RenameVariableToMatchMethodCallReturnTypeRector/Fixture/static_call.php.inc deleted file mode 100644 index 0e4da489929d..000000000000 --- a/rules/naming/tests/Rector/Assign/RenameVariableToMatchMethodCallReturnTypeRector/Fixture/static_call.php.inc +++ /dev/null @@ -1,49 +0,0 @@ -exit(); - } - - /** - * @return \Rector\Naming\Tests\Rector\Assign\RenameVariableToMatchMethodCallReturnTypeRector\Source\FastRunner - */ - public static function getFastRunner() - { - return new FastRunner(); - } -} - -?> ------ -exit(); - } - - /** - * @return \Rector\Naming\Tests\Rector\Assign\RenameVariableToMatchMethodCallReturnTypeRector\Source\FastRunner - */ - public static function getFastRunner() - { - return new FastRunner(); - } -} - -?> diff --git a/rules/nette-code-quality/src/Contract/FormControlTypeResolverInterface.php b/rules/nette-code-quality/src/Contract/FormControlTypeResolverInterface.php index df22bb568fd7..e81edacf10a7 100644 --- a/rules/nette-code-quality/src/Contract/FormControlTypeResolverInterface.php +++ b/rules/nette-code-quality/src/Contract/FormControlTypeResolverInterface.php @@ -9,7 +9,7 @@ interface FormControlTypeResolverInterface { /** - * @return array + * @return array */ public function resolve(Node $node): array; } diff --git a/rules/nette-code-quality/src/FormControlTypeResolver/MagicNetteFactoryInterfaceFormControlTypeResolver.php b/rules/nette-code-quality/src/FormControlTypeResolver/MagicNetteFactoryInterfaceFormControlTypeResolver.php index 629a4ec4209e..7c2d0a8e310a 100644 --- a/rules/nette-code-quality/src/FormControlTypeResolver/MagicNetteFactoryInterfaceFormControlTypeResolver.php +++ b/rules/nette-code-quality/src/FormControlTypeResolver/MagicNetteFactoryInterfaceFormControlTypeResolver.php @@ -7,15 +7,16 @@ use PhpParser\Node; use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Stmt\ClassMethod; -use PhpParser\Node\Stmt\Interface_; +use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\TypeWithClassName; +use Rector\Core\PhpParser\Parser\FunctionLikeParser; use Rector\Core\ValueObject\MethodName; use Rector\NetteCodeQuality\Contract\FormControlTypeResolverInterface; use Rector\NetteCodeQuality\Contract\MethodNamesByInputNamesResolverAwareInterface; use Rector\NetteCodeQuality\NodeResolver\MethodNamesByInputNamesResolver; use Rector\NodeCollector\NodeCollector\NodeRepository; use Rector\NodeNameResolver\NodeNameResolver; -use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\NodeTypeResolver\NodeTypeResolver; final class MagicNetteFactoryInterfaceFormControlTypeResolver implements FormControlTypeResolverInterface, MethodNamesByInputNamesResolverAwareInterface @@ -40,14 +41,28 @@ final class MagicNetteFactoryInterfaceFormControlTypeResolver implements FormCon */ private $nodeRepository; + /** + * @var FunctionLikeParser + */ + private $functionLikeParser; + + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + public function __construct( NodeRepository $nodeRepository, NodeNameResolver $nodeNameResolver, - NodeTypeResolver $nodeTypeResolver + NodeTypeResolver $nodeTypeResolver, + FunctionLikeParser $functionLikeParser, + ReflectionProvider $reflectionProvider ) { $this->nodeTypeResolver = $nodeTypeResolver; $this->nodeNameResolver = $nodeNameResolver; $this->nodeRepository = $nodeRepository; + $this->functionLikeParser = $functionLikeParser; + $this->reflectionProvider = $reflectionProvider; } /** @@ -64,15 +79,21 @@ public function resolve(Node $node): array return []; } - $classMethod = $this->nodeRepository->findClassMethodByMethodCall($node); - if (! $classMethod instanceof ClassMethod) { + $methodName = $this->nodeNameResolver->getName($node->name); + if ($methodName === null) { return []; } - $classLike = $classMethod->getAttribute(AttributeKey::CLASS_NODE); + $classMethod = $this->nodeRepository->findClassMethodByMethodCall($node); + if (! $classMethod instanceof ClassMethod) { + $classMethod = $this->resolveReflectionClassMethod($node, $methodName); + if (! $classMethod instanceof ClassMethod) { + return []; + } + } - // magic interface handled esle where - if (! $classLike instanceof Interface_) { + $classReflection = $this->resolveClassReflectionByMethodCall($node); + if ($classReflection === null) { return []; } @@ -86,6 +107,16 @@ public function resolve(Node $node): array MethodName::CONSTRUCT ); + if (! $constructorClassMethod instanceof ClassMethod) { + $constructorClassMethod = $this->resolveReflectionClassMethodFromClassNameAndMethod( + $returnedType->getClassName(), + MethodName::CONSTRUCT + ); + if (! $classMethod instanceof ClassMethod) { + return []; + } + } + if (! $constructorClassMethod instanceof ClassMethod) { return []; } @@ -97,4 +128,43 @@ public function setResolver(MethodNamesByInputNamesResolver $methodNamesByInputN { $this->methodNamesByInputNamesResolver = $methodNamesByInputNamesResolver; } + + private function resolveReflectionClassMethod(MethodCall $methodCall, string $methodName): ?ClassMethod + { + $classReflection = $this->resolveClassReflectionByMethodCall($methodCall); + if ($classReflection === null) { + return null; + } + + $methodReflection = $classReflection->getNativeMethod($methodName); + + return $this->functionLikeParser->parseMethodReflection($methodReflection); + } + + private function resolveReflectionClassMethodFromClassNameAndMethod( + string $className, + string $methodName + ): ?ClassMethod { + if (! $this->reflectionProvider->hasClass($className)) { + return null; + } + + $classReflection = $this->reflectionProvider->getClass($className); + $methodReflection = $classReflection->getNativeMethod($methodName); + return $this->functionLikeParser->parseMethodReflection($methodReflection); + } + + private function resolveClassReflectionByMethodCall(MethodCall $methodCall): ?ClassReflection + { + $callerClassName = $this->nodeRepository->resolveCallerClassName($methodCall); + if ($callerClassName === null) { + return null; + } + + if (! $this->reflectionProvider->hasClass($callerClassName)) { + return null; + } + + return $this->reflectionProvider->getClass($callerClassName); + } } diff --git a/rules/nette-code-quality/src/FormControlTypeResolver/VariableConstructorFormControlTypeResolver.php b/rules/nette-code-quality/src/FormControlTypeResolver/VariableConstructorFormControlTypeResolver.php index 402557ea01de..8b4c62c296a3 100644 --- a/rules/nette-code-quality/src/FormControlTypeResolver/VariableConstructorFormControlTypeResolver.php +++ b/rules/nette-code-quality/src/FormControlTypeResolver/VariableConstructorFormControlTypeResolver.php @@ -7,6 +7,7 @@ use PhpParser\Node; use PhpParser\Node\Expr\Variable; use PhpParser\Node\Stmt\ClassMethod; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\TypeWithClassName; use Rector\Core\ValueObject\MethodName; use Rector\NetteCodeQuality\Contract\FormControlTypeResolverInterface; @@ -38,14 +39,21 @@ final class VariableConstructorFormControlTypeResolver implements FormControlTyp */ private $nodeRepository; + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + public function __construct( NodeTypeResolver $nodeTypeResolver, NodeNameResolver $nodeNameResolver, - NodeRepository $nodeRepository + NodeRepository $nodeRepository, + ReflectionProvider $reflectionProvider ) { $this->nodeTypeResolver = $nodeTypeResolver; $this->nodeNameResolver = $nodeNameResolver; $this->nodeRepository = $nodeRepository; + $this->reflectionProvider = $reflectionProvider; } /** @@ -67,7 +75,9 @@ public function resolve(Node $node): array return []; } - if (! is_a($formType->getClassName(), 'Nette\Application\UI\Form', true)) { + $formClassReflection = $this->reflectionProvider->getClass($formType->getClassName()); + + if (! $formClassReflection->isSubclassOf('Nette\Application\UI\Form')) { return []; } diff --git a/rules/nette-code-quality/src/NodeAnalyzer/ControlDimFetchAnalyzer.php b/rules/nette-code-quality/src/NodeAnalyzer/ControlDimFetchAnalyzer.php index 24366957922c..1d5d039bff82 100644 --- a/rules/nette-code-quality/src/NodeAnalyzer/ControlDimFetchAnalyzer.php +++ b/rules/nette-code-quality/src/NodeAnalyzer/ControlDimFetchAnalyzer.php @@ -26,12 +26,12 @@ public function __construct(NodeTypeResolver $nodeTypeResolver) public function matchNameOnFormOrControlVariable(Node $node): ?string { - return $this->matchNameOnVariableTypes($node, [new ObjectType('Nette\Application\UI\Form')]); + return $this->matchNameOnVariableType($node, new ObjectType('Nette\Application\UI\Form')); } public function matchNameOnControlVariable(Node $node): ?string { - return $this->matchNameOnVariableTypes($node, [new ObjectType('Nette\Application\UI\Control')]); + return $this->matchNameOnVariableType($node, new ObjectType('Nette\Application\UI\Control')); } public function matchName(Node $node): ?string @@ -51,10 +51,7 @@ public function matchName(Node $node): ?string return $node->dim->value; } - /** - * @param ObjectType[] $objectTypes - */ - private function matchNameOnVariableTypes(Node $node, array $objectTypes): ?string + private function matchNameOnVariableType(Node $node, ObjectType $objectType): ?string { $matchedName = $this->matchName($node); if ($matchedName === null) { @@ -62,7 +59,7 @@ private function matchNameOnVariableTypes(Node $node, array $objectTypes): ?stri } /** @var Assign $node */ - if (! $this->isVariableTypes($node->var, $objectTypes)) { + if (! $this->isVariableTypes($node->var, [$objectType])) { return null; } diff --git a/rules/nette-code-quality/src/NodeResolver/MethodNamesByInputNamesResolver.php b/rules/nette-code-quality/src/NodeResolver/MethodNamesByInputNamesResolver.php index 6ba606ace0d8..c8e4575932a4 100644 --- a/rules/nette-code-quality/src/NodeResolver/MethodNamesByInputNamesResolver.php +++ b/rules/nette-code-quality/src/NodeResolver/MethodNamesByInputNamesResolver.php @@ -30,7 +30,7 @@ public function __construct(array $formControlTypeResolvers) } /** - * @return array + * @return array */ public function resolveExpr(Node $node): array { diff --git a/rules/nette-code-quality/src/Rector/ArrayDimFetch/AbstractArrayDimFetchToAnnotatedControlVariableRector.php b/rules/nette-code-quality/src/Rector/ArrayDimFetch/AbstractArrayDimFetchToAnnotatedControlVariableRector.php index b3512591e06c..1eeef5638631 100644 --- a/rules/nette-code-quality/src/Rector/ArrayDimFetch/AbstractArrayDimFetchToAnnotatedControlVariableRector.php +++ b/rules/nette-code-quality/src/Rector/ArrayDimFetch/AbstractArrayDimFetchToAnnotatedControlVariableRector.php @@ -36,7 +36,7 @@ abstract class AbstractArrayDimFetchToAnnotatedControlVariableRector extends Abs /** * @var VarAnnotationManipulator */ - protected $varAnnotationManipulator; + private $varAnnotationManipulator; /** * @var string[] diff --git a/rules/nette-code-quality/src/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector.php b/rules/nette-code-quality/src/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector.php index 9aadf2824282..011ef7007910 100644 --- a/rules/nette-code-quality/src/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector.php +++ b/rules/nette-code-quality/src/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector.php @@ -160,8 +160,6 @@ private function resolveControlType(ArrayDimFetch $arrayDimFetch, string $contro throw new ShouldNotHappenException($controlName); } - $controlType = $controlTypes[$controlName]; - - return new ObjectType($controlType); + return new ObjectType($controlTypes[$controlName]); } } diff --git a/rules/nette-code-quality/tests/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/form_build_in_external_class.php.inc b/rules/nette-code-quality/tests/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/form_build_in_external_class.php.inc index cc0fc665bc40..83e57d44f61d 100644 --- a/rules/nette-code-quality/tests/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/form_build_in_external_class.php.inc +++ b/rules/nette-code-quality/tests/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/form_build_in_external_class.php.inc @@ -2,9 +2,9 @@ namespace Rector\NetteCodeQuality\Tests\Rector\ArrayDimFetch\ChangeFormArrayAccessToAnnotatedControlVariableRector\Fixture; -use Nette\Application\UI\Form; +use Rector\NetteCodeQuality\Tests\Rector\ArrayDimFetch\ChangeFormArrayAccessToAnnotatedControlVariableRector\Source\SomeFormFactory; -class FormBuilderInExternalClass +final class FormBuilderInExternalClass { /** * @var SomeFormFactory @@ -23,26 +23,15 @@ class FormBuilderInExternalClass } } -class SomeFormFactory -{ - public function createForm(): Form - { - $form = new Form(); - $form->addSelect('items'); - - return $form; - } -} - ?> ----- addSelect('items'); - - return $form; - } -} - ?> diff --git a/rules/nette-code-quality/tests/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/form_factory_interface.php.inc b/rules/nette-code-quality/tests/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/form_factory_interface.php.inc deleted file mode 100644 index 7a83b459e24d..000000000000 --- a/rules/nette-code-quality/tests/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/form_factory_interface.php.inc +++ /dev/null @@ -1,63 +0,0 @@ -create(); - $form['video']->value = '12345'; - } -} - -interface VideoFormFactory -{ - public function create(): VideoForm; -} - - -class VideoForm extends Form -{ - public function __construct() - { - $this->addCheckboxList('video'); - } -} - -?> ------ -create(); - /** @var \Nette\Forms\Controls\CheckboxList $videoControl */ - $videoControl = $form['video']; - $videoControl->value = '12345'; - } -} - -interface VideoFormFactory -{ - public function create(): VideoForm; -} - - -class VideoForm extends Form -{ - public function __construct() - { - $this->addCheckboxList('video'); - } -} - -?> diff --git a/rules/nette-code-quality/tests/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/form_via_get_component.php.inc b/rules/nette-code-quality/tests/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/form_via_get_component.php.inc deleted file mode 100644 index 21713fab670c..000000000000 --- a/rules/nette-code-quality/tests/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/form_via_get_component.php.inc +++ /dev/null @@ -1,63 +0,0 @@ -getComponent('some'); - $form['title']->value = 'wohoooo'; - } - - public function createComponentSome(): ExternalFormWithTitle - { - return new ExternalFormWithTitle(); - } -} - -final class ExternalFormWithTitle extends Form -{ - public function __construct() - { - $this->addText('title'); - } -} - -?> ------ -getComponent('some'); - /** @var \Nette\Forms\Controls\TextInput $titleControl */ - $titleControl = $form['title']; - $titleControl->value = 'wohoooo'; - } - - public function createComponentSome(): ExternalFormWithTitle - { - return new ExternalFormWithTitle(); - } -} - -final class ExternalFormWithTitle extends Form -{ - public function __construct() - { - $this->addText('title'); - } -} - -?> diff --git a/rules/nette-code-quality/tests/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/nested_factory.php.inc b/rules/nette-code-quality/tests/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/nested_factory.php.inc deleted file mode 100644 index bcfd68a1cbb0..000000000000 --- a/rules/nette-code-quality/tests/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/nested_factory.php.inc +++ /dev/null @@ -1,103 +0,0 @@ -formWithTitleFactory = $formWithTitleFactory; - } - - public function run() - { - $form = $this->makeForm(); - $form['title']->value = 'foo'; - - $form['another_title']->value = 'bar'; - } - - public function makeForm(): Form - { - $form = $this->formWithTitleFactory->create(); - $form->addText('another_title'); - - return $form; - } -} - -interface FormWithTitleFactory -{ - public function create(): FormWithTitle; -} - -class FormWithTitle extends Form -{ - public function __construct() - { - $this->addText('title'); - } -} - -?> ------ -formWithTitleFactory = $formWithTitleFactory; - } - - public function run() - { - $form = $this->makeForm(); - /** @var \Nette\Forms\Controls\TextInput $titleControl */ - $titleControl = $form['title']; - $titleControl->value = 'foo'; - /** @var \Nette\Forms\Controls\TextInput $anotherTitleControl */ - $anotherTitleControl = $form['another_title']; - - $anotherTitleControl->value = 'bar'; - } - - public function makeForm(): Form - { - $form = $this->formWithTitleFactory->create(); - $form->addText('another_title'); - - return $form; - } -} - -interface FormWithTitleFactory -{ - public function create(): FormWithTitle; -} - -class FormWithTitle extends Form -{ - public function __construct() - { - $this->addText('title'); - } -} - -?> diff --git a/rules/nette-code-quality/tests/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/nested_factory_and_new.php.inc b/rules/nette-code-quality/tests/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/nested_factory_and_new.php.inc deleted file mode 100644 index e22c315e78f5..000000000000 --- a/rules/nette-code-quality/tests/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/nested_factory_and_new.php.inc +++ /dev/null @@ -1,73 +0,0 @@ -makeForm(); - $form['title']->value = 'foo'; - - $form['another_title']->value = 'bar'; - } - - public function makeForm(): Form - { - $form = new FormWithTitleLocal(); - $form->addText('another_title'); - - return $form; - } -} - -class FormWithTitleLocal extends Form -{ - public function __construct() - { - $this->addText('title'); - } -} - -?> ------ -makeForm(); - /** @var \Nette\Forms\Controls\TextInput $titleControl */ - $titleControl = $form['title']; - $titleControl->value = 'foo'; - /** @var \Nette\Forms\Controls\TextInput $anotherTitleControl */ - $anotherTitleControl = $form['another_title']; - - $anotherTitleControl->value = 'bar'; - } - - public function makeForm(): Form - { - $form = new FormWithTitleLocal(); - $form->addText('another_title'); - - return $form; - } -} - -class FormWithTitleLocal extends Form -{ - public function __construct() - { - $this->addText('title'); - } -} - -?> diff --git a/rules/nette-code-quality/tests/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Source/FormWithTitleLocal.php b/rules/nette-code-quality/tests/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Source/FormWithTitleLocal.php new file mode 100644 index 000000000000..aaa4662f9925 --- /dev/null +++ b/rules/nette-code-quality/tests/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Source/FormWithTitleLocal.php @@ -0,0 +1,15 @@ +addText('title'); + } +} diff --git a/rules/nette-code-quality/tests/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Source/SomeFormFactory.php b/rules/nette-code-quality/tests/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Source/SomeFormFactory.php new file mode 100644 index 000000000000..c257ed883a23 --- /dev/null +++ b/rules/nette-code-quality/tests/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Source/SomeFormFactory.php @@ -0,0 +1,18 @@ +addSelect('items'); + + return $form; + } +} diff --git a/rules/nette-code-quality/tests/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Source/VideoForm.php b/rules/nette-code-quality/tests/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Source/VideoForm.php new file mode 100644 index 000000000000..cba1a0e9e922 --- /dev/null +++ b/rules/nette-code-quality/tests/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Source/VideoForm.php @@ -0,0 +1,15 @@ +addCheckboxList('video'); + } +} diff --git a/rules/nette-code-quality/tests/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Source/VideoFormFactory.php b/rules/nette-code-quality/tests/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Source/VideoFormFactory.php new file mode 100644 index 000000000000..6484f577fed0 --- /dev/null +++ b/rules/nette-code-quality/tests/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Source/VideoFormFactory.php @@ -0,0 +1,10 @@ +someDependency; - } -} - -?> ------ -someDependency; - } -} - -?> diff --git a/rules/nette-kdyby/src/DataProvider/EventAndListenerTreeProvider.php b/rules/nette-kdyby/src/DataProvider/EventAndListenerTreeProvider.php index eba4be7db3fd..cb6093603d24 100644 --- a/rules/nette-kdyby/src/DataProvider/EventAndListenerTreeProvider.php +++ b/rules/nette-kdyby/src/DataProvider/EventAndListenerTreeProvider.php @@ -128,8 +128,6 @@ private function initializeEventAndListenerTrees(): void $dispatchMethodCall = $this->dispatchMethodCallFactory->createFromEventClassName($eventClassName); - $listeningClassMethodsByClass = $this->getListeningClassMethodsInEventSubscriberByClass($eventClassName); - // getter names by variable name and type $getterMethodsBlueprints = $this->resolveGetterMethodBlueprint($eventClassInNamespace); @@ -140,7 +138,7 @@ private function initializeEventAndListenerTrees(): void $eventFileLocation, $eventClassInNamespace, $dispatchMethodCall, - $listeningClassMethodsByClass, + $this->getListeningClassMethodsInEventSubscriberByClass($eventClassName), $getterMethodsBlueprints ); @@ -160,7 +158,7 @@ private function resolveMagicProperty(MethodCall $methodCall): ?Property } /** - * @return ClassMethod[][] + * @return array */ private function getListeningClassMethodsInEventSubscriberByClass(string $eventClassName): array { diff --git a/rules/nette-kdyby/src/DataProvider/OnPropertyMagicCallProvider.php b/rules/nette-kdyby/src/DataProvider/OnPropertyMagicCallProvider.php index cff15aad9d26..e9bb89592860 100644 --- a/rules/nette-kdyby/src/DataProvider/OnPropertyMagicCallProvider.php +++ b/rules/nette-kdyby/src/DataProvider/OnPropertyMagicCallProvider.php @@ -6,6 +6,8 @@ use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\StaticCall; +use PHPStan\Analyser\Scope; +use PHPStan\Reflection\ClassReflection; use Rector\NodeCollector\NodeCollector\NodeRepository; use Rector\NodeNameResolver\NodeNameResolver; use Rector\NodeTypeResolver\Node\AttributeKey; @@ -52,7 +54,12 @@ public function provide(): array } foreach ($this->nodeRepository->getMethodsCalls() as $methodCall) { - if (! $this->isLocalOnPropertyCall($methodCall)) { + $scope = $methodCall->getAttribute(AttributeKey::SCOPE); + if (! $scope instanceof Scope) { + continue; + } + + if (! $this->isLocalOnPropertyCall($methodCall, $scope)) { continue; } @@ -66,7 +73,7 @@ public function provide(): array * Detects method call on, e.g: * public $onSomeProperty; */ - private function isLocalOnPropertyCall(MethodCall $methodCall): bool + private function isLocalOnPropertyCall(MethodCall $methodCall, Scope $scope): bool { if ($methodCall->var instanceof StaticCall) { return false; @@ -89,19 +96,20 @@ private function isLocalOnPropertyCall(MethodCall $methodCall): bool return false; } - $className = $methodCall->getAttribute(AttributeKey::CLASS_NAME); - if ($className === null) { + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { return false; } + // control event, inner only - if (is_a($className, self::CONTROL_CLASS, true)) { + if ($classReflection->isSubclassOf(self::CONTROL_CLASS)) { return false; } - if (method_exists($className, $methodName)) { + if ($classReflection->hasMethod($methodName)) { return false; } - return property_exists($className, $methodName); + return $classReflection->hasProperty($methodName); } } diff --git a/rules/nette-kdyby/src/Naming/VariableNaming.php b/rules/nette-kdyby/src/Naming/VariableNaming.php index 5c5787987e0a..d931f97ec6a6 100644 --- a/rules/nette-kdyby/src/Naming/VariableNaming.php +++ b/rules/nette-kdyby/src/Naming/VariableNaming.php @@ -30,9 +30,6 @@ use Rector\NodeNameResolver\NodeNameResolver; use Rector\NodeTypeResolver\NodeTypeResolver; -/** - * @todo decouple to collector? - */ final class VariableNaming { /** @@ -144,7 +141,9 @@ private function resolveBareFromNode(Node $node): ?string return $this->resolveFromPropertyFetch($node); } - if ($this->isCall($node)) { + if ($node !== null && StaticInstanceOf::isOneOf( + $node, + [MethodCall::class, NullsafeMethodCall::class, StaticCall::class])) { return $this->resolveFromMethodCall($node); } @@ -242,25 +241,11 @@ private function resolveFromPropertyFetch(PropertyFetch $propertyFetch): string return $varName . ucfirst($propertyName); } - private function isCall(?Node $node): bool - { - if (! $node instanceof Node) { - return false; - } - - return StaticInstanceOf::isOneOf($node, [MethodCall::class, NullsafeMethodCall::class, StaticCall::class]); - } - - private function resolveFromMethodCall(?Node $node): ?string + /** + * @param MethodCall|NullsafeMethodCall|StaticCall $node + */ + private function resolveFromMethodCall(Node $node): ?string { - if (! $node instanceof Node) { - return null; - } - - if (! property_exists($node, 'name')) { - return null; - } - if ($node->name instanceof MethodCall) { return $this->resolveFromMethodCall($node->name); } diff --git a/rules/nette-kdyby/src/Rector/ClassMethod/ReplaceMagicPropertyWithEventClassRector.php b/rules/nette-kdyby/src/Rector/ClassMethod/ReplaceMagicPropertyWithEventClassRector.php index e8288a3b4bd2..c477ca913f50 100644 --- a/rules/nette-kdyby/src/Rector/ClassMethod/ReplaceMagicPropertyWithEventClassRector.php +++ b/rules/nette-kdyby/src/Rector/ClassMethod/ReplaceMagicPropertyWithEventClassRector.php @@ -117,6 +117,9 @@ public function refactor(Node $node): ?Node $this->replaceEventPropertyReferenceWithEventClassReference($node); $eventAndListenerTrees = $this->eventAndListenerTreeProvider->provide(); + if ($eventAndListenerTrees === []) { + return null; + } /** @var string $className */ $className = $node->getAttribute(AttributeKey::CLASS_NAME); @@ -153,6 +156,7 @@ private function replaceEventPropertyReferenceWithEventClassReference(ClassMetho $eventClassName = $this->eventClassNaming->createEventClassNameFromClassPropertyReference( $eventPropertyReferenceName ); + $node->key = $this->nodeFactory->createClassConstReference($eventClassName); }); } diff --git a/rules/nette-kdyby/src/ValueObject/EventAndListenerTree.php b/rules/nette-kdyby/src/ValueObject/EventAndListenerTree.php index 07575836c692..a5a1e0fe61c6 100644 --- a/rules/nette-kdyby/src/ValueObject/EventAndListenerTree.php +++ b/rules/nette-kdyby/src/ValueObject/EventAndListenerTree.php @@ -22,7 +22,7 @@ final class EventAndListenerTree private $eventFileLocation; /** - * @var ClassMethod[][] + * @var array */ private $listenerMethodsByEventSubscriberClass = []; @@ -52,7 +52,7 @@ final class EventAndListenerTree private $onMagicProperty; /** - * @param ClassMethod[][] $listenerMethodsByEventSubscriberClass + * @param array $listenerMethodsByEventSubscriberClass * @param GetterMethodBlueprint[] $getterMethodsBlueprints */ public function __construct( diff --git a/rules/nette-kdyby/tests/Rector/ClassMethod/ReplaceMagicPropertyWithEventClassRector/ReplaceMagicPropertyWithEventClassRectorTest.php b/rules/nette-kdyby/tests/Rector/ClassMethod/ReplaceMagicPropertyWithEventClassRector/ReplaceMagicPropertyWithEventClassRectorTest.php index cf160b752be9..4aee25a293dc 100644 --- a/rules/nette-kdyby/tests/Rector/ClassMethod/ReplaceMagicPropertyWithEventClassRector/ReplaceMagicPropertyWithEventClassRectorTest.php +++ b/rules/nette-kdyby/tests/Rector/ClassMethod/ReplaceMagicPropertyWithEventClassRector/ReplaceMagicPropertyWithEventClassRectorTest.php @@ -4,16 +4,24 @@ namespace Rector\NetteKdyby\Tests\Rector\ClassMethod\ReplaceMagicPropertyWithEventClassRector; +use Iterator; use Rector\NetteKdyby\Rector\ClassMethod\ReplaceMagicPropertyWithEventClassRector; use Rector\Testing\PHPUnit\AbstractRectorTestCase; use Symplify\SmartFileSystem\SmartFileInfo; final class ReplaceMagicPropertyWithEventClassRectorTest extends AbstractRectorTestCase { - public function test(): void + /** + * @dataProvider provideData() + */ + public function test(SmartFileInfo $fileInfo): void { - $fixtureFileInfo = new SmartFileInfo(__DIR__ . '/Fixture/fixture.php.inc'); - $this->doTestFileInfo($fixtureFileInfo); + $this->doTestFileInfo($fileInfo); + } + + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); } protected function getRectorClass(): string diff --git a/rules/nette-to-symfony/src/Rector/ClassMethod/RouterListToControllerAnnotationsRector.php b/rules/nette-to-symfony/src/Rector/ClassMethod/RouterListToControllerAnnotationsRector.php index cedaa5d95922..cb025307d202 100644 --- a/rules/nette-to-symfony/src/Rector/ClassMethod/RouterListToControllerAnnotationsRector.php +++ b/rules/nette-to-symfony/src/Rector/ClassMethod/RouterListToControllerAnnotationsRector.php @@ -11,7 +11,9 @@ use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\ObjectType; +use PHPStan\Type\TypeWithClassName; use Rector\BetterPhpDocParser\ValueObject\PhpDocNode\Symfony\SymfonyRouteTagValueNode; use Rector\BetterPhpDocParser\ValueObjectFactory\PhpDocNode\Symfony\SymfonyRouteTagValueNodeFactory; use Rector\Core\Rector\AbstractRector; @@ -20,7 +22,6 @@ use Rector\NetteToSymfony\Routing\ExplicitRouteAnnotationDecorator; use Rector\NetteToSymfony\ValueObject\RouteInfo; use Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer; -use ReflectionMethod; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -38,14 +39,6 @@ final class RouterListToControllerAnnotationsRector extends AbstractRector */ private const ACTION_RENDER_NAME_MATCHING_REGEX = '#^(action|render)(?.*?$)#sm'; - /** - * Package "nette/application" is required for DEV, might not exist for PROD. - * So access the class throgh the string - * - * @var string - */ - private const ROUTE_LIST_CLASS = 'Nette\Application\Routers\RouteList'; - /** * @var RouteInfoFactory */ @@ -71,21 +64,35 @@ final class RouterListToControllerAnnotationsRector extends AbstractRector */ private $routerObjectTypes = []; + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + /** + * @var ObjectType + */ + private $routeListObjectType; + public function __construct( ExplicitRouteAnnotationDecorator $explicitRouteAnnotationDecorator, ReturnTypeInferer $returnTypeInferer, RouteInfoFactory $routeInfoFactory, - SymfonyRouteTagValueNodeFactory $symfonyRouteTagValueNodeFactory + SymfonyRouteTagValueNodeFactory $symfonyRouteTagValueNodeFactory, + ReflectionProvider $reflectionProvider ) { $this->routeInfoFactory = $routeInfoFactory; $this->returnTypeInferer = $returnTypeInferer; $this->explicitRouteAnnotationDecorator = $explicitRouteAnnotationDecorator; $this->symfonyRouteTagValueNodeFactory = $symfonyRouteTagValueNodeFactory; + $this->reflectionProvider = $reflectionProvider; $this->routerObjectTypes = [ new ObjectType('Nette\Application\IRouter'), new ObjectType('Nette\Routing\Router'), ]; + + $this->routeListObjectType = new ObjectType('Nette\Application\Routers\RouteList'); } public function getRuleDefinition(): RuleDefinition @@ -159,16 +166,12 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - if ($node->stmts === null) { - return null; - } if ($node->stmts === []) { return null; } - $inferedReturnType = $this->returnTypeInferer->inferFunctionLike($node); - $routeListObjectType = new ObjectType(self::ROUTE_LIST_CLASS); - if (! $inferedReturnType->isSuperTypeOf($routeListObjectType)->yes()) { + $inferedReturnType = $this->returnTypeInferer->inferFunctionLike($node); + if (! $inferedReturnType->isSuperTypeOf($this->routeListObjectType)->yes()) { return null; } @@ -244,7 +247,7 @@ private function createRouteInfosFromAssignNodes(array $assignNodes): array // collect annotations and target controllers foreach ($assignNodes as $assignNode) { $routeNameToControllerMethod = $this->routeInfoFactory->createFromNode($assignNode->expr); - if (! $routeNameToControllerMethod instanceof \Rector\NetteToSymfony\ValueObject\RouteInfo) { + if (! $routeNameToControllerMethod instanceof RouteInfo) { continue; } @@ -307,17 +310,25 @@ private function isRouteStaticCallMatch(StaticCall $staticCall): bool return false; } - if (! method_exists($className, $methodName)) { + if (! $this->reflectionProvider->hasClass($className)) { return false; } - $reflectionMethod = new ReflectionMethod($className, $methodName); - if ($reflectionMethod->getReturnType() === null) { + $classReflection = $this->reflectionProvider->getClass($className); + if (! $classReflection->hasMethod($methodName)) { return false; } - $staticCallReturnType = (string) $reflectionMethod->getReturnType(); - return is_a($staticCallReturnType, 'Nette\Application\IRouter', true); + $reflectionMethod = $classReflection->getNativeMethod($methodName); + $parametersAcceptor = $reflectionMethod->getVariants()[0]; + + $returnType = $parametersAcceptor->getReturnType(); + + if ($returnType instanceof TypeWithClassName) { + return is_a($returnType->getClassName(), 'Nette\Application\IRouter', true); + } + + return false; } private function shouldSkipClassMethod(ClassMethod $classMethod): bool diff --git a/rules/nette-to-symfony/src/Rector/MethodCall/FromHttpRequestGetHeaderToHeadersGetRector.php b/rules/nette-to-symfony/src/Rector/MethodCall/FromHttpRequestGetHeaderToHeadersGetRector.php index c7b079c1bf93..4fa3f8b181eb 100644 --- a/rules/nette-to-symfony/src/Rector/MethodCall/FromHttpRequestGetHeaderToHeadersGetRector.php +++ b/rules/nette-to-symfony/src/Rector/MethodCall/FromHttpRequestGetHeaderToHeadersGetRector.php @@ -12,7 +12,6 @@ use PHPStan\Type\ObjectType; use Rector\Core\NodeManipulator\ClassMethodManipulator; use Rector\Core\Rector\AbstractRector; -use Symfony\Component\HttpFoundation\Request; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -90,7 +89,7 @@ public function refactor(Node $node): ?Node $requestName = $this->classMethodManipulator->addMethodParameterIfMissing( $node, - Request::class, + new ObjectType('Symfony\Component\HttpFoundation\Request'), ['request', 'symfonyRequest'] ); diff --git a/rules/nette-to-symfony/src/Route/RouteInfoFactory.php b/rules/nette-to-symfony/src/Route/RouteInfoFactory.php index fab896c10e8e..c0181dc4544b 100644 --- a/rules/nette-to-symfony/src/Route/RouteInfoFactory.php +++ b/rules/nette-to-symfony/src/Route/RouteInfoFactory.php @@ -11,6 +11,7 @@ use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Scalar\String_; use PhpParser\Node\Stmt\Class_; +use PHPStan\Reflection\ReflectionProvider; use Rector\Core\PhpParser\Node\Value\ValueResolver; use Rector\NetteToSymfony\ValueObject\RouteInfo; use Rector\NodeCollector\NodeCollector\NodeRepository; @@ -33,14 +34,21 @@ final class RouteInfoFactory */ private $nodeRepository; + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + public function __construct( NodeNameResolver $nodeNameResolver, NodeRepository $nodeRepository, - ValueResolver $valueResolver + ValueResolver $valueResolver, + ReflectionProvider $reflectionProvider ) { $this->nodeNameResolver = $nodeNameResolver; $this->valueResolver = $valueResolver; $this->nodeRepository = $nodeRepository; + $this->reflectionProvider = $reflectionProvider; } public function createFromNode(Node $node): ?RouteInfo @@ -139,11 +147,12 @@ private function createForClassConstFetch(Node $node, array $methods, string $ro return null; } - if (! class_exists($presenterClass)) { + if (! $this->reflectionProvider->hasClass($presenterClass)) { return null; } - if (method_exists($presenterClass, 'run')) { + $classReflection = $this->reflectionProvider->getClass($presenterClass); + if ($classReflection->hasMethod('run')) { return new RouteInfo($presenterClass, 'run', $routePath, $methods); } } @@ -178,17 +187,22 @@ private function createForString(String_ $string, string $routePath): ?RouteInfo return null; } - $methodName = null; - if (method_exists($controllerClass, 'render' . ucfirst($method))) { - $methodName = 'render' . ucfirst($method); - } elseif (method_exists($controllerClass, 'action' . ucfirst($method))) { - $methodName = 'action' . ucfirst($method); + if (! $this->reflectionProvider->hasClass($controllerClass)) { + return null; } - if ($methodName === null) { - return null; + $controllerClassReflection = $this->reflectionProvider->getClass($controllerClass); + + $renderMethodName = 'render' . ucfirst($method); + if ($controllerClassReflection->hasMethod($renderMethodName)) { + return new RouteInfo($controllerClass, $renderMethodName, $routePath, []); + } + + $actionMethodName = 'action' . ucfirst($method); + if ($controllerClassReflection->hasMethod($actionMethodName)) { + return new RouteInfo($controllerClass, $actionMethodName, $routePath, []); } - return new RouteInfo($controllerClass, $methodName, $routePath, []); + return null; } } diff --git a/rules/nette/src/NodeAnalyzer/NetteClassAnalyzer.php b/rules/nette/src/NodeAnalyzer/NetteClassAnalyzer.php index a359d09bf687..546c92cdec9c 100644 --- a/rules/nette/src/NodeAnalyzer/NetteClassAnalyzer.php +++ b/rules/nette/src/NodeAnalyzer/NetteClassAnalyzer.php @@ -38,6 +38,10 @@ public function isInComponent(Node $node): bool return false; } + if (! $this->nodeTypeResolver->isObjectType($class, new ObjectType('Nette\Application\UI\Control'))) { + return false; + } + return ! $this->nodeTypeResolver->isObjectType($class, new ObjectType('Nette\Application\UI\Presenter')); } } diff --git a/rules/nette/src/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector.php b/rules/nette/src/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector.php index 0612709dc178..0f0abdfef90a 100644 --- a/rules/nette/src/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector.php +++ b/rules/nette/src/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector.php @@ -9,9 +9,10 @@ use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Expr\Variable; use PhpParser\Node\Param; -use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; +use PHPStan\Analyser\Scope; use PHPStan\Type\ObjectType; +use Rector\Core\Exception\ShouldNotHappenException; use Rector\Core\Rector\AbstractRector; use Rector\Core\ValueObject\MethodName; use Rector\Nette\NodeAnalyzer\StaticCallAnalyzer; @@ -54,6 +55,11 @@ final class RemoveParentAndNameFromComponentConstructorRector extends AbstractRe */ private $paramFinder; + /** + * @var ObjectType + */ + private $controlObjectType; + public function __construct( ParamFinder $paramFinder, StaticCallAnalyzer $staticCallAnalyzer, @@ -62,6 +68,8 @@ public function __construct( $this->staticCallAnalyzer = $staticCallAnalyzer; $this->methodReflectionProvider = $methodReflectionProvider; $this->paramFinder = $paramFinder; + + $this->controlObjectType = new ObjectType('Nette\Application\UI\Control'); } public function getRuleDefinition(): RuleDefinition @@ -197,16 +205,22 @@ private function refactorNew(New_ $new): New_ private function isInsideNetteComponentClass(Node $node): bool { - $classLike = $node->getAttribute(AttributeKey::CLASS_NODE); - if (! $classLike instanceof Class_) { + $scope = $node->getAttribute(AttributeKey::SCOPE); + if (! $scope instanceof Scope) { return false; } - if ($this->isObjectType($classLike, new ObjectType('Nette\Application\UI\Presenter'))) { + $classReflection = $scope->getClassReflection(); + if ($classReflection === null) { + throw new ShouldNotHappenException(); + } + + // presenter is not a component + if ($classReflection->isSubclassOf('Nette\Application\UI\Presenter')) { return false; } - return $this->isObjectType($classLike, new ObjectType('Nette\Application\UI\Control')); + return $classReflection->isSubclassOf($this->controlObjectType->getClassName()); } private function removeClassMethodParams(ClassMethod $classMethod): ClassMethod diff --git a/rules/nette/tests/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector/Fixture/do_not_remove_future_use_both.php.inc b/rules/nette/tests/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector/Fixture/do_not_remove_future_use_both.php.inc deleted file mode 100644 index e11779f76bd9..000000000000 --- a/rules/nette/tests/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector/Fixture/do_not_remove_future_use_both.php.inc +++ /dev/null @@ -1,50 +0,0 @@ -parent = $parent; - $this->name = $name; - $this->value = $value; - } -} - -?> ------ -parent = $parent; - $this->name = $name; - $this->value = $value; - } -} - -?> diff --git a/rules/nette/tests/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector/Fixture/do_not_remove_future_use_name.php.inc b/rules/nette/tests/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector/Fixture/do_not_remove_future_use_name.php.inc index 999f74c29989..6c9741dca272 100644 --- a/rules/nette/tests/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector/Fixture/do_not_remove_future_use_name.php.inc +++ b/rules/nette/tests/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector/Fixture/do_not_remove_future_use_name.php.inc @@ -5,16 +5,16 @@ namespace Rector\Nette\Tests\Rector\ClassMethod\RemoveParentAndNameFromComponent use Nette\Application\UI\Control; use Nette\ComponentModel\IContainer; -class DoNotRemoveFutureUseName extends Control +final class DoNotRemoveFutureUseName extends Control { - private $name; + private $customName; private $value; public function __construct(IContainer $parent = null, $name = null, int $value) { parent::__construct($parent, $name); - $this->name = $name; + $this->customName = $name; $this->value = $value; } } @@ -28,15 +28,15 @@ namespace Rector\Nette\Tests\Rector\ClassMethod\RemoveParentAndNameFromComponent use Nette\Application\UI\Control; use Nette\ComponentModel\IContainer; -class DoNotRemoveFutureUseName extends Control +final class DoNotRemoveFutureUseName extends Control { - private $name; + private $customName; private $value; public function __construct($name = null, int $value) { - $this->name = $name; + $this->customName = $name; $this->value = $value; } } diff --git a/rules/php-spec-to-phpunit/src/PHPUnitTypeDeclarationDecorator.php b/rules/php-spec-to-phpunit/src/PHPUnitTypeDeclarationDecorator.php index cdb8bdfc723c..cd1089b85eac 100644 --- a/rules/php-spec-to-phpunit/src/PHPUnitTypeDeclarationDecorator.php +++ b/rules/php-spec-to-phpunit/src/PHPUnitTypeDeclarationDecorator.php @@ -6,18 +6,28 @@ use PhpParser\Node\Identifier; use PhpParser\Node\Stmt\ClassMethod; +use PHPStan\Reflection\ReflectionProvider; use Rector\Core\ValueObject\MethodName; use Rector\Testing\PHPUnit\StaticPHPUnitEnvironment; -use ReflectionMethod; /** * Decorate setUp() and tearDown() with "void" when local TestClass class uses them */ final class PHPUnitTypeDeclarationDecorator { + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + public function decorate(ClassMethod $classMethod): void { - if (! class_exists('PHPUnit\Framework\TestCase')) { + if (! $this->reflectionProvider->hasClass('PHPUnit\Framework\TestCase')) { return; } @@ -26,7 +36,10 @@ public function decorate(ClassMethod $classMethod): void return; } - $reflectionMethod = new ReflectionMethod('PHPUnit\Framework\TestCase', MethodName::SET_UP); + $classReflection = $this->reflectionProvider->getClass('PHPUnit\Framework\TestCase'); + $nativeClassReflection = $classReflection->getNativeReflection(); + + $reflectionMethod = $nativeClassReflection->getMethod(MethodName::SET_UP); if (! $reflectionMethod->hasReturnType()) { return; } diff --git a/rules/php52/tests/Rector/Switch_/ContinueToBreakInSwitchRector/ContinueToBreakInSwitchRectorTest.php b/rules/php52/tests/Rector/Switch_/ContinueToBreakInSwitchRector/ContinueToBreakInSwitchRectorTest.php index d1f826f9110b..25f738eca90d 100644 --- a/rules/php52/tests/Rector/Switch_/ContinueToBreakInSwitchRector/ContinueToBreakInSwitchRectorTest.php +++ b/rules/php52/tests/Rector/Switch_/ContinueToBreakInSwitchRector/ContinueToBreakInSwitchRectorTest.php @@ -16,7 +16,7 @@ final class ContinueToBreakInSwitchRectorTest extends AbstractRectorTestCase */ public function test(SmartFileInfo $fileInfo): void { - $this->doTestFileInfoWithoutAutoload($fileInfo); + $this->doTestFileInfo($fileInfo); } public function provideData(): Iterator diff --git a/rules/php53/tests/Rector/AssignRef/ClearReturnNewByReferenceRector/ClearReturnNewByReferenceRectorTest.php b/rules/php53/tests/Rector/AssignRef/ClearReturnNewByReferenceRector/ClearReturnNewByReferenceRectorTest.php index c37fd95776a8..68edcdd34242 100644 --- a/rules/php53/tests/Rector/AssignRef/ClearReturnNewByReferenceRector/ClearReturnNewByReferenceRectorTest.php +++ b/rules/php53/tests/Rector/AssignRef/ClearReturnNewByReferenceRector/ClearReturnNewByReferenceRectorTest.php @@ -17,7 +17,7 @@ final class ClearReturnNewByReferenceRectorTest extends AbstractRectorTestCase */ public function test(SmartFileInfo $fileInfo): void { - $this->doTestFileInfoWithoutAutoload($fileInfo); + $this->doTestFileInfo($fileInfo); } /** diff --git a/rules/php54/tests/Rector/Break_/RemoveZeroBreakContinueRector/RemoveZeroBreakContinueRectorTest.php b/rules/php54/tests/Rector/Break_/RemoveZeroBreakContinueRector/RemoveZeroBreakContinueRectorTest.php index 42ee2d3aa922..fad2a73c03e8 100644 --- a/rules/php54/tests/Rector/Break_/RemoveZeroBreakContinueRector/RemoveZeroBreakContinueRectorTest.php +++ b/rules/php54/tests/Rector/Break_/RemoveZeroBreakContinueRector/RemoveZeroBreakContinueRectorTest.php @@ -17,7 +17,7 @@ final class RemoveZeroBreakContinueRectorTest extends AbstractRectorTestCase public function test(SmartFileInfo $fileInfo): void { // to prevent loading PHP 5.4+ invalid code - $this->doTestFileInfoWithoutAutoload($fileInfo); + $this->doTestFileInfo($fileInfo); } public function provideData(): Iterator diff --git a/rules/php54/tests/Rector/FuncCall/RemoveReferenceFromCallRector/RemoveReferenceFromCallRectorTest.php b/rules/php54/tests/Rector/FuncCall/RemoveReferenceFromCallRector/RemoveReferenceFromCallRectorTest.php index 576736865a6c..1a1361bd7cf1 100644 --- a/rules/php54/tests/Rector/FuncCall/RemoveReferenceFromCallRector/RemoveReferenceFromCallRectorTest.php +++ b/rules/php54/tests/Rector/FuncCall/RemoveReferenceFromCallRector/RemoveReferenceFromCallRectorTest.php @@ -16,7 +16,7 @@ final class RemoveReferenceFromCallRectorTest extends AbstractRectorTestCase */ public function test(SmartFileInfo $fileInfo): void { - $this->doTestFileInfoWithoutAutoload($fileInfo); + $this->doTestFileInfo($fileInfo); } public function provideData(): Iterator diff --git a/rules/php70/src/Rector/Assign/ListSplitStringRector.php b/rules/php70/src/Rector/Assign/ListSplitStringRector.php index 8269a4714f7e..ecdff1185d3b 100644 --- a/rules/php70/src/Rector/Assign/ListSplitStringRector.php +++ b/rules/php70/src/Rector/Assign/ListSplitStringRector.php @@ -45,7 +45,7 @@ public function refactor(Node $node): ?Node return null; } - if (! $this->isStaticType($node->expr, StringType::class)) { + if (! $this->nodeTypeResolver->isStaticType($node->expr, StringType::class)) { return null; } diff --git a/rules/php70/src/Rector/MethodCall/ThisCallOnStaticMethodToStaticCallRector.php b/rules/php70/src/Rector/MethodCall/ThisCallOnStaticMethodToStaticCallRector.php index e6605e2fc7e8..8095462838ea 100644 --- a/rules/php70/src/Rector/MethodCall/ThisCallOnStaticMethodToStaticCallRector.php +++ b/rules/php70/src/Rector/MethodCall/ThisCallOnStaticMethodToStaticCallRector.php @@ -7,12 +7,12 @@ use PhpParser\Node; use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Stmt\Class_; +use PHPStan\Reflection\Php\PhpMethodReflection; use PHPStan\Type\ObjectType; use Rector\Core\Rector\AbstractRector; use Rector\NodeCollector\Reflection\MethodReflectionProvider; use Rector\NodeCollector\StaticAnalyzer; use Rector\NodeTypeResolver\Node\AttributeKey; -use ReflectionMethod; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -130,12 +130,14 @@ private function resolveClassSelf(MethodCall $methodCall): string } $methodReflection = $this->methodReflectionProvider->provideByMethodCall($methodCall); - if (! $methodReflection instanceof ReflectionMethod) { + if (! $methodReflection instanceof PhpMethodReflection) { return 'static'; } + if (! $methodReflection->isPrivate()) { return 'static'; } + return 'self'; } } diff --git a/rules/php70/src/Rector/StaticCall/StaticCallOnNonStaticToInstanceCallRector.php b/rules/php70/src/Rector/StaticCall/StaticCallOnNonStaticToInstanceCallRector.php index 1c89ed8310ab..3eae69fcfc23 100644 --- a/rules/php70/src/Rector/StaticCall/StaticCallOnNonStaticToInstanceCallRector.php +++ b/rules/php70/src/Rector/StaticCall/StaticCallOnNonStaticToInstanceCallRector.php @@ -11,12 +11,12 @@ use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Stmt\ClassMethod; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\ObjectType; use Rector\Core\NodeManipulator\ClassMethodManipulator; use Rector\Core\Rector\AbstractRector; use Rector\NodeCollector\StaticAnalyzer; use Rector\NodeTypeResolver\Node\AttributeKey; -use ReflectionClass; use ReflectionMethod; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -41,10 +41,19 @@ final class StaticCallOnNonStaticToInstanceCallRector extends AbstractRector */ private $staticAnalyzer; - public function __construct(ClassMethodManipulator $classMethodManipulator, StaticAnalyzer $staticAnalyzer) - { + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct( + ClassMethodManipulator $classMethodManipulator, + StaticAnalyzer $staticAnalyzer, + ReflectionProvider $reflectionProvider + ) { $this->classMethodManipulator = $classMethodManipulator; $this->staticAnalyzer = $staticAnalyzer; + $this->reflectionProvider = $reflectionProvider; } public function getRuleDefinition(): RuleDefinition @@ -172,22 +181,23 @@ private function shouldSkip(string $methodName, string $className, StaticCall $s private function isInstantiable(string $className): bool { - if (! class_exists($className)) { + if (! $this->reflectionProvider->hasClass($className)) { return false; } - $reflectionClass = new ReflectionClass($className); - $classConstructorReflection = $reflectionClass->getConstructor(); + $classReflection = $this->reflectionProvider->getClass($className); + $nativeClassReflection = $classReflection->getNativeReflection(); - if (! $classConstructorReflection instanceof ReflectionMethod) { + $constructorMethodReflection = $nativeClassReflection->getConstructor(); + if (! $constructorMethodReflection instanceof ReflectionMethod) { return true; } - if (! $classConstructorReflection->isPublic()) { + if (! $constructorMethodReflection->isPublic()) { return false; } // required parameters in constructor, nothing we can do - return ! (bool) $classConstructorReflection->getNumberOfRequiredParameters(); + return ! (bool) $constructorMethodReflection->getNumberOfRequiredParameters(); } } diff --git a/rules/php70/tests/Rector/Break_/BreakNotInLoopOrSwitchToReturnRector/BreakNotInLoopOrSwitchToReturnRectorTest.php b/rules/php70/tests/Rector/Break_/BreakNotInLoopOrSwitchToReturnRector/BreakNotInLoopOrSwitchToReturnRectorTest.php index 2b0a662ce8e2..f097fb21b14a 100644 --- a/rules/php70/tests/Rector/Break_/BreakNotInLoopOrSwitchToReturnRector/BreakNotInLoopOrSwitchToReturnRectorTest.php +++ b/rules/php70/tests/Rector/Break_/BreakNotInLoopOrSwitchToReturnRector/BreakNotInLoopOrSwitchToReturnRectorTest.php @@ -16,7 +16,7 @@ final class BreakNotInLoopOrSwitchToReturnRectorTest extends AbstractRectorTestC */ public function test(SmartFileInfo $fileInfo): void { - $this->doTestFileInfoWithoutAutoload($fileInfo); + $this->doTestFileInfo($fileInfo); } public function provideData(): Iterator diff --git a/rules/php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Fixture/array_callable.php.inc b/rules/php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Fixture/array_callable.php.inc deleted file mode 100644 index 4c211589e6eb..000000000000 --- a/rules/php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Fixture/array_callable.php.inc +++ /dev/null @@ -1,51 +0,0 @@ - ------ - diff --git a/rules/php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Fixture/method_calls.php.inc b/rules/php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Fixture/method_calls.php.inc index cb438a4bde49..4fa36e94f167 100644 --- a/rules/php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Fixture/method_calls.php.inc +++ b/rules/php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Fixture/method_calls.php.inc @@ -2,63 +2,39 @@ namespace Rector\Php70\Tests\Rector\FuncCall\NonVariableToVariableOnFunctionCallRector\Fixture; -class Child -{ - public function bar(&$bar) {} -} +use Rector\Php70\Tests\Rector\FuncCall\NonVariableToVariableOnFunctionCallRector\Source\VariousCallsClass; -class AClass +final class MethodCalls { - public static function staticMethod(&$bar) {} - - public function baz(&$bar) {} - - public function child(): Child + public function run() { - return new Child(); + VariousCallsClass::staticMethod(bar()); + $aClass = new VariousCallsClass(); + $aClass->baz(baz()); + $aClass->child()->bar(bar()); } } -function methodCalls() -{ - AClass::staticMethod(bar()); - $aClass = new AClass(); - $aClass->baz(baz()); - $aClass->child()->bar(bar()); -} - ?> ----- baz($baz); + $bar2 = bar(); + $aClass->child()->bar($bar2); } } -function methodCalls() -{ - $bar = bar(); - AClass::staticMethod($bar); - $aClass = new AClass(); - $baz = baz(); - $aClass->baz($baz); - $bar2 = bar(); - $aClass->child()->bar($bar2); -} - ?> diff --git a/rules/php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Fixture/some_array_callable.php.inc b/rules/php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Fixture/some_array_callable.php.inc new file mode 100644 index 000000000000..8495238a6f39 --- /dev/null +++ b/rules/php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Fixture/some_array_callable.php.inc @@ -0,0 +1,49 @@ + +----- + diff --git a/rules/php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Source/ArrayCallable.php b/rules/php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Source/ArrayCallable.php new file mode 100644 index 000000000000..201082023e87 --- /dev/null +++ b/rules/php70/tests/Rector/FuncCall/NonVariableToVariableOnFunctionCallRector/Source/ArrayCallable.php @@ -0,0 +1,16 @@ +doTestFileInfoWithoutAutoload($fileInfo); + $this->doTestFileInfo($fileInfo); } public function provideData(): Iterator diff --git a/rules/php70/tests/Rector/Switch_/ReduceMultipleDefaultSwitchRector/ReduceMultipleDefaultSwitchRectorTest.php b/rules/php70/tests/Rector/Switch_/ReduceMultipleDefaultSwitchRector/ReduceMultipleDefaultSwitchRectorTest.php index 4cb419a0348e..913d409a5fba 100644 --- a/rules/php70/tests/Rector/Switch_/ReduceMultipleDefaultSwitchRector/ReduceMultipleDefaultSwitchRectorTest.php +++ b/rules/php70/tests/Rector/Switch_/ReduceMultipleDefaultSwitchRector/ReduceMultipleDefaultSwitchRectorTest.php @@ -16,7 +16,7 @@ final class ReduceMultipleDefaultSwitchRectorTest extends AbstractRectorTestCase */ public function test(SmartFileInfo $fileInfo): void { - $this->doTestFileInfoWithoutAutoload($fileInfo); + $this->doTestFileInfo($fileInfo); } public function provideData(): Iterator diff --git a/rules/php71/src/NodeAnalyzer/CountableAnalyzer.php b/rules/php71/src/NodeAnalyzer/CountableAnalyzer.php index 7fb154a355de..c0ca79622c0a 100644 --- a/rules/php71/src/NodeAnalyzer/CountableAnalyzer.php +++ b/rules/php71/src/NodeAnalyzer/CountableAnalyzer.php @@ -7,11 +7,12 @@ use PhpParser\Node\Expr; use PhpParser\Node\Expr\Array_; use PhpParser\Node\Expr\PropertyFetch; +use PhpParser\Node\Stmt; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\TypeWithClassName; use PHPStan\Type\UnionType; use Rector\NodeNameResolver\NodeNameResolver; use Rector\NodeTypeResolver\NodeTypeResolver; -use ReflectionClass; final class CountableAnalyzer { @@ -25,10 +26,19 @@ final class CountableAnalyzer */ private $nodeNameResolver; - public function __construct(NodeTypeResolver $nodeTypeResolver, NodeNameResolver $nodeNameResolver) - { + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct( + NodeTypeResolver $nodeTypeResolver, + NodeNameResolver $nodeNameResolver, + ReflectionProvider $reflectionProvider + ) { $this->nodeTypeResolver = $nodeTypeResolver; $this->nodeNameResolver = $nodeNameResolver; + $this->reflectionProvider = $reflectionProvider; } public function isCastableArrayType(Expr $expr): bool @@ -52,7 +62,7 @@ public function isCastableArrayType(Expr $expr): bool return false; } - if (is_a($callerObjectType->getClassName(), 'PhpParser\Node\Stmt', true)) { + if (is_a($callerObjectType->getClassName(), Stmt::class, true)) { return false; } @@ -61,8 +71,10 @@ public function isCastableArrayType(Expr $expr): bool } // this must be handled reflection, as PHPStan ReflectionProvider does not provide default values for properties in any way - $reflectionClass = new ReflectionClass($callerObjectType->getClassName()); - $propertiesDefaults = $reflectionClass->getDefaultProperties(); + + $reflectionClass = $this->reflectionProvider->getClass($callerObjectType->getClassName()); + $nativeReflectionClass = $reflectionClass->getNativeReflection(); + $propertiesDefaults = $nativeReflectionClass->getDefaultProperties(); if (! array_key_exists($propertyName, $propertiesDefaults)) { return false; diff --git a/rules/php71/src/Rector/BooleanOr/IsIterableRector.php b/rules/php71/src/Rector/BooleanOr/IsIterableRector.php index eaab7857d73b..96342f0541b5 100644 --- a/rules/php71/src/Rector/BooleanOr/IsIterableRector.php +++ b/rules/php71/src/Rector/BooleanOr/IsIterableRector.php @@ -6,6 +6,8 @@ use PhpParser\Node; use PhpParser\Node\Expr\BinaryOp\BooleanOr; +use PhpParser\Node\Name; +use PHPStan\Reflection\ReflectionProvider; use Rector\Core\Rector\AbstractRector; use Rector\Core\ValueObject\PhpVersionFeature; use Rector\Php71\IsArrayAndDualCheckToAble; @@ -22,9 +24,17 @@ final class IsIterableRector extends AbstractRector */ private $isArrayAndDualCheckToAble; - public function __construct(IsArrayAndDualCheckToAble $isArrayAndDualCheckToAble) - { + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct( + IsArrayAndDualCheckToAble $isArrayAndDualCheckToAble, + ReflectionProvider $reflectionProvider + ) { $this->isArrayAndDualCheckToAble = $isArrayAndDualCheckToAble; + $this->reflectionProvider = $reflectionProvider; } public function getRuleDefinition(): RuleDefinition @@ -56,7 +66,7 @@ public function refactor(Node $node): ?Node private function shouldSkip(): bool { - if (function_exists('is_iterable')) { + if ($this->reflectionProvider->hasFunction(new Name('is_iterable'), null)) { return false; } diff --git a/rules/php71/src/Rector/FuncCall/CountOnNullRector.php b/rules/php71/src/Rector/FuncCall/CountOnNullRector.php index a786001977b1..37cfa5f4c9a3 100644 --- a/rules/php71/src/Rector/FuncCall/CountOnNullRector.php +++ b/rules/php71/src/Rector/FuncCall/CountOnNullRector.php @@ -109,7 +109,7 @@ public function refactor(Node $node): ?Node return $this->castToArray($countedNode, $node); } - if ($this->nodeTypeResolver->isNullableType($countedNode) || $this->isStaticType( + if ($this->nodeTypeResolver->isNullableType($countedNode) || $this->nodeTypeResolver->isStaticType( $countedNode, NullType::class )) { diff --git a/rules/php71/src/Rector/FuncCall/RemoveExtraParametersRector.php b/rules/php71/src/Rector/FuncCall/RemoveExtraParametersRector.php index 661b48911aed..de6f6c297a6c 100644 --- a/rules/php71/src/Rector/FuncCall/RemoveExtraParametersRector.php +++ b/rules/php71/src/Rector/FuncCall/RemoveExtraParametersRector.php @@ -104,6 +104,7 @@ private function shouldSkip(Node $node): bool $this->callReflectionResolver->resolveCall($node), $node ); + if (! $parametersAcceptor instanceof ParametersAcceptor) { return true; } diff --git a/rules/php72/src/Rector/FuncCall/GetClassOnNullRector.php b/rules/php72/src/Rector/FuncCall/GetClassOnNullRector.php index 612132a621a5..1e2e0f368356 100644 --- a/rules/php72/src/Rector/FuncCall/GetClassOnNullRector.php +++ b/rules/php72/src/Rector/FuncCall/GetClassOnNullRector.php @@ -87,7 +87,7 @@ public function refactor(Node $node): ?Node return null; } - if (! $this->nodeTypeResolver->isNullableType($firstArgValue) && ! $this->isStaticType( + if (! $this->nodeTypeResolver->isNullableType($firstArgValue) && ! $this->nodeTypeResolver->isStaticType( $firstArgValue, NullType::class )) { diff --git a/rules/php73/src/Rector/BooleanOr/IsCountableRector.php b/rules/php73/src/Rector/BooleanOr/IsCountableRector.php index 6f9384e05992..856fe247a28c 100644 --- a/rules/php73/src/Rector/BooleanOr/IsCountableRector.php +++ b/rules/php73/src/Rector/BooleanOr/IsCountableRector.php @@ -6,6 +6,8 @@ use PhpParser\Node; use PhpParser\Node\Expr\BinaryOp\BooleanOr; +use PhpParser\Node\Name; +use PHPStan\Reflection\ReflectionProvider; use Rector\Core\Rector\AbstractRector; use Rector\Core\ValueObject\PhpVersionFeature; use Rector\Php71\IsArrayAndDualCheckToAble; @@ -22,9 +24,17 @@ final class IsCountableRector extends AbstractRector */ private $isArrayAndDualCheckToAble; - public function __construct(IsArrayAndDualCheckToAble $isArrayAndDualCheckToAble) - { + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct( + IsArrayAndDualCheckToAble $isArrayAndDualCheckToAble, + ReflectionProvider $reflectionProvider + ) { $this->isArrayAndDualCheckToAble = $isArrayAndDualCheckToAble; + $this->reflectionProvider = $reflectionProvider; } public function getRuleDefinition(): RuleDefinition @@ -67,7 +77,7 @@ public function refactor(Node $node): ?Node private function shouldSkip(): bool { - if (function_exists('is_countable')) { + if ($this->reflectionProvider->hasFunction(new Name('is_countable'), null)) { return false; } diff --git a/rules/php73/src/Rector/FuncCall/ArrayKeyFirstLastRector.php b/rules/php73/src/Rector/FuncCall/ArrayKeyFirstLastRector.php index a115629036cf..44d82e298ad6 100644 --- a/rules/php73/src/Rector/FuncCall/ArrayKeyFirstLastRector.php +++ b/rules/php73/src/Rector/FuncCall/ArrayKeyFirstLastRector.php @@ -7,6 +7,7 @@ use PhpParser\Node; use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Name; +use PHPStan\Reflection\ReflectionProvider; use Rector\Core\Rector\AbstractRector; use Rector\Core\ValueObject\PhpVersionFeature; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; @@ -17,6 +18,7 @@ * * This needs to removed 1 floor above, because only nodes in arrays can be removed why traversing, * see https://github.com/nikic/PHP-Parser/issues/389 + * * @see \Rector\Php73\Tests\Rector\FuncCall\ArrayKeyFirstLastRector\ArrayKeyFirstLastRectorTest */ final class ArrayKeyFirstLastRector extends AbstractRector @@ -39,6 +41,16 @@ final class ArrayKeyFirstLastRector extends AbstractRector 'end' => self::ARRAY_KEY_LAST, ]; + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + public function getRuleDefinition(): RuleDefinition { return new RuleDefinition( @@ -128,6 +140,11 @@ private function shouldSkip(FuncCall $funcCall): bool if ($this->isAtLeastPhpVersion(PhpVersionFeature::ARRAY_KEY_FIRST_LAST)) { return false; } - return ! (function_exists(self::ARRAY_KEY_FIRST) && function_exists(self::ARRAY_KEY_LAST)); + + if (! $this->reflectionProvider->hasFunction(new Name(self::ARRAY_KEY_FIRST), null)) { + return true; + } + + return ! $this->reflectionProvider->hasFunction(new Name(self::ARRAY_KEY_LAST), null); } } diff --git a/rules/php73/src/Rector/FuncCall/StringifyStrNeedlesRector.php b/rules/php73/src/Rector/FuncCall/StringifyStrNeedlesRector.php index 08e772216c20..ed4c3f171062 100644 --- a/rules/php73/src/Rector/FuncCall/StringifyStrNeedlesRector.php +++ b/rules/php73/src/Rector/FuncCall/StringifyStrNeedlesRector.php @@ -80,12 +80,11 @@ public function refactor(Node $node): ?Node // is argument string? $needleArgNode = $node->args[1]->value; - if ($this->nodeTypeAnalyzer->isStringTypeExpr($needleArgNode)) { return null; } - if ($node->args[1]->value instanceof String_) { + if ($needleArgNode instanceof String_) { return null; } diff --git a/rules/php73/tests/Rector/FuncCall/StringifyStrNeedlesRector/Fixture/skip_already_string.php.inc b/rules/php73/tests/Rector/FuncCall/StringifyStrNeedlesRector/Fixture/skip_already_string.php.inc index 66e5d15f54f2..b6482be9423c 100644 --- a/rules/php73/tests/Rector/FuncCall/StringifyStrNeedlesRector/Fixture/skip_already_string.php.inc +++ b/rules/php73/tests/Rector/FuncCall/StringifyStrNeedlesRector/Fixture/skip_already_string.php.inc @@ -2,15 +2,12 @@ namespace Rector\Php73\Tests\Rector\FuncCall\StringifyStrNeedlesRector\Fixture; +use Rector\Php73\Tests\Rector\FuncCall\StringifyStrNeedlesRector\Source\ReturnsString; + final class SkipAlreadyString { - public function getNeedle(): string - { - return 'needle'; - } - - public function __invoke() + public function run(ReturnsString $returnsString) { - return strpos('needle', (new SkipAlreadyString)->getNeedle()); + return strpos('needle', $returnsString->getString()); } } diff --git a/rules/php73/tests/Rector/FuncCall/StringifyStrNeedlesRector/Source/ReturnsString.php b/rules/php73/tests/Rector/FuncCall/StringifyStrNeedlesRector/Source/ReturnsString.php new file mode 100644 index 000000000000..9c4fc2d48521 --- /dev/null +++ b/rules/php73/tests/Rector/FuncCall/StringifyStrNeedlesRector/Source/ReturnsString.php @@ -0,0 +1,13 @@ +propertyTypeInferer = $propertyTypeInferer; $this->vendorLockResolver = $vendorLockResolver; $this->doctrineTypeAnalyzer = $doctrineTypeAnalyzer; $this->varTagRemover = $varTagRemover; + $this->reflectionProvider = $reflectionProvider; } public function getRuleDefinition(): RuleDefinition @@ -204,7 +211,7 @@ private function shouldSkipNonClassLikeType(Node $node): bool return false; } - return ! ClassExistenceStaticHelper::doesClassLikeExist($typeName); + return ! $this->reflectionProvider->hasClass($typeName); } private function removeDefaultValueForDoctrineCollection(Property $property, Type $propertyType): void diff --git a/rules/php74/tests/Rector/Property/TypedPropertyRector/Fixture/skip_parent_conflicting.php.inc b/rules/php74/tests/Rector/Property/TypedPropertyRector/Fixture/skip_parent_conflicting.php.inc deleted file mode 100644 index f1de5daf6221..000000000000 --- a/rules/php74/tests/Rector/Property/TypedPropertyRector/Fixture/skip_parent_conflicting.php.inc +++ /dev/null @@ -1,23 +0,0 @@ -nodeNameResolver = $nodeNameResolver; $this->valueResolver = $valueResolver; - $this->betterStandardPrinter = $betterStandardPrinter; $this->nodeComparator = $nodeComparator; } diff --git a/rules/php80/src/Rector/Class_/AnnotationToAttributeRector.php b/rules/php80/src/Rector/Class_/AnnotationToAttributeRector.php index 7bcd27114b6e..ec78a1e6dde6 100644 --- a/rules/php80/src/Rector/Class_/AnnotationToAttributeRector.php +++ b/rules/php80/src/Rector/Class_/AnnotationToAttributeRector.php @@ -40,22 +40,28 @@ public function getRuleDefinition(): RuleDefinition return new RuleDefinition('Change annotation to attribute', [ new CodeSample( <<<'CODE_SAMPLE' -use Doctrine\ORM\Attributes as ORM; +use Symfony\Component\Routing\Annotation\Route; -/** - * @ORM\Entity - */ -class SomeClass +class SymfonyRoute { + /** + * @Route("/path", name="action") + */ + public function action() + { + } } CODE_SAMPLE , <<<'CODE_SAMPLE' -use Doctrine\ORM\Attributes as ORM; +use Symfony\Component\Routing\Annotation\Route; -#[ORM\Entity] -class SomeClass +class SymfonyRoute { + #[Route(path: '/path', name: 'action')] + public function action() + { + } } CODE_SAMPLE ), diff --git a/rules/php80/src/Rector/Class_/StringableForToStringRector.php b/rules/php80/src/Rector/Class_/StringableForToStringRector.php index 6a0250455d9d..ab38cc4c53e5 100644 --- a/rules/php80/src/Rector/Class_/StringableForToStringRector.php +++ b/rules/php80/src/Rector/Class_/StringableForToStringRector.php @@ -9,6 +9,7 @@ use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; +use PHPStan\Type\ObjectType; use Rector\Core\NodeManipulator\ClassManipulator; use Rector\Core\Rector\AbstractRector; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; @@ -84,7 +85,7 @@ public function refactor(Node $node): ?Node return null; } - if ($this->classManipulator->hasInterface($node, self::STRINGABLE)) { + if ($this->classManipulator->hasInterface($node, new ObjectType(self::STRINGABLE))) { return null; } diff --git a/rules/phpunit/src/PhpDoc/PhpDocValueToNodeMapper.php b/rules/phpunit/src/PhpDoc/PhpDocValueToNodeMapper.php index 704a671fc292..36cb97ae0041 100644 --- a/rules/phpunit/src/PhpDoc/PhpDocValueToNodeMapper.php +++ b/rules/phpunit/src/PhpDoc/PhpDocValueToNodeMapper.php @@ -7,6 +7,7 @@ use PhpParser\Node\Expr; use PhpParser\Node\Scalar\String_; use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode; +use PHPStan\Reflection\ReflectionProvider; use Rector\Core\PhpParser\Node\NodeFactory; final class PhpDocValueToNodeMapper @@ -16,9 +17,15 @@ final class PhpDocValueToNodeMapper */ private $nodeFactory; - public function __construct(NodeFactory $nodeFactory) + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct(NodeFactory $nodeFactory, ReflectionProvider $reflectionProvider) { $this->nodeFactory = $nodeFactory; + $this->reflectionProvider = $reflectionProvider; } public function mapGenericTagValueNode(GenericTagValueNode $genericTagValueNode): Expr @@ -30,7 +37,7 @@ public function mapGenericTagValueNode(GenericTagValueNode $genericTagValueNode) $reference = ltrim($genericTagValueNode->value, '\\'); - if (class_exists($reference)) { + if ($this->reflectionProvider->hasClass($reference)) { return $this->nodeFactory->createClassConstReference($reference); } diff --git a/rules/phpunit/src/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector.php b/rules/phpunit/src/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector.php index 3f769b873881..c945a044775f 100644 --- a/rules/phpunit/src/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector.php +++ b/rules/phpunit/src/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector.php @@ -179,7 +179,7 @@ private function containsAssertCall(ClassMethod $classMethod): bool $hasDirectAssertCall = $this->hasDirectAssertCall($classMethod); if ($hasDirectAssertCall) { $this->containsAssertCallByClassMethod[$cacheHash] = $hasDirectAssertCall; - return $hasDirectAssertCall; + return true; } // B. look for nested calls diff --git a/rules/phpunit/src/Rector/MethodCall/SpecificAssertContainsWithoutIdentityRector.php b/rules/phpunit/src/Rector/MethodCall/SpecificAssertContainsWithoutIdentityRector.php index adec563ede4a..500f06b51a51 100644 --- a/rules/phpunit/src/Rector/MethodCall/SpecificAssertContainsWithoutIdentityRector.php +++ b/rules/phpunit/src/Rector/MethodCall/SpecificAssertContainsWithoutIdentityRector.php @@ -92,7 +92,7 @@ public function refactor(Node $node): ?Node } //when second argument is string: do nothing - if ($this->isStaticType($node->args[1]->value, StringType::class)) { + if ($this->nodeTypeResolver->isStaticType($node->args[1]->value, StringType::class)) { return null; } //when less then 5 arguments given: do nothing diff --git a/rules/phpunit/src/Rector/MethodCall/WithConsecutiveArgToArrayRector.php b/rules/phpunit/src/Rector/MethodCall/WithConsecutiveArgToArrayRector.php index d7e832d9aabb..cd1148d16400 100644 --- a/rules/phpunit/src/Rector/MethodCall/WithConsecutiveArgToArrayRector.php +++ b/rules/phpunit/src/Rector/MethodCall/WithConsecutiveArgToArrayRector.php @@ -10,13 +10,12 @@ use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\Variable; use PhpParser\Node\Scalar\String_; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\ArrayType; use PHPStan\Type\ObjectType; use Rector\Core\Exception\ShouldNotHappenException; use Rector\Core\NodeManipulator\MethodCallManipulator; use Rector\Core\Rector\AbstractRector; -use ReflectionMethod; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -33,9 +32,15 @@ final class WithConsecutiveArgToArrayRector extends AbstractRector */ private $methodCallManipulator; - public function __construct(MethodCallManipulator $methodCallManipulator) + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct(MethodCallManipulator $methodCallManipulator, ReflectionProvider $reflectionProvider) { $this->methodCallManipulator = $methodCallManipulator; + $this->reflectionProvider = $reflectionProvider; } public function getRuleDefinition(): RuleDefinition @@ -119,7 +124,15 @@ public function refactor(Node $node): ?Node } $mockMethod = $this->inferMockedMethodName($node); - $reflectionMethod = new ReflectionMethod($mockClass, $mockMethod); + + if (! $this->reflectionProvider->hasClass($mockClass)) { + return null; + } + + $classReflection = $this->reflectionProvider->getClass($mockClass); + $classReflection = $classReflection->getNativeReflection(); + + $reflectionMethod = $classReflection->getMethod($mockMethod); $numberOfParameters = $reflectionMethod->getNumberOfParameters(); $values = []; diff --git a/rules/phpunit/src/TestClassResolver/TestClassResolver.php b/rules/phpunit/src/TestClassResolver/TestClassResolver.php index 0761e3cf7f04..3d032b86d11a 100644 --- a/rules/phpunit/src/TestClassResolver/TestClassResolver.php +++ b/rules/phpunit/src/TestClassResolver/TestClassResolver.php @@ -6,6 +6,7 @@ use Nette\Utils\Strings; use PhpParser\Node\Stmt\Class_; +use PHPStan\Reflection\ReflectionProvider; use Rector\NodeNameResolver\NodeNameResolver; /** @@ -28,23 +29,29 @@ final class TestClassResolver */ private $phpUnitTestCaseClassesProvider; + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + public function __construct( NodeNameResolver $nodeNameResolver, - PHPUnitTestCaseClassesProvider $phpUnitTestCaseClassesProvider + PHPUnitTestCaseClassesProvider $phpUnitTestCaseClassesProvider, + ReflectionProvider $reflectionProvider ) { $this->nodeNameResolver = $nodeNameResolver; $this->phpUnitTestCaseClassesProvider = $phpUnitTestCaseClassesProvider; + $this->reflectionProvider = $reflectionProvider; } public function resolveFromClassName(string $className): ?string { // fallback for unit tests that only have extra "Test" suffix - if (class_exists($className . self::TEST)) { + if ($this->reflectionProvider->hasClass($className . self::TEST)) { return $className . self::TEST; } $shortClassName = $this->resolveShortClassName($className); - $testShortClassName = $shortClassName . self::TEST; $phpUnitTestCaseClasses = $this->phpUnitTestCaseClassesProvider->provide(); diff --git a/rules/phpunit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/keep_with_parent_method_assert.php.inc b/rules/phpunit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/keep_with_parent_method_assert.php.inc index dc9de7cbf151..c5519ecef9e1 100644 --- a/rules/phpunit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/keep_with_parent_method_assert.php.inc +++ b/rules/phpunit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/keep_with_parent_method_assert.php.inc @@ -4,7 +4,7 @@ namespace Rector\PHPUnit\Tests\Rector\ClassMethod\AddDoesNotPerformAssertionToNo use Rector\PHPUnit\Tests\Rector\ClassMethod\AddDoesNotPerformAssertionToNonAssertingTestRector\Source\AbstractClassWithAssert; -class KeepWithParentMethodAssert extends AbstractClassWithAssert +final class KeepWithParentMethodAssert extends AbstractClassWithAssert { public function test() { diff --git a/rules/privatization/src/NodeAnalyzer/ClassMethodExternalCallNodeAnalyzer.php b/rules/privatization/src/NodeAnalyzer/ClassMethodExternalCallNodeAnalyzer.php index cc584dbd7ad6..f18041696da7 100644 --- a/rules/privatization/src/NodeAnalyzer/ClassMethodExternalCallNodeAnalyzer.php +++ b/rules/privatization/src/NodeAnalyzer/ClassMethodExternalCallNodeAnalyzer.php @@ -8,6 +8,7 @@ use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\ObjectType; use PHPStan\Type\TypeWithClassName; use Rector\NodeCollector\NodeCollector\NodeRepository; @@ -15,7 +16,6 @@ use Rector\NodeNameResolver\NodeNameResolver; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\NodeTypeResolver\NodeTypeResolver; -use ReflectionMethod; final class ClassMethodExternalCallNodeAnalyzer { @@ -39,16 +39,23 @@ final class ClassMethodExternalCallNodeAnalyzer */ private $nodeRepository; + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + public function __construct( EventSubscriberMethodNamesResolver $eventSubscriberMethodNamesResolver, NodeRepository $nodeRepository, NodeNameResolver $nodeNameResolver, - NodeTypeResolver $nodeTypeResolver + NodeTypeResolver $nodeTypeResolver, + ReflectionProvider $reflectionProvider ) { $this->nodeNameResolver = $nodeNameResolver; $this->nodeTypeResolver = $nodeTypeResolver; $this->eventSubscriberMethodNamesResolver = $eventSubscriberMethodNamesResolver; $this->nodeRepository = $nodeRepository; + $this->reflectionProvider = $reflectionProvider; } public function hasExternalCall(ClassMethod $classMethod): bool @@ -98,9 +105,16 @@ public function getExternalCalls(ClassMethod $classMethod, array $methodCalls = return $methodCalls; } - /** @var string $methodName */ + if (! $this->reflectionProvider->hasClass($callerType->getClassName())) { + return $methodCalls; + } + $methodName = $this->nodeNameResolver->getName($classMethod); - $reflectionMethod = new ReflectionMethod($nodeClassName, $methodName); + + $classReflection = $this->reflectionProvider->getClass($callerType->getClassName()); + $scope = $methodCall->getAttribute(AttributeKey::SCOPE); + $reflectionMethod = $classReflection->getMethod($methodName, $scope); + // parent class name, must be at least protected $reflectionClass = $reflectionMethod->getDeclaringClass(); if ($reflectionClass->getName() !== $nodeClassName) { diff --git a/rules/privatization/src/NodeManipulator/VisibilityManipulator.php b/rules/privatization/src/NodeManipulator/VisibilityManipulator.php index d0d0799bb4ed..d18b8d711958 100644 --- a/rules/privatization/src/NodeManipulator/VisibilityManipulator.php +++ b/rules/privatization/src/NodeManipulator/VisibilityManipulator.php @@ -12,6 +12,7 @@ use PhpParser\Node\Stmt\Property; use Rector\Core\Exception\InvalidNodeTypeException; use Rector\Core\ValueObject\Visibility; +use Rector\Privatization\ValueObject\ConstantVisibility; use Webmozart\Assert\Assert; final class VisibilityManipulator @@ -142,6 +143,19 @@ public function removeFinal(Class_ $class): void $class->flags -= Class_::MODIFIER_FINAL; } + public function makeClassConstPrivateOrWeaker( + ClassConst $classConst, + ?ConstantVisibility $parentConstantVisibility + ): void { + if ($parentConstantVisibility !== null && $parentConstantVisibility->isProtected()) { + $this->makeProtected($classConst); + } elseif ($parentConstantVisibility !== null && $parentConstantVisibility->isPrivate() && ! $parentConstantVisibility->isProtected()) { + $this->makePrivate($classConst); + } elseif ($parentConstantVisibility === null) { + $this->makePrivate($classConst); + } + } + /** * @param Class_|ClassMethod|Property|ClassConst $node */ diff --git a/rules/privatization/src/Rector/ClassConst/PrivatizeLocalClassConstantRector.php b/rules/privatization/src/Rector/ClassConst/PrivatizeLocalClassConstantRector.php index abd7c74fc52d..8f14f9e34c6c 100644 --- a/rules/privatization/src/Rector/ClassConst/PrivatizeLocalClassConstantRector.php +++ b/rules/privatization/src/Rector/ClassConst/PrivatizeLocalClassConstantRector.php @@ -6,8 +6,12 @@ use PhpParser\Node; use PhpParser\Node\Stmt\ClassConst; +use PhpParser\Node\Stmt\Interface_; +use PHPStan\Analyser\Scope; +use PHPStan\Reflection\ClassReflection; use Rector\BetterPhpDocParser\ValueObject\PhpDocNode\ApiPhpDocTagNode; use Rector\Caching\Contract\Rector\ZeroCacheRectorInterface; +use Rector\Core\Exception\ShouldNotHappenException; use Rector\Core\Rector\AbstractRector; use Rector\Core\ValueObject\PhpVersionFeature; use Rector\NodeTypeResolver\Node\AttributeKey; @@ -100,12 +104,23 @@ public function refactor(Node $node): ?Node /** @var string $class */ $class = $node->getAttribute(AttributeKey::CLASS_NAME); + $scope = $node->getAttribute(AttributeKey::SCOPE); + if (! $scope instanceof Scope) { + return null; + } + + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { + throw new ShouldNotHappenException(); + } + // Remember when we have already processed this constant recursively $node->setAttribute(self::HAS_NEW_ACCESS_LEVEL, true); - $nodeRepositoryFindInterface = $this->nodeRepository->findInterface($class); + + $interface = $this->nodeRepository->findInterface($class); // 0. constants declared in interfaces have to be public - if ($nodeRepositoryFindInterface !== null) { + if ($interface instanceof Interface_) { $this->visibilityManipulator->makePublic($node); return $node; } @@ -121,15 +136,21 @@ public function refactor(Node $node): ?Node return $node; } - $directUseClasses = $this->nodeRepository->findDirectClassConstantFetches($class, $constant); - $indirectUseClasses = $this->nodeRepository->findIndirectClassConstantFetches($class, $constant); + $directUsingClassReflections = $this->nodeRepository->findDirectClassConstantFetches( + $classReflection, + $constant + ); + $indirectUsingClassReflections = $this->nodeRepository->findIndirectClassConstantFetches( + $classReflection, + $constant + ); $this->changeConstantVisibility( $node, - $directUseClasses, - $indirectUseClasses, + $directUsingClassReflections, + $indirectUsingClassReflections, $parentClassConstantVisibility, - $class + $classReflection ); return $node; @@ -176,75 +197,66 @@ private function findParentClassConstantAndRefactorIfPossible(string $class, str // If the constant isn't declared in the parent, it might be declared in the parent's parent } - $parentClassConstantReflection = $this->parentConstantReflectionResolver->resolve($class, $constant); - if (! $parentClassConstantReflection instanceof ReflectionClassConstant) { + $reflectionClassConstant = $this->parentConstantReflectionResolver->resolve($class, $constant); + if (! $reflectionClassConstant instanceof ReflectionClassConstant) { return null; } return new ConstantVisibility( - $parentClassConstantReflection->isPublic(), - $parentClassConstantReflection->isProtected(), - $parentClassConstantReflection->isPrivate() + $reflectionClassConstant->isPublic(), + $reflectionClassConstant->isProtected(), + $reflectionClassConstant->isPrivate() ); } /** - * @param string[] $directUseClasses - * @param string[] $indirectUseClasses + * @param ClassReflection[] $directUsingClassReflections + * @param ClassReflection[] $indirectUsingClassReflections */ private function changeConstantVisibility( ClassConst $classConst, - array $directUseClasses, - array $indirectUseClasses, + array $directUsingClassReflections, + array $indirectUsingClassReflections, ?ConstantVisibility $constantVisibility, - string $class + ClassReflection $classReflection ): void { // 1. is actually never used - if ($directUseClasses === []) { - if ($indirectUseClasses !== [] && $constantVisibility !== null) { - $this->makePrivateOrWeaker($classConst, $constantVisibility); + if ($directUsingClassReflections === []) { + if ($indirectUsingClassReflections !== [] && $constantVisibility !== null) { + $this->visibilityManipulator->makeClassConstPrivateOrWeaker($classConst, $constantVisibility); } return; } // 2. is only local use? → private - if ($directUseClasses === [$class]) { - if ($indirectUseClasses === []) { - $this->makePrivateOrWeaker($classConst, $constantVisibility); + if ($directUsingClassReflections === [$classReflection]) { + if ($indirectUsingClassReflections === []) { + $this->visibilityManipulator->makeClassConstPrivateOrWeaker($classConst, $constantVisibility); } return; } + $usingClassReflections = array_merge($indirectUsingClassReflections, $directUsingClassReflections); + // 3. used by children → protected - if ($this->isUsedByChildrenOnly($directUseClasses, $class)) { + if ($this->isUsedByChildrenOnly($usingClassReflections, $classReflection)) { $this->visibilityManipulator->makeProtected($classConst); } else { $this->visibilityManipulator->makePublic($classConst); } } - private function makePrivateOrWeaker(ClassConst $classConst, ?ConstantVisibility $parentConstantVisibility): void - { - if ($parentConstantVisibility !== null && $parentConstantVisibility->isProtected()) { - $this->visibilityManipulator->makeProtected($classConst); - } elseif ($parentConstantVisibility !== null && $parentConstantVisibility->isPrivate() && ! $parentConstantVisibility->isProtected()) { - $this->visibilityManipulator->makePrivate($classConst); - } elseif ($parentConstantVisibility === null) { - $this->visibilityManipulator->makePrivate($classConst); - } - } - /** - * @param string[] $useClasses + * @param ClassReflection[] $constantUsingClassReflections */ - private function isUsedByChildrenOnly(array $useClasses, string $class): bool + private function isUsedByChildrenOnly(array $constantUsingClassReflections, ClassReflection $classReflection): bool { $isChild = false; - foreach ($useClasses as $useClass) { - if (is_a($useClass, $class, true)) { + foreach ($constantUsingClassReflections as $constantUsingObjectType) { + if ($constantUsingObjectType->isSubclassOf($classReflection->getName())) { $isChild = true; } else { // not a child, must be public diff --git a/rules/privatization/src/Rector/ClassMethod/MakeOnlyUsedByChildrenProtectedRector.php b/rules/privatization/src/Rector/ClassMethod/MakeOnlyUsedByChildrenProtectedRector.php index 407e932cb3a5..7295f2438676 100644 --- a/rules/privatization/src/Rector/ClassMethod/MakeOnlyUsedByChildrenProtectedRector.php +++ b/rules/privatization/src/Rector/ClassMethod/MakeOnlyUsedByChildrenProtectedRector.php @@ -8,12 +8,14 @@ use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; +use PHPStan\Analyser\Scope; +use PHPStan\Reflection\ClassReflection; use PHPStan\Type\ObjectType; +use Rector\Core\Exception\ShouldNotHappenException; use Rector\Core\Rector\AbstractRector; use Rector\FamilyTree\Reflection\FamilyRelationsAnalyzer; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\Privatization\NodeAnalyzer\ClassMethodExternalCallNodeAnalyzer; -use ReflectionClass; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -101,6 +103,16 @@ public function refactor(Node $node): ?Node return null; } + $scope = $node->getAttribute(AttributeKey::SCOPE); + if (! $scope instanceof Scope) { + throw new ShouldNotHappenException(); + } + + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { + throw new ShouldNotHappenException(); + } + $externalCalls = $this->classMethodExternalCallNodeAnalyzer->getExternalCalls($node); if ($externalCalls === []) { return null; @@ -121,7 +133,7 @@ public function refactor(Node $node): ?Node } $methodName = $this->getName($node); - if ($this->isOverriddenInChildClass($className, $methodName)) { + if ($this->isOverriddenInChildClass($classReflection, $methodName)) { return null; } @@ -156,19 +168,19 @@ private function shouldSkip(ClassMethod $classMethod): bool return ! $classMethod->isPublic(); } - private function isOverriddenInChildClass(string $className, string $methodName): bool + private function isOverriddenInChildClass(ClassReflection $classReflection, string $methodName): bool { - $childrenClassNames = $this->familyRelationsAnalyzer->getChildrenOfClass($className); - foreach ($childrenClassNames as $childrenClassName) { - $reflectionClass = new ReflectionClass($childrenClassName); - $isMethodExists = method_exists($childrenClassName, $methodName); - if (! $isMethodExists) { + $childrenClassReflection = $this->familyRelationsAnalyzer->getChildrenOfClassReflection($classReflection); + + foreach ($childrenClassReflection as $childClassReflection) { + if (! $childClassReflection->hasMethod($methodName)) { continue; } - $isMethodInChildrenClass = $reflectionClass->getMethod($methodName) - ->class === $childrenClassName; - if ($isMethodInChildrenClass) { + $methodReflection = $childClassReflection->getNativeMethod($methodName); + $methodDeclaringClass = $methodReflection->getDeclaringClass(); + + if ($methodDeclaringClass->getName() === $childClassReflection->getName()) { return true; } } diff --git a/rules/privatization/src/Rector/ClassMethod/PrivatizeFinalClassMethodRector.php b/rules/privatization/src/Rector/ClassMethod/PrivatizeFinalClassMethodRector.php index 79503c7aaea3..47ebea93add9 100644 --- a/rules/privatization/src/Rector/ClassMethod/PrivatizeFinalClassMethodRector.php +++ b/rules/privatization/src/Rector/ClassMethod/PrivatizeFinalClassMethodRector.php @@ -5,8 +5,9 @@ namespace Rector\Privatization\Rector\ClassMethod; use PhpParser\Node; -use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; +use PHPStan\Analyser\Scope; +use PHPStan\Reflection\ClassReflection; use Rector\Core\Rector\AbstractRector; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\Privatization\VisibilityGuard\ClassMethodVisibilityGuard; @@ -69,12 +70,17 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - $classLike = $node->getAttribute(AttributeKey::CLASS_NODE); - if (! $classLike instanceof Class_) { + $scope = $node->getAttribute(AttributeKey::SCOPE); + if (! $scope instanceof Scope) { return null; } - if (! $classLike->isFinal()) { + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { + return null; + } + + if (! $classReflection->isFinal()) { return null; } @@ -82,11 +88,11 @@ public function refactor(Node $node): ?Node return null; } - if ($this->classMethodVisibilityGuard->isClassMethodVisibilityGuardedByParent($node)) { + if ($this->classMethodVisibilityGuard->isClassMethodVisibilityGuardedByParent($node, $classReflection)) { return null; } - if ($this->classMethodVisibilityGuard->isClassMethodVisibilityGuardedByTrait($node)) { + if ($this->classMethodVisibilityGuard->isClassMethodVisibilityGuardedByTrait($node, $classReflection)) { return null; } diff --git a/rules/privatization/src/Rector/ClassMethod/PrivatizeLocalOnlyMethodRector.php b/rules/privatization/src/Rector/ClassMethod/PrivatizeLocalOnlyMethodRector.php index d9dcf39fae66..2cad3a49ed15 100644 --- a/rules/privatization/src/Rector/ClassMethod/PrivatizeLocalOnlyMethodRector.php +++ b/rules/privatization/src/Rector/ClassMethod/PrivatizeLocalOnlyMethodRector.php @@ -227,7 +227,7 @@ private function shouldSkipClassMethod(ClassMethod $classMethod, PhpDocInfo $php return true; } - if ($this->isName($classMethod, '__*')) { + if ($classMethod->isMagic()) { return true; } diff --git a/rules/privatization/src/Rector/Property/PrivatizeFinalClassPropertyRector.php b/rules/privatization/src/Rector/Property/PrivatizeFinalClassPropertyRector.php index 385063373c02..eb25daa3990a 100644 --- a/rules/privatization/src/Rector/Property/PrivatizeFinalClassPropertyRector.php +++ b/rules/privatization/src/Rector/Property/PrivatizeFinalClassPropertyRector.php @@ -7,6 +7,8 @@ use PhpParser\Node; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\Property; +use PHPStan\Analyser\Scope; +use PHPStan\Reflection\ClassReflection; use Rector\Core\Rector\AbstractRector; use Rector\NodeTypeResolver\Node\AttributeKey; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; @@ -93,31 +95,20 @@ private function isPropertyVisibilityGuardedByParent(Property $property, Class_ return false; } - $parentClasses = $this->getParentClasses($class); + /** @var Scope $scope */ + $scope = $property->getAttribute(AttributeKey::SCOPE); + + /** @var ClassReflection $classReflection */ + $classReflection = $scope->getClassReflection(); + $propertyName = $this->getName($property); - foreach ($parentClasses as $parentClass) { - if (property_exists($parentClass, $propertyName)) { + foreach ($classReflection->getParents() as $parentClassReflection) { + if ($parentClassReflection->hasProperty($propertyName)) { return true; } } return false; } - - /** - * @return string[] - */ - private function getParentClasses(Class_ $class): array - { - /** @var string $className */ - $className = $this->getName($class); - - $classParents = class_parents($className); - if ($classParents === false) { - return []; - } - - return $classParents; - } } diff --git a/rules/privatization/src/Reflection/ClassConstantsResolver.php b/rules/privatization/src/Reflection/ClassConstantsResolver.php index b9c23507873f..5a1c0cb64db4 100644 --- a/rules/privatization/src/Reflection/ClassConstantsResolver.php +++ b/rules/privatization/src/Reflection/ClassConstantsResolver.php @@ -4,7 +4,7 @@ namespace Rector\Privatization\Reflection; -use ReflectionClass; +use PHPStan\Reflection\ReflectionProvider; final class ClassConstantsResolver { @@ -14,7 +14,17 @@ final class ClassConstantsResolver private $cachedConstantNamesToValues = []; /** - * @return array + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + + /** + * @return array */ public function getClassConstantNamesToValues(string $classWithConstants): array { @@ -22,9 +32,14 @@ public function getClassConstantNamesToValues(string $classWithConstants): array return $this->cachedConstantNamesToValues[$classWithConstants]; } - $reflectionClass = new ReflectionClass($classWithConstants); - $constantNamesToValues = $reflectionClass->getConstants(); + if (! $this->reflectionProvider->hasClass($classWithConstants)) { + return []; + } + $classReflection = $this->reflectionProvider->getClass($classWithConstants); + $reflectionClass = $classReflection->getNativeReflection(); + + $constantNamesToValues = $reflectionClass->getConstants(); $this->cachedConstantNamesToValues = $constantNamesToValues; return $constantNamesToValues; diff --git a/rules/privatization/src/Reflection/ParentConstantReflectionResolver.php b/rules/privatization/src/Reflection/ParentConstantReflectionResolver.php index b4960d433cf7..9315c4d3c2d2 100644 --- a/rules/privatization/src/Reflection/ParentConstantReflectionResolver.php +++ b/rules/privatization/src/Reflection/ParentConstantReflectionResolver.php @@ -4,22 +4,39 @@ namespace Rector\Privatization\Reflection; -use ReflectionClass; +use PHPStan\Reflection\ReflectionProvider; use ReflectionClassConstant; final class ParentConstantReflectionResolver { + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + public function resolve(string $class, string $constant): ?ReflectionClassConstant { - $reflectionClass = (new ReflectionClass($class)); - $parentReflectionClass = $reflectionClass->getParentClass(); + if (! $this->reflectionProvider->hasClass($class)) { + return null; + } - while ($parentReflectionClass !== false) { - if ($parentReflectionClass->hasConstant($constant)) { - return $parentReflectionClass->getReflectionConstant($constant); + $classReflection = $this->reflectionProvider->getClass($class); + foreach ($classReflection->getParents() as $parentClassReflection) { + if (! $parentClassReflection->hasConstant($constant)) { + continue; } - $parentReflectionClass = $parentReflectionClass->getParentClass(); + $nativeClassReflection = $parentClassReflection->getNativeReflection(); + + $constantReflection = $nativeClassReflection->getConstant($constant); + if ($constantReflection instanceof ReflectionClassConstant) { + return $constantReflection; + } } return null; diff --git a/rules/privatization/src/VisibilityGuard/ClassMethodVisibilityGuard.php b/rules/privatization/src/VisibilityGuard/ClassMethodVisibilityGuard.php index d7da2de41ea1..03f15cd5b5cb 100644 --- a/rules/privatization/src/VisibilityGuard/ClassMethodVisibilityGuard.php +++ b/rules/privatization/src/VisibilityGuard/ClassMethodVisibilityGuard.php @@ -4,10 +4,9 @@ namespace Rector\Privatization\VisibilityGuard; -use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; +use PHPStan\Reflection\ClassReflection; use Rector\NodeNameResolver\NodeNameResolver; -use Rector\NodeTypeResolver\Node\AttributeKey; final class ClassMethodVisibilityGuard { @@ -21,94 +20,54 @@ public function __construct(NodeNameResolver $nodeNameResolver) $this->nodeNameResolver = $nodeNameResolver; } - public function isClassMethodVisibilityGuardedByParent(ClassMethod $classMethod): bool - { - $classLike = $classMethod->getAttribute(AttributeKey::CLASS_NODE); - if (! $classLike instanceof Class_) { - return false; - } - + public function isClassMethodVisibilityGuardedByParent( + ClassMethod $classMethod, + ClassReflection $classReflection + ): bool { $methodName = $this->nodeNameResolver->getName($classMethod); - $parentClasses = $this->getParentClasses($classLike); - $classInterfaces = $this->getClassInterfaces($classLike); - $classClassLikes = array_merge($parentClasses, $classInterfaces); - - return $this->methodExistsInClasses($classClassLikes, $methodName); - } + /** @var ClassReflection[] $parentClassReflections */ + $parentClassReflections = array_merge($classReflection->getParents(), $classReflection->getInterfaces()); - public function isClassMethodVisibilityGuardedByTrait(ClassMethod $classMethod): bool - { - $classLike = $classMethod->getAttribute(AttributeKey::CLASS_NODE); - if (! $classLike instanceof Class_) { - return false; + foreach ($parentClassReflections as $parentClassReflection) { + if ($parentClassReflection->hasMethod($methodName)) { + return true; + } } - $traits = $this->getParentTraits($classLike); - $methodName = $this->nodeNameResolver->getName($classMethod); - - return $this->methodExistsInClasses($traits, $methodName); + return false; } - /** - * @return string[] - */ - public function getParentTraits(Class_ $class): array - { - /** @var string $className */ - $className = $this->nodeNameResolver->getName($class); - - $traits = class_uses($className); - if ($traits === false) { - return []; - } - - return $traits; - } + public function isClassMethodVisibilityGuardedByTrait( + ClassMethod $classMethod, + ClassReflection $classReflection + ): bool { + $parentTraitReflections = $this->getLocalAndParentTraitReflections($classReflection); - /** - * @return string[] - */ - private function getParentClasses(Class_ $class): array - { - /** @var string $className */ - $className = $this->nodeNameResolver->getName($class); + $methodName = $this->nodeNameResolver->getName($classMethod); - $classParents = class_parents($className); - if ($classParents === false) { - return []; + foreach ($parentTraitReflections as $parentTraitReflection) { + if ($parentTraitReflection->hasMethod($methodName)) { + return true; + } } - return $classParents; + return false; } /** - * @return string[] + * @return ClassReflection[] */ - private function getClassInterfaces(Class_ $class): array + private function getLocalAndParentTraitReflections(ClassReflection $classReflection): array { - /** @var string $className */ - $className = $this->nodeNameResolver->getName($class); + $traitReflections = $classReflection->getTraits(); - $classInterfaces = class_implements($className); - if ($classInterfaces === false) { - return []; - } - - return $classInterfaces; - } - - /** - * @param string[] $classes - */ - private function methodExistsInClasses(array $classes, string $method): bool - { - foreach ($classes as $class) { - if (method_exists($class, $method)) { - return true; + foreach ($classReflection->getParents() as $parentClassReflection) { + foreach ($parentClassReflection->getTraits() as $parentTraitReflection) { + $traitReflections[] = $parentTraitReflection; } } - return false; + return $traitReflections; } } diff --git a/rules/privatization/tests/Rector/ClassConst/PrivatizeLocalClassConstantRector/Fixture/override_protected_constant.php.inc b/rules/privatization/tests/Rector/ClassConst/PrivatizeLocalClassConstantRector/Fixture/override_protected_constant.php.inc deleted file mode 100644 index 0f62c65649f1..000000000000 --- a/rules/privatization/tests/Rector/ClassConst/PrivatizeLocalClassConstantRector/Fixture/override_protected_constant.php.inc +++ /dev/null @@ -1,47 +0,0 @@ - ------ - diff --git a/rules/privatization/tests/Rector/ClassConst/PrivatizeLocalClassConstantRector/Fixture/override_public_constant.php.inc b/rules/privatization/tests/Rector/ClassConst/PrivatizeLocalClassConstantRector/Fixture/override_public_constant.php.inc deleted file mode 100644 index 51ee3ac81de8..000000000000 --- a/rules/privatization/tests/Rector/ClassConst/PrivatizeLocalClassConstantRector/Fixture/override_public_constant.php.inc +++ /dev/null @@ -1,55 +0,0 @@ - ------ - diff --git a/rules/privatization/tests/Rector/ClassConst/PrivatizeLocalClassConstantRector/Fixture/protected_parent_parent.php.inc b/rules/privatization/tests/Rector/ClassConst/PrivatizeLocalClassConstantRector/Fixture/protected_parent_parent.php.inc deleted file mode 100644 index f2afa375887e..000000000000 --- a/rules/privatization/tests/Rector/ClassConst/PrivatizeLocalClassConstantRector/Fixture/protected_parent_parent.php.inc +++ /dev/null @@ -1,45 +0,0 @@ - ------ - diff --git a/rules/privatization/tests/Rector/ClassMethod/MakeOnlyUsedByChildrenProtectedRector/Fixture/skip_extended.php.inc b/rules/privatization/tests/Rector/ClassMethod/MakeOnlyUsedByChildrenProtectedRector/Fixture/skip_extended.php.inc deleted file mode 100644 index 35d31b1468cd..000000000000 --- a/rules/privatization/tests/Rector/ClassMethod/MakeOnlyUsedByChildrenProtectedRector/Fixture/skip_extended.php.inc +++ /dev/null @@ -1,29 +0,0 @@ -run(); - } -} - -?> diff --git a/rules/privatization/tests/Rector/ClassMethod/PrivatizeLocalOnlyMethodRector/Fixture/skip_child_method_usage.php.inc b/rules/privatization/tests/Rector/ClassMethod/PrivatizeLocalOnlyMethodRector/Fixture/skip_child_method_usage.php.inc deleted file mode 100644 index f3b4cabf3647..000000000000 --- a/rules/privatization/tests/Rector/ClassMethod/PrivatizeLocalOnlyMethodRector/Fixture/skip_child_method_usage.php.inc +++ /dev/null @@ -1,26 +0,0 @@ -tearDown(); - } - - protected function tearDown(): void - { - } -} diff --git a/rules/privatization/tests/Rector/Property/PrivatizeLocalPropertyToPrivatePropertyRector/Fixture/skip_child_property_usage.php.inc b/rules/privatization/tests/Rector/Property/PrivatizeLocalPropertyToPrivatePropertyRector/Fixture/skip_child_property_usage.php.inc deleted file mode 100644 index a363754f4eae..000000000000 --- a/rules/privatization/tests/Rector/Property/PrivatizeLocalPropertyToPrivatePropertyRector/Fixture/skip_child_property_usage.php.inc +++ /dev/null @@ -1,21 +0,0 @@ -value; - } - - protected $value; -} diff --git a/rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Expected/MyClass.php b/rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Expected/ClassTraitAndInterface.php similarity index 78% rename from rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Expected/MyClass.php rename to rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Expected/ClassTraitAndInterface.php index f90993ae602a..42c9bebd8f24 100644 --- a/rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Expected/MyClass.php +++ b/rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Expected/ClassTraitAndInterface.php @@ -3,6 +3,6 @@ declare(strict_types=1); namespace Rector\PSR4\Tests\Rector\Namespace_\MultipleClassFileToPsr4ClassesRector\Expected; -final class MyClass +final class ClassTraitAndInterface { } diff --git a/rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Expected/JustOneExceptionWithoutNamespace.php b/rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Expected/SkipWithoutNamespace.php similarity index 100% rename from rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Expected/JustOneExceptionWithoutNamespace.php rename to rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Expected/SkipWithoutNamespace.php diff --git a/rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Source/ClassTraitAndInterface.php.inc b/rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Fixture/class_trait_and_interface.php.inc similarity index 61% rename from rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Source/ClassTraitAndInterface.php.inc rename to rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Fixture/class_trait_and_interface.php.inc index 81be9345fb0a..f16a6a315a3a 100644 --- a/rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Source/ClassTraitAndInterface.php.inc +++ b/rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Fixture/class_trait_and_interface.php.inc @@ -1,17 +1,16 @@ diff --git a/rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Source/nette-exceptions.php.inc b/rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Fixture/nette_exceptions.php.inc similarity index 85% rename from rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Source/nette-exceptions.php.inc rename to rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Fixture/nette_exceptions.php.inc index 296b1d98a865..14131632b0ee 100644 --- a/rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Source/nette-exceptions.php.inc +++ b/rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Fixture/nette_exceptions.php.inc @@ -7,7 +7,7 @@ declare(strict_types=1); * Copyright (c) 2004 David Grudl (https://davidgrudl.com) */ -namespace NettePostfixedToUniqueAutoload\Utils; +namespace Rector\PSR4\Tests\Rector\Namespace_\MultipleClassFileToPsr4ClassesRector\Fixture; /** * The exception that indicates invalid image file. @@ -42,7 +42,7 @@ declare(strict_types=1); * Copyright (c) 2004 David Grudl (https://davidgrudl.com) */ -namespace NettePostfixedToUniqueAutoload\Utils; +namespace Rector\PSR4\Tests\Rector\Namespace_\MultipleClassFileToPsr4ClassesRector\Fixture; ?> diff --git a/rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Fixture/skip_without_namespace.php.inc b/rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Fixture/skip_without_namespace.php.inc new file mode 100644 index 000000000000..493d6802ca35 --- /dev/null +++ b/rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Fixture/skip_without_namespace.php.inc @@ -0,0 +1,9 @@ + diff --git a/rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/MultipleClassFileToPsr4ClassesRectorTest.php b/rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/MultipleClassFileToPsr4ClassesRectorTest.php index e5d8678d10e9..114454ddb622 100644 --- a/rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/MultipleClassFileToPsr4ClassesRectorTest.php +++ b/rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/MultipleClassFileToPsr4ClassesRectorTest.php @@ -43,19 +43,20 @@ public function provideData(): Iterator $smartFileSystem->readFile(__DIR__ . '/Expected/UnknownImageFileException.php') ), ]; - yield [new SmartFileInfo(__DIR__ . '/Source/nette-exceptions.php.inc'), $filePathsWithContents]; + yield [new SmartFileInfo(__DIR__ . '/Fixture/nette_exceptions.php.inc'), $filePathsWithContents]; $filePathsWithContents = [ new AddedFileWithContent( - $this->getFixtureTempDirectory() . '/JustOneExceptionWithoutNamespace.php', - $smartFileSystem->readFile(__DIR__ . '/Expected/JustOneExceptionWithoutNamespace.php') + $this->getFixtureTempDirectory() . '/SkipWithoutNamespace.php', + $smartFileSystem->readFile(__DIR__ . '/Expected/SkipWithoutNamespace.php') ), new AddedFileWithContent( $this->getFixtureTempDirectory() . '/JustTwoExceptionWithoutNamespace.php', $smartFileSystem->readFile(__DIR__ . '/Expected/JustTwoExceptionWithoutNamespace.php') ), ]; - yield [new SmartFileInfo(__DIR__ . '/Source/without-namespace.php'), $filePathsWithContents]; + + yield [new SmartFileInfo(__DIR__ . '/Fixture/skip_without_namespace.php.inc'), $filePathsWithContents]; $filePathsWithContents = [ new AddedFileWithContent( @@ -63,8 +64,8 @@ public function provideData(): Iterator $smartFileSystem->readFile(__DIR__ . '/Expected/MyTrait.php') ), new AddedFileWithContent( - $this->getFixtureTempDirectory() . '/MyClass.php', - $smartFileSystem->readFile(__DIR__ . '/Expected/MyClass.php') + $this->getFixtureTempDirectory() . '/ClassTraitAndInterface.php', + $smartFileSystem->readFile(__DIR__ . '/Expected/ClassTraitAndInterface.php') ), new AddedFileWithContent( $this->getFixtureTempDirectory() . '/MyInterface.php', @@ -72,11 +73,11 @@ public function provideData(): Iterator ), ]; - yield [new SmartFileInfo(__DIR__ . '/Source/ClassTraitAndInterface.php.inc'), $filePathsWithContents]; + yield [new SmartFileInfo(__DIR__ . '/Fixture/class_trait_and_interface.php.inc'), $filePathsWithContents]; // keep original class yield [ - new SmartFileInfo(__DIR__ . '/Source/SomeClass.php'), + new SmartFileInfo(__DIR__ . '/Fixture/some_class.php.inc'), // extra files [ new AddedFileWithContent( diff --git a/rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Source/without-namespace.php b/rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Source/without-namespace.php deleted file mode 100644 index 875162240b14..000000000000 --- a/rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Source/without-namespace.php +++ /dev/null @@ -1,11 +0,0 @@ -hasMethodStaticCallOnType($classMethod, $objectType); - if ($hasStaticCall) { + if ($this->hasMethodStaticCallOnType($classMethod, $objectType)) { return true; } } diff --git a/rules/removing-static/src/Rector/ClassMethod/LocallyCalledStaticMethodToNonStaticRector.php b/rules/removing-static/src/Rector/ClassMethod/LocallyCalledStaticMethodToNonStaticRector.php index 257a6bcd3812..f34ed1d73fc8 100644 --- a/rules/removing-static/src/Rector/ClassMethod/LocallyCalledStaticMethodToNonStaticRector.php +++ b/rules/removing-static/src/Rector/ClassMethod/LocallyCalledStaticMethodToNonStaticRector.php @@ -9,6 +9,7 @@ use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Expr\Variable; use PhpParser\Node\Stmt\ClassMethod; +use PHPStan\Reflection\ClassReflection; use Rector\Core\Rector\AbstractRector; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\Privatization\VisibilityGuard\ClassMethodVisibilityGuard; @@ -100,7 +101,13 @@ private function refactorClassMethod(ClassMethod $classMethod): ?ClassMethod return null; } - if ($this->classMethodVisibilityGuard->isClassMethodVisibilityGuardedByParent($classMethod)) { + $scope = $classMethod->getAttribute(AttributeKey::SCOPE); + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { + return null; + } + + if ($this->classMethodVisibilityGuard->isClassMethodVisibilityGuardedByParent($classMethod, $classReflection)) { return null; } diff --git a/rules/removing-static/src/Rector/Class_/DesiredClassTypeToDynamicRector.php b/rules/removing-static/src/Rector/Class_/DesiredClassTypeToDynamicRector.php index 6b7496df7890..24dd362d292b 100644 --- a/rules/removing-static/src/Rector/Class_/DesiredClassTypeToDynamicRector.php +++ b/rules/removing-static/src/Rector/Class_/DesiredClassTypeToDynamicRector.php @@ -171,11 +171,7 @@ private function completeDependencyToConstructorOnly(Class_ $class, ObjectType $ return; } - $constructClassMethod->params[] = new Param( - new Variable($propertyExpectedName), - null, - new FullyQualified($objectType->getClassName()) - ); + $constructClassMethod->params[] = $this->createParam($propertyExpectedName, $objectType); } private function isTypeAlreadyInParamMethod(ClassMethod $classMethod, ObjectType $objectType): bool @@ -192,4 +188,9 @@ private function isTypeAlreadyInParamMethod(ClassMethod $classMethod, ObjectType return false; } + + private function createParam(string $propertyName, ObjectType $objectType): Param + { + return new Param(new Variable($propertyName), null, new FullyQualified($objectType->getClassName())); + } } diff --git a/rules/removing-static/src/Rector/Class_/NewUniqueObjectToEntityFactoryRector.php b/rules/removing-static/src/Rector/Class_/NewUniqueObjectToEntityFactoryRector.php index 008b3f002f96..62b680ff03fb 100644 --- a/rules/removing-static/src/Rector/Class_/NewUniqueObjectToEntityFactoryRector.php +++ b/rules/removing-static/src/Rector/Class_/NewUniqueObjectToEntityFactoryRector.php @@ -63,6 +63,11 @@ final class NewUniqueObjectToEntityFactoryRector extends AbstractRector implemen */ private $staticTypesInClassResolver; + /** + * @var array|mixed + */ + private $typesToServices; + public function __construct(PropertyNaming $propertyNaming, StaticTypesInClassResolver $staticTypesInClassResolver) { $this->propertyNaming = $propertyNaming; @@ -176,7 +181,7 @@ public function refactor(Node $node): ?Node } /** - * @param array $configuration + * @param array $configuration */ public function configure(array $configuration): void { diff --git a/rules/removing-static/src/Rector/Class_/PassFactoryToUniqueObjectRector.php b/rules/removing-static/src/Rector/Class_/PassFactoryToUniqueObjectRector.php index f1a9a978cb54..274482fc0279 100644 --- a/rules/removing-static/src/Rector/Class_/PassFactoryToUniqueObjectRector.php +++ b/rules/removing-static/src/Rector/Class_/PassFactoryToUniqueObjectRector.php @@ -186,7 +186,7 @@ public function refactor(Node $node): ?Node } /** - * @param array $configuration + * @param array $configuration */ public function configure(array $configuration): void { diff --git a/rules/removing-static/src/Rector/StaticCall/DesiredStaticCallTypeToDynamicRector.php b/rules/removing-static/src/Rector/StaticCall/DesiredStaticCallTypeToDynamicRector.php index aa439be6fa5a..4e99f31d8313 100644 --- a/rules/removing-static/src/Rector/StaticCall/DesiredStaticCallTypeToDynamicRector.php +++ b/rules/removing-static/src/Rector/StaticCall/DesiredStaticCallTypeToDynamicRector.php @@ -84,8 +84,8 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - foreach ($this->staticObjectTypes as $classType) { - if (! $this->isObjectType($node->class, $classType)) { + foreach ($this->staticObjectTypes as $objectType) { + if (! $this->isObjectType($node->class, $objectType)) { continue; } @@ -95,7 +95,7 @@ public function refactor(Node $node): ?Node return $this->createFromSelf($node); } - $propertyName = $this->propertyNaming->fqnToVariableName($classType); + $propertyName = $this->propertyNaming->fqnToVariableName($objectType); $currentMethodName = $node->getAttribute(AttributeKey::METHOD_NAME); if ($currentMethodName === MethodName::CONSTRUCT) { diff --git a/rules/renaming/src/NodeManipulator/ClassRenamer.php b/rules/renaming/src/NodeManipulator/ClassRenamer.php index 54888dc82fe8..ed13656dbc62 100644 --- a/rules/renaming/src/NodeManipulator/ClassRenamer.php +++ b/rules/renaming/src/NodeManipulator/ClassRenamer.php @@ -14,6 +14,8 @@ use PhpParser\Node\Stmt\Namespace_; use PhpParser\Node\Stmt\Use_; use PhpParser\Node\Stmt\UseUse; +use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\ObjectType; use Rector\BetterPhpDocParser\Contract\PhpDocNode\TypeAwareTagValueNodeInterface; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; @@ -21,11 +23,9 @@ use Rector\CodingStyle\Naming\ClassNaming; use Rector\Core\PhpParser\Node\BetterNodeFinder; use Rector\NodeNameResolver\NodeNameResolver; -use Rector\NodeTypeResolver\ClassExistenceStaticHelper; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockClassRenamer; use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType; -use ReflectionClass; use Symplify\Astral\NodeTraverser\SimpleCallableNodeTraverser; final class ClassRenamer @@ -70,6 +70,11 @@ final class ClassRenamer */ private $docBlockClassRenamer; + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + public function __construct( BetterNodeFinder $betterNodeFinder, SimpleCallableNodeTraverser $simpleCallableNodeTraverser, @@ -77,7 +82,8 @@ public function __construct( NodeNameResolver $nodeNameResolver, PhpDocClassRenamer $phpDocClassRenamer, PhpDocInfoFactory $phpDocInfoFactory, - DocBlockClassRenamer $docBlockClassRenamer + DocBlockClassRenamer $docBlockClassRenamer, + ReflectionProvider $reflectionProvider ) { $this->nodeNameResolver = $nodeNameResolver; $this->simpleCallableNodeTraverser = $simpleCallableNodeTraverser; @@ -86,6 +92,7 @@ public function __construct( $this->betterNodeFinder = $betterNodeFinder; $this->phpDocInfoFactory = $phpDocInfoFactory; $this->docBlockClassRenamer = $docBlockClassRenamer; + $this->reflectionProvider = $reflectionProvider; } /** @@ -180,16 +187,16 @@ private function refactorNamespace(Namespace_ $namespace, array $oldToNewClasses } $currentName = $this->nodeNameResolver->getName($classLike); - $newClassFqn = $oldToNewClasses[$currentName]; + $newClassFullyQualified = $oldToNewClasses[$currentName]; - if (ClassExistenceStaticHelper::doesClassLikeExist($newClassFqn)) { + if ($this->reflectionProvider->hasClass($newClassFullyQualified)) { return null; } - $newNamespace = $this->classNaming->getNamespace($newClassFqn); + $newNamespace = $this->classNaming->getNamespace($newClassFullyQualified); // Renaming to class without namespace (example MyNamespace\DateTime -> DateTimeImmutable) if (! $newNamespace) { - $classLike->name = new Identifier($newClassFqn); + $classLike->name = new Identifier($newClassFullyQualified); return $classLike; } @@ -222,13 +229,11 @@ private function refactorClassLike(ClassLike $classLike, array $oldToNewClasses) return null; } - /** @var string $name */ $this->alreadyProcessedClasses[] = $name; $newName = $oldToNewClasses[$name]; $newClassNamePart = $this->nodeNameResolver->getShortName($newName); $newNamespacePart = $this->classNaming->getNamespace($newName); - if ($this->isClassAboutToBeDuplicated($newName)) { return null; } @@ -262,21 +267,27 @@ private function refactorClassLike(ClassLike $classLike, array $oldToNewClasses) * - implements SomeInterface * - implements SomeClass */ - private function isClassToInterfaceValidChange(Name $name, string $newName): bool + private function isClassToInterfaceValidChange(Name $name, string $newClassName): bool { + if (! $this->reflectionProvider->hasClass($newClassName)) { + return true; + } + + $classReflection = $this->reflectionProvider->getClass($newClassName); + // ensure new is not with interface $parentNode = $name->getAttribute(AttributeKey::PARENT_NODE); - if ($parentNode instanceof New_ && interface_exists($newName)) { + if ($parentNode instanceof New_ && $classReflection->isInterface()) { return false; } if ($parentNode instanceof Class_) { - return $this->isValidClassNameChange($name, $newName, $parentNode); + return $this->isValidClassNameChange($name, $parentNode, $classReflection); } // prevent to change to import, that already exists if ($parentNode instanceof UseUse) { - return $this->isValidUseImportChange($newName, $parentNode); + return $this->isValidUseImportChange($newClassName, $parentNode); } return true; @@ -335,7 +346,7 @@ private function renameClassImplements(ClassLike $classLike, array $oldToNewClas private function isClassAboutToBeDuplicated(string $newName): bool { - return ClassExistenceStaticHelper::doesClassLikeExist($newName); + return $this->reflectionProvider->hasClass($newName); } private function changeNameToFullyQualifiedName(ClassLike $classLike): void @@ -350,27 +361,21 @@ private function changeNameToFullyQualifiedName(ClassLike $classLike): void }); } - private function isValidClassNameChange(Name $name, string $newName, Class_ $class): bool + private function isValidClassNameChange(Name $name, Class_ $class, ClassReflection $classReflection): bool { - // is class to interface? - if ($class->extends === $name && interface_exists($newName)) { - return false; - } - - // is interface to class? - if (in_array($name, $class->implements, true) && class_exists($newName)) { - return false; - } + if ($class->extends === $name) { + // is class to interface? + if ($classReflection->isInterface()) { + return false; + } - if ($class->extends === $name && class_exists($newName)) { - // is final class? - $reflectionClass = new ReflectionClass($newName); - if ($reflectionClass->isFinal()) { + if ($classReflection->isFinalByKeyword()) { return false; } } - return true; + // is interface to class? + return ! (in_array($name, $class->implements, true) && $classReflection->isClass()); } private function isValidUseImportChange(string $newName, UseUse $useUse): bool diff --git a/rules/renaming/src/NodeManipulator/IdentifierManipulator.php b/rules/renaming/src/NodeManipulator/IdentifierManipulator.php index 34453a675755..2024dec30bcf 100644 --- a/rules/renaming/src/NodeManipulator/IdentifierManipulator.php +++ b/rules/renaming/src/NodeManipulator/IdentifierManipulator.php @@ -40,14 +40,9 @@ public function __construct(NodeNameResolver $nodeNameResolver) */ public function renameNodeWithMap(Node $node, array $renameMethodMap): void { - Assert::isAnyOf( - $node, - [ClassConstFetch::class, - MethodCall::class, - PropertyFetch::class, - StaticCall::class, - ClassMethod::class, - ]); + Assert::isAnyOf($node, [ + ClassConstFetch::class, MethodCall::class, PropertyFetch::class, StaticCall::class, ClassMethod::class, + ]); $oldNodeMethodName = $this->resolveOldMethodName($node); if ($oldNodeMethodName === null) { @@ -81,16 +76,15 @@ public function removeSuffix(Node $node, string $suffixToRemove): void $node->name = new Identifier($newName); } + /** + * @param ClassConstFetch|MethodCall|PropertyFetch|StaticCall|ClassMethod $node + */ private function resolveOldMethodName(Node $node): ?string { - if (! property_exists($node, 'name')) { - return $this->nodeNameResolver->getName($node); - } - if (StaticInstanceOf::isOneOf($node, [StaticCall::class, MethodCall::class])) { return $this->nodeNameResolver->getName($node->name); } - return null; + return $this->nodeNameResolver->getName($node); } } diff --git a/rules/renaming/src/Rector/MethodCall/RenameMethodRector.php b/rules/renaming/src/Rector/MethodCall/RenameMethodRector.php index 481952bd01ad..b2f603ec49f4 100644 --- a/rules/renaming/src/Rector/MethodCall/RenameMethodRector.php +++ b/rules/renaming/src/Rector/MethodCall/RenameMethodRector.php @@ -121,6 +121,9 @@ public function refactor(Node $node): ?Node return null; } + /** + * @param array $configuration + */ public function configure(array $configuration): void { $methodCallRenames = $configuration[self::METHOD_CALL_RENAMES] ?? []; diff --git a/rules/renaming/tests/Rector/MethodCall/RenameMethodRector/Fixture/demo_file.php.inc b/rules/renaming/tests/Rector/MethodCall/RenameMethodRector/Fixture/demo_file.php.inc deleted file mode 100644 index 66ddcb42867f..000000000000 --- a/rules/renaming/tests/Rector/MethodCall/RenameMethodRector/Fixture/demo_file.php.inc +++ /dev/null @@ -1,45 +0,0 @@ -notify(); - } -} -?> ------ -__invoke(); - } -} -?> diff --git a/rules/renaming/tests/Rector/MethodCall/RenameMethodRector/Fixture/skip_when_interface.php.inc b/rules/renaming/tests/Rector/MethodCall/RenameMethodRector/Fixture/skip_when_interface.php.inc deleted file mode 100644 index ea3211defbab..000000000000 --- a/rules/renaming/tests/Rector/MethodCall/RenameMethodRector/Fixture/skip_when_interface.php.inc +++ /dev/null @@ -1,26 +0,0 @@ -old(); - } -} -?> diff --git a/rules/renaming/tests/Rector/MethodCall/RenameMethodRector/config/configured_rule.php b/rules/renaming/tests/Rector/MethodCall/RenameMethodRector/config/configured_rule.php index a6958b95d251..ebe3f8cd702c 100644 --- a/rules/renaming/tests/Rector/MethodCall/RenameMethodRector/config/configured_rule.php +++ b/rules/renaming/tests/Rector/MethodCall/RenameMethodRector/config/configured_rule.php @@ -1,5 +1,7 @@ doTestFileInfoWithoutAutoload($fileInfo); + $this->doTestFileInfo($fileInfo); } public function provideData(): Iterator diff --git a/rules/restoration/src/NameMatcher/FullyQualifiedNameMatcher.php b/rules/restoration/src/NameMatcher/FullyQualifiedNameMatcher.php index 97bbd5d844c6..78033a9f05ee 100644 --- a/rules/restoration/src/NameMatcher/FullyQualifiedNameMatcher.php +++ b/rules/restoration/src/NameMatcher/FullyQualifiedNameMatcher.php @@ -9,8 +9,8 @@ use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\NullableType; use PhpParser\Node\UnionType; +use PHPStan\Reflection\ReflectionProvider; use Rector\NodeNameResolver\NodeNameResolver; -use Rector\NodeTypeResolver\ClassExistenceStaticHelper; final class FullyQualifiedNameMatcher { @@ -24,10 +24,19 @@ final class FullyQualifiedNameMatcher */ private $nameMatcher; - public function __construct(NodeNameResolver $nodeNameResolver, NameMatcher $nameMatcher) - { + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct( + NodeNameResolver $nodeNameResolver, + NameMatcher $nameMatcher, + ReflectionProvider $reflectionProvider + ) { $this->nodeNameResolver = $nodeNameResolver; $this->nameMatcher = $nameMatcher; + $this->reflectionProvider = $reflectionProvider; } /** @@ -51,7 +60,7 @@ public function matchFullyQualifiedName($name) } $resolvedName = $this->nodeNameResolver->getName($name); - if (ClassExistenceStaticHelper::doesClassLikeExist($resolvedName)) { + if ($this->reflectionProvider->hasClass($resolvedName)) { return null; } diff --git a/rules/restoration/src/Rector/ClassConstFetch/MissingClassConstantReferenceToStringRector.php b/rules/restoration/src/Rector/ClassConstFetch/MissingClassConstantReferenceToStringRector.php index 83885b4e45a9..49ed4dbeef9d 100644 --- a/rules/restoration/src/Rector/ClassConstFetch/MissingClassConstantReferenceToStringRector.php +++ b/rules/restoration/src/Rector/ClassConstFetch/MissingClassConstantReferenceToStringRector.php @@ -7,8 +7,8 @@ use PhpParser\Node; use PhpParser\Node\Expr\ClassConstFetch; use PhpParser\Node\Scalar\String_; +use PHPStan\Reflection\ReflectionProvider; use Rector\Core\Rector\AbstractRector; -use Rector\NodeTypeResolver\ClassExistenceStaticHelper; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -17,6 +17,16 @@ */ final class MissingClassConstantReferenceToStringRector extends AbstractRector { + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + public function getRuleDefinition(): RuleDefinition { return new RuleDefinition('Convert missing class reference to string', [ @@ -66,7 +76,7 @@ public function refactor(Node $node): ?Node return null; } - if (ClassExistenceStaticHelper::doesClassLikeExist($referencedClass)) { + if ($this->reflectionProvider->hasClass($referencedClass)) { return null; } diff --git a/rules/restoration/src/Rector/ClassMethod/InferParamFromClassMethodReturnRector.php b/rules/restoration/src/Rector/ClassMethod/InferParamFromClassMethodReturnRector.php index 5c7fcc28824d..8e52f5ede08b 100644 --- a/rules/restoration/src/Rector/ClassMethod/InferParamFromClassMethodReturnRector.php +++ b/rules/restoration/src/Rector/ClassMethod/InferParamFromClassMethodReturnRector.php @@ -156,7 +156,7 @@ public function refactor(Node $node): ?Node } /** - * @param mixed[] $configuration + * @param array $configuration */ public function configure(array $configuration): void { diff --git a/rules/restoration/src/Rector/New_/CompleteMissingDependencyInNewRector.php b/rules/restoration/src/Rector/New_/CompleteMissingDependencyInNewRector.php index e2735ff4dcea..2e0dd021904f 100644 --- a/rules/restoration/src/Rector/New_/CompleteMissingDependencyInNewRector.php +++ b/rules/restoration/src/Rector/New_/CompleteMissingDependencyInNewRector.php @@ -8,9 +8,9 @@ use PhpParser\Node\Arg; use PhpParser\Node\Expr\New_; use PhpParser\Node\Name\FullyQualified; +use PHPStan\Reflection\ReflectionProvider; use Rector\Core\Contract\Rector\ConfigurableRectorInterface; use Rector\Core\Rector\AbstractRector; -use ReflectionClass; use ReflectionMethod; use ReflectionParameter; use ReflectionType; @@ -28,13 +28,23 @@ final class CompleteMissingDependencyInNewRector extends AbstractRector implemen * @api * @var string */ - public const CLASS_TO_INSTANTIATE_BY_TYPE = '$classToInstantiateByType'; + public const CLASS_TO_INSTANTIATE_BY_TYPE = 'class_to_instantiate_by_type'; /** - * @var string[] + * @var array */ private $classToInstantiateByType = []; + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + public function getRuleDefinition(): RuleDefinition { return new RuleDefinition('Complete missing constructor dependency instance by type', [ @@ -141,13 +151,13 @@ private function getNewNodeClassConstructorMethodReflection(New_ $new): ?Reflect return null; } - if (! class_exists($className)) { + if (! $this->reflectionProvider->hasClass($className)) { return null; } - $reflectionClass = new ReflectionClass($className); - - return $reflectionClass->getConstructor(); + $classReflection = $this->reflectionProvider->getClass($className); + $nativeClassReflection = $classReflection->getNativeReflection(); + return $nativeClassReflection->getConstructor(); } private function resolveClassToInstantiateByParameterReflection(ReflectionParameter $reflectionParameter): ?string diff --git a/rules/sensio/src/BundleClassResolver.php b/rules/sensio/src/BundleClassResolver.php index 6a6cdac94503..f456249ca303 100644 --- a/rules/sensio/src/BundleClassResolver.php +++ b/rules/sensio/src/BundleClassResolver.php @@ -8,10 +8,10 @@ use PhpParser\Node\Stmt\ClassLike; use PhpParser\NodeTraverser; use PhpParser\NodeVisitor\NameResolver; +use PHPStan\Reflection\ReflectionProvider; use Rector\Core\PhpParser\Node\BetterNodeFinder; use Rector\Core\PhpParser\Parser\Parser; use Rector\NodeNameResolver\NodeNameResolver; -use ReflectionClass; use Symplify\SmartFileSystem\SmartFileInfo; final class BundleClassResolver @@ -31,22 +31,29 @@ final class BundleClassResolver */ private $nodeNameResolver; + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + public function __construct( BetterNodeFinder $betterNodeFinder, NodeNameResolver $nodeNameResolver, - Parser $parser + Parser $parser, + ReflectionProvider $reflectionProvider ) { $this->parser = $parser; $this->betterNodeFinder = $betterNodeFinder; $this->nodeNameResolver = $nodeNameResolver; + $this->reflectionProvider = $reflectionProvider; } public function resolveShortBundleClassFromControllerClass(string $class): ?string { - // resolve bundle from existing ones - $reflectionClass = new ReflectionClass($class); + $classReflection = $this->reflectionProvider->getClass($class); - $fileName = $reflectionClass->getFileName(); + // resolve bundle from existing ones + $fileName = $classReflection->getFileName(); if (! $fileName) { return null; } diff --git a/rules/symfony-code-quality/src/NodeFactory/EventReferenceFactory.php b/rules/symfony-code-quality/src/NodeFactory/EventReferenceFactory.php index be285769349c..3ac6517aab9d 100644 --- a/rules/symfony-code-quality/src/NodeFactory/EventReferenceFactory.php +++ b/rules/symfony-code-quality/src/NodeFactory/EventReferenceFactory.php @@ -7,6 +7,7 @@ use PhpParser\Node; use PhpParser\Node\Expr\ClassConstFetch; use PhpParser\Node\Scalar\String_; +use PHPStan\Reflection\ReflectionProvider; use Rector\Core\PhpParser\Node\NodeFactory; use Rector\SymfonyCodeQuality\ValueObject\EventNameToClassAndConstant; @@ -17,9 +18,15 @@ final class EventReferenceFactory */ private $nodeFactory; - public function __construct(NodeFactory $nodeFactory) + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct(NodeFactory $nodeFactory, ReflectionProvider $reflectionProvider) { $this->nodeFactory = $nodeFactory; + $this->reflectionProvider = $reflectionProvider; } /** @@ -28,7 +35,7 @@ public function __construct(NodeFactory $nodeFactory) */ public function createEventName(string $eventName, array $eventNamesToClassConstants): Node { - if (class_exists($eventName)) { + if ($this->reflectionProvider->hasClass($eventName)) { return $this->nodeFactory->createClassConstReference($eventName); } diff --git a/rules/symfony-code-quality/src/Rector/Attribute/ExtractAttributeRouteNameConstantsRector.php b/rules/symfony-code-quality/src/Rector/Attribute/ExtractAttributeRouteNameConstantsRector.php index 8860d309312a..345dec15cb79 100644 --- a/rules/symfony-code-quality/src/Rector/Attribute/ExtractAttributeRouteNameConstantsRector.php +++ b/rules/symfony-code-quality/src/Rector/Attribute/ExtractAttributeRouteNameConstantsRector.php @@ -6,6 +6,7 @@ use PhpParser\Node; use PhpParser\Node\Attribute; +use Rector\BetterPhpDocParser\ValueObject\PhpDocNode\Symfony\SymfonyRouteTagValueNode; use Rector\Core\Rector\AbstractRector; use Rector\FileSystemRector\ValueObject\AddedFileWithNodes; use Rector\SymfonyCodeQuality\ConstantNameAndValueMatcher; @@ -13,7 +14,6 @@ use Rector\SymfonyCodeQuality\NodeFactory\RouteNameClassFactory; use Rector\SymfonyCodeQuality\ValueObject\ClassName; use Rector\SymfonyCodeQuality\ValueObject\ConstantNameAndValue; -use Symfony\Component\Routing\Annotation\Route; use Symplify\RuleDocGenerator\ValueObject\CodeSample\ExtraFileCodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; use Symplify\SmartFileSystem\SmartFileSystem; @@ -122,7 +122,7 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - if (! $this->isName($node->name, Route::class)) { + if (! $this->isName($node->name, SymfonyRouteTagValueNode::CLASS_NAME)) { return null; } @@ -158,7 +158,7 @@ private function createRouteNameValueObject(): void return; } - $routeAttributes = $this->nodeRepository->findAttributes(Route::class); + $routeAttributes = $this->nodeRepository->findAttributes(SymfonyRouteTagValueNode::CLASS_NAME); $constantNameAndValues = $this->constantNameAndValueResolver->resolveFromAttributes($routeAttributes, 'ROUTE_'); $namespace = $this->routeNameClassFactory->create($constantNameAndValues, self::ROUTE_NAME_FILE_LOCATION); diff --git a/rules/symfony/src/Rector/Class_/ChangeFileLoaderInExtensionAndKernelRector.php b/rules/symfony/src/Rector/Class_/ChangeFileLoaderInExtensionAndKernelRector.php index 6169f086e547..a74b5f0934a3 100644 --- a/rules/symfony/src/Rector/Class_/ChangeFileLoaderInExtensionAndKernelRector.php +++ b/rules/symfony/src/Rector/Class_/ChangeFileLoaderInExtensionAndKernelRector.php @@ -151,10 +151,7 @@ public function configure(array $configuration): void private function isKernelOrExtensionClass(Class_ $class): bool { - if ($this->isObjectType( - $class, - new ObjectType('Symfony\Component\HttpKernel\DependencyInjection\Extension') - )) { + if ($this->isObjectType($class, new ObjectType('Symfony\Component\HttpKernel\DependencyInjection\Extension'))) { return true; } diff --git a/rules/symfony/src/Rector/MethodCall/AbstractToConstructorInjectionRector.php b/rules/symfony/src/Rector/MethodCall/AbstractToConstructorInjectionRector.php index afc86a99d2e2..028bdadd5988 100644 --- a/rules/symfony/src/Rector/MethodCall/AbstractToConstructorInjectionRector.php +++ b/rules/symfony/src/Rector/MethodCall/AbstractToConstructorInjectionRector.php @@ -24,7 +24,7 @@ abstract class AbstractToConstructorInjectionRector extends AbstractRector /** * @var PropertyNaming */ - protected $propertyNaming; + private $propertyNaming; /** * @var ServiceMapProvider diff --git a/rules/symfony2/src/Rector/MethodCall/AddFlashRector.php b/rules/symfony2/src/Rector/MethodCall/AddFlashRector.php index 51e520d70a4b..d62451cd3be0 100644 --- a/rules/symfony2/src/Rector/MethodCall/AddFlashRector.php +++ b/rules/symfony2/src/Rector/MethodCall/AddFlashRector.php @@ -10,7 +10,6 @@ use Rector\Core\Rector\AbstractRector; use Rector\Defluent\NodeAnalyzer\FluentChainMethodCallNodeAnalyzer; use Rector\NodeTypeResolver\Node\AttributeKey; -use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -74,7 +73,7 @@ public function getNodeTypes(): array public function refactor(Node $node): ?Node { $parentClassName = $node->getAttribute(AttributeKey::PARENT_CLASS_NAME); - if ($parentClassName !== Controller::class) { + if ($parentClassName !== 'Symfony\Bundle\FrameworkBundle\Controller\Controller') { return null; } diff --git a/rules/symfony2/src/Rector/MethodCall/RedirectToRouteRector.php b/rules/symfony2/src/Rector/MethodCall/RedirectToRouteRector.php index 1f02d0150fd6..9a64e4bb6553 100644 --- a/rules/symfony2/src/Rector/MethodCall/RedirectToRouteRector.php +++ b/rules/symfony2/src/Rector/MethodCall/RedirectToRouteRector.php @@ -8,7 +8,6 @@ use PhpParser\Node\Expr\MethodCall; use Rector\Core\Rector\AbstractRector; use Rector\NodeTypeResolver\Node\AttributeKey; -use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -44,9 +43,10 @@ public function getNodeTypes(): array public function refactor(Node $node): ?Node { $parentClassName = $node->getAttribute(AttributeKey::PARENT_CLASS_NAME); - if ($parentClassName !== Controller::class) { + if ($parentClassName !== 'Symfony\Bundle\FrameworkBundle\Controller\Controller') { return null; } + if (! $this->isName($node->name, 'redirect')) { return null; } diff --git a/rules/symfony3/src/Rector/MethodCall/AbstractFormAddRector.php b/rules/symfony3/src/Rector/MethodCall/AbstractFormAddRector.php index 2bb287e563e9..cedf204f744e 100644 --- a/rules/symfony3/src/Rector/MethodCall/AbstractFormAddRector.php +++ b/rules/symfony3/src/Rector/MethodCall/AbstractFormAddRector.php @@ -74,9 +74,11 @@ protected function isCollectionType(MethodCall $methodCall): bool if (! $typeValue instanceof ClassConstFetch) { return $this->valueResolver->isValue($typeValue, 'collection'); } + if (! $this->isName($typeValue->class, 'Symfony\Component\Form\Extension\Core\Type\CollectionType')) { return $this->valueResolver->isValue($typeValue, 'collection'); } + return true; } } diff --git a/rules/symfony3/src/Rector/MethodCall/FormTypeInstanceToClassConstRector.php b/rules/symfony3/src/Rector/MethodCall/FormTypeInstanceToClassConstRector.php index c441513f0e5b..b8ac0e8cdbbe 100644 --- a/rules/symfony3/src/Rector/MethodCall/FormTypeInstanceToClassConstRector.php +++ b/rules/symfony3/src/Rector/MethodCall/FormTypeInstanceToClassConstRector.php @@ -15,11 +15,11 @@ use PhpParser\Node\Scalar\String_; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\ObjectType; use Rector\Core\ValueObject\MethodName; use Rector\Symfony3\NodeFactory\BuilderFormNodeFactory; use Rector\Symfony3\NodeFactory\ConfigureOptionsNodeFactory; -use ReflectionClass; use ReflectionMethod; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; @@ -52,12 +52,19 @@ final class FormTypeInstanceToClassConstRector extends AbstractFormAddRector */ private $configureOptionsNodeFactory; + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + public function __construct( BuilderFormNodeFactory $builderFormNodeFactory, - ConfigureOptionsNodeFactory $configureOptionsNodeFactory + ConfigureOptionsNodeFactory $configureOptionsNodeFactory, + ReflectionProvider $reflectionProvider ) { $this->builderFormNodeFactory = $builderFormNodeFactory; $this->configureOptionsNodeFactory = $configureOptionsNodeFactory; + $this->reflectionProvider = $reflectionProvider; $this->controllerObjectTypes = [ new ObjectType('Symfony\Bundle\FrameworkBundle\Controller\Controller'), @@ -252,16 +259,21 @@ private function moveArgumentsToOptions( */ private function resolveNamesToArgs(string $className, array $argNodes): array { - $reflectionClass = new ReflectionClass($className); - $constructorReflectionMethod = $reflectionClass->getConstructor(); + if (! $this->reflectionProvider->hasClass($className)) { + return []; + } + + $classReflection = $this->reflectionProvider->getClass($className); + $nativeClassReflection = $classReflection->getNativeReflection(); + $constructorReflectionMethod = $nativeClassReflection->getConstructor(); if (! $constructorReflectionMethod instanceof ReflectionMethod) { return []; } $namesToArgs = []; - foreach ($constructorReflectionMethod->getParameters() as $reflectionParameter) { - $namesToArgs[$reflectionParameter->getName()] = $argNodes[$reflectionParameter->getPosition()]; + foreach ($constructorReflectionMethod->getParameters() as $position => $reflectionParameter) { + $namesToArgs[$reflectionParameter->getName()] = $argNodes[$position]; } return $namesToArgs; diff --git a/rules/symfony4/src/Rector/MethodCall/ContainerBuilderCompileEnvArgumentRector.php b/rules/symfony4/src/Rector/MethodCall/ContainerBuilderCompileEnvArgumentRector.php index 48f077c7f8a4..3d55a933ff37 100644 --- a/rules/symfony4/src/Rector/MethodCall/ContainerBuilderCompileEnvArgumentRector.php +++ b/rules/symfony4/src/Rector/MethodCall/ContainerBuilderCompileEnvArgumentRector.php @@ -53,10 +53,7 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - if (! $this->isObjectType( - $node, - new ObjectType('Symfony\Component\DependencyInjection\ContainerBuilder') - )) { + if (! $this->isObjectType($node, new ObjectType('Symfony\Component\DependencyInjection\ContainerBuilder'))) { return null; } diff --git a/rules/symfony4/src/Rector/New_/StringToArrayArgumentProcessRector.php b/rules/symfony4/src/Rector/New_/StringToArrayArgumentProcessRector.php index 3e5f74c88330..55ef344d15ed 100644 --- a/rules/symfony4/src/Rector/New_/StringToArrayArgumentProcessRector.php +++ b/rules/symfony4/src/Rector/New_/StringToArrayArgumentProcessRector.php @@ -100,7 +100,7 @@ private function processArgumentPosition(Node $node, int $argumentPosition): ?No } // type analyzer - if ($this->isStaticType($firstArgument, StringType::class)) { + if ($this->nodeTypeResolver->isStaticType($firstArgument, StringType::class)) { $this->processStringType($node, $argumentPosition, $firstArgument); } diff --git a/rules/symfony5/src/Rector/Class_/LogoutSuccessHandlerToLogoutEventSubscriberRector.php b/rules/symfony5/src/Rector/Class_/LogoutSuccessHandlerToLogoutEventSubscriberRector.php index 70cb8244b6b4..05032d32e7f1 100644 --- a/rules/symfony5/src/Rector/Class_/LogoutSuccessHandlerToLogoutEventSubscriberRector.php +++ b/rules/symfony5/src/Rector/Class_/LogoutSuccessHandlerToLogoutEventSubscriberRector.php @@ -152,7 +152,10 @@ public function refactor(Node $node): ?Node 'Symfony\Component\Security\Http\Event\LogoutEvent' ); - $eventReferencesToMethodNames = [new EventReferenceToMethodNameWithPriority($classConstFetch, 'onLogout', 64)]; + $eventReferencesToMethodNames = [ + new EventReferenceToMethodNameWithPriority($classConstFetch, 'onLogout', 64), + ]; + $getSubscribedEventsClassMethod = $this->getSubscribedEventsClassMethodFactory->create( $eventReferencesToMethodNames ); diff --git a/rules/symfony5/src/Rector/MethodCall/FormBuilderSetDataMapperRector.php b/rules/symfony5/src/Rector/MethodCall/FormBuilderSetDataMapperRector.php index 067dffb358d8..f825db3417d9 100644 --- a/rules/symfony5/src/Rector/MethodCall/FormBuilderSetDataMapperRector.php +++ b/rules/symfony5/src/Rector/MethodCall/FormBuilderSetDataMapperRector.php @@ -20,6 +20,16 @@ */ final class FormBuilderSetDataMapperRector extends AbstractRector { + /** + * @var ObjectType + */ + private $dataMapperObjectType; + + public function __construct() + { + $this->dataMapperObjectType = new ObjectType('Symfony\Component\Form\Extension\Core\DataMapper\DataMapper'); + } + public function getRuleDefinition(): RuleDefinition { return new RuleDefinition( @@ -77,10 +87,7 @@ public function refactor(Node $node): ?Node } $argumentValue = $node->args[0]->value; - if ($this->isObjectType( - $argumentValue, - new ObjectType('Symfony\Component\Form\Extension\Core\DataMapper\DataMapper') - )) { + if ($this->isObjectType($argumentValue, $this->dataMapperObjectType)) { return null; } @@ -88,9 +95,9 @@ public function refactor(Node $node): ?Node 'Symfony\Component\Form\Extension\Core\DataAccessor\PropertyPathAccessor' )); - $newArgumentValue = new New_(new FullyQualified( - 'Symfony\Component\Form\Extension\Core\DataMapper\DataMapper' - ), [new Arg($propertyPathAccessor)]); + $newArgumentValue = new New_(new FullyQualified($this->dataMapperObjectType->getClassName()), [ + new Arg($propertyPathAccessor), + ]); $node->args[0]->value = $newArgumentValue; return $node; diff --git a/rules/symfony5/src/Rector/MethodCall/ValidatorBuilderEnableAnnotationMappingRector.php b/rules/symfony5/src/Rector/MethodCall/ValidatorBuilderEnableAnnotationMappingRector.php index 9854648ca3fb..4c958b8707f3 100644 --- a/rules/symfony5/src/Rector/MethodCall/ValidatorBuilderEnableAnnotationMappingRector.php +++ b/rules/symfony5/src/Rector/MethodCall/ValidatorBuilderEnableAnnotationMappingRector.php @@ -83,6 +83,7 @@ public function refactor(Node $node): ?Node $readerType = $node->args[0]->value; $node->args[0]->value = $this->nodeFactory->createTrue(); + return $this->nodeFactory->createMethodCall($node, 'setDoctrineAnnotationReader', [$readerType]); } } diff --git a/rules/transform/src/NodeFactory/PropertyFetchFactory.php b/rules/transform/src/NodeFactory/PropertyFetchFactory.php index fdcfc1f7384c..e084ce9d8f0b 100644 --- a/rules/transform/src/NodeFactory/PropertyFetchFactory.php +++ b/rules/transform/src/NodeFactory/PropertyFetchFactory.php @@ -6,6 +6,7 @@ use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Expr\Variable; +use PHPStan\Type\ObjectType; use Rector\Naming\Naming\PropertyNaming; final class PropertyFetchFactory @@ -20,10 +21,10 @@ public function __construct(PropertyNaming $propertyNaming) $this->propertyNaming = $propertyNaming; } - public function createFromType(string $type): PropertyFetch + public function createFromType(ObjectType $objectType): PropertyFetch { $thisVariable = new Variable('this'); - $propertyName = $this->propertyNaming->fqnToVariableName($type); + $propertyName = $this->propertyNaming->fqnToVariableName($objectType->getClassName()); return new PropertyFetch($thisVariable, $propertyName); } diff --git a/rules/transform/src/NodeTypeAnalyzer/TypeProvidingExprFromClassResolver.php b/rules/transform/src/NodeTypeAnalyzer/TypeProvidingExprFromClassResolver.php index ed5346d40471..ac9c2bf2fcee 100644 --- a/rules/transform/src/NodeTypeAnalyzer/TypeProvidingExprFromClassResolver.php +++ b/rules/transform/src/NodeTypeAnalyzer/TypeProvidingExprFromClassResolver.php @@ -18,6 +18,7 @@ use PHPStan\Reflection\Php\PhpPropertyReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\MixedType; +use PHPStan\Type\ObjectType; use PHPStan\Type\Type; use PHPStan\Type\TypeWithClassName; use Rector\Core\ValueObject\MethodName; @@ -64,8 +65,11 @@ public function __construct( * @param ClassMethod|Function_ $functionLike * @return MethodCall|PropertyFetch|Variable|null */ - public function resolveTypeProvidingExprFromClass(Class_ $class, FunctionLike $functionLike, string $type): ?Expr - { + public function resolveTypeProvidingExprFromClass( + Class_ $class, + FunctionLike $functionLike, + ObjectType $objectType + ): ?Expr { $className = $class->getAttribute(AttributeKey::CLASS_NAME); if ($className === null) { return null; @@ -73,7 +77,7 @@ public function resolveTypeProvidingExprFromClass(Class_ $class, FunctionLike $f // A. match a method $classReflection = $this->reflectionProvider->getClass($className); - $methodCallProvidingType = $this->resolveMethodCallProvidingType($classReflection, $type); + $methodCallProvidingType = $this->resolveMethodCallProvidingType($classReflection, $objectType); if ($methodCallProvidingType !== null) { return $methodCallProvidingType; } @@ -84,22 +88,24 @@ public function resolveTypeProvidingExprFromClass(Class_ $class, FunctionLike $f return null; } - $propertyFetch = $this->resolvePropertyFetchProvidingType($classReflection, $scope, $type); + $propertyFetch = $this->resolvePropertyFetchProvidingType($classReflection, $scope, $objectType); if ($propertyFetch !== null) { return $propertyFetch; } // C. param in constructor? - return $this->resolveConstructorParamProvidingType($functionLike, $type); + return $this->resolveConstructorParamProvidingType($functionLike, $objectType); } - private function resolveMethodCallProvidingType(ClassReflection $classReflection, string $type): ?MethodCall - { + private function resolveMethodCallProvidingType( + ClassReflection $classReflection, + ObjectType $objectType + ): ?MethodCall { foreach ($classReflection->getNativeMethods() as $methodReflection) { $functionVariant = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants()); $returnType = $functionVariant->getReturnType(); - if (! $this->isMatchingType($returnType, $type)) { + if (! $this->isMatchingType($returnType, $objectType)) { continue; } @@ -113,7 +119,7 @@ private function resolveMethodCallProvidingType(ClassReflection $classReflection private function resolvePropertyFetchProvidingType( ClassReflection $classReflection, Scope $scope, - string $type + ObjectType $objectType ): ?PropertyFetch { $nativeReflection = $classReflection->getNativeReflection(); @@ -122,7 +128,7 @@ private function resolvePropertyFetchProvidingType( $phpPropertyReflection = $classReflection->getProperty($reflectionProperty->getName(), $scope); $readableType = $phpPropertyReflection->getReadableType(); - if (! $this->isMatchingType($readableType, $type)) { + if (! $this->isMatchingType($readableType, $objectType)) { continue; } @@ -132,7 +138,7 @@ private function resolvePropertyFetchProvidingType( return null; } - private function resolveConstructorParamProvidingType(FunctionLike $functionLike, string $type): ?Variable + private function resolveConstructorParamProvidingType(FunctionLike $functionLike, ObjectType $objectType): ?Variable { if (! $functionLike instanceof ClassMethod) { return null; @@ -142,11 +148,11 @@ private function resolveConstructorParamProvidingType(FunctionLike $functionLike return null; } - $variableName = $this->propertyNaming->fqnToVariableName($type); + $variableName = $this->propertyNaming->fqnToVariableName($objectType); return new Variable($variableName); } - private function isMatchingType(Type $readableType, string $type): bool + private function isMatchingType(Type $readableType, ObjectType $objectType): bool { if ($readableType instanceof MixedType) { return false; @@ -158,6 +164,6 @@ private function isMatchingType(Type $readableType, string $type): bool return false; } - return $readableType->getClassName() === $type; + return $readableType->equals($objectType); } } diff --git a/rules/transform/src/Rector/AbstractToMethodCallRector.php b/rules/transform/src/Rector/AbstractToMethodCallRector.php index ca0d71229e68..fecc29191341 100644 --- a/rules/transform/src/Rector/AbstractToMethodCallRector.php +++ b/rules/transform/src/Rector/AbstractToMethodCallRector.php @@ -12,10 +12,10 @@ use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Function_; +use PHPStan\Type\ObjectType; use Rector\Core\Contract\Rector\ConfigurableRectorInterface; use Rector\Core\Rector\AbstractRector; use Rector\Naming\Naming\PropertyNaming; -use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType; use Rector\Transform\NodeFactory\PropertyFetchFactory; use Rector\Transform\NodeTypeAnalyzer\TypeProvidingExprFromClassResolver; @@ -53,43 +53,38 @@ public function autowireAbstractToMethodCallRector( * @param ClassMethod|Function_ $functionLike * @return MethodCall|PropertyFetch|Variable */ - protected function matchTypeProvidingExpr(Class_ $class, FunctionLike $functionLike, string $type): Expr + protected function matchTypeProvidingExpr(Class_ $class, FunctionLike $functionLike, ObjectType $objectType): Expr { $expr = $this->typeProvidingExprFromClassResolver->resolveTypeProvidingExprFromClass( $class, $functionLike, - $type + $objectType ); if ($expr !== null) { if ($expr instanceof Variable) { - $this->addClassMethodParamForVariable($expr, $type, $functionLike); + $this->addClassMethodParamForVariable($expr, $objectType, $functionLike); } return $expr; } - $this->addPropertyTypeToClass($type, $class); - return $this->propertyFetchFactory->createFromType($type); + $propertyName = $this->propertyNaming->fqnToVariableName($objectType); + $this->addConstructorDependencyToClass($class, $objectType, $propertyName); + return $this->propertyFetchFactory->createFromType($objectType); } /** * @param ClassMethod|Function_ $functionLike */ - private function addClassMethodParamForVariable(Variable $variable, string $type, FunctionLike $functionLike): void - { + private function addClassMethodParamForVariable( + Variable $variable, + ObjectType $objectType, + FunctionLike $functionLike + ): void { /** @var string $variableName */ $variableName = $this->getName($variable); // add variable to __construct as dependency - $param = $this->nodeFactory->createParamFromNameAndType($variableName, new FullyQualifiedObjectType($type)); - - $functionLike->params[] = $param; - } - - private function addPropertyTypeToClass(string $type, Class_ $class): void - { - $fullyQualifiedObjectType = new FullyQualifiedObjectType($type); - $propertyName = $this->propertyNaming->fqnToVariableName($fullyQualifiedObjectType); - $this->addConstructorDependencyToClass($class, $fullyQualifiedObjectType, $propertyName); + $functionLike->params[] = $this->nodeFactory->createParamFromNameAndType($variableName, $objectType); } } diff --git a/rules/transform/src/Rector/FuncCall/FuncCallToMethodCallRector.php b/rules/transform/src/Rector/FuncCall/FuncCallToMethodCallRector.php index 616bc6b44684..332691ef5c89 100644 --- a/rules/transform/src/Rector/FuncCall/FuncCallToMethodCallRector.php +++ b/rules/transform/src/Rector/FuncCall/FuncCallToMethodCallRector.php @@ -108,7 +108,7 @@ public function refactor(Node $node): ?Node $expr = $this->matchTypeProvidingExpr( $classLike, $classMethod, - $funcNameToMethodCallName->getNewClassName() + $funcNameToMethodCallName->getNewObjectType() ); return $this->nodeFactory->createMethodCall( $expr, diff --git a/rules/transform/src/Rector/New_/NewToConstructorInjectionRector.php b/rules/transform/src/Rector/New_/NewToConstructorInjectionRector.php index d6ec155ba7a8..ff81fc0035b5 100644 --- a/rules/transform/src/Rector/New_/NewToConstructorInjectionRector.php +++ b/rules/transform/src/Rector/New_/NewToConstructorInjectionRector.php @@ -28,7 +28,7 @@ final class NewToConstructorInjectionRector extends AbstractRector implements Co /** * @var string */ - public const TYPES_TO_CONSTRUCTOR_INJECTION = 'TYPES_TO_CONSTRUCTOR_INJECTION'; + public const TYPES_TO_CONSTRUCTOR_INJECTION = 'types_to_constructor_injection'; /** * @var ObjectType[] @@ -143,9 +143,11 @@ private function refactorMethodCall(MethodCall $methodCall): ?MethodCall continue; } - $methodCall->var = $this->propertyFetchFactory->createFromType( - $constructorInjectionObjectType->getClassName() - ); + if (! $this->nodeTypeResolver->isObjectType($methodCall->var, $constructorInjectionObjectType)) { + continue; + } + + $methodCall->var = $this->propertyFetchFactory->createFromType($constructorInjectionObjectType); return $methodCall; } diff --git a/rules/transform/src/Rector/New_/NewToMethodCallRector.php b/rules/transform/src/Rector/New_/NewToMethodCallRector.php index 9c98860f203d..5addfa7750d7 100644 --- a/rules/transform/src/Rector/New_/NewToMethodCallRector.php +++ b/rules/transform/src/Rector/New_/NewToMethodCallRector.php @@ -4,7 +4,6 @@ namespace Rector\Transform\Rector\New_; -use Nette\Utils\Strings; use PhpParser\Node; use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\New_; @@ -12,11 +11,11 @@ use PhpParser\Node\Expr\Variable; use PhpParser\Node\Stmt\Class_; use PHPStan\Type\ObjectType; +use Rector\CodingStyle\Naming\ClassNaming; use Rector\Core\Contract\Rector\ConfigurableRectorInterface; use Rector\Core\Rector\AbstractRector; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\Transform\ValueObject\NewToMethodCall; -use ReflectionClass; use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; use Webmozart\Assert\Assert; @@ -36,6 +35,16 @@ final class NewToMethodCallRector extends AbstractRector implements Configurable */ private $newsToMethodCalls = []; + /** + * @var ClassNaming + */ + private $classNaming; + + public function __construct(ClassNaming $classNaming) + { + $this->classNaming = $classNaming; + } + public function getRuleDefinition(): RuleDefinition { return new RuleDefinition('Replaces creating object instances with "new" keyword with factory method.', [ @@ -96,11 +105,19 @@ public function refactor(Node $node): ?Node /** @var Class_ $classNode */ $classNode = $node->getAttribute(AttributeKey::CLASS_NODE); - $propertyName = $this->getExistingFactoryPropertyName($classNode, $serviceObjectType); + + $propertyName = $this->getExistingFactoryPropertyName($classNode, $newToMethodCall->getServiceObjectType()); if ($propertyName === null) { - $propertyName = $this->getFactoryPropertyName($serviceObjectType->getClassName()); - $this->addConstructorDependencyToClass($classNode, $serviceObjectType, $propertyName); + $serviceObjectType = $newToMethodCall->getServiceObjectType(); + $propertyName = $this->classNaming->getShortName($serviceObjectType->getClassName()); + $propertyName = lcfirst($propertyName); + + $this->addConstructorDependencyToClass( + $classNode, + $newToMethodCall->getServiceObjectType(), + $propertyName + ); } $propertyFetch = new PropertyFetch(new Variable('this'), $propertyName); @@ -133,12 +150,4 @@ private function getExistingFactoryPropertyName(Class_ $class, ObjectType $facto return null; } - - private function getFactoryPropertyName(string $factoryFullQualifiedName): string - { - $reflectionClass = new ReflectionClass($factoryFullQualifiedName); - $shortName = $reflectionClass->getShortName(); - - return Strings::firstLower($shortName); - } } diff --git a/rules/transform/src/Rector/StaticCall/StaticCallToMethodCallRector.php b/rules/transform/src/Rector/StaticCall/StaticCallToMethodCallRector.php index 3186c47df229..55e8be0d0dd1 100644 --- a/rules/transform/src/Rector/StaticCall/StaticCallToMethodCallRector.php +++ b/rules/transform/src/Rector/StaticCall/StaticCallToMethodCallRector.php @@ -117,7 +117,11 @@ public function refactor(Node $node): ?Node return $this->refactorToInstanceCall($node, $staticCallToMethodCall); } - $expr = $this->matchTypeProvidingExpr($classLike, $classMethod, $staticCallToMethodCall->getClassType()); + $expr = $this->matchTypeProvidingExpr( + $classLike, + $classMethod, + $staticCallToMethodCall->getClassObjectType() + ); if ($staticCallToMethodCall->getMethodName() === '*') { $methodName = $this->getName($node->name); diff --git a/rules/transform/src/ValueObject/FuncCallToMethodCall.php b/rules/transform/src/ValueObject/FuncCallToMethodCall.php index e45591dc5d04..2378cc13c093 100644 --- a/rules/transform/src/ValueObject/FuncCallToMethodCall.php +++ b/rules/transform/src/ValueObject/FuncCallToMethodCall.php @@ -4,6 +4,8 @@ namespace Rector\Transform\ValueObject; +use PHPStan\Type\ObjectType; + final class FuncCallToMethodCall { /** @@ -33,9 +35,9 @@ public function getOldFuncName(): string return $this->oldFuncName; } - public function getNewClassName(): string + public function getNewObjectType(): ObjectType { - return $this->newClassName; + return new ObjectType($this->newClassName); } public function getNewMethodName(): string diff --git a/rules/transform/src/ValueObject/StaticCallToMethodCall.php b/rules/transform/src/ValueObject/StaticCallToMethodCall.php index a892b83b475b..95277f67e9f8 100644 --- a/rules/transform/src/ValueObject/StaticCallToMethodCall.php +++ b/rules/transform/src/ValueObject/StaticCallToMethodCall.php @@ -7,6 +7,7 @@ use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Identifier; use PhpParser\Node\Name; +use PHPStan\Type\ObjectType; final class StaticCallToMethodCall { @@ -38,6 +39,11 @@ public function __construct(string $staticClass, string $staticMethod, string $c $this->methodName = $methodName; } + public function getClassObjectType(): ObjectType + { + return new ObjectType($this->classType); + } + public function getClassType(): string { return $this->classType; diff --git a/rules/transform/tests/Rector/New_/NewToMethodCallRector/config/configured_rule.php b/rules/transform/tests/Rector/New_/NewToMethodCallRector/config/configured_rule.php index 39cc5d3d3a46..12d766ee795d 100644 --- a/rules/transform/tests/Rector/New_/NewToMethodCallRector/config/configured_rule.php +++ b/rules/transform/tests/Rector/New_/NewToMethodCallRector/config/configured_rule.php @@ -9,12 +9,11 @@ return static function (ContainerConfigurator $containerConfigurator): void { $services = $containerConfigurator->services(); + $services->set(NewToMethodCallRector::class) ->call('configure', [[ NewToMethodCallRector::NEWS_TO_METHOD_CALLS => ValueObjectInliner::inline([ - new NewToMethodCall(MyClass::class, MyClassFactory::class, 'create'), - ]), ]]); }; diff --git a/rules/type-declaration/src/ChildPopulator/ChildReturnPopulator.php b/rules/type-declaration/src/ChildPopulator/ChildReturnPopulator.php index 2cecc84e126e..e0db4c5a3c66 100644 --- a/rules/type-declaration/src/ChildPopulator/ChildReturnPopulator.php +++ b/rules/type-declaration/src/ChildPopulator/ChildReturnPopulator.php @@ -35,8 +35,6 @@ public function __construct(NodeNameResolver $nodeNameResolver, NodeRepository $ */ public function populateChildren(ClassMethod $classMethod, Type $returnType): void { - $methodName = $this->nodeNameResolver->getName($classMethod); - $className = $classMethod->getAttribute(AttributeKey::CLASS_NAME); if (! is_string($className)) { throw new ShouldNotHappenException(); diff --git a/rules/type-declaration/src/PHPStan/Type/ObjectTypeSpecifier.php b/rules/type-declaration/src/PHPStan/Type/ObjectTypeSpecifier.php index cc7c22105b2c..63403cf59ef2 100644 --- a/rules/type-declaration/src/PHPStan/Type/ObjectTypeSpecifier.php +++ b/rules/type-declaration/src/PHPStan/Type/ObjectTypeSpecifier.php @@ -10,10 +10,10 @@ use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Stmt\Use_; use PhpParser\Node\Stmt\UseUse; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\MixedType; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; -use Rector\NodeTypeResolver\ClassExistenceStaticHelper; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\StaticTypeMapper\ValueObject\Type\AliasedObjectType; use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType; @@ -21,6 +21,16 @@ final class ObjectTypeSpecifier { + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + /** * @return AliasedObjectType|FullyQualifiedObjectType|ObjectType|MixedType */ @@ -33,7 +43,6 @@ public function narrowToFullyQualifiedOrAliasedObjectType(Node $node, ObjectType } $aliasedObjectType = $this->matchAliasedObjectType($node, $objectType); - if ($aliasedObjectType !== null) { return $aliasedObjectType; } @@ -49,7 +58,8 @@ public function narrowToFullyQualifiedOrAliasedObjectType(Node $node, ObjectType } $className = ltrim($objectType->getClassName(), '\\'); - if (ClassExistenceStaticHelper::doesClassLikeExist($className)) { + + if ($this->reflectionProvider->hasClass($className)) { return new FullyQualifiedObjectType($className); } @@ -66,7 +76,9 @@ private function matchAliasedObjectType(Node $node, ObjectType $objectType): ?Al } $className = $objectType->getClassName(); - $parentNode = $node->getAttribute(AttributeKey::PARENT_NODE); + + $parent = $node->getAttribute(AttributeKey::PARENT_NODE); + foreach ($uses as $use) { foreach ($use->uses as $useUse) { if ($useUse->alias === null) { @@ -81,7 +93,7 @@ private function matchAliasedObjectType(Node $node, ObjectType $objectType): ?Al $alias, $className, $useName, - $parentNode, + $parent, $fullyQualifiedName ); if ($processAliasedObject instanceof AliasedObjectType) { @@ -160,7 +172,7 @@ private function matchSameNamespacedObjectType(Node $node, ObjectType $objectTyp $namespacedObject = $namespaceName . '\\' . ltrim($objectType->getClassName(), '\\'); - if (ClassExistenceStaticHelper::doesClassLikeExist($namespacedObject)) { + if ($this->reflectionProvider->hasClass($namespacedObject)) { return new FullyQualifiedObjectType($namespacedObject); } @@ -177,7 +189,7 @@ private function matchPartialNamespaceObjectType(ObjectType $objectType, UseUse $classNameWithoutLastUsePart = Strings::after($objectType->getClassName(), '\\', 1); $connectedClassName = $useUse->name->toString() . '\\' . $classNameWithoutLastUsePart; - if (! ClassExistenceStaticHelper::doesClassLikeExist($connectedClassName)) { + if (! $this->reflectionProvider->hasClass($connectedClassName)) { return null; } @@ -197,7 +209,7 @@ private function matchClassWithLastUseImportPart(ObjectType $objectType, UseUse return null; } - if (! ClassExistenceStaticHelper::doesClassLikeExist($useUse->name->toString())) { + if (! $this->reflectionProvider->hasClass($useUse->name->toString())) { return null; } diff --git a/rules/type-declaration/src/PhpParserTypeAnalyzer.php b/rules/type-declaration/src/PhpParserTypeAnalyzer.php index 9ef57ee0d5f4..f34fbe60effd 100644 --- a/rules/type-declaration/src/PhpParserTypeAnalyzer.php +++ b/rules/type-declaration/src/PhpParserTypeAnalyzer.php @@ -9,9 +9,20 @@ use PhpParser\Node\Name; use PhpParser\Node\NullableType; use PhpParser\Node\UnionType; +use Rector\NodeNameResolver\NodeNameResolver; final class PhpParserTypeAnalyzer { + /** + * @var NodeNameResolver + */ + private $nodeNameResolver; + + public function __construct(NodeNameResolver $nodeNameResolver) + { + $this->nodeNameResolver = $nodeNameResolver; + } + /** * @param Name|NullableType|UnionType|Identifier $possibleSubtype * @param Name|NullableType|UnionType|Identifier $possibleParentType @@ -65,13 +76,11 @@ private function isUnionType(Node $possibleSubtype, Node $possibleParentType): b private function unwrapNullableAndToString(Node $node): string { - if (! $node instanceof NullableType && method_exists($node, 'toString')) { - return $node->toString(); + if (! $node instanceof NullableType) { + return $this->nodeNameResolver->getName($node); } - /** @var NullableType $type */ - $type = $node; - return $type->type->toString(); + return $this->nodeNameResolver->getName($node->type); } private function isTraversableOrIterableSubtype(string $possibleSubtype, string $possibleParentType): bool diff --git a/rules/type-declaration/src/Rector/ClassMethod/AddVoidReturnTypeWhereNoReturnRector.php b/rules/type-declaration/src/Rector/ClassMethod/AddVoidReturnTypeWhereNoReturnRector.php index a30552612618..f5b7886cd1c8 100644 --- a/rules/type-declaration/src/Rector/ClassMethod/AddVoidReturnTypeWhereNoReturnRector.php +++ b/rules/type-declaration/src/Rector/ClassMethod/AddVoidReturnTypeWhereNoReturnRector.php @@ -9,7 +9,6 @@ use PhpParser\Node\Identifier; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Function_; -use Rector\CodingStyle\ValueObject\ObjectMagicMethods; use Rector\Core\Rector\AbstractRector; use Rector\Core\ValueObject\PhpVersionFeature; use Rector\TypeDeclaration\TypeInferer\SilentVoidResolver; @@ -83,7 +82,7 @@ public function refactor(Node $node): ?Node return null; } - if ($this->isNames($node, ObjectMagicMethods::METHOD_NAMES)) { + if ($node instanceof ClassMethod && $node->isMagic()) { return null; } diff --git a/rules/type-declaration/src/Rector/FunctionLike/ReturnTypeDeclarationRector.php b/rules/type-declaration/src/Rector/FunctionLike/ReturnTypeDeclarationRector.php index b31ed5a0c960..9294a134c38b 100644 --- a/rules/type-declaration/src/Rector/FunctionLike/ReturnTypeDeclarationRector.php +++ b/rules/type-declaration/src/Rector/FunctionLike/ReturnTypeDeclarationRector.php @@ -8,6 +8,7 @@ use PhpParser\Node\FunctionLike; use PhpParser\Node\Name; use PhpParser\Node\NullableType; +use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Function_; use PhpParser\Node\UnionType as PhpParserUnionType; @@ -15,7 +16,6 @@ use PHPStan\Type\ObjectType; use PHPStan\Type\Type; use PHPStan\Type\UnionType; -use Rector\Core\ValueObject\MethodName; use Rector\Core\ValueObject\PhpVersionFeature; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\PHPStanStaticTypeMapper\PHPStanStaticTypeMapper; @@ -35,16 +35,6 @@ */ final class ReturnTypeDeclarationRector extends AbstractTypeDeclarationRector { - /** - * @var string[] - */ - private const EXCLUDED_METHOD_NAMES = [MethodName::CONSTRUCT, MethodName::DESCTRUCT, MethodName::CLONE]; - - /** - * @var bool - */ - private $overrideExistingReturnTypes = true; - /** * @var ReturnTypeInferer */ @@ -75,11 +65,9 @@ public function __construct( ChildReturnPopulator $childReturnPopulator, ReturnTypeAlreadyAddedChecker $returnTypeAlreadyAddedChecker, NonInformativeReturnTagRemover $nonInformativeReturnTagRemover, - ClassMethodReturnTypeOverrideGuard $classMethodReturnTypeOverrideGuard, - bool $overrideExistingReturnTypes = true + ClassMethodReturnTypeOverrideGuard $classMethodReturnTypeOverrideGuard ) { $this->returnTypeInferer = $returnTypeInferer; - $this->overrideExistingReturnTypes = $overrideExistingReturnTypes; $this->returnTypeAlreadyAddedChecker = $returnTypeAlreadyAddedChecker; $this->nonInformativeReturnTagRemover = $nonInformativeReturnTagRemover; $this->childReturnPopulator = $childReturnPopulator; @@ -122,7 +110,15 @@ public function getCount(): int */ public function refactor(Node $node): ?Node { - if ($node instanceof ClassMethod && $this->shouldSkip($node)) { + if (! $this->isAtLeastPhpVersion(PhpVersionFeature::SCALAR_TYPES)) { + return null; + } + + if ($this->shouldSkipClassLike($node)) { + return null; + } + + if ($node instanceof ClassMethod && $this->shouldSkipClassMethod($node)) { return null; } @@ -157,7 +153,7 @@ private function processType(Node $node, Type $inferedType): ?Node return null; } - if ($this->shouldSkipInferredReturnNode($node, $inferredReturnNode)) { + if ($this->shouldSkipInferredReturnNode($node)) { return null; } @@ -168,6 +164,7 @@ private function processType(Node $node, Type $inferedType): ?Node /** @var Name|NullableType|PhpParserUnionType $inferredReturnNode */ $this->addReturnType($node, $inferredReturnNode); + $this->nonInformativeReturnTagRemover->removeReturnTagIfNotUseful($node); if ($node instanceof ClassMethod) { @@ -177,36 +174,25 @@ private function processType(Node $node, Type $inferedType): ?Node return $node; } - private function shouldSkip(ClassMethod $classMethod): bool + private function shouldSkipClassMethod(ClassMethod $classMethod): bool { - if (! $this->isAtLeastPhpVersion(PhpVersionFeature::SCALAR_TYPES)) { - return true; - } - if ($this->classMethodReturnTypeOverrideGuard->shouldSkipClassMethod($classMethod)) { return true; } - if (! $this->overrideExistingReturnTypes && $classMethod->returnType !== null) { - return true; - } - - if ($this->isNames($classMethod, self::EXCLUDED_METHOD_NAMES)) { - return true; - } - return $this->vendorLockResolver->isReturnChangeVendorLockedIn($classMethod); } /** * @param ClassMethod|Function_ $functionLike */ - private function shouldSkipInferredReturnNode(FunctionLike $functionLike, Node $inferredReturnNode): bool + private function shouldSkipInferredReturnNode(FunctionLike $functionLike): bool { // already overridden by previous populateChild() method run if ($functionLike->returnType === null) { return false; } + return (bool) $functionLike->returnType->getAttribute(AttributeKey::DO_NOT_CHANGE); } @@ -219,14 +205,13 @@ private function shouldSkipExistingReturnType(FunctionLike $functionLike, Type $ return false; } - $currentType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($functionLike->returnType); - if ($functionLike instanceof ClassMethod && $this->vendorLockResolver->isReturnChangeVendorLockedIn( $functionLike )) { return true; } + $currentType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($functionLike->returnType); if ($this->isCurrentObjectTypeSubType($currentType, $inferedType)) { return true; } @@ -246,6 +231,7 @@ private function addReturnType(FunctionLike $functionLike, Node $inferredReturnN } $isSubtype = $this->phpParserTypeAnalyzer->isSubtypeOf($inferredReturnNode, $functionLike->returnType); + if ($this->isAtLeastPhpVersion(PhpVersionFeature::COVARIANT_RETURN) && $isSubtype) { $functionLike->returnType = $inferredReturnNode; return; @@ -254,7 +240,6 @@ private function addReturnType(FunctionLike $functionLike, Node $inferredReturnN if (! $isSubtype) { // type override with correct one $functionLike->returnType = $inferredReturnNode; - return; } } @@ -287,4 +272,14 @@ private function isNullableTypeSubType(Type $currentType, Type $inferedType): bo return $inferedType->isSubTypeOf($currentType) ->yes(); } + + private function shouldSkipClassLike(FunctionLike $functionLike): bool + { + if (! $functionLike instanceof ClassMethod) { + return false; + } + + $classLike = $functionLike->getAttribute(AttributeKey::CLASS_NODE); + return ! $classLike instanceof Class_; + } } diff --git a/rules/type-declaration/src/Rector/Property/TypedPropertyFromStrictConstructorRector.php b/rules/type-declaration/src/Rector/Property/TypedPropertyFromStrictConstructorRector.php index 8ab3ba6d644b..a637dab5020a 100644 --- a/rules/type-declaration/src/Rector/Property/TypedPropertyFromStrictConstructorRector.php +++ b/rules/type-declaration/src/Rector/Property/TypedPropertyFromStrictConstructorRector.php @@ -107,7 +107,9 @@ public function refactor(Node $node): ?Node } $node->type = $propertyTypeNode; - $this->varTagRemover->removeVarPhpTagValueNodeIfNotComment($node, $varType); + + $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node); + $this->varTagRemover->removeVarTagIfUseless($phpDocInfo, $node); return $node; } diff --git a/rules/type-declaration/src/TypeInferer/PropertyTypeInferer.php b/rules/type-declaration/src/TypeInferer/PropertyTypeInferer.php index edd283580f0a..1adf34f5a2a3 100644 --- a/rules/type-declaration/src/TypeInferer/PropertyTypeInferer.php +++ b/rules/type-declaration/src/TypeInferer/PropertyTypeInferer.php @@ -69,6 +69,7 @@ public function inferProperty(Property $property): Type if ($type instanceof VoidType) { continue; } + if ($type instanceof MixedType) { continue; } @@ -84,12 +85,10 @@ public function inferProperty(Property $property): Type } // default value type must be added to each resolved type if set - // @todo include in one of inferrers above $propertyDefaultValue = $property->props[0]->default; if ($propertyDefaultValue !== null) { $defaultValueType = $this->defaultValuePropertyTypeInferer->inferProperty($property); - if ($this->shouldUnionWithDefaultValue($defaultValueType, $resolvedType)) { return $this->unionWithDefaultValueType($defaultValueType, $resolvedType); } diff --git a/rules/type-declaration/src/TypeInferer/PropertyTypeInferer/ConstructorPropertyTypeInferer.php b/rules/type-declaration/src/TypeInferer/PropertyTypeInferer/ConstructorPropertyTypeInferer.php index 68bfd0512b38..1017b1223af7 100644 --- a/rules/type-declaration/src/TypeInferer/PropertyTypeInferer/ConstructorPropertyTypeInferer.php +++ b/rules/type-declaration/src/TypeInferer/PropertyTypeInferer/ConstructorPropertyTypeInferer.php @@ -15,6 +15,7 @@ use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Property; use PhpParser\NodeTraverser; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\ArrayType; use PHPStan\Type\MixedType; use PHPStan\Type\NullType; @@ -34,9 +35,17 @@ final class ConstructorPropertyTypeInferer extends AbstractTypeInferer implement */ private $classMethodPropertyFetchManipulator; - public function __construct(ClassMethodPropertyFetchManipulator $classMethodPropertyFetchManipulator) - { + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct( + ClassMethodPropertyFetchManipulator $classMethodPropertyFetchManipulator, + ReflectionProvider $reflectionProvider + ) { $this->classMethodPropertyFetchManipulator = $classMethodPropertyFetchManipulator; + $this->reflectionProvider = $reflectionProvider; } public function inferProperty(Property $property): Type @@ -180,7 +189,7 @@ private function resolveFullyQualifiedOrAliasedObjectType(Param $param): ?Type if (! Strings::endsWith($fullyQualifiedName, '\\' . $originalName->toString())) { $className = $originalName->toString(); - if (class_exists($className)) { + if ($this->reflectionProvider->hasClass($className)) { return new FullyQualifiedObjectType($className); } diff --git a/rules/type-declaration/src/TypeInferer/ReturnTypeInferer.php b/rules/type-declaration/src/TypeInferer/ReturnTypeInferer.php index 406518b793bd..b511239d3378 100644 --- a/rules/type-declaration/src/TypeInferer/ReturnTypeInferer.php +++ b/rules/type-declaration/src/TypeInferer/ReturnTypeInferer.php @@ -9,7 +9,6 @@ use PHPStan\Type\Type; use Rector\TypeDeclaration\Contract\TypeInferer\ReturnTypeInfererInterface; use Rector\TypeDeclaration\TypeNormalizer; -use Webmozart\Assert\Assert; final class ReturnTypeInferer extends AbstractPriorityAwareTypeInferer { @@ -38,7 +37,7 @@ public function inferFunctionLike(FunctionLike $functionLike): Type } /** - * @param string[] $excludedInferers + * @param array> $excludedInferers */ public function inferFunctionLikeWithExcludedInferers(FunctionLike $functionLike, array $excludedInferers): Type { @@ -66,14 +65,12 @@ public function inferFunctionLikeWithExcludedInferers(FunctionLike $functionLike } /** - * @param string[] $excludedInferers + * @param array> $excludedInferers */ private function shouldSkipExcludedTypeInferer( ReturnTypeInfererInterface $returnTypeInferer, array $excludedInferers ): bool { - Assert::allIsAOf($excludedInferers, ReturnTypeInfererInterface::class); - foreach ($excludedInferers as $excludedInferer) { if (is_a($returnTypeInferer, $excludedInferer)) { return true; diff --git a/rules/type-declaration/tests/Rector/ClassMethod/ReturnTypeFromStrictTypedCallRector/Fixture/external_function_caller.php.inc b/rules/type-declaration/tests/Rector/ClassMethod/ReturnTypeFromStrictTypedCallRector/Fixture/external_function_caller.php.inc deleted file mode 100644 index 040388a39fec..000000000000 --- a/rules/type-declaration/tests/Rector/ClassMethod/ReturnTypeFromStrictTypedCallRector/Fixture/external_function_caller.php.inc +++ /dev/null @@ -1,31 +0,0 @@ - ------ - diff --git a/rules/type-declaration/tests/Rector/ClassMethod/ReturnTypeFromStrictTypedCallRector/Fixture/skip_external_function_caller.php.inc b/rules/type-declaration/tests/Rector/ClassMethod/ReturnTypeFromStrictTypedCallRector/Fixture/skip_external_function_caller.php.inc new file mode 100644 index 000000000000..3249883c7ea9 --- /dev/null +++ b/rules/type-declaration/tests/Rector/ClassMethod/ReturnTypeFromStrictTypedCallRector/Fixture/skip_external_function_caller.php.inc @@ -0,0 +1,13 @@ + ------ - diff --git a/rules/type-declaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/Fixture/respect_vendor_return_type2.php.inc b/rules/type-declaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/Fixture/respect_vendor_return_type2.php.inc new file mode 100644 index 000000000000..bc97d2e144b2 --- /dev/null +++ b/rules/type-declaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/Fixture/respect_vendor_return_type2.php.inc @@ -0,0 +1,35 @@ + +----- + diff --git a/rules/type-declaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/Fixture/skip_respect_children_return_type_mixed.php.inc b/rules/type-declaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/Fixture/skip_respect_children_return_type_mixed.php.inc index 7de86607c0f8..f913687f1e84 100644 --- a/rules/type-declaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/Fixture/skip_respect_children_return_type_mixed.php.inc +++ b/rules/type-declaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/Fixture/skip_respect_children_return_type_mixed.php.inc @@ -14,16 +14,10 @@ class SkipRespectChildrenReturnTypeParentMixed } } -class RespectChildrenReturnTypeMixed extends SkipRespectChildrenReturnTypeParentMixed { - public function run() : Babax { - if (rand(0,1)) { - return new Babax(); - } - - throw new \Exception('test'); +final class RespectChildrenReturnTypeMixed extends SkipRespectChildrenReturnTypeParentMixed +{ + public function run(): string + { + return 'hey'; } } - -class Babax { -} -?> diff --git a/rules/type-declaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/Fixture/skip_respect_vendor_return_type2.php.inc b/rules/type-declaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/Fixture/skip_respect_vendor_return_type2.php.inc deleted file mode 100644 index cdbfd63f8e59..000000000000 --- a/rules/type-declaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/Fixture/skip_respect_vendor_return_type2.php.inc +++ /dev/null @@ -1,10 +0,0 @@ - ------ - diff --git a/rules/type-declaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/FixtureInheritance/nullable_inheritance.php.inc b/rules/type-declaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/FixtureInheritance/nullable_inheritance.php.inc index 2c3b4a547cca..6658b1933e50 100644 --- a/rules/type-declaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/FixtureInheritance/nullable_inheritance.php.inc +++ b/rules/type-declaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/FixtureInheritance/nullable_inheritance.php.inc @@ -1,44 +1,50 @@ ----- diff --git a/rules/type-declaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/Source/vendor/RespectVendorReturnType.php b/rules/type-declaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/Source/RespectVendorReturnType.php similarity index 84% rename from rules/type-declaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/Source/vendor/RespectVendorReturnType.php rename to rules/type-declaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/Source/RespectVendorReturnType.php index 6f20161d92fe..5e145fd9c7a7 100644 --- a/rules/type-declaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/Source/vendor/RespectVendorReturnType.php +++ b/rules/type-declaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/Source/RespectVendorReturnType.php @@ -1,5 +1,5 @@ cacheFile = $this->coreCache(); + $this->cacheFiles[] = $this->coreCache(); } } @@ -31,21 +31,21 @@ class StaticPropertyWithDefaultNull namespace Rector\TypeDeclaration\Tests\Rector\Property\PropertyTypeDeclarationRector\Fixture; -class StaticPropertyWithDefaultNull +final class StaticPropertyWithDefaultNull { /** * @var null|string */ - protected static $cacheFile = null; + private $cacheFile = null; /** * @var null|string[] */ - protected static $cacheFiles = null; + private $cacheFiles = null; /** * @return string */ - public static function coreCache($file = '') + public function coreCache($file = '') { return $file; } @@ -53,10 +53,10 @@ class StaticPropertyWithDefaultNull /** * Register rex_autoload in spl autoloader. */ - public static function register() + public function register() { - self::$cacheFile = self::coreCache(); - self::$cacheFiles[] = self::coreCache(); + $this->cacheFile = $this->coreCache(); + $this->cacheFiles[] = $this->coreCache(); } } diff --git a/src/Application/RectorApplication.php b/src/Application/RectorApplication.php index 7524b18d471a..75137d5cdc91 100644 --- a/src/Application/RectorApplication.php +++ b/src/Application/RectorApplication.php @@ -12,6 +12,7 @@ use Rector\Core\Configuration\Configuration; use Rector\Core\Contract\PostRunnerInterface; use Rector\Core\Exception\ShouldNotHappenException; +use Rector\NodeTypeResolver\Reflection\BetterReflection\SourceLocatorProvider\DynamicSourceLocatorProvider; use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Style\SymfonyStyle; use Symplify\PackageBuilder\Reflection\PrivatesAccessor; @@ -91,6 +92,11 @@ final class RectorApplication */ private $privatesAccessor; + /** + * @var DynamicSourceLocatorProvider + */ + private $dynamicSourceLocatorProvider; + /** * @param PostRunnerInterface[] $postRunners */ @@ -103,6 +109,7 @@ public function __construct( RemovedAndAddedFilesProcessor $removedAndAddedFilesProcessor, SymfonyStyle $symfonyStyle, PrivatesAccessor $privatesAccessor, + DynamicSourceLocatorProvider $dynamicSourceLocatorProvider, array $postRunners ) { $this->symfonyStyle = $symfonyStyle; @@ -114,6 +121,7 @@ public function __construct( $this->nodeScopeResolver = $nodeScopeResolver; $this->privatesAccessor = $privatesAccessor; $this->postRunners = $postRunners; + $this->dynamicSourceLocatorProvider = $dynamicSourceLocatorProvider; } /** @@ -188,6 +196,7 @@ private function configurePHPStanNodeScopeResolver(array $fileInfos): void } $this->nodeScopeResolver->setAnalysedFiles($filePaths); + $this->dynamicSourceLocatorProvider->addFileInfos($fileInfos); } /** diff --git a/src/NodeAnalyzer/PropertyPresenceChecker.php b/src/NodeAnalyzer/PropertyPresenceChecker.php index 65c6e1968f57..4c63cd34b8d9 100644 --- a/src/NodeAnalyzer/PropertyPresenceChecker.php +++ b/src/NodeAnalyzer/PropertyPresenceChecker.php @@ -6,10 +6,9 @@ use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\Property; +use PHPStan\Reflection\ReflectionProvider; use Rector\NodeNameResolver\NodeNameResolver; -use Rector\NodeTypeResolver\ClassExistenceStaticHelper; use Rector\Php80\NodeAnalyzer\PromotedPropertyResolver; -use ReflectionClass; use ReflectionProperty; final class PropertyPresenceChecker @@ -24,12 +23,19 @@ final class PropertyPresenceChecker */ private $nodeNameResolver; + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + public function __construct( PromotedPropertyResolver $promotedPropertyResolver, - NodeNameResolver $nodeNameResolver + NodeNameResolver $nodeNameResolver, + ReflectionProvider $reflectionProvider ) { $this->promotedPropertyResolver = $promotedPropertyResolver; $this->nodeNameResolver = $nodeNameResolver; + $this->reflectionProvider = $reflectionProvider; } /** @@ -42,7 +48,7 @@ public function hasClassContextPropertyByName(Class_ $class, string $propertyNam return false; } - if (! ClassExistenceStaticHelper::doesClassLikeExist($className)) { + if (! $this->reflectionProvider->hasClass($className)) { return false; } @@ -76,17 +82,20 @@ public function hasClassContextPropertyByName(Class_ $class, string $propertyNam */ private function getParentClassPublicAndProtectedPropertyReflections(string $className): array { - /** @var string[] $parentClassNames */ - $parentClassNames = (array) class_parents($className); + if (! $this->reflectionProvider->hasClass($className)) { + return []; + } - $propertyReflections = []; + $classReflection = $this->reflectionProvider->getClass($className); - foreach ($parentClassNames as $parentClassName) { - $parentClassReflection = new ReflectionClass($parentClassName); + $propertyReflections = []; + foreach ($classReflection->getParents() as $parentClassReflection) { + $nativeReflectionClass = $parentClassReflection->getNativeReflection(); - $currentPropertyReflections = $parentClassReflection->getProperties( + $currentPropertyReflections = $nativeReflectionClass->getProperties( ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED ); + $propertyReflections = array_merge($propertyReflections, $currentPropertyReflections); } diff --git a/src/NodeManipulator/ChildAndParentClassManipulator.php b/src/NodeManipulator/ChildAndParentClassManipulator.php index 0c13ff794694..025e5a3caa6f 100644 --- a/src/NodeManipulator/ChildAndParentClassManipulator.php +++ b/src/NodeManipulator/ChildAndParentClassManipulator.php @@ -7,6 +7,7 @@ use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Expression; +use PHPStan\Reflection\ReflectionProvider; use Rector\Core\NodeAnalyzer\PromotedPropertyParamCleaner; use Rector\Core\PhpParser\Node\NodeFactory; use Rector\Core\ValueObject\MethodName; @@ -36,16 +37,23 @@ final class ChildAndParentClassManipulator */ private $promotedPropertyParamCleaner; + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + public function __construct( NodeFactory $nodeFactory, NodeNameResolver $nodeNameResolver, NodeRepository $nodeRepository, - PromotedPropertyParamCleaner $promotedPropertyParamCleaner + PromotedPropertyParamCleaner $promotedPropertyParamCleaner, + ReflectionProvider $reflectionProvider ) { $this->nodeFactory = $nodeFactory; $this->nodeNameResolver = $nodeNameResolver; $this->nodeRepository = $nodeRepository; $this->promotedPropertyParamCleaner = $promotedPropertyParamCleaner; + $this->reflectionProvider = $reflectionProvider; } /** @@ -53,21 +61,30 @@ public function __construct( */ public function completeParentConstructor(Class_ $class, ClassMethod $classMethod): void { - /** @var string|null $parentClassName */ - $parentClassName = $class->getAttribute(AttributeKey::PARENT_CLASS_NAME); - if ($parentClassName === null) { + $className = $this->nodeNameResolver->getName($class); + if ($className === null) { + return; + } + + if (! $this->reflectionProvider->hasClass($className)) { + return; + } + + $classReflection = $this->reflectionProvider->getClass($className); + $parentClassReflection = $classReflection->getParentClass(); + if ($parentClassReflection === false) { return; } // not in analyzed scope, nothing we can do - $parentClassNode = $this->nodeRepository->findClass($parentClassName); - if ($parentClassNode !== null) { + $parentClassNode = $this->nodeRepository->findClass($parentClassReflection->getName()); + if ($parentClassNode instanceof Class_) { $this->completeParentConstructorBasedOnParentNode($parentClassNode, $classMethod); return; } // complete parent call for __construct() - if ($parentClassName !== '' && method_exists($parentClassName, MethodName::CONSTRUCT)) { + if ($parentClassReflection->hasMethod(MethodName::CONSTRUCT)) { $staticCall = $this->nodeFactory->createParentConstructWithParams([]); $classMethod->stmts[] = new Expression($staticCall); } diff --git a/src/NodeManipulator/ClassDependencyManipulator.php b/src/NodeManipulator/ClassDependencyManipulator.php index 3f4c4ef006fc..981b8297c75e 100644 --- a/src/NodeManipulator/ClassDependencyManipulator.php +++ b/src/NodeManipulator/ClassDependencyManipulator.php @@ -7,11 +7,11 @@ use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Name; -use PhpParser\Node\Param; use PhpParser\Node\Stmt; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Expression; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\Type; use Rector\Core\NodeAnalyzer\PropertyPresenceChecker; use Rector\Core\Php\PhpVersionProvider; @@ -64,6 +64,11 @@ final class ClassDependencyManipulator */ private $nodeNameResolver; + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + public function __construct( ChildAndParentClassManipulator $childAndParentClassManipulator, ClassInsertManipulator $classInsertManipulator, @@ -72,7 +77,8 @@ public function __construct( StmtsManipulator $stmtsManipulator, PhpVersionProvider $phpVersionProvider, PropertyPresenceChecker $propertyPresenceChecker, - NodeNameResolver $nodeNameResolver + NodeNameResolver $nodeNameResolver, + ReflectionProvider $reflectionProvider ) { $this->classMethodAssignManipulator = $classMethodAssignManipulator; $this->nodeFactory = $nodeFactory; @@ -82,6 +88,7 @@ public function __construct( $this->phpVersionProvider = $phpVersionProvider; $this->propertyPresenceChecker = $propertyPresenceChecker; $this->nodeNameResolver = $nodeNameResolver; + $this->reflectionProvider = $reflectionProvider; } public function addConstructorDependency(Class_ $class, PropertyMetadata $propertyMetadata): void @@ -203,7 +210,17 @@ private function hasClassParentClassMethod(Class_ $class, string $methodName): b return false; } - return method_exists($parentClassName, $methodName); + if (! $this->reflectionProvider->hasClass($parentClassName)) { + return false; + } + + $classReflection = $this->reflectionProvider->getClass($parentClassName); + $parentClassReflection = $classReflection->getParentClass(); + if ($parentClassReflection === false) { + return false; + } + + return $parentClassReflection->hasMethod($methodName); } private function createParentClassMethodCall(string $methodName): Expression diff --git a/src/NodeManipulator/ClassManipulator.php b/src/NodeManipulator/ClassManipulator.php index 4c223255eda7..c6232c28a37d 100644 --- a/src/NodeManipulator/ClassManipulator.php +++ b/src/NodeManipulator/ClassManipulator.php @@ -12,6 +12,8 @@ use PhpParser\Node\Stmt\Interface_; use PhpParser\Node\Stmt\Property; use PhpParser\Node\Stmt\Trait_; +use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\ObjectType; use Rector\NodeNameResolver\NodeNameResolver; use Rector\PostRector\Collector\NodesToRemoveCollector; @@ -28,12 +30,19 @@ final class ClassManipulator */ private $nodesToRemoveCollector; + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + public function __construct( NodeNameResolver $nodeNameResolver, + ReflectionProvider $reflectionProvider, NodesToRemoveCollector $nodesToRemoveCollector ) { $this->nodeNameResolver = $nodeNameResolver; $this->nodesToRemoveCollector = $nodesToRemoveCollector; + $this->reflectionProvider = $reflectionProvider; } /** @@ -54,25 +63,18 @@ public function getUsedTraits(ClassLike $classLike): array return $usedTraits; } - public function hasParentMethodOrInterface(ObjectType $objectType, string $method): bool + public function hasParentMethodOrInterface(ObjectType $objectType, string $methodName): bool { - if (! class_exists($objectType->getClassName())) { + if (! $this->reflectionProvider->hasClass($objectType->getClassName())) { return false; } - $class = $objectType->getClassName(); - - $parentClass = $class; - while ($parentClass = get_parent_class($parentClass)) { - if (method_exists($parentClass, $method)) { - return true; - } - } + $classReflection = $this->reflectionProvider->getClass($objectType->getClassName()); - $implementedInterfaces = (array) class_implements($class); - foreach ($implementedInterfaces as $implementedInterface) { - /** @var string $implementedInterface */ - if (method_exists($implementedInterface, $method)) { + /** @var ClassReflection[] $parentClassReflections */ + $parentClassReflections = array_merge($classReflection->getParents(), $classReflection->getInterfaces()); + foreach ($parentClassReflections as $parentClassReflection) { + if ($parentClassReflection->hasMethod($methodName)) { return true; } } @@ -116,9 +118,9 @@ public function getImplementedInterfaceNames(Class_ $class): array return $this->nodeNameResolver->getNames($class->implements); } - public function hasInterface(Class_ $class, string $desiredInterface): bool + public function hasInterface(Class_ $class, ObjectType $interfaceObjectType): bool { - return $this->nodeNameResolver->isName($class->implements, $desiredInterface); + return $this->nodeNameResolver->isName($class->implements, $interfaceObjectType->getClassName()); } public function hasTrait(Class_ $class, string $desiredTrait): bool diff --git a/src/NodeManipulator/ClassMethodAssignManipulator.php b/src/NodeManipulator/ClassMethodAssignManipulator.php index a713ab2347e4..19990a2accdf 100644 --- a/src/NodeManipulator/ClassMethodAssignManipulator.php +++ b/src/NodeManipulator/ClassMethodAssignManipulator.php @@ -280,13 +280,13 @@ private function collectReferenceVariableNames(ClassMethod $classMethod): array private function findParentForeach(Assign $assign): ?Foreach_ { - /** @var Foreach_|FunctionLike|null $foreach */ - $foreach = $this->betterNodeFinder->findFirstPreviousOfTypes($assign, [Foreach_::class, FunctionLike::class]); - if (! $foreach instanceof Foreach_) { + /** @var Foreach_|FunctionLike|null $foundNode */ + $foundNode = $this->betterNodeFinder->findFirstPreviousOfTypes($assign, [Foreach_::class, FunctionLike::class]); + if (! $foundNode instanceof Foreach_) { return null; } - return $foreach; + return $foundNode; } private function isExplicitlyReferenced(Node $node): bool diff --git a/src/NodeManipulator/ClassMethodManipulator.php b/src/NodeManipulator/ClassMethodManipulator.php index c75f32ac030d..849a4bdd7027 100644 --- a/src/NodeManipulator/ClassMethodManipulator.php +++ b/src/NodeManipulator/ClassMethodManipulator.php @@ -11,6 +11,8 @@ use PhpParser\Node\Param; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; +use PHPStan\Analyser\Scope; +use PHPStan\Reflection\ClassReflection; use PHPStan\Type\ObjectType; use Rector\Core\Exception\ShouldNotHappenException; use Rector\Core\PhpParser\Comparing\NodeComparator; @@ -109,29 +111,28 @@ public function isNamedConstructor(ClassMethod $classMethod): bool public function hasParentMethodOrInterfaceMethod(ClassMethod $classMethod, ?string $methodName = null): bool { $methodName = $methodName ?? $this->nodeNameResolver->getName($classMethod->name); - - $class = $classMethod->getAttribute(AttributeKey::CLASS_NAME); - if (! is_string($class)) { + if ($methodName === null) { return false; } - if (! class_exists($class)) { + $scope = $classMethod->getAttribute(AttributeKey::SCOPE); + if (! $scope instanceof Scope) { return false; } - if (! is_string($methodName)) { + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { return false; } - if ($this->isMethodInParent($class, $methodName)) { - return true; + foreach ($classReflection->getParents() as $parentClassReflection) { + if ($parentClassReflection->hasMethod($methodName)) { + return true; + } } - $implementedInterfaces = (array) class_implements($class); - - foreach ($implementedInterfaces as $implementedInterface) { - /** @var string $implementedInterface */ - if (method_exists($implementedInterface, $methodName)) { + foreach ($classReflection->getInterfaces() as $interfaceReflection) { + if ($interfaceReflection->hasMethod($methodName)) { return true; } } @@ -156,7 +157,7 @@ public function isStaticClassMethod(ClassMethod $classMethod): bool /** * @param string[] $possibleNames */ - public function addMethodParameterIfMissing(Node $node, string $type, array $possibleNames): string + public function addMethodParameterIfMissing(Node $node, ObjectType $objectType, array $possibleNames): string { $classMethodNode = $node->getAttribute(AttributeKey::METHOD_NODE); if (! $classMethodNode instanceof ClassMethod) { @@ -165,7 +166,7 @@ public function addMethodParameterIfMissing(Node $node, string $type, array $pos } foreach ($classMethodNode->params as $paramNode) { - if (! $this->nodeTypeResolver->isObjectType($paramNode, new ObjectType($type))) { + if (! $this->nodeTypeResolver->isObjectType($paramNode, $objectType)) { continue; } @@ -178,7 +179,9 @@ public function addMethodParameterIfMissing(Node $node, string $type, array $pos } $paramName = $this->resolveName($classMethodNode, $possibleNames); - $classMethodNode->params[] = new Param(new Variable($paramName), null, new FullyQualified($type)); + $classMethodNode->params[] = new Param(new Variable($paramName), null, new FullyQualified( + $objectType->getClassName() + )); return $paramName; } @@ -195,18 +198,6 @@ public function isPropertyPromotion(ClassMethod $classMethod): bool return false; } - private function isMethodInParent(string $class, string $method): bool - { - foreach ((array) class_parents($class) as $parentClass) { - /** @var string $parentClass */ - if (method_exists($parentClass, $method)) { - return true; - } - } - - return false; - } - /** * @param string[] $possibleNames */ diff --git a/src/NodeManipulator/IfManipulator.php b/src/NodeManipulator/IfManipulator.php index c221eaaece56..9ce95d7e4f1f 100644 --- a/src/NodeManipulator/IfManipulator.php +++ b/src/NodeManipulator/IfManipulator.php @@ -290,6 +290,9 @@ public function collectNestedIfsWithNonBreaking(Foreach_ $foreach): array return $ifs; } + /** + * @param class-string $className + */ public function isIfWithOnly(Node $node, string $className): bool { if (! $node instanceof If_) { @@ -402,6 +405,9 @@ private function isIfWithOnlyStmtIf(If_ $if): bool return $this->hasOnlyStmtOfType($if, If_::class); } + /** + * @param class-string $desiredType + */ private function hasOnlyStmtOfType(If_ $if, string $desiredType): bool { $stmts = $if->stmts; diff --git a/src/PHPStan/Reflection/CallReflectionResolver.php b/src/PHPStan/Reflection/CallReflectionResolver.php index ff271f676bad..d43cbca3df16 100644 --- a/src/PHPStan/Reflection/CallReflectionResolver.php +++ b/src/PHPStan/Reflection/CallReflectionResolver.php @@ -15,8 +15,8 @@ use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ParametersAcceptor; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\ReflectionProvider; +use PHPStan\Type\ObjectType; use PHPStan\Type\TypeWithClassName; use PHPStan\Type\UnionType; use Rector\Core\PHPStan\Reflection\TypeToCallReflectionResolver\TypeToCallReflectionResolverRegistry; @@ -102,23 +102,7 @@ public function resolveParametersAcceptor($reflection, Node $node): ?ParametersA return null; } - $variants = $reflection->getVariants(); - $nbVariants = count($variants); - - if ($nbVariants === 0) { - return null; - } - - if ($nbVariants === 1) { - return ParametersAcceptorSelector::selectSingle($variants); - } - - $scope = $node->getAttribute(AttributeKey::SCOPE); - if (! $scope instanceof Scope) { - return null; - } - - return ParametersAcceptorSelector::selectFromArgs($scope, $node->args, $variants); + return $reflection->getVariants()[0]; } private function matchConstructorMethodInUnionType(UnionType $unionType, Scope $scope): ?MethodReflection @@ -169,15 +153,24 @@ private function resolveMethodCall(Node $node): ?MethodReflection return null; } - $classType = $this->nodeTypeResolver->resolve($node instanceof MethodCall ? $node->var : $node->class); $methodName = $this->nodeNameResolver->getName($node->name); if ($methodName === null) { return null; } - if (! $classType->hasMethod($methodName)->yes()) { - return null; + + $classType = $this->nodeTypeResolver->resolve($node instanceof MethodCall ? $node->var : $node->class); + + if ($classType instanceof ObjectType) { + if (! $this->reflectionProvider->hasClass($classType->getClassName())) { + return null; + } + + $classReflection = $this->reflectionProvider->getClass($classType->getClassName()); + if ($classReflection->hasMethod($methodName)) { + return $classReflection->getMethod($methodName, $scope); + } } - return $classType->getMethod($methodName, $scope); + return null; } } diff --git a/src/PhpParser/Node/NodeFactory.php b/src/PhpParser/Node/NodeFactory.php index bedb7490d76b..4807bb7449f6 100644 --- a/src/PhpParser/Node/NodeFactory.php +++ b/src/PhpParser/Node/NodeFactory.php @@ -594,7 +594,7 @@ public function createClassConstFetchFromName(Name $className, string $constantN } /** - * @param NotIdentical[]|BooleanAnd[] $newNodes + * @param array $newNodes */ public function createReturnBooleanAnd(array $newNodes): ?Expr { diff --git a/src/PhpParser/Node/Value/ValueResolver.php b/src/PhpParser/Node/Value/ValueResolver.php index 748b2738f5da..73511a751141 100644 --- a/src/PhpParser/Node/Value/ValueResolver.php +++ b/src/PhpParser/Node/Value/ValueResolver.php @@ -13,6 +13,7 @@ use PhpParser\Node\Expr\ConstFetch; use PhpParser\Node\Scalar\MagicConst\Dir; use PhpParser\Node\Scalar\MagicConst\File; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\ConstantScalarType; use Rector\Core\Exception\ShouldNotHappenException; @@ -20,8 +21,6 @@ use Rector\NodeNameResolver\NodeNameResolver; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\NodeTypeResolver\NodeTypeResolver; -use ReflectionClass; -use Symplify\PackageBuilder\Reflection\ClassLikeExistenceChecker; use Symplify\SmartFileSystem\SmartFileInfo; /** @@ -50,20 +49,20 @@ final class ValueResolver private $constFetchAnalyzer; /** - * @var ClassLikeExistenceChecker + * @var ReflectionProvider */ - private $classLikeExistenceChecker; + private $reflectionProvider; public function __construct( NodeNameResolver $nodeNameResolver, NodeTypeResolver $nodeTypeResolver, ConstFetchAnalyzer $constFetchAnalyzer, - ClassLikeExistenceChecker $classLikeExistenceChecker + ReflectionProvider $reflectionProvider ) { $this->nodeNameResolver = $nodeNameResolver; $this->nodeTypeResolver = $nodeTypeResolver; $this->constFetchAnalyzer = $constFetchAnalyzer; - $this->classLikeExistenceChecker = $classLikeExistenceChecker; + $this->reflectionProvider = $reflectionProvider; } /** @@ -292,11 +291,12 @@ private function resolveClassConstFetch(ClassConstFetch $classConstFetch) return constant($classConstantReference); } - if ($this->classLikeExistenceChecker->doesClassLikeExist($class)) { - $reflectionClass = new ReflectionClass($class); - $reflectionClassHasConstant = $reflectionClass->hasConstant($constant); - if ($reflectionClassHasConstant) { - return $reflectionClass->getConstant($constant); + if ($this->reflectionProvider->hasClass($class)) { + $classReflection = $this->reflectionProvider->getClass($class); + + if ($classReflection->hasConstant($constant)) { + $constantReflection = $classReflection->getConstant($constant); + return $constantReflection->getValue(); } } diff --git a/src/PhpParser/Parser/FunctionLikeParser.php b/src/PhpParser/Parser/FunctionLikeParser.php index 57d2f8d9f804..7adafa169acb 100644 --- a/src/PhpParser/Parser/FunctionLikeParser.php +++ b/src/PhpParser/Parser/FunctionLikeParser.php @@ -6,11 +6,11 @@ use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; -use PhpParser\Node\Stmt\Namespace_; use PhpParser\NodeFinder; use PhpParser\Parser; use PHPStan\Reflection\MethodReflection; -use ReflectionFunction; +use Rector\NodeTypeResolver\NodeScopeAndMetadataDecorator; +use Symplify\SmartFileSystem\SmartFileInfo; use Symplify\SmartFileSystem\SmartFileSystem; final class FunctionLikeParser @@ -30,33 +30,21 @@ final class FunctionLikeParser */ private $nodeFinder; - public function __construct(Parser $parser, SmartFileSystem $smartFileSystem, NodeFinder $nodeFinder) - { + /** + * @var NodeScopeAndMetadataDecorator + */ + private $nodeScopeAndMetadataDecorator; + + public function __construct( + Parser $parser, + SmartFileSystem $smartFileSystem, + NodeFinder $nodeFinder, + NodeScopeAndMetadataDecorator $nodeScopeAndMetadataDecorator + ) { $this->parser = $parser; $this->smartFileSystem = $smartFileSystem; $this->nodeFinder = $nodeFinder; - } - - public function parseFunction(ReflectionFunction $reflectionFunction): ?Namespace_ - { - $fileName = $reflectionFunction->getFileName(); - if (! is_string($fileName)) { - return null; - } - - $functionCode = $this->smartFileSystem->readFile($fileName); - if (! is_string($functionCode)) { - return null; - } - - $nodes = (array) $this->parser->parse($functionCode); - - $firstNode = $nodes[0] ?? null; - if (! $firstNode instanceof Namespace_) { - return null; - } - - return $firstNode; + $this->nodeScopeAndMetadataDecorator = $nodeScopeAndMetadataDecorator; } public function parseMethodReflection(MethodReflection $methodReflection): ?ClassMethod @@ -74,6 +62,7 @@ public function parseMethodReflection(MethodReflection $methodReflection): ?Clas } $nodes = (array) $this->parser->parse($fileContent); + $nodes = $this->nodeScopeAndMetadataDecorator->decorateNodesFromFile($nodes, new SmartFileInfo($fileName)); $class = $this->nodeFinder->findFirstInstanceOf($nodes, Class_::class); if (! $class instanceof Class_) { diff --git a/src/Rector/AbstractRector.php b/src/Rector/AbstractRector.php index 9e649ea713f5..e1c2903abf52 100644 --- a/src/Rector/AbstractRector.php +++ b/src/Rector/AbstractRector.php @@ -6,6 +6,9 @@ use Rector\Core\Contract\Rector\CorePhpRectorInterface; +/** + * @see \Rector\Testing\PHPUnit\AbstractRectorTestCase + */ abstract class AbstractRector extends AbstractTemporaryRector implements CorePhpRectorInterface { } diff --git a/src/Rector/AbstractTemporaryRector.php b/src/Rector/AbstractTemporaryRector.php index ca651f0eaa45..384e2601aa96 100644 --- a/src/Rector/AbstractTemporaryRector.php +++ b/src/Rector/AbstractTemporaryRector.php @@ -4,7 +4,6 @@ namespace Rector\Core\Rector; -use PhpParser\Comment\Doc; use PhpParser\Node; use PhpParser\Node\Arg; use PhpParser\Node\Expr; @@ -297,6 +296,7 @@ final public function enterNode(Node $node) $originalNode = $node->getAttribute(AttributeKey::ORIGINAL_NODE) ?? clone $node; $originalNodeWithAttributes = clone $node; + $node = $this->refactor($node); // nothing to change → continue @@ -353,16 +353,15 @@ protected function isNumberType(Node $node): bool return $this->nodeTypeResolver->isNumberType($node); } - protected function isStaticType(Node $node, string $staticTypeClass): bool - { - return $this->nodeTypeResolver->isStaticType($node, $staticTypeClass); - } - protected function getStaticType(Node $node): Type { return $this->nodeTypeResolver->getStaticType($node); } + /** + * @deprecated + * Use getStaticType() instead, as single method to get types + */ protected function getObjectType(Node $node): Type { return $this->nodeTypeResolver->resolve($node); diff --git a/src/Reflection/ClassMethodReflectionFactory.php b/src/Reflection/ClassMethodReflectionFactory.php index 7c07dd6c67ea..bd136f7d959f 100644 --- a/src/Reflection/ClassMethodReflectionFactory.php +++ b/src/Reflection/ClassMethodReflectionFactory.php @@ -4,6 +4,7 @@ namespace Rector\Core\Reflection; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\IntersectionType; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; @@ -13,6 +14,16 @@ final class ClassMethodReflectionFactory { + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + public function createFromPHPStanTypeAndMethodName(Type $type, string $methodName): ?ReflectionMethod { if ($type instanceof ShortenedObjectType) { @@ -43,10 +54,17 @@ public function createFromPHPStanTypeAndMethodName(Type $type, string $methodNam public function createReflectionMethodIfExists(string $class, string $method): ?ReflectionMethod { - if (! method_exists($class, $method)) { + if (! $this->reflectionProvider->hasClass($class)) { + return null; + } + + $classReflection = $this->reflectionProvider->getClass($class); + + $reflectionClass = $classReflection->getNativeReflection(); + if (! $reflectionClass->hasMethod($method)) { return null; } - return new ReflectionMethod($class, $method); + return $reflectionClass->getMethod($method); } } diff --git a/src/Reflection/ClassReflectionToAstResolver.php b/src/Reflection/ClassReflectionToAstResolver.php index 807d604f3793..663d2dfc614b 100644 --- a/src/Reflection/ClassReflectionToAstResolver.php +++ b/src/Reflection/ClassReflectionToAstResolver.php @@ -8,6 +8,7 @@ use PhpParser\Node\Stmt\Class_; use PhpParser\Parser; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\ObjectType; use Rector\Core\PhpParser\Node\BetterNodeFinder; use Symplify\SmartFileSystem\SmartFileSystem; @@ -29,23 +30,31 @@ final class ClassReflectionToAstResolver */ private $betterNodeFinder; - public function __construct(Parser $parser, SmartFileSystem $smartFileSystem, BetterNodeFinder $betterNodeFinder) - { + /** + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct( + Parser $parser, + SmartFileSystem $smartFileSystem, + BetterNodeFinder $betterNodeFinder, + ReflectionProvider $reflectionProvider + ) { $this->parser = $parser; $this->smartFileSystem = $smartFileSystem; $this->betterNodeFinder = $betterNodeFinder; + $this->reflectionProvider = $reflectionProvider; } public function getClassFromObjectType(ObjectType $objectType): ?Class_ { - $classReflection = $objectType->getClassReflection(); - if (! $classReflection instanceof ClassReflection) { + if (! $this->reflectionProvider->hasClass($objectType->getClassName())) { return null; } - $className = $objectType->getClassName(); - - return $this->getClass($classReflection, $className); + $classReflection = $this->reflectionProvider->getClass($objectType->getClassName()); + return $this->getClass($classReflection, $objectType->getClassName()); } private function getClass(ClassReflection $classReflection, string $className): ?Class_ @@ -68,7 +77,6 @@ private function getClass(ClassReflection $classReflection, string $className): $reflectionClassName = $classReflection->getName(); foreach ($classes as $class) { - $shortClassName = $class->name; if ($reflectionClassName === $className) { return $class; } diff --git a/utils/doctrine-annotation-parser-syncer/src/Rector/StaticCall/RemoveAnnotationRegistryRegisterFileRector.php b/utils/doctrine-annotation-parser-syncer/src/Rector/StaticCall/RemoveAnnotationRegistryRegisterFileRector.php index 0e928fff14ee..0edb79b5276b 100644 --- a/utils/doctrine-annotation-parser-syncer/src/Rector/StaticCall/RemoveAnnotationRegistryRegisterFileRector.php +++ b/utils/doctrine-annotation-parser-syncer/src/Rector/StaticCall/RemoveAnnotationRegistryRegisterFileRector.php @@ -4,35 +4,15 @@ namespace Rector\Utils\DoctrineAnnotationParserSyncer\Rector\StaticCall; -use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\Common\Annotations\AnnotationRegistry; -use Doctrine\Common\Annotations\DocParser; use PhpParser\Node; use PhpParser\Node\Expr\StaticCall; -use Rector\Core\Rector\AbstractRector; +use PHPStan\Type\ObjectType; +use Rector\Core\Rector\AbstractTemporaryRector; use Rector\Utils\DoctrineAnnotationParserSyncer\Contract\Rector\ClassSyncerRectorInterface; -use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; -use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; -final class RemoveAnnotationRegistryRegisterFileRector extends AbstractRector implements ClassSyncerRectorInterface +final class RemoveAnnotationRegistryRegisterFileRector extends AbstractTemporaryRector implements ClassSyncerRectorInterface { - public function getRuleDefinition(): RuleDefinition - { - return new RuleDefinition( - 'Remove AnnotationRegistry::registerFile() that is now covered by composer autoload', - [ - new CodeSample( - <<<'CODE_SAMPLE' -AnnotationRegistry::registerFile() -CODE_SAMPLE -, - <<<'CODE_SAMPLE' -CODE_SAMPLE - ), - ] - ); - } - /** * @return array> */ @@ -46,7 +26,12 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - if (! $this->nodeNameResolver->isInClassNames($node, [DocParser::class, AnnotationReader::class])) { + $desiredObjectTypes = [ + new ObjectType('Doctrine\Common\Annotations\DocParser'), + new ObjectType('Doctrine\Common\Annotations\AnnotationReader'), + ]; + + if (! $this->nodeNameResolver->isInClassNames($node, $desiredObjectTypes)) { return null; } diff --git a/utils/phpstan-extensions/config/rector-rules.neon b/utils/phpstan-extensions/config/rector-rules.neon index 7e9c2bafdfc8..225ba239fd45 100644 --- a/utils/phpstan-extensions/config/rector-rules.neon +++ b/utils/phpstan-extensions/config/rector-rules.neon @@ -58,6 +58,10 @@ services: - 'getDocComment' - 'getComments' - 'setDocComment' + # relies on internal broker service that breaks sometimes; use RefletionProvider service instead + PHPStan\Type\ObjectType: + - 'getClassReflection' + - 'getAncestors' # use NodeTypeResolver instead # PHPStan\Analyser\Scope: # - 'getType' @@ -180,3 +184,34 @@ services: PhpParser\Builder\Param: Rector\Core\PhpParser\Builder\ParamBuilder PhpParser\Builder\Property: Rector\Core\PhpParser\Builder\PropertyBuilder PhpParser\Builder\TraitUse: Rector\Core\PhpParser\Builder\TraitUseBuilder + ReflectionClass: PHPStan\Reflection\ClassReflection + ReflectionFunction: PHPStan\Reflection\FunctionReflection + + - + class: Symplify\PHPStanRules\Rules\ForbiddenFuncCallRule + tags: [phpstan.rules.rule] + arguments: + forbiddenFunctions: + - 'd' + - 'dd' + - 'dump' + - 'var_dump' + - 'curl_*' + - 'extract' + - 'compact' + - 'spl_autoload_register' + - 'spl_autoload_unregister' + - array_walk + # creates messy nested logic + - array_filter + # there are handled in ReflectionProvider->has*() + - 'class_exists' + - 'interface_exists' + - 'method_exists' + - 'property_exists' + - 'function_exists' + - class_parents + - class_implements + - get_parent_class + - get_declared_classes +# - is_a diff --git a/utils/project-validator/src/Finder/FixtureFinder.php b/utils/project-validator/src/Finder/FixtureFinder.php index c50818c08d13..b488054f1c2d 100644 --- a/utils/project-validator/src/Finder/FixtureFinder.php +++ b/utils/project-validator/src/Finder/FixtureFinder.php @@ -51,6 +51,7 @@ public function findFixtureFileInfos(): array ->notPath('#keep_annotated\.php\.inc$#') ->notPath('#double_same_variable\.php\.inc$#') ->notName('#_\.php\.inc$#') + ->notName('#Fixture/without_namespace.php.inc$#') ->in(__DIR__ . '/../../../../tests') ->in(__DIR__ . '/../../../../packages') ->in(__DIR__ . '/../../../../rules');