diff --git a/config/set/phpunit/phpunit-injector.yaml b/config/set/phpunit/phpunit-injector.yaml new file mode 100644 index 00000000000..ab56c29b50b --- /dev/null +++ b/config/set/phpunit/phpunit-injector.yaml @@ -0,0 +1,2 @@ +services: + Rector\PHPUnit\Rector\Class_\SelfContainerGetMethodCallFromTestToInjectPropertyRector: ~ diff --git a/packages/CodeQuality/src/Rector/Class_/CompleteDynamicPropertiesRector.php b/packages/CodeQuality/src/Rector/Class_/CompleteDynamicPropertiesRector.php index 78b2215aec2..f2aa807610c 100644 --- a/packages/CodeQuality/src/Rector/Class_/CompleteDynamicPropertiesRector.php +++ b/packages/CodeQuality/src/Rector/Class_/CompleteDynamicPropertiesRector.php @@ -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; diff --git a/packages/CodeQuality/tests/Rector/Class_/CompleteDynamicPropertiesRector/CompleteDynamicPropertiesRectorTest.php b/packages/CodeQuality/tests/Rector/Class_/CompleteDynamicPropertiesRector/CompleteDynamicPropertiesRectorTest.php index 7379b413059..63032816fef 100644 --- a/packages/CodeQuality/tests/Rector/Class_/CompleteDynamicPropertiesRector/CompleteDynamicPropertiesRectorTest.php +++ b/packages/CodeQuality/tests/Rector/Class_/CompleteDynamicPropertiesRector/CompleteDynamicPropertiesRectorTest.php @@ -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; } } diff --git a/packages/PHPUnit/src/Manipulator/OnContainerGetCallManipulator.php b/packages/PHPUnit/src/Manipulator/OnContainerGetCallManipulator.php new file mode 100644 index 00000000000..05f6ae748cd --- /dev/null +++ b/packages/PHPUnit/src/Manipulator/OnContainerGetCallManipulator.php @@ -0,0 +1,167 @@ +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); + } +} diff --git a/packages/PHPUnit/src/Rector/Class_/SelfContainerGetMethodCallFromTestToInjectPropertyRector.php b/packages/PHPUnit/src/Rector/Class_/SelfContainerGetMethodCallFromTestToInjectPropertyRector.php new file mode 100644 index 00000000000..4b2554109d6 --- /dev/null +++ b/packages/PHPUnit/src/Rector/Class_/SelfContainerGetMethodCallFromTestToInjectPropertyRector.php @@ -0,0 +1,171 @@ +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); + } +} diff --git a/packages/PHPUnit/tests/Rector/Class_/SelfContainerGetMethodCallFromTestToInjectPropertyRector/Fixture/another_case_test.php.inc b/packages/PHPUnit/tests/Rector/Class_/SelfContainerGetMethodCallFromTestToInjectPropertyRector/Fixture/another_case_test.php.inc new file mode 100644 index 00000000000..6266fc74848 --- /dev/null +++ b/packages/PHPUnit/tests/Rector/Class_/SelfContainerGetMethodCallFromTestToInjectPropertyRector/Fixture/another_case_test.php.inc @@ -0,0 +1,39 @@ +getContainer()->get(Domain::class); + + return $domain->getUrl(); + } +} + +?> +----- +domain->getUrl(); + } +} + +?> diff --git a/packages/PHPUnit/tests/Rector/Class_/SelfContainerGetMethodCallFromTestToInjectPropertyRector/Fixture/fixture.php.inc b/packages/PHPUnit/tests/Rector/Class_/SelfContainerGetMethodCallFromTestToInjectPropertyRector/Fixture/fixture.php.inc new file mode 100644 index 00000000000..27077615fc1 --- /dev/null +++ b/packages/PHPUnit/tests/Rector/Class_/SelfContainerGetMethodCallFromTestToInjectPropertyRector/Fixture/fixture.php.inc @@ -0,0 +1,41 @@ +getContainer()->get(SomeService::class); + $someService->someMethod(); + } +} + +class SomeService { } + +?> +----- +someService->someMethod(); + } +} + +class SomeService { } + +?> diff --git a/packages/PHPUnit/tests/Rector/Class_/SelfContainerGetMethodCallFromTestToInjectPropertyRector/Fixture/from_set_up_to_inject.php.inc b/packages/PHPUnit/tests/Rector/Class_/SelfContainerGetMethodCallFromTestToInjectPropertyRector/Fixture/from_set_up_to_inject.php.inc new file mode 100644 index 00000000000..8fa4d21a38c --- /dev/null +++ b/packages/PHPUnit/tests/Rector/Class_/SelfContainerGetMethodCallFromTestToInjectPropertyRector/Fixture/from_set_up_to_inject.php.inc @@ -0,0 +1,53 @@ +elasticsearchClient = $this->getContainer()->get(RandomElasticClient::class); + } + + public function testSomething() + { + $this->elasticsearchClient->init(); + } +} + +?> +----- +elasticsearchClient->init(); + } +} + +?> diff --git a/packages/PHPUnit/tests/Rector/Class_/SelfContainerGetMethodCallFromTestToInjectPropertyRector/SelfContainerGetMethodCallFromTestToInjectPropertyRectorTest.php b/packages/PHPUnit/tests/Rector/Class_/SelfContainerGetMethodCallFromTestToInjectPropertyRector/SelfContainerGetMethodCallFromTestToInjectPropertyRectorTest.php new file mode 100644 index 00000000000..11caef3c48a --- /dev/null +++ b/packages/PHPUnit/tests/Rector/Class_/SelfContainerGetMethodCallFromTestToInjectPropertyRector/SelfContainerGetMethodCallFromTestToInjectPropertyRectorTest.php @@ -0,0 +1,30 @@ +doTestFile($file); + } + + public function provideDataForTest(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return SelfContainerGetMethodCallFromTestToInjectPropertyRector::class; + } +} diff --git a/packages/PHPUnit/tests/Rector/Class_/SelfContainerGetMethodCallFromTestToInjectPropertyRector/Source/RandomElasticClient.php b/packages/PHPUnit/tests/Rector/Class_/SelfContainerGetMethodCallFromTestToInjectPropertyRector/Source/RandomElasticClient.php new file mode 100644 index 00000000000..586082fb631 --- /dev/null +++ b/packages/PHPUnit/tests/Rector/Class_/SelfContainerGetMethodCallFromTestToInjectPropertyRector/Source/RandomElasticClient.php @@ -0,0 +1,8 @@ +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( diff --git a/packages/Php74/src/Rector/Assign/NullCoalescingOperatorRector.php b/packages/Php74/src/Rector/Assign/NullCoalescingOperatorRector.php index 7583978bba3..9faa04ec934 100644 --- a/packages/Php74/src/Rector/Assign/NullCoalescingOperatorRector.php +++ b/packages/Php74/src/Rector/Assign/NullCoalescingOperatorRector.php @@ -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; } diff --git a/packages/Php74/src/Rector/Closure/ClosureToArrowFunctionRector.php b/packages/Php74/src/Rector/Closure/ClosureToArrowFunctionRector.php index 7975cf0b8e7..53ec55dc7db 100644 --- a/packages/Php74/src/Rector/Closure/ClosureToArrowFunctionRector.php +++ b/packages/Php74/src/Rector/Closure/ClosureToArrowFunctionRector.php @@ -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; } diff --git a/packages/Php74/src/Rector/LNumber/AddLiteralSeparatorToNumberRector.php b/packages/Php74/src/Rector/LNumber/AddLiteralSeparatorToNumberRector.php index b443555f121..1fabd7f5f2b 100644 --- a/packages/Php74/src/Rector/LNumber/AddLiteralSeparatorToNumberRector.php +++ b/packages/Php74/src/Rector/LNumber/AddLiteralSeparatorToNumberRector.php @@ -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; } diff --git a/packages/Php74/src/Rector/Property/TypedPropertyRector.php b/packages/Php74/src/Rector/Property/TypedPropertyRector.php index 481eb749760..35d3664facc 100644 --- a/packages/Php74/src/Rector/Property/TypedPropertyRector.php +++ b/packages/Php74/src/Rector/Property/TypedPropertyRector.php @@ -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; } diff --git a/packages/SymfonyPHPUnit/src/Node/KernelTestCaseNodeAnalyzer.php b/packages/SymfonyPHPUnit/src/Node/KernelTestCaseNodeAnalyzer.php index 1a3b562370c..36e6b0e35c2 100644 --- a/packages/SymfonyPHPUnit/src/Node/KernelTestCaseNodeAnalyzer.php +++ b/packages/SymfonyPHPUnit/src/Node/KernelTestCaseNodeAnalyzer.php @@ -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); } } diff --git a/packages/SymfonyPHPUnit/src/Rector/Class_/SelfContainerGetMethodCallFromTestToSetUpMethodRector.php b/packages/SymfonyPHPUnit/src/Rector/Class_/SelfContainerGetMethodCallFromTestToSetUpMethodRector.php index 46dfc6ec4ac..2b13132e758 100644 --- a/packages/SymfonyPHPUnit/src/Rector/Class_/SelfContainerGetMethodCallFromTestToSetUpMethodRector.php +++ b/packages/SymfonyPHPUnit/src/Rector/Class_/SelfContainerGetMethodCallFromTestToSetUpMethodRector.php @@ -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() $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); - }); - } } diff --git a/packages/SymfonyPHPUnit/src/SelfContainerMethodCallCollector.php b/packages/SymfonyPHPUnit/src/SelfContainerMethodCallCollector.php index 3711adde9c8..cc3cea00c84 100644 --- a/packages/SymfonyPHPUnit/src/SelfContainerMethodCallCollector.php +++ b/packages/SymfonyPHPUnit/src/SelfContainerMethodCallCollector.php @@ -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; } diff --git a/packages/SymfonyPHPUnit/tests/Rector/Class_/SelfContainerGetMethodCallFromTestToSetUpMethodRector/Fixture/existing_setup.php.inc b/packages/SymfonyPHPUnit/tests/Rector/Class_/SelfContainerGetMethodCallFromTestToSetUpMethodRector/Fixture/existing_setup.php.inc index edfa799a16b..a648a3400c2 100644 --- a/packages/SymfonyPHPUnit/tests/Rector/Class_/SelfContainerGetMethodCallFromTestToSetUpMethodRector/Fixture/existing_setup.php.inc +++ b/packages/SymfonyPHPUnit/tests/Rector/Class_/SelfContainerGetMethodCallFromTestToSetUpMethodRector/Fixture/existing_setup.php.inc @@ -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); diff --git a/packages/TypeDeclaration/tests/Rector/ClassMethod/AddMethodCallBasedParamTypeRector/AddMethodCallBasedParamTypeRectorTest.php b/packages/TypeDeclaration/tests/Rector/ClassMethod/AddMethodCallBasedParamTypeRector/AddMethodCallBasedParamTypeRectorTest.php index 8a8c6567ee8..795901e4480 100644 --- a/packages/TypeDeclaration/tests/Rector/ClassMethod/AddMethodCallBasedParamTypeRector/AddMethodCallBasedParamTypeRectorTest.php +++ b/packages/TypeDeclaration/tests/Rector/ClassMethod/AddMethodCallBasedParamTypeRector/AddMethodCallBasedParamTypeRectorTest.php @@ -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; } } diff --git a/packages/TypeDeclaration/tests/Rector/Closure/AddClosureReturnTypeRector/AddClosureReturnTypeRectorTest.php b/packages/TypeDeclaration/tests/Rector/Closure/AddClosureReturnTypeRector/AddClosureReturnTypeRectorTest.php index 95734b6a402..b2ae8b560fd 100644 --- a/packages/TypeDeclaration/tests/Rector/Closure/AddClosureReturnTypeRector/AddClosureReturnTypeRectorTest.php +++ b/packages/TypeDeclaration/tests/Rector/Closure/AddClosureReturnTypeRector/AddClosureReturnTypeRectorTest.php @@ -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; } } diff --git a/packages/TypeDeclaration/tests/Rector/FunctionLike/ParamTypeDeclarationRector/ParamTypeDeclarationRectorTest.php b/packages/TypeDeclaration/tests/Rector/FunctionLike/ParamTypeDeclarationRector/ParamTypeDeclarationRectorTest.php index 082d63fb7ac..912ba074901 100644 --- a/packages/TypeDeclaration/tests/Rector/FunctionLike/ParamTypeDeclarationRector/ParamTypeDeclarationRectorTest.php +++ b/packages/TypeDeclaration/tests/Rector/FunctionLike/ParamTypeDeclarationRector/ParamTypeDeclarationRectorTest.php @@ -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; } } diff --git a/packages/TypeDeclaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/CorrectionTest.php b/packages/TypeDeclaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/CorrectionTest.php index 8591ccbea3d..5ae0380890a 100644 --- a/packages/TypeDeclaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/CorrectionTest.php +++ b/packages/TypeDeclaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/CorrectionTest.php @@ -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; } } diff --git a/packages/TypeDeclaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/ReturnTypeDeclarationRectorTest.php b/packages/TypeDeclaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/ReturnTypeDeclarationRectorTest.php index 56d369523e3..c1e18e64539 100644 --- a/packages/TypeDeclaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/ReturnTypeDeclarationRectorTest.php +++ b/packages/TypeDeclaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/ReturnTypeDeclarationRectorTest.php @@ -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; } } diff --git a/src/Naming/PropertyNaming.php b/src/Naming/PropertyNaming.php index 73b8906c771..ff1b40d7925 100644 --- a/src/Naming/PropertyNaming.php +++ b/src/Naming/PropertyNaming.php @@ -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)); } /** diff --git a/src/PhpParser/Node/Manipulator/ClassManipulator.php b/src/PhpParser/Node/Manipulator/ClassManipulator.php index ed273d11941..7eaf645ea18 100644 --- a/src/PhpParser/Node/Manipulator/ClassManipulator.php +++ b/src/PhpParser/Node/Manipulator/ClassManipulator.php @@ -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) { diff --git a/src/ValueObject/PhpVersionFeature.php b/src/ValueObject/PhpVersionFeature.php index f411a5f0247..efff4c13290 100644 --- a/src/ValueObject/PhpVersionFeature.php +++ b/src/ValueObject/PhpVersionFeature.php @@ -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 */ diff --git a/stubs/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php b/stubs/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php index eebc9fbd663..8396c5a17a0 100644 --- a/stubs/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php +++ b/stubs/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php @@ -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; } diff --git a/stubs/Symfony/Bundle/FrameworkBundle/Test/WebTestCase.php b/stubs/Symfony/Bundle/FrameworkBundle/Test/WebTestCase.php index d2a6fc25b88..38877315e07 100644 --- a/stubs/Symfony/Bundle/FrameworkBundle/Test/WebTestCase.php +++ b/stubs/Symfony/Bundle/FrameworkBundle/Test/WebTestCase.php @@ -8,7 +8,7 @@ if (class_exists('Symfony\Bundle\FrameworkBundle\Test\WebTestCase')) { return; } -class WebTestCase +class WebTestCase extends KernelTestCase { }