[PHPUnit][Symfony] Add jakzal-injetor Rector

This commit is contained in:
TomasVotruba 2019-11-17 14:57:45 +01:00
parent 47025a95ee
commit 5f523b5727
30 changed files with 678 additions and 160 deletions

View File

@ -0,0 +1,2 @@
services:
Rector\PHPUnit\Rector\Class_\SelfContainerGetMethodCallFromTestToInjectPropertyRector: ~

View File

@ -20,6 +20,7 @@ use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
use Rector\ValueObject\PhpVersionFeature;
/**
* @see https://3v4l.org/GL6II
@ -210,7 +211,7 @@ PHP
$propertyBuilder->makePublic();
$property = $propertyBuilder->getNode();
if ($this->isAtLeastPhpVersion('7.4')) {
if ($this->isAtLeastPhpVersion(PhpVersionFeature::TYPED_PROPERTIES)) {
$phpStanNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($propertyType);
if ($phpStanNode !== null) {
$property->type = $phpStanNode;

View File

@ -7,6 +7,7 @@ namespace Rector\CodeQuality\Tests\Rector\Class_\CompleteDynamicPropertiesRector
use Iterator;
use Rector\CodeQuality\Rector\Class_\CompleteDynamicPropertiesRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Rector\ValueObject\PhpVersionFeature;
final class CompleteDynamicPropertiesRectorTest extends AbstractRectorTestCase
{
@ -30,7 +31,6 @@ final class CompleteDynamicPropertiesRectorTest extends AbstractRectorTestCase
protected function getPhpVersion(): string
{
// prevents union types
return '7.4';
return PhpVersionFeature::BEFORE_UNION_TYPES;
}
}

View File

@ -0,0 +1,167 @@
<?php
declare(strict_types=1);
namespace Rector\PHPUnit\Manipulator;
use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\Class_;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\PhpParser\Node\Commander\NodeRemovingCommander;
use Rector\PhpParser\Node\Resolver\NameResolver;
use Rector\PhpParser\Node\Value\ValueResolver;
use Rector\PhpParser\NodeTraverser\CallableNodeTraverser;
use Rector\SymfonyPHPUnit\Naming\ServiceNaming;
use Rector\SymfonyPHPUnit\Node\KernelTestCaseNodeAnalyzer;
final class OnContainerGetCallManipulator
{
/**
* @var NameResolver
*/
private $nameResolver;
/**
* @var CallableNodeTraverser
*/
private $callableNodeTraverser;
/**
* @var ServiceNaming
*/
private $serviceNaming;
/**
* @var NodeRemovingCommander
*/
private $nodeRemovingCommander;
/**
* @var KernelTestCaseNodeAnalyzer
*/
private $kernelTestCaseNodeAnalyzer;
/**
* @var ValueResolver
*/
private $valueResolver;
public function __construct(
NameResolver $nameResolver,
CallableNodeTraverser $callableNodeTraverser,
ServiceNaming $serviceNaming,
NodeRemovingCommander $nodeRemovingCommander,
KernelTestCaseNodeAnalyzer $kernelTestCaseNodeAnalyzer,
ValueResolver $valueResolver
) {
$this->nameResolver = $nameResolver;
$this->callableNodeTraverser = $callableNodeTraverser;
$this->serviceNaming = $serviceNaming;
$this->nodeRemovingCommander = $nodeRemovingCommander;
$this->kernelTestCaseNodeAnalyzer = $kernelTestCaseNodeAnalyzer;
$this->valueResolver = $valueResolver;
}
/**
* E.g. $someService
* $this->someService
*
* @param string[][] $formerVariablesByMethods
*/
public function replaceFormerVariablesWithPropertyFetch(Class_ $class, array $formerVariablesByMethods): void
{
$this->callableNodeTraverser->traverseNodesWithCallable($class->stmts, function (Node $node) use (
$formerVariablesByMethods
): ?PropertyFetch {
if (! $node instanceof Variable) {
return null;
}
$variableName = $this->nameResolver->getName($node);
if ($variableName === null) {
return null;
}
/** @var string $methodName */
$methodName = $node->getAttribute(AttributeKey::METHOD_NAME);
if (! isset($formerVariablesByMethods[$methodName][$variableName])) {
return null;
}
$serviceType = $formerVariablesByMethods[$methodName][$variableName];
$propertyName = $this->serviceNaming->resolvePropertyNameFromServiceType($serviceType);
return new PropertyFetch(new Variable('this'), $propertyName);
});
}
/**
* @return string[][]
*/
public function removeAndCollectFormerAssignedVariables(Class_ $class, bool $skipSetUpMethod = true): array
{
$formerVariablesByMethods = [];
$this->callableNodeTraverser->traverseNodesWithCallable($class->stmts, function (Node $node) use (
&$formerVariablesByMethods,
$skipSetUpMethod
): ?PropertyFetch {
if (! $node instanceof MethodCall) {
return null;
}
if ($skipSetUpMethod) {
if ($this->kernelTestCaseNodeAnalyzer->isSetUpOrEmptyMethod($node)) {
return null;
}
}
if (! $this->kernelTestCaseNodeAnalyzer->isOnContainerGetMethodCall($node)) {
return null;
}
$type = $this->valueResolver->getValue($node->args[0]->value);
if ($type === null) {
return null;
}
$parentNode = $node->getAttribute(AttributeKey::PARENT_NODE);
if ($parentNode instanceof Assign) {
$this->processAssign($node, $parentNode, $type, $formerVariablesByMethods);
return null;
}
$propertyName = $this->serviceNaming->resolvePropertyNameFromServiceType($type);
return new PropertyFetch(new Variable('this'), $propertyName);
});
return $formerVariablesByMethods;
}
/**
* @param string[][] $formerVariablesByMethods
*/
private function processAssign(
MethodCall $methodCall,
Assign $assign,
string $type,
array &$formerVariablesByMethods
): void {
$variableName = $this->nameResolver->getName($assign->var);
if ($variableName === null) {
return;
}
/** @var string $methodName */
$methodName = $methodCall->getAttribute(AttributeKey::METHOD_NAME);
$formerVariablesByMethods[$methodName][$variableName] = $type;
$this->nodeRemovingCommander->addNode($assign);
}
}

