diff --git a/packages/NodeTypeResolver/src/Reflection/ClassReflectionTypesResolver.php b/packages/NodeTypeResolver/src/Reflection/ClassReflectionTypesResolver.php index 02f7718ea71..365af017932 100644 --- a/packages/NodeTypeResolver/src/Reflection/ClassReflectionTypesResolver.php +++ b/packages/NodeTypeResolver/src/Reflection/ClassReflectionTypesResolver.php @@ -2,10 +2,21 @@ namespace Rector\NodeTypeResolver\Reflection; +use PHPStan\Broker\Broker; use PHPStan\Reflection\ClassReflection; final class ClassReflectionTypesResolver { + /** + * @var Broker + */ + private $broker; + + public function __construct(Broker $broker) + { + $this->broker = $broker; + } + /** * @return string[] */ @@ -21,13 +32,22 @@ final class ClassReflectionTypesResolver $types = array_merge($types, $classReflection->getParentClassesNames()); // interfaces - foreach ($classReflection->getInterfaces() as $classReflection) { - $types[] = $classReflection->getName(); + foreach ($classReflection->getInterfaces() as $interfaceReflection) { + $types[] = $interfaceReflection->getName(); } // traits - foreach ($classReflection->getTraits() as $classReflection) { - $types[] = $classReflection->getName(); + foreach ($classReflection->getTraits() as $traitReflection) { + $types[] = $traitReflection->getName(); + } + + // to cover traits of parent classes + foreach ($classReflection->getParentClassesNames() as $parentClassName) { + $parentClassReflection = $this->broker->getClass($parentClassName); + + foreach ($parentClassReflection->getTraits() as $parentClassTrait) { + $types[] = $parentClassTrait->getName(); + } } return $types; diff --git a/packages/NodeTypeResolver/tests/PerNodeTypeResolver/ClassAndInterfaceTypeResolver/ClassAndInterfaceTypeResolverTest.php b/packages/NodeTypeResolver/tests/PerNodeTypeResolver/ClassAndInterfaceTypeResolver/ClassAndInterfaceTypeResolverTest.php new file mode 100644 index 00000000000..a3ca5a0723e --- /dev/null +++ b/packages/NodeTypeResolver/tests/PerNodeTypeResolver/ClassAndInterfaceTypeResolver/ClassAndInterfaceTypeResolverTest.php @@ -0,0 +1,57 @@ +getNodesForFileOfType($file, Class_::class); + + $this->assertSame($expectedTypes, $this->nodeTypeResolver->resolve($variableNodes[$nodePosition])); + } + + public function dataProvider(): Iterator + { + yield [__DIR__ . '/Source/ClassWithParentInterface.php', 0, [ + ClassWithParentInterface::class, + SomeInterface::class, + ]]; + + yield [__DIR__ . '/Source/ClassWithParentClass.php', 0, [ + ClassWithParentClass::class, + ParentClass::class, + ]]; + + yield [__DIR__ . '/Source/ClassWithTrait.php', 0, [ClassWithTrait::class, AnotherTrait::class]]; + + yield [ + __DIR__ . '/Source/ClassWithParentTrait.php', + 0, + [ClassWithParentTrait::class, ClassWithTrait::class, AnotherTrait::class], + ]; + yield [ + __DIR__ . '/Source/AnonymousClass.php', + 0, + [ParentClass::class, SomeInterface::class, AnotherTrait::class], + ]; + } +} diff --git a/packages/NodeTypeResolver/tests/PerNodeTypeResolver/ClassTypeResolver/Source/AnonymousClass.php b/packages/NodeTypeResolver/tests/PerNodeTypeResolver/ClassAndInterfaceTypeResolver/Source/AnonymousClass.php similarity index 84% rename from packages/NodeTypeResolver/tests/PerNodeTypeResolver/ClassTypeResolver/Source/AnonymousClass.php rename to packages/NodeTypeResolver/tests/PerNodeTypeResolver/ClassAndInterfaceTypeResolver/Source/AnonymousClass.php index aa481ca23aa..ca915ae9ccd 100644 --- a/packages/NodeTypeResolver/tests/PerNodeTypeResolver/ClassTypeResolver/Source/AnonymousClass.php +++ b/packages/NodeTypeResolver/tests/PerNodeTypeResolver/ClassAndInterfaceTypeResolver/Source/AnonymousClass.php @@ -1,6 +1,6 @@ getNodesForFileOfType($file, Class_::class); - - $this->assertSame($expectedTypes, $this->nodeTypeResolver->resolve($variableNodes[$nodePosition])); - } - - public function dataProvider(): Iterator - { - yield [__DIR__ . '/Source/ClassWithParentInterface.php', 0, [ - ClassWithParentInterface::class, - SomeInterface::class, - ]]; - - yield [__DIR__ . '/Source/ClassWithParentClass.php', 0, [ - ClassWithParentClass::class, - ParentClass::class, - ]]; - - yield [__DIR__ . '/Source/ClassWithTrait.php', 0, [ClassWithTrait::class, AnotherTrait::class]]; - - // traits in anonymous classes are ignored in PHPStan - yield [__DIR__ . '/Source/AnonymousClass.php', 0, [ParentClass::class, SomeInterface::class]]; - } -} diff --git a/packages/Symfony/src/Bridge/DependencyInjection/ContainerFactory.php b/packages/Symfony/src/Bridge/DependencyInjection/ContainerFactory.php index 32dbe39b495..64743d00cc7 100644 --- a/packages/Symfony/src/Bridge/DependencyInjection/ContainerFactory.php +++ b/packages/Symfony/src/Bridge/DependencyInjection/ContainerFactory.php @@ -25,7 +25,7 @@ final class ContainerFactory } /** - * Mimics https://github.com/symfony/symfony/blob/226e2f3949c5843b67826aca4839c2c6b95743cf/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php#L200-L203 + * Mimics https://github.com/symfony/symfony/blob/f834c9262b411aa5793fcea23694e3ad3b5acbb4/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php#L200-L203 */ private function createContainerFromKernelClass(string $kernelClass): Container { @@ -35,6 +35,7 @@ final class ContainerFactory /** @var ContainerBuilder $containerBuilder */ $containerBuilder = (new PrivatesCaller())->callPrivateMethod($kernel, 'buildContainer'); + $containerBuilder->getCompilerPassConfig()->setRemovingPasses([]); // anonymous class on intention, since this depends on Symfony\DependencyInjection in rector-prefixed $containerBuilder->getCompilerPassConfig()->addPass(new class() implements CompilerPassInterface { @@ -49,6 +50,8 @@ final class ContainerFactory } }); + $containerBuilder->compile(); + return $containerBuilder; } diff --git a/packages/Symfony/src/Rector/FrameworkBundle/GetToConstructorInjectionRector.php b/packages/Symfony/src/Rector/FrameworkBundle/GetToConstructorInjectionRector.php index ee476ad5c48..40285a2a8bf 100644 --- a/packages/Symfony/src/Rector/FrameworkBundle/GetToConstructorInjectionRector.php +++ b/packages/Symfony/src/Rector/FrameworkBundle/GetToConstructorInjectionRector.php @@ -14,9 +14,17 @@ final class GetToConstructorInjectionRector extends AbstractToConstructorInjecti */ private $controllerClass; - public function __construct(string $controllerClass = 'Symfony\Bundle\FrameworkBundle\Controller\Controller') - { + /** + * @var string + */ + private $traitClass; + + public function __construct( + string $controllerClass = 'Symfony\Bundle\FrameworkBundle\Controller\Controller', + string $traitClass = 'Symfony\Bundle\FrameworkBundle\Controller\ControllerTrait' + ) { $this->controllerClass = $controllerClass; + $this->traitClass = $traitClass; } public function getDefinition(): RectorDefinition @@ -61,6 +69,6 @@ CODE_SAMPLE return false; } - return $this->methodCallAnalyzer->isTypeAndMethod($node, $this->controllerClass, 'get'); + return $this->methodCallAnalyzer->isTypesAndMethod($node, [$this->controllerClass, $this->traitClass], 'get'); } } diff --git a/packages/Symfony/tests/Rector/FrameworkBundle/GetToConstructorInjectionRector/Correct/correct3.php.inc b/packages/Symfony/tests/Rector/FrameworkBundle/GetToConstructorInjectionRector/Correct/correct3.php.inc new file mode 100644 index 00000000000..69de8198e59 --- /dev/null +++ b/packages/Symfony/tests/Rector/FrameworkBundle/GetToConstructorInjectionRector/Correct/correct3.php.inc @@ -0,0 +1,21 @@ +someTranslator = $someTranslator; + } + + public function render() + { + $this->someTranslator; + } +} diff --git a/packages/Symfony/tests/Rector/FrameworkBundle/GetToConstructorInjectionRector/Correct/correct4.php.inc b/packages/Symfony/tests/Rector/FrameworkBundle/GetToConstructorInjectionRector/Correct/correct4.php.inc new file mode 100644 index 00000000000..f1d12cfb5c7 --- /dev/null +++ b/packages/Symfony/tests/Rector/FrameworkBundle/GetToConstructorInjectionRector/Correct/correct4.php.inc @@ -0,0 +1,19 @@ +someTranslator = $someTranslator; + } + public function render() + { + $this->someTranslator; + } +} diff --git a/packages/Symfony/tests/Rector/FrameworkBundle/GetToConstructorInjectionRector/GetToConstructorInjectionRectorTest.php b/packages/Symfony/tests/Rector/FrameworkBundle/GetToConstructorInjectionRector/GetToConstructorInjectionRectorTest.php index f06ac6e4f82..a11b9b05063 100644 --- a/packages/Symfony/tests/Rector/FrameworkBundle/GetToConstructorInjectionRector/GetToConstructorInjectionRectorTest.php +++ b/packages/Symfony/tests/Rector/FrameworkBundle/GetToConstructorInjectionRector/GetToConstructorInjectionRectorTest.php @@ -29,6 +29,8 @@ final class GetToConstructorInjectionRectorTest extends AbstractRectorTestCase { yield [__DIR__ . '/Wrong/wrong.php.inc', __DIR__ . '/Correct/correct.php.inc']; yield [__DIR__ . '/Wrong/wrong2.php.inc', __DIR__ . '/Correct/correct2.php.inc']; + yield [__DIR__ . '/Wrong/wrong3.php.inc', __DIR__ . '/Correct/correct3.php.inc']; + yield [__DIR__ . '/Wrong/wrong4.php.inc', __DIR__ . '/Correct/correct4.php.inc']; } protected function provideConfig(): string diff --git a/packages/Symfony/tests/Rector/FrameworkBundle/GetToConstructorInjectionRector/Source/GetTrait.php b/packages/Symfony/tests/Rector/FrameworkBundle/GetToConstructorInjectionRector/Source/GetTrait.php new file mode 100644 index 00000000000..537f7b266a5 --- /dev/null +++ b/packages/Symfony/tests/Rector/FrameworkBundle/GetToConstructorInjectionRector/Source/GetTrait.php @@ -0,0 +1,13 @@ +get('translator'); + } +} diff --git a/packages/Symfony/tests/Rector/FrameworkBundle/GetToConstructorInjectionRector/Wrong/wrong4.php.inc b/packages/Symfony/tests/Rector/FrameworkBundle/GetToConstructorInjectionRector/Wrong/wrong4.php.inc new file mode 100644 index 00000000000..1404a918731 --- /dev/null +++ b/packages/Symfony/tests/Rector/FrameworkBundle/GetToConstructorInjectionRector/Wrong/wrong4.php.inc @@ -0,0 +1,11 @@ +get('translator'); + } +} diff --git a/packages/Symfony/tests/Rector/FrameworkBundle/GetToConstructorInjectionRector/config.yml b/packages/Symfony/tests/Rector/FrameworkBundle/GetToConstructorInjectionRector/config.yml index b97090fb8cb..54b7c462082 100644 --- a/packages/Symfony/tests/Rector/FrameworkBundle/GetToConstructorInjectionRector/config.yml +++ b/packages/Symfony/tests/Rector/FrameworkBundle/GetToConstructorInjectionRector/config.yml @@ -4,3 +4,4 @@ parameters: services: Rector\Symfony\Rector\FrameworkBundle\GetToConstructorInjectionRector: $controllerClass: 'Rector\Symfony\Tests\Rector\Source\SymfonyController' + $traitClass: 'Rector\Symfony\Tests\Rector\FrameworkBundle\GetToConstructorInjectionRector\Source\GetTrait' diff --git a/src/NodeAnalyzer/MethodCallAnalyzer.php b/src/NodeAnalyzer/MethodCallAnalyzer.php index 8c5021e5ccb..8802e1885a3 100644 --- a/src/NodeAnalyzer/MethodCallAnalyzer.php +++ b/src/NodeAnalyzer/MethodCallAnalyzer.php @@ -30,7 +30,7 @@ final class MethodCallAnalyzer */ public function isTypeAndMethods(Node $node, string $type, array $methods): bool { - if (! $this->isType($node, $type)) { + if (! $this->isTypes($node, [$type])) { return false; } @@ -45,7 +45,24 @@ final class MethodCallAnalyzer */ public function isTypeAndMethod(Node $node, string $type, string $method): bool { - if (! $this->isType($node, $type)) { + if (! $this->isTypes($node, [$type])) { + return false; + } + + /** @var Identifier $methodName */ + $methodName = $node->name; + + return $methodName->toString() === $method; + } + + /** + * Checks "$this->classOfSpecificType->specificMethodName()" + * + * @param string[] $types + */ + public function isTypesAndMethod(Node $node, array $types, string $method): bool + { + if (! $this->isTypes($node, $types)) { return false; } @@ -87,20 +104,6 @@ final class MethodCallAnalyzer return in_array($node->name->name, $methods, true); } - /** - * Checks "$this->methodCall()" - */ - public function isType(Node $node, string $type): bool - { - if (! $node instanceof MethodCall) { - return false; - } - - $calledNodeTypes = $this->nodeTypeResolver->resolve($node->var); - - return in_array($type, $calledNodeTypes, true); - } - /** * @param string[] $types * @return string[] @@ -115,4 +118,18 @@ final class MethodCallAnalyzer return array_intersect($nodeTypes, $types) ? $nodeTypes : null; } + + /** + * @param string[] $types + */ + private function isTypes(Node $node, array $types): bool + { + if (! $node instanceof MethodCall) { + return false; + } + + $calledNodeTypes = $this->nodeTypeResolver->resolve($node->var); + + return (bool) array_intersect($types, $calledNodeTypes); + } }