[JMS] Add JmsInjectAnnotationRector

This commit is contained in:
Tomas Votruba 2018-10-17 03:25:53 +08:00
parent 4b1e61ed58
commit 70ea05352d
14 changed files with 213 additions and 23 deletions

View File

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

View File

@ -0,0 +1,131 @@
<?php declare(strict_types=1);
namespace Rector\Jms\Rector\Property;
use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Stmt\Property;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use Rector\Bridge\Contract\AnalyzedApplicationContainerInterface;
use Rector\NodeTypeResolver\Node\Attribute;
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockAnalyzer;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
/**
* @see https://jmsyst.com/bundles/JMSDiExtraBundle/master/annotations#inject
*/
final class JmsInjectAnnotationRector extends AbstractRector
{
/**
* @var string
*/
private const INJECT_ANNOTATION = 'JMS\DiExtraBundle\Annotation\Inject';
/**
* @var DocBlockAnalyzer
*/
private $docBlockAnalyzer;
/**
* @var AnalyzedApplicationContainerInterface
*/
private $analyzedApplicationContainer;
public function __construct(
DocBlockAnalyzer $docBlockAnalyzer,
AnalyzedApplicationContainerInterface $analyzedApplicationContainer
) {
$this->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, '#(\'|")(?<serviceName>.*?)(\'|")#');
return $match['serviceName'] ?? null;
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Rector\Jms\Tests\Rector\Property\JmsInjectAnnotationRector\Wrong;
use JMS\DiExtraBundle\Annotation as DI;
class SomeController
{
/**
* @var \Rector\Symfony\Tests\Rector\FrameworkBundle\AbstractToConstructorInjectionRectorSource\SomeEntityManager
*/
private $entityManager;
public function __construct(\Rector\Symfony\Tests\Rector\FrameworkBundle\AbstractToConstructorInjectionRectorSource\SomeEntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
}

View File

@ -1,14 +1,14 @@
<?php declare(strict_types=1);
namespace Rector\Tests\Rector\Architecture\DependencyInjection\AnnotatedPropertyInjectToConstructorInjectionRector;
namespace Rector\Jms\Tests\Rector\Property\JmsInjectAnnotationRector;
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
/**
* @covers \Rector\Rector\Architecture\DependencyInjection\AnnotatedPropertyInjectToConstructorInjectionRector
* @covers \Rector\Jms\Rector\Property\JmsInjectAnnotationRector
*/
final class JmsRectorTest extends AbstractRectorTestCase
final class JmsInjectAnnotationRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideWrongToFixedFiles()
@ -20,11 +20,12 @@ final class JmsRectorTest extends AbstractRectorTestCase
public function provideWrongToFixedFiles(): Iterator
{
yield [__DIR__ . '/Wrong/wrong7.php.inc', __DIR__ . '/Correct/correct7.php.inc'];
yield [__DIR__ . '/Wrong/wrong.php.inc', __DIR__ . '/Correct/correct.php.inc'];
yield [__DIR__ . '/Wrong/wrong2.php.inc', __DIR__ . '/Correct/correct2.php.inc'];
}
protected function provideConfig(): string
{
return __DIR__ . '/jms-config.yml';
return __DIR__ . '/config.yml';
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace Rector\Jms\Tests\Rector\Property\JmsInjectAnnotationRector\Wrong;
use JMS\DiExtraBundle\Annotation as DI;
class SomeController
{
/**
* @DI\Inject("entity.manager")
*/
private $entityManager;
}

View File

@ -0,0 +1,5 @@
parameters:
kernel_class: 'Rector\Symfony\Tests\FrameworkBundle\AbstractToConstructorInjectionRectorSource\SomeKernelClass'
services:
Rector\Jms\Rector\Property\JmsInjectAnnotationRector: ~

View File

@ -6,6 +6,8 @@ use Nette\Utils\Strings;
use PhpParser\Comment\Doc;
use PhpParser\Node;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use Rector\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\Exception\MissingTagException;
use Rector\PhpParser\CurrentNodeProvider;
@ -71,7 +73,7 @@ final class DocBlockAnalyzer
$phpDocInfo = $this->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, '\\');
}
}

View File

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

View File

@ -0,0 +1,8 @@
<?php declare(strict_types=1);
namespace Rector\Symfony\Tests\Rector\FrameworkBundle\AbstractToConstructorInjectionRectorSource;
final class SomeEntityManager
{
}

View File

@ -2,6 +2,7 @@
namespace Rector\Symfony\Tests\FrameworkBundle\AbstractToConstructorInjectionRectorSource;
use Rector\Symfony\Tests\Rector\FrameworkBundle\AbstractToConstructorInjectionRectorSource\SomeEntityManager;
use Rector\Symfony\Tests\Rector\FrameworkBundle\AbstractToConstructorInjectionRectorSource\SomeTranslator;
use Rector\Symfony\Tests\Rector\FrameworkBundle\AbstractToConstructorInjectionRectorSource\SomeTranslatorInterface;
use Symfony\Component\Config\Loader\LoaderInterface;
@ -27,6 +28,8 @@ final class SomeKernelClass extends Kernel
$containerBuilder->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()

View File

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

View File

@ -1,3 +0,0 @@
services:
Rector\Rector\Architecture\DependencyInjection\AnnotatedPropertyInjectToConstructorInjectionRector:
$annotation: 'JMS\DiExtraBundle\Annotation\Inject'