diff --git a/composer.json b/composer.json index 2303906a5aa..c11e1fc28cb 100644 --- a/composer.json +++ b/composer.json @@ -45,6 +45,7 @@ "Rector\\Symfony\\": "packages/Symfony/src", "Rector\\CakePHP\\": "packages/CakePHP/src", "Rector\\Php\\": "packages/Php/src", + "Rector\\Jms\\": "packages/Jms/src", "Rector\\Silverstripe\\": "packages/Silverstripe/src", "Rector\\Sensio\\": "packages/Sensio/src", "Rector\\Sylius\\": "packages/Sylius/src", @@ -65,6 +66,7 @@ "Rector\\CodeQuality\\Tests\\": "packages/CodeQuality/tests", "Rector\\DomainDrivenDesign\\Tests\\": "packages/DomainDrivenDesign/tests", "Rector\\Php\\Tests\\": "packages/Php/tests", + "Rector\\Jms\\Tests\\": "packages/Jms/tests", "Rector\\Symfony\\Tests\\": "packages/Symfony/tests", "Rector\\Silverstripe\\Tests\\": "packages/Silverstripe/tests", "Rector\\Sensio\\Tests\\": "packages/Sensio/tests", @@ -176,6 +178,7 @@ "packages/Symfony/tests/Rector/Process/ProcessBuilderGetProcessRector/Wrong", "packages/Symfony/tests/Rector/Process/ProcessBuilderInstanceRector/Wrong", "packages/Twig/tests/Rector/SimpleFunctionAndFilterRector/Wrong", + "packages/Jms/tests/Rector/Property/JmsInjectAnnotationRector/Wrong", "packages/Doctrine/tests/Rector/AliasToClassRector/Wrong", "packages/PhpParser/tests/Rector/RemoveNodeRector/Wrong", "packages/PhpParser/tests/Rector/IdentifierRector/Wrong", diff --git a/packages/Jms/src/Rector/Property/JmsInjectAnnotationRector.php b/packages/Jms/src/Rector/Property/JmsInjectAnnotationRector.php new file mode 100644 index 00000000000..a1bb52459f8 --- /dev/null +++ b/packages/Jms/src/Rector/Property/JmsInjectAnnotationRector.php @@ -0,0 +1,131 @@ +docBlockAnalyzer = $docBlockAnalyzer; + $this->analyzedApplicationContainer = $analyzedApplicationContainer; + } + + public function getDefinition(): RectorDefinition + { + return new RectorDefinition( + 'Changes properties with @JMS\DiExtraBundle\Annotation\Inject to constructor injection', + [ + new CodeSample( + <<<'CODE_SAMPLE' +use JMS\DiExtraBundle\Annotation as DI; + +class SomeController +{ + /** + * @DI\Inject("entity.manager") + */ + private $entityManager; +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +use JMS\DiExtraBundle\Annotation as DI; + +class SomeController +{ + /** + * @var EntityManager + */ + private $entityManager; +} +CODE_SAMPLE + ), + ] + ); + } + + /** + * @return string[] + */ + public function getNodeTypes(): array + { + return [Property::class]; + } + + /** + * @param Property $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->docBlockAnalyzer->hasTag($node, self::INJECT_ANNOTATION)) { + return null; + } + + /** @var PhpDocTagNode $injectTagNode */ + $injectTagNode = $this->docBlockAnalyzer->getTagByName($node, self::INJECT_ANNOTATION); + + $serviceName = $this->resolveServiceNameFromInjectTag($injectTagNode); + if ($serviceName === null) { + return null; + } + + if (! $this->analyzedApplicationContainer->hasService($serviceName)) { + return null; + } + + $type = $this->analyzedApplicationContainer->getTypeForName($serviceName); + + if (! $this->docBlockAnalyzer->hasTag($node, 'var')) { + $this->docBlockAnalyzer->addVarTag($node, $type); + } + + $this->docBlockAnalyzer->removeTagFromNode($node, self::INJECT_ANNOTATION); + + $this->addPropertyToClass( + (string) $node->getAttribute(Attribute::CLASS_NAME), + $type, + (string) $node->props[0]->name + ); + + return $node; + } + + private function resolveServiceNameFromInjectTag(PhpDocTagNode $phpDocTagNode): ?string + { + $injectTagContent = (string) $phpDocTagNode->value; + $match = Strings::match($injectTagContent, '#(\'|")(?.*?)(\'|")#'); + + return $match['serviceName'] ?? null; + } +} diff --git a/packages/Jms/tests/Rector/Property/JmsInjectAnnotationRector/Correct/correct.php.inc b/packages/Jms/tests/Rector/Property/JmsInjectAnnotationRector/Correct/correct.php.inc new file mode 100644 index 00000000000..b27da1a4dbc --- /dev/null +++ b/packages/Jms/tests/Rector/Property/JmsInjectAnnotationRector/Correct/correct.php.inc @@ -0,0 +1,17 @@ +entityManager = $entityManager; + } +} diff --git a/tests/Rector/Architecture/DependencyInjection/AnnotatedPropertyInjectToConstructorInjectionRector/Correct/correct7.php.inc b/packages/Jms/tests/Rector/Property/JmsInjectAnnotationRector/Correct/correct2.php.inc similarity index 100% rename from tests/Rector/Architecture/DependencyInjection/AnnotatedPropertyInjectToConstructorInjectionRector/Correct/correct7.php.inc rename to packages/Jms/tests/Rector/Property/JmsInjectAnnotationRector/Correct/correct2.php.inc diff --git a/tests/Rector/Architecture/DependencyInjection/AnnotatedPropertyInjectToConstructorInjectionRector/JmsRectorTest.php b/packages/Jms/tests/Rector/Property/JmsInjectAnnotationRector/JmsInjectAnnotationRectorTest.php similarity index 50% rename from tests/Rector/Architecture/DependencyInjection/AnnotatedPropertyInjectToConstructorInjectionRector/JmsRectorTest.php rename to packages/Jms/tests/Rector/Property/JmsInjectAnnotationRector/JmsInjectAnnotationRectorTest.php index 255b252332f..7649f21f11c 100644 --- a/tests/Rector/Architecture/DependencyInjection/AnnotatedPropertyInjectToConstructorInjectionRector/JmsRectorTest.php +++ b/packages/Jms/tests/Rector/Property/JmsInjectAnnotationRector/JmsInjectAnnotationRectorTest.php @@ -1,14 +1,14 @@ createPhpDocInfoWithFqnTypesFromNode($node); // is namespaced annotation? - if (Strings::contains($name, '\\')) { + if ($this->isNamespaced($name)) { $this->fqnAnnotationTypeDecorator->decorate($phpDocInfo, $node); } @@ -98,8 +100,7 @@ final class DocBlockAnalyzer $phpDocInfo = $this->createPhpDocInfoFromNode($node); - // is namespaced annotation? - if (Strings::contains($name, '\\')) { + if ($this->isNamespaced($name)) { $this->fqnAnnotationTypeDecorator->decorate($phpDocInfo, $node); } @@ -186,9 +187,24 @@ final class DocBlockAnalyzer $phpDocInfo = $this->createPhpDocInfoWithFqnTypesFromNode($node); + if ($this->isNamespaced($name)) { + $this->fqnAnnotationTypeDecorator->decorate($phpDocInfo, $node); + } + return $phpDocInfo->getTagsByName($name); } + public function addVarTag(Node $node, string $type): void + { + $phpDocInfo = $this->createPhpDocInfoFromNode($node); + $phpDocNode = $phpDocInfo->getPhpDocNode(); + + $varTagValueNode = new VarTagValueNode(new IdentifierTypeNode('\\' . $type), '', ''); + $phpDocNode->children[] = new PhpDocTagNode('@var', $varTagValueNode); + + $this->updateNodeWithPhpDocInfo($node, $phpDocInfo); + } + /** * @final */ @@ -244,4 +260,9 @@ final class DocBlockAnalyzer return $phpDocInfo; } + + private function isNamespaced(string $name): bool + { + return Strings::contains($name, '\\'); + } } diff --git a/packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/FqnAnnotationTypeDecorator.php b/packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/FqnAnnotationTypeDecorator.php index 48d247022c3..4c5830ac4d9 100644 --- a/packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/FqnAnnotationTypeDecorator.php +++ b/packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/FqnAnnotationTypeDecorator.php @@ -5,7 +5,6 @@ namespace Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer; use Nette\Utils\Reflection; use Nette\Utils\Strings; use PhpParser\Node; -use PhpParser\Node\Stmt\Class_; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; use Rector\NodeTypeResolver\Node\Attribute; use ReflectionClass; @@ -23,6 +22,7 @@ final class FqnAnnotationTypeDecorator } $tagShortName = ltrim($phpDocChildNode->name, '@'); + // probably not a class like type if (ctype_lower($tagShortName[0])) { continue; @@ -36,13 +36,11 @@ final class FqnAnnotationTypeDecorator private function resolveTagFqnName(Node $node, string $tagShortName): string { - $classNode = $node->getAttribute(Attribute::CLASS_NODE); - if (! $classNode instanceof Class_) { + $className = $node->getAttribute(Attribute::CLASS_NAME); + if (! $className) { return $tagShortName; } - $className = (string) $classNode->name; - return Reflection::expandClassName($tagShortName, new ReflectionClass($className)); } diff --git a/packages/Symfony/tests/Rector/FrameworkBundle/AbstractToConstructorInjectionRectorSource/SomeEntityManager.php b/packages/Symfony/tests/Rector/FrameworkBundle/AbstractToConstructorInjectionRectorSource/SomeEntityManager.php new file mode 100644 index 00000000000..2946d75a1ce --- /dev/null +++ b/packages/Symfony/tests/Rector/FrameworkBundle/AbstractToConstructorInjectionRectorSource/SomeEntityManager.php @@ -0,0 +1,8 @@ +register('translator.data_collector', SomeTranslator::class); $containerBuilder->setAlias('translator', 'translator.data_collector'); $containerBuilder->setAlias(SomeTranslatorInterface::class, 'translator.data_collector'); + + $containerBuilder->register('entity.manager', SomeEntityManager::class); } public function getCacheDir() diff --git a/packages/Symfony/tests/Rector/HttpKernel/GetterToPropertyRector/Source/rector.yml b/packages/Symfony/tests/Rector/HttpKernel/GetterToPropertyRector/Source/rector.yml deleted file mode 100644 index 406b2b63796..00000000000 --- a/packages/Symfony/tests/Rector/HttpKernel/GetterToPropertyRector/Source/rector.yml +++ /dev/null @@ -1,7 +0,0 @@ -services: - Rector\Symfony\Rector\HttpKernel\GetterToPropertyRector: ~ - - Rector\Symfony\Tests\HttpKernel\GetterToPropertyRector\Source\DummyProvider: ~ - - # require for interface autowiring - 'Rector\Bridge\Contract\ServiceTypeForNameProviderInterface': '@Rector\Symfony\Tests\HttpKernel\GetterToPropertyRector\Source\DummyProvider' diff --git a/tests/Rector/Architecture/DependencyInjection/AnnotatedPropertyInjectToConstructorInjectionRector/jms-config.yml b/tests/Rector/Architecture/DependencyInjection/AnnotatedPropertyInjectToConstructorInjectionRector/jms-config.yml deleted file mode 100644 index 73455913c75..00000000000 --- a/tests/Rector/Architecture/DependencyInjection/AnnotatedPropertyInjectToConstructorInjectionRector/jms-config.yml +++ /dev/null @@ -1,3 +0,0 @@ -services: - Rector\Rector\Architecture\DependencyInjection\AnnotatedPropertyInjectToConstructorInjectionRector: - $annotation: 'JMS\DiExtraBundle\Annotation\Inject'