View File

@ -0,0 +1,171 @@
<?php
declare(strict_types=1);
namespace Rector\PHPUnit\Rector\Class_;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Property;
use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode;
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\AttributeAwarePhpDocTagNode;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\PhpParser\Node\Manipulator\ClassManipulator;
use Rector\PHPUnit\Manipulator\OnContainerGetCallManipulator;
use Rector\Rector\AbstractPHPUnitRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
use Rector\SymfonyPHPUnit\Node\KernelTestCaseNodeFactory;
use Rector\SymfonyPHPUnit\Rector\Class_\SelfContainerGetMethodCallFromTestToSetUpMethodRector;
use Rector\SymfonyPHPUnit\SelfContainerMethodCallCollector;
/**
* Inspiration
* @see SelfContainerGetMethodCallFromTestToSetUpMethodRector
*
* @see https://github.com/shopsys/shopsys/pull/1392
* @see https://github.com/jakzal/phpunit-injector
*
* @see \Rector\PHPUnit\Tests\Rector\Class_\SelfContainerGetMethodCallFromTestToInjectPropertyRector\SelfContainerGetMethodCallFromTestToInjectPropertyRectorTest
*/
final class SelfContainerGetMethodCallFromTestToInjectPropertyRector extends AbstractPHPUnitRector
{
/**
* @var SelfContainerMethodCallCollector
*/
private $selfContainerMethodCallCollector;
/**
* @var KernelTestCaseNodeFactory
*/
private $kernelTestCaseNodeFactory;
/**
* @var OnContainerGetCallManipulator
*/
private $onContainerGetCallManipulator;
/**
* @var ClassManipulator
*/
private $classManipulator;
public function __construct(
SelfContainerMethodCallCollector $selfContainerMethodCallCollector,
KernelTestCaseNodeFactory $kernelTestCaseNodeFactory,
OnContainerGetCallManipulator $onContainerGetCallManipulator,
ClassManipulator $classManipulator
) {
$this->selfContainerMethodCallCollector = $selfContainerMethodCallCollector;
$this->kernelTestCaseNodeFactory = $kernelTestCaseNodeFactory;
$this->onContainerGetCallManipulator = $onContainerGetCallManipulator;
$this->classManipulator = $classManipulator;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition(
'Change $container->get() calls in PHPUnit to @inject properties autowired by jakzal/phpunit-injector',
[
new CodeSample(
<<<'PHP'
use PHPUnit\Framework\TestCase;
class SomeClassTest extends TestCase {
public function test()
{
$someService = $this->getContainer()->get(SomeService::class);
}
}
class SomeService { }
PHP
,
<<<'PHP'
use PHPUnit\Framework\TestCase;
class SomeClassTest extends TestCase {
/**
* @var SomeService
* @inject
*/
private $someService;
public function test()
{
$someService = $this->someService;
}
}
class SomeService { }
PHP
),
]
);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Class_::class];
}
/**
* @param Class_ $node
*/
public function refactor(Node $node): ?Node
{
if (! $this->isInTestClass($node)) {
return null;
}
// 1. find self::$container->get(x)
$serviceTypes = $this->selfContainerMethodCallCollector->collectContainerGetServiceTypes($node, false);
if (count($serviceTypes) === 0) {
return null;
}
// 2,5 - add @inject to existing properties of that type, to prevent re-adding them
foreach ($serviceTypes as $key => $serviceType) {
$existingProperty = $this->classManipulator->findPropertyByType($node, $serviceType);
if ($existingProperty !== null) {
$this->addInjectAnnotationToProperty($existingProperty);
unset($serviceTypes[$key]);
}
}
// 2. create private properties with this types
$privateProperties = $this->kernelTestCaseNodeFactory->createPrivatePropertiesFromTypes($node, $serviceTypes);
$this->addInjectAnnotationToProperties($privateProperties);
$node->stmts = array_merge($privateProperties, $node->stmts);
// 3. remove old in-method $property assigns
$formerVariablesByMethods = $this->onContainerGetCallManipulator->removeAndCollectFormerAssignedVariables(
$node,
false
);
// 4. replace former variables by $this->someProperty
$this->onContainerGetCallManipulator->replaceFormerVariablesWithPropertyFetch($node, $formerVariablesByMethods);
return $node;
}
/**
* @param Property[] $properties
*/
private function addInjectAnnotationToProperties(array $properties): void
{
foreach ($properties as $privateProperty) {
$this->addInjectAnnotationToProperty($privateProperty);
}
}
private function addInjectAnnotationToProperty(Property $privateProperty): void
{
/** @var PhpDocInfo $phpDocInfo */
$phpDocInfo = $this->getPhpDocInfo($privateProperty);
$phpDocNode = $phpDocInfo->getPhpDocNode();
$phpDocNode->children[] = new AttributeAwarePhpDocTagNode('@inject', new GenericTagValueNode(''));
$this->docBlockManipulator->updateNodeWithPhpDocInfo($privateProperty, $phpDocInfo);
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace Rector\PHPUnit\Tests\Rector\Class_\SelfContainerGetMethodCallFromTestToInjectPropertyRector\Fixture;
use Rector\PHPUnit\Tests\Rector\Class_\SelfContainerGetMethodCallFromTestToInjectPropertyRector\Source\TestCaseWithGetContainer;
class AnotherCaseTest extends TestCaseWithGetContainer
{
protected function getDomainBaseUrl(): string
{
/** @var \Shopsys\FrameworkBundle\Component\Domain\Domain $domain */
$domain = $this->getContainer()->get(Domain::class);
return $domain->getUrl();
}
}
?>
-----
<?php
namespace Rector\PHPUnit\Tests\Rector\Class_\SelfContainerGetMethodCallFromTestToInjectPropertyRector\Fixture;
use Rector\PHPUnit\Tests\Rector\Class_\SelfContainerGetMethodCallFromTestToInjectPropertyRector\Source\TestCaseWithGetContainer;
class AnotherCaseTest extends TestCaseWithGetContainer
{
/**
* @var Rector\PHPUnit\Tests\Rector\Class_\SelfContainerGetMethodCallFromTestToInjectPropertyRector\Fixture\Domain
* @inject
*/
private $domain;
protected function getDomainBaseUrl(): string
{
return $this->domain->getUrl();
}
}
?>

View File

@ -0,0 +1,41 @@
<?php
namespace Rector\PHPUnit\Tests\Rector\Class_\SelfContainerGetMethodCallFromTestToInjectPropertyRector\Fixture;
use Rector\PHPUnit\Tests\Rector\Class_\SelfContainerGetMethodCallFromTestToInjectPropertyRector\Source\TestCaseWithGetContainer;
class SomeClassTest extends TestCaseWithGetContainer
{
public function test()
{
$someService = $this->getContainer()->get(SomeService::class);
$someService->someMethod();
}
}
class SomeService { }
?>
-----
<?php
namespace Rector\PHPUnit\Tests\Rector\Class_\SelfContainerGetMethodCallFromTestToInjectPropertyRector\Fixture;
use Rector\PHPUnit\Tests\Rector\Class_\SelfContainerGetMethodCallFromTestToInjectPropertyRector\Source\TestCaseWithGetContainer;
class SomeClassTest extends TestCaseWithGetContainer
{
/**
* @var \Rector\PHPUnit\Tests\Rector\Class_\SelfContainerGetMethodCallFromTestToInjectPropertyRector\Fixture\SomeService
* @inject
*/
private $someService;
public function test()
{
$this->someService->someMethod();
}
}
class SomeService { }
?>

View File

@ -0,0 +1,53 @@
<?php
namespace Rector\PHPUnit\Tests\Rector\Class_\SelfContainerGetMethodCallFromTestToInjectPropertyRector\Fixture;
use Rector\PHPUnit\Tests\Rector\Class_\SelfContainerGetMethodCallFromTestToInjectPropertyRector\Source\RandomElasticClient;
use Rector\PHPUnit\Tests\Rector\Class_\SelfContainerGetMethodCallFromTestToInjectPropertyRector\Source\TestCaseWithGetContainer;
class FromSetUpToInject extends TestCaseWithGetContainer
{
/**
* @var RandomElasticClient
*/
private $elasticsearchClient;
protected function setUp(): void
{
$this->elasticsearchClient = $this->getContainer()->get(RandomElasticClient::class);
}
public function testSomething()
{
$this->elasticsearchClient->init();
}
}
?>
-----
<?php
namespace Rector\PHPUnit\Tests\Rector\Class_\SelfContainerGetMethodCallFromTestToInjectPropertyRector\Fixture;
use Rector\PHPUnit\Tests\Rector\Class_\SelfContainerGetMethodCallFromTestToInjectPropertyRector\Source\RandomElasticClient;
use Rector\PHPUnit\Tests\Rector\Class_\SelfContainerGetMethodCallFromTestToInjectPropertyRector\Source\TestCaseWithGetContainer;
class FromSetUpToInject extends TestCaseWithGetContainer
{
/**
* @var RandomElasticClient
* @inject
*/
private $elasticsearchClient;
protected function setUp(): void
{
}
public function testSomething()
{
$this->elasticsearchClient->init();
}
}
?>

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Rector\PHPUnit\Tests\Rector\Class_\SelfContainerGetMethodCallFromTestToInjectPropertyRector;
use Iterator;
use Rector\PHPUnit\Rector\Class_\SelfContainerGetMethodCallFromTestToInjectPropertyRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
final class SelfContainerGetMethodCallFromTestToInjectPropertyRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideDataForTest()
*/
public function test(string $file): void
{
$this->doTestFile($file);
}
public function provideDataForTest(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
protected function getRectorClass(): string
{
return SelfContainerGetMethodCallFromTestToInjectPropertyRector::class;
}
}

View File

@ -0,0 +1,8 @@
<?php declare(strict_types=1);
namespace Rector\PHPUnit\Tests\Rector\Class_\SelfContainerGetMethodCallFromTestToInjectPropertyRector\Source;
final class RandomElasticClient
{
}

View File

@ -0,0 +1,13 @@
<?php declare(strict_types=1);
namespace Rector\PHPUnit\Tests\Rector\Class_\SelfContainerGetMethodCallFromTestToInjectPropertyRector\Source;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\ContainerInterface;
abstract class TestCaseWithGetContainer extends TestCase
{
public function getContainer(): ContainerInterface
{
}
}

View File

@ -19,6 +19,7 @@ use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
use Rector\ValueObject\PhpVersionFeature;
/**
* @see https://3v4l.org/Bndc9
@ -80,7 +81,7 @@ PHP
$identicalNode = new Identical($countedNode, $this->createNull());
$ternaryNode = new Ternary($identicalNode, new LNumber(0), $node);
} else {
if ($this->isAtLeastPhpVersion('7.3')) {
if ($this->isAtLeastPhpVersion(PhpVersionFeature::IS_COUNTABLE)) {
$conditionNode = new FuncCall(new Name('is_countable'), [new Arg($countedNode)]);
} else {
$conditionNode = new BooleanOr(

View File

@ -11,6 +11,7 @@ use PhpParser\Node\Expr\BinaryOp\Coalesce;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
use Rector\ValueObject\PhpVersionFeature;
/**
* @see https://wiki.php.net/rfc/null_coalesce_equal_operator
@ -48,7 +49,7 @@ PHP
*/
public function refactor(Node $node): ?Node
{
if (! $this->isAtLeastPhpVersion('7.4')) {
if (! $this->isAtLeastPhpVersion(PhpVersionFeature::NULL_COALESCE_ASSIGN)) {
return null;
}

View File

@ -13,6 +13,7 @@ use PhpParser\Node\Stmt\Return_;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
use Rector\ValueObject\PhpVersionFeature;
/**
* @see https://wiki.php.net/rfc/arrow_functions_v2
@ -62,7 +63,7 @@ PHP
*/
public function refactor(Node $node): ?Node
{
if (! $this->isAtLeastPhpVersion('7.4')) {
if (! $this->isAtLeastPhpVersion(PhpVersionFeature::ARROW_FUNCTION)) {
return null;
}

View File

@ -11,6 +11,7 @@ use PhpParser\Node\Scalar\LNumber;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
use Rector\ValueObject\PhpVersionFeature;
/**
* @see https://wiki.php.net/rfc/numeric_literal_separator
@ -69,7 +70,7 @@ PHP
*/
public function refactor(Node $node): ?Node
{
if (! $this->isAtLeastPhpVersion('7.4')) {
if (! $this->isAtLeastPhpVersion(PhpVersionFeature::LITERAL_SEPARATOR)) {
return null;
}

View File

@ -11,6 +11,7 @@ use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
use Rector\TypeDeclaration\TypeInferer\PropertyTypeInferer;
use Rector\ValueObject\PhpVersionFeature;
/**
* @source https://wiki.php.net/rfc/typed_properties_v2#proposal
@ -69,7 +70,7 @@ PHP
*/
public function refactor(Node $node): ?Node
{
if (! $this->isAtLeastPhpVersion('7.4')) {
if (! $this->isAtLeastPhpVersion(PhpVersionFeature::TYPED_PROPERTIES)) {
return null;
}

View File

@ -6,8 +6,10 @@ namespace Rector\SymfonyPHPUnit\Node;
use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticPropertyFetch;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\NodeTypeResolver;
use Rector\PhpParser\Node\Resolver\NameResolver;
use Symfony\Component\DependencyInjection\ContainerInterface;
final class KernelTestCaseNodeAnalyzer
{
@ -16,33 +18,46 @@ final class KernelTestCaseNodeAnalyzer
*/
private $nameResolver;
public function __construct(NameResolver $nameResolver)
/**
* @var NodeTypeResolver
*/
private $nodeTypeResolver;
public function __construct(NameResolver $nameResolver, NodeTypeResolver $nodeTypeResolver)
{
$this->nameResolver = $nameResolver;
$this->nodeTypeResolver = $nodeTypeResolver;
}
public function isOnContainerGetMethodCall(Node $node): bool
{
return $this->isSelfContainerGetMethodCall($node);
}
/**
* Is inside setUp() class method
*/
public function isSetUpOrEmptyMethod(Node $node): bool
{
$methodName = $node->getAttribute(AttributeKey::METHOD_NAME);
return $methodName === 'setUp' || $methodName === null;
}
/**
* Matches:
* self::$container->get()
*/
public function isSelfContainerGetMethodCall(Node $node): bool
private function isSelfContainerGetMethodCall(Node $node): bool
{
if (! $node instanceof MethodCall) {
return false;
}
if (! $node->var instanceof StaticPropertyFetch) {
if (! $this->nameResolver->isName($node->name, 'get')) {
return false;
}
if (! $this->nameResolver->isName($node->var->class, 'self')) {
return false;
}
if (! $this->nameResolver->isName($node->var->name, 'container')) {
return false;
}
return $this->nameResolver->isName($node->name, 'get');
return $this->nodeTypeResolver->isObjectType($node->var, ContainerInterface::class);
}
}

View File

@ -5,56 +5,42 @@ declare(strict_types=1);
namespace Rector\SymfonyPHPUnit\Rector\Class_;
use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\Class_;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\Rector\AbstractRector;
use Rector\PHPUnit\Manipulator\OnContainerGetCallManipulator;
use Rector\Rector\AbstractPHPUnitRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
use Rector\SymfonyPHPUnit\Naming\ServiceNaming;
use Rector\SymfonyPHPUnit\Node\KernelTestCaseNodeAnalyzer;
use Rector\SymfonyPHPUnit\Node\KernelTestCaseNodeFactory;
use Rector\SymfonyPHPUnit\SelfContainerMethodCallCollector;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
/**
* @see \Rector\SymfonyPHPUnit\Tests\Rector\Class_\SelfContainerGetMethodCallFromTestToSetUpMethodRector\SelfContainerGetMethodCallFromTestToSetUpMethodRectorTest
*/
final class SelfContainerGetMethodCallFromTestToSetUpMethodRector extends AbstractRector
final class SelfContainerGetMethodCallFromTestToSetUpMethodRector extends AbstractPHPUnitRector
{
/**
* @var KernelTestCaseNodeAnalyzer
*/
private $kernelTestCaseNodeAnalyzer;
/**
* @var KernelTestCaseNodeFactory
*/
private $kernelTestCaseNodeFactory;
/**
* @var ServiceNaming
*/
private $serviceNaming;
/**
* @var SelfContainerMethodCallCollector
*/
private $selfContainerMethodCallCollector;
/**
* @var OnContainerGetCallManipulator
*/
private $onContainerGetCallManipulator;
public function __construct(
KernelTestCaseNodeAnalyzer $kernelTestCaseNodeAnalyzer,
KernelTestCaseNodeFactory $kernelTestCaseNodeFactory,
ServiceNaming $serviceNaming,
SelfContainerMethodCallCollector $selfContainerMethodCallCollector
SelfContainerMethodCallCollector $selfContainerMethodCallCollector,
OnContainerGetCallManipulator $onContainerGetCallManipulator
) {
$this->kernelTestCaseNodeAnalyzer = $kernelTestCaseNodeAnalyzer;
$this->kernelTestCaseNodeFactory = $kernelTestCaseNodeFactory;
$this->selfContainerMethodCallCollector = $selfContainerMethodCallCollector;
$this->serviceNaming = $serviceNaming;
$this->onContainerGetCallManipulator = $onContainerGetCallManipulator;
}
public function getDefinition(): RectorDefinition
@ -126,15 +112,11 @@ PHP
*/
public function refactor(Node $node): ?Node
{
if ($node->extends === null) {
if (! $this->isInTestClass($node)) {
return null;
}
if (! $this->isObjectType($node, KernelTestCase::class)) {
return null;
}
// 1. find self::$container->get(x) that are called more than in 1 method
// 1. find self::$container->get(<X>)
$serviceTypes = $this->selfContainerMethodCallCollector->collectContainerGetServiceTypes($node);
if (count($serviceTypes) === 0) {
return null;
@ -160,91 +142,13 @@ PHP
$node->stmts = array_merge($privateProperties, $node->stmts);
// 4. remove old in-method $property assigns
$formerVariablesByMethods = $this->removeAndCollectFormerAssignedVariables($node);
$formerVariablesByMethods = $this->onContainerGetCallManipulator->removeAndCollectFormerAssignedVariables(
$node
);
// 5. replace former variables by $this->someProperty
$this->replaceFormerVariablesWithPropertyFetch($node, $formerVariablesByMethods);
$this->onContainerGetCallManipulator->replaceFormerVariablesWithPropertyFetch($node, $formerVariablesByMethods);
return $node;
}
/**
* @return string[][]
*/
private function removeAndCollectFormerAssignedVariables(Class_ $class): array
{
$formerVariablesByMethods = [];
$this->traverseNodesWithCallable($class->stmts, function (Node $node) use (
&$formerVariablesByMethods
): ?PropertyFetch {
if (! $node instanceof MethodCall) {
return null;
}
// skip setUp() method
$methodName = $node->getAttribute(AttributeKey::METHOD_NAME);
if ($methodName === 'setUp' || $methodName === null) {
return null;
}
if (! $this->kernelTestCaseNodeAnalyzer->isSelfContainerGetMethodCall($node)) {
return null;
}
$type = $this->getValue($node->args[0]->value);
if ($type === null) {
return null;
}
$parentNode = $node->getAttribute(AttributeKey::PARENT_NODE);
if ($parentNode instanceof Assign) {
$variableName = $this->getName($parentNode->var);
if ($variableName === null) {
return null;
}
$formerVariablesByMethods[$methodName][$variableName] = $type;
$this->removeNode($parentNode);
return null;
}
$propertyName = $this->serviceNaming->resolvePropertyNameFromServiceType($type);
return new PropertyFetch(new Variable('this'), $propertyName);
});
return $formerVariablesByMethods;
}
/**
* @param string[][] $formerVariablesByMethods
*/
private function replaceFormerVariablesWithPropertyFetch(Class_ $class, array $formerVariablesByMethods): void
{
$this->traverseNodesWithCallable($class->stmts, function (Node $node) use (
$formerVariablesByMethods
): ?PropertyFetch {
if (! $node instanceof Variable) {
return null;
}
/** @var string $methodName */
$methodName = $node->getAttribute(AttributeKey::METHOD_NAME);
$variableName = $this->getName($node);
if ($variableName === null) {
return null;
}
if (! isset($formerVariablesByMethods[$methodName][$variableName])) {
return null;
}
$serviceType = $formerVariablesByMethods[$methodName][$variableName];
$propertyName = $this->serviceNaming->resolvePropertyNameFromServiceType($serviceType);
return new PropertyFetch(new Variable('this'), $propertyName);
});
}
}

View File

@ -7,7 +7,6 @@ namespace Rector\SymfonyPHPUnit;
use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Stmt\Class_;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\PhpParser\Node\Value\ValueResolver;
use Rector\PhpParser\NodeTraverser\CallableNodeTraverser;
use Rector\SymfonyPHPUnit\Node\KernelTestCaseNodeAnalyzer;
@ -43,25 +42,30 @@ final class SelfContainerMethodCallCollector
/**
* @return string[]
*/
public function collectContainerGetServiceTypes(Class_ $class): array
public function collectContainerGetServiceTypes(Class_ $class, bool $skipSetUpMethod = true): array
{
$serviceTypes = [];
$this->callableNodeTraverser->traverseNodesWithCallable($class->stmts, function (Node $node) use (
&$serviceTypes
&$serviceTypes,
$skipSetUpMethod
) {
if (! $this->kernelTestCaseNodeAnalyzer->isSelfContainerGetMethodCall($node)) {
if (! $this->kernelTestCaseNodeAnalyzer->isOnContainerGetMethodCall($node)) {
return null;
}
// skip setUp() method
$methodName = $node->getAttribute(AttributeKey::METHOD_NAME);
if ($methodName === 'setUp' || $methodName === null) {
return null;
if ($skipSetUpMethod) {
if ($this->kernelTestCaseNodeAnalyzer->isSetUpOrEmptyMethod($node)) {
return null;
}
}
/** @var MethodCall $node */
$serviceType = $this->valueResolver->getValue($node->args[0]->value);
if ($serviceType === null || ! is_string($serviceType)) {
return null;
}
if ($this->shouldSkipServiceType($serviceType)) {
return null;
}

View File

@ -7,7 +7,7 @@ use Rector\SymfonyPHPUnit\Tests\Rector\Class_\SelfContainerGetMethodCallFromTest
class ExistingSetUpTest extends KernelTestCase
{
protected function setUp()
protected function setUp(): void
{
$value = 5;
}
@ -40,7 +40,7 @@ class ExistingSetUpTest extends KernelTestCase
* @var \Rector\SymfonyPHPUnit\Tests\Rector\Class_\SelfContainerGetMethodCallFromTestToSetUpMethodRector\Source\ItemRepository
*/
private $itemRepository;
protected function setUp()
protected function setUp(): void
{
$value = 5;
$this->itemRepository = self::$container->get(\Rector\SymfonyPHPUnit\Tests\Rector\Class_\SelfContainerGetMethodCallFromTestToSetUpMethodRector\Source\ItemRepository::class);

View File

@ -7,6 +7,7 @@ namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddMethodCallBasedPara
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Rector\TypeDeclaration\Rector\ClassMethod\AddMethodCallBasedParamTypeRector;
use Rector\ValueObject\PhpVersionFeature;
final class AddMethodCallBasedParamTypeRectorTest extends AbstractRectorTestCase
{
@ -30,7 +31,6 @@ final class AddMethodCallBasedParamTypeRectorTest extends AbstractRectorTestCase
protected function getPhpVersion(): string
{
// prevents union types
return '7.4';
return PhpVersionFeature::BEFORE_UNION_TYPES;
}
}

View File

@ -7,6 +7,7 @@ namespace Rector\TypeDeclaration\Tests\Rector\Closure\AddClosureReturnTypeRector
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Rector\TypeDeclaration\Rector\Closure\AddClosureReturnTypeRector;
use Rector\ValueObject\PhpVersionFeature;
final class AddClosureReturnTypeRectorTest extends AbstractRectorTestCase
{
@ -30,7 +31,6 @@ final class AddClosureReturnTypeRectorTest extends AbstractRectorTestCase
protected function getPhpVersion(): string
{
// prevent union types
return '7.4';
return PhpVersionFeature::BEFORE_UNION_TYPES;
}
}

View File

@ -7,6 +7,7 @@ namespace Rector\TypeDeclaration\Tests\Rector\FunctionLike\ParamTypeDeclarationR
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Rector\TypeDeclaration\Rector\FunctionLike\ParamTypeDeclarationRector;
use Rector\ValueObject\PhpVersionFeature;
final class ParamTypeDeclarationRectorTest extends AbstractRectorTestCase
{
@ -30,7 +31,6 @@ final class ParamTypeDeclarationRectorTest extends AbstractRectorTestCase
protected function getPhpVersion(): string
{
// prevent union types
return '7.4';
return PhpVersionFeature::BEFORE_UNION_TYPES;
}
}

View File

@ -7,6 +7,7 @@ namespace Rector\TypeDeclaration\Tests\Rector\FunctionLike\ReturnTypeDeclaration
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Rector\TypeDeclaration\Rector\FunctionLike\ReturnTypeDeclarationRector;
use Rector\ValueObject\PhpVersionFeature;
final class CorrectionTest extends AbstractRectorTestCase
{
@ -30,7 +31,6 @@ final class CorrectionTest extends AbstractRectorTestCase
protected function getPhpVersion(): string
{
// to prevent union types
return '7.4';
return PhpVersionFeature::BEFORE_UNION_TYPES;
}
}

View File

@ -7,6 +7,7 @@ namespace Rector\TypeDeclaration\Tests\Rector\FunctionLike\ReturnTypeDeclaration
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Rector\TypeDeclaration\Rector\FunctionLike\ReturnTypeDeclarationRector;
use Rector\ValueObject\PhpVersionFeature;
final class ReturnTypeDeclarationRectorTest extends AbstractRectorTestCase
{
@ -30,7 +31,6 @@ final class ReturnTypeDeclarationRectorTest extends AbstractRectorTestCase
protected function getPhpVersion(): string
{
// to prevent union types
return '7.4';
return PhpVersionFeature::BEFORE_UNION_TYPES;
}
}

View File

@ -9,9 +9,17 @@ use PHPStan\Type\ObjectType;
final class PropertyNaming
{
public function fqnToVariableName(ObjectType $objectType): string
/**
* @param ObjectType|string $objectType
* @return string
*/
public function fqnToVariableName($objectType): string
{
return lcfirst($this->fqnToShortName($objectType->getClassName()));
if ($objectType instanceof ObjectType) {
$objectType = $objectType->getClassName();
}
return lcfirst($this->fqnToShortName($objectType));
}
/**

View File

@ -21,6 +21,7 @@ use PhpParser\Node\Stmt\TraitUse;
use PHPStan\Type\Type;
use Rector\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\NodeTypeResolver;
use Rector\PhpParser\Node\Commander\NodeRemovingCommander;
use Rector\PhpParser\Node\NodeFactory;
use Rector\PhpParser\Node\Resolver\NameResolver;
@ -59,13 +60,19 @@ final class ClassManipulator
*/
private $betterStandardPrinter;
/**
* @var NodeTypeResolver
*/
private $nodeTypeResolver;
public function __construct(
NameResolver $nameResolver,
NodeFactory $nodeFactory,
ChildAndParentClassManipulator $childAndParentClassManipulator,
CallableNodeTraverser $callableNodeTraverser,
NodeRemovingCommander $nodeRemovingCommander,
BetterStandardPrinter $betterStandardPrinter
BetterStandardPrinter $betterStandardPrinter,
NodeTypeResolver $nodeTypeResolver
) {
$this->nodeFactory = $nodeFactory;
$this->nameResolver = $nameResolver;
@ -73,6 +80,7 @@ final class ClassManipulator
$this->callableNodeTraverser = $callableNodeTraverser;
$this->nodeRemovingCommander = $nodeRemovingCommander;
$this->betterStandardPrinter = $betterStandardPrinter;
$this->nodeTypeResolver = $nodeTypeResolver;
}
public function addConstructorDependency(Class_ $classNode, string $name, ?Type $type): void
@ -341,6 +349,19 @@ final class ClassManipulator
$classMethod->stmts = array_merge($stmts, (array) $classMethod->stmts);
}
public function findPropertyByType(Class_ $class, string $serviceType): ?Property
{
foreach ($class->getProperties() as $property) {
if (! $this->nodeTypeResolver->isObjectType($property, $serviceType)) {
continue;
}
return $property;
}
return null;
}
private function tryInsertBeforeFirstMethod(Class_ $classNode, Stmt $stmt): bool
{
foreach ($classNode->stmts as $key => $classStmt) {

View File

@ -36,6 +36,36 @@ final class PhpVersionFeature
*/
public const OBJECT_TYPE = '7.2';
/**
* @var string
*/
public const IS_COUNTABLE = '7.3';
/**
* @var string
*/
public const ARROW_FUNCTION = '7.4';
/**
* @var string
*/
public const LITERAL_SEPARATOR = '7.4';
/**
* @var string
*/
public const NULL_COALESCE_ASSIGN = '7.4';
/**
* @var string
*/
public const TYPED_PROPERTIES = '7.4';
/**
* @var string
*/
public const BEFORE_UNION_TYPES = '7.4';
/**
* @var string
*/

View File

@ -4,11 +4,17 @@ declare(strict_types=1);
namespace Symfony\Bundle\FrameworkBundle\Test;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\ContainerInterface;
if (class_exists('Symfony\Bundle\FrameworkBundle\Test\KernelTestCase')) {
return;
}
class KernelTestCase
class KernelTestCase extends TestCase
{
/**
* @var ContainerInterface
*/
protected static $container;
}

View File

@ -8,7 +8,7 @@ if (class_exists('Symfony\Bundle\FrameworkBundle\Test\WebTestCase')) {
return;
}
class WebTestCase
class WebTestCase extends KernelTestCase
{
}