Merge pull request #564 from rectorphp/controller-trait

[FrameworkBundle] Let GetToConstructorInjectionRector work with trait as well
This commit is contained in:
Tomáš Votruba 2018-08-09 13:36:05 +02:00 committed by GitHub
commit 2bbeffc74e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 233 additions and 80 deletions

View File

@ -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;

View File

@ -0,0 +1,57 @@
<?php declare(strict_types=1);
namespace Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\ClassAndInterfaceTypeResolver;
use Iterator;
use PhpParser\Node\Stmt\Class_;
use Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\AbstractNodeTypeResolverTest;
use Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\ClassAndInterfaceTypeResolver\Source\AnotherTrait;
use Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\ClassAndInterfaceTypeResolver\Source\ClassWithParentClass;
use Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\ClassAndInterfaceTypeResolver\Source\ClassWithParentInterface;
use Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\ClassAndInterfaceTypeResolver\Source\ClassWithParentTrait;
use Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\ClassAndInterfaceTypeResolver\Source\ClassWithTrait;
use Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\ClassAndInterfaceTypeResolver\Source\ParentClass;
use Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\ClassAndInterfaceTypeResolver\Source\SomeInterface;
/**
* @covers \Rector\NodeTypeResolver\PerNodeTypeResolver\ClassAndInterfaceTypeResolver
*/
final class ClassAndInterfaceTypeResolverTest extends AbstractNodeTypeResolverTest
{
/**
* @dataProvider dataProvider()
* @param string[] $expectedTypes
*/
public function test(string $file, int $nodePosition, array $expectedTypes): void
{
$variableNodes = $this->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],
];
}
}

View File

@ -1,6 +1,6 @@
<?php declare(strict_types=1);
namespace Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\ClassTypeResolver\Source;
namespace Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\ClassAndInterfaceTypeResolver\Source;
new class extends ParentClass implements SomeInterface
{

View File

@ -1,6 +1,6 @@
<?php declare(strict_types=1);
namespace Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\ClassTypeResolver\Source;
namespace Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\ClassAndInterfaceTypeResolver\Source;
trait AnotherTrait
{

View File

@ -1,6 +1,6 @@
<?php declare(strict_types=1);
namespace Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\ClassTypeResolver\Source;
namespace Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\ClassAndInterfaceTypeResolver\Source;
final class ClassWithParentClass extends ParentClass
{

View File

@ -1,6 +1,6 @@
<?php declare(strict_types=1);
namespace Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\ClassTypeResolver\Source;
namespace Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\ClassAndInterfaceTypeResolver\Source;
final class ClassWithParentInterface implements SomeInterface
{

View File

@ -0,0 +1,7 @@
<?php declare(strict_types=1);
namespace Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\ClassAndInterfaceTypeResolver\Source;
class ClassWithParentTrait extends ClassWithTrait
{
}

View File

@ -1,8 +1,8 @@
<?php declare(strict_types=1);
namespace Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\ClassTypeResolver\Source;
namespace Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\ClassAndInterfaceTypeResolver\Source;
final class ClassWithTrait
class ClassWithTrait
{
use AnotherTrait;
}

View File

@ -1,6 +1,6 @@
<?php declare(strict_types=1);
namespace Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\ClassTypeResolver\Source;
namespace Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\ClassAndInterfaceTypeResolver\Source;
class ParentClass
{

View File

@ -1,6 +1,6 @@
<?php declare(strict_types=1);
namespace Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\ClassTypeResolver\Source;
namespace Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\ClassAndInterfaceTypeResolver\Source;
interface SomeInterface
{

View File

@ -1,48 +0,0 @@
<?php declare(strict_types=1);
namespace Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\ClassTypeResolver;
use Iterator;
use PhpParser\Node\Stmt\Class_;
use Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\AbstractNodeTypeResolverTest;
use Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\ClassTypeResolver\Source\AnotherTrait;
use Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\ClassTypeResolver\Source\ClassWithParentClass;
use Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\ClassTypeResolver\Source\ClassWithParentInterface;
use Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\ClassTypeResolver\Source\ClassWithTrait;
use Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\ClassTypeResolver\Source\ParentClass;
use Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\ClassTypeResolver\Source\SomeInterface;
/**
* @covers \Rector\NodeTypeResolver\PerNodeTypeResolver\ClassTypeResolver
*/
final class ClassTypeResolverTest extends AbstractNodeTypeResolverTest
{
/**
* @dataProvider dataProvider()
* @param string[] $expectedTypes
*/
public function test(string $file, int $nodePosition, array $expectedTypes): void
{
$variableNodes = $this->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]];
}
}

View File

@ -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;
}

View File

@ -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');
}
}

View File

@ -0,0 +1,21 @@
<?php declare (strict_types=1);
use Rector\Symfony\Tests\Rector\FrameworkBundle\GetToConstructorInjectionRector\Source\GetTrait;
class ClassWithNamedServiceAndTrait
{
use GetTrait;
/**
* @var \Rector\Symfony\Tests\Rector\FrameworkBundle\AbstractToConstructorInjectionRectorSource\SomeTranslator
*/
private $someTranslator;
public function __construct(\Rector\Symfony\Tests\Rector\FrameworkBundle\AbstractToConstructorInjectionRectorSource\SomeTranslator $someTranslator)
{
$this->someTranslator = $someTranslator;
}
public function render()
{
$this->someTranslator;
}
}

View File

@ -0,0 +1,19 @@
<?php declare (strict_types=1);
use Rector\Symfony\Tests\Rector\FrameworkBundle\GetToConstructorInjectionRector\Source\ParentClassWithGetTrait;
class ClassWithNamedServiceAndParentTrait extends ParentClassWithGetTrait
{
/**
* @var \Rector\Symfony\Tests\Rector\FrameworkBundle\AbstractToConstructorInjectionRectorSource\SomeTranslator
*/
private $someTranslator;
public function __construct(\Rector\Symfony\Tests\Rector\FrameworkBundle\AbstractToConstructorInjectionRectorSource\SomeTranslator $someTranslator)
{
$this->someTranslator = $someTranslator;
}
public function render()
{
$this->someTranslator;
}
}

View File

@ -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

View File

@ -0,0 +1,13 @@
<?php declare(strict_types=1);
namespace Rector\Symfony\Tests\Rector\FrameworkBundle\GetToConstructorInjectionRector\Source;
trait GetTrait
{
/**
* @return object
*/
public function get(string $serviceName)
{
}
}

View File

@ -0,0 +1,9 @@
<?php declare(strict_types=1);
namespace Rector\Symfony\Tests\Rector\FrameworkBundle\GetToConstructorInjectionRector\Source;
class ParentClassWithGetTrait
{
use GetTrait;
}

View File

@ -0,0 +1,13 @@
<?php declare (strict_types=1);
use Rector\Symfony\Tests\Rector\FrameworkBundle\GetToConstructorInjectionRector\Source\GetTrait;
class ClassWithNamedServiceAndTrait
{
use GetTrait;
public function render()
{
$this->get('translator');
}
}

View File

@ -0,0 +1,11 @@
<?php declare (strict_types=1);
use Rector\Symfony\Tests\Rector\FrameworkBundle\GetToConstructorInjectionRector\Source\ParentClassWithGetTrait;
class ClassWithNamedServiceAndParentTrait extends ParentClassWithGetTrait
{
public function render()
{
$this->get('translator');
}
}

View File

@ -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'

View File

@ -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);
}
}