mirror of
https://github.com/rectorphp/rector.git
synced 2024-07-01 15:13:31 +00:00
[PHP 8.0] Add annotation removal in case of nested (#403)
* cleanup * cleanup empty annotation with just remove children * handle removing of parent * decouple AttrGroupsFactory
This commit is contained in:
parent
32c0d7d1b7
commit
e066024702
|
@ -22,6 +22,11 @@ abstract class AbstractValuesAwareNode implements PhpDocTagValueNode
|
|||
|
||||
protected bool $hasChanged = false;
|
||||
|
||||
/**
|
||||
* @var mixed[]
|
||||
*/
|
||||
private array $originalValues = [];
|
||||
|
||||
/**
|
||||
* @param mixed[] $values Must be public so node traverser can go through them
|
||||
*/
|
||||
|
@ -30,6 +35,7 @@ abstract class AbstractValuesAwareNode implements PhpDocTagValueNode
|
|||
protected ?string $originalContent = null,
|
||||
protected ?string $silentKey = null
|
||||
) {
|
||||
$this->originalValues = $values;
|
||||
}
|
||||
|
||||
public function removeValue(string $key): void
|
||||
|
@ -158,6 +164,14 @@ abstract class AbstractValuesAwareNode implements PhpDocTagValueNode
|
|||
$this->hasChanged = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function getOriginalValues(): array
|
||||
{
|
||||
return $this->originalValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed|string $value
|
||||
* @return mixed|string
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
namespace Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Fixture;
|
||||
|
||||
use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\Attribute\Path;
|
||||
use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericAnnotation;
|
||||
|
||||
/**
|
||||
* @Path("/")
|
||||
* @GenericAnnotation("/")
|
||||
*/
|
||||
final class FromImplicitToName
|
||||
{
|
||||
|
@ -17,9 +17,9 @@ final class FromImplicitToName
|
|||
|
||||
namespace Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Fixture;
|
||||
|
||||
use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\Attribute\Path;
|
||||
use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericAnnotation;
|
||||
|
||||
#[\Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\Attribute\Path(path: '/')]
|
||||
#[\Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericAnnotation(some: '/')]
|
||||
final class FromImplicitToName
|
||||
{
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericA
|
|||
|
||||
final class FunctionCallInside
|
||||
{
|
||||
#[\Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericAnnotation("is_granted('ROLE_USER')")]
|
||||
#[\Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericAnnotation(some: "is_granted('ROLE_USER')")]
|
||||
public function action()
|
||||
{
|
||||
}
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
namespace Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Fixture;
|
||||
|
||||
use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\Attribute\Path;
|
||||
use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericAnnotation;
|
||||
|
||||
/**
|
||||
* @Path("
|
||||
* @GenericAnnotation("
|
||||
* summary: Send webcam reward
|
||||
* ")
|
||||
*/
|
||||
|
@ -19,9 +19,9 @@ final class MultilineContent
|
|||
|
||||
namespace Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Fixture;
|
||||
|
||||
use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\Attribute\Path;
|
||||
use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericAnnotation;
|
||||
|
||||
#[\Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\Attribute\Path(path: '
|
||||
#[\Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericAnnotation(some: '
|
||||
summary: Send webcam reward
|
||||
')]
|
||||
final class MultilineContent
|
||||
|
|
|
@ -28,11 +28,6 @@ use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\Response
|
|||
|
||||
final class NestedUnwrap
|
||||
{
|
||||
/**
|
||||
* @Responses({
|
||||
* @Response(code="200", description="Contests", entity=ContestListView::class),
|
||||
* })
|
||||
*/
|
||||
#[\Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\Response(code: '200', description: 'Contests', entity: ContestListView::class)]
|
||||
public function action()
|
||||
{
|
||||
|
|
|
@ -24,7 +24,7 @@ use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source as Asser
|
|||
|
||||
final class EntityColumnAndAssertChoice
|
||||
{
|
||||
#[\Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericAnnotation(['php5', 'php7', 'php8'])]
|
||||
#[\Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericAnnotation(some: ['php5', 'php7', 'php8'])]
|
||||
#[\Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericAnnotation(choices: ['5.0', '5.1', 'id' => '5.2'])]
|
||||
#[\Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericAnnotation(choices: [2, 3, 5, 7, 11, 13, 17, 19])]
|
||||
public $primeNumbers;
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Fixture;
|
||||
|
||||
use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericAnnotation;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
final class SkipUnwrapSymfonyNestedValidators
|
||||
|
@ -11,41 +10,9 @@ final class SkipUnwrapSymfonyNestedValidators
|
|||
* @var array
|
||||
* @Assert\All(
|
||||
* constraints={
|
||||
* @GenericAnnotation("firstConstraint"),
|
||||
* @GenericAnnotation("secondConstraint"),
|
||||
* @Assert\NotNull()
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
public $all;
|
||||
|
||||
/**
|
||||
* @Assert\Sequentially({
|
||||
* @GenericAnnotation("stepOne"),
|
||||
* @GenericAnnotation("stepTwo")
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
public $sequentially;
|
||||
|
||||
/**
|
||||
* @Assert\AtLeastOneOf({
|
||||
* @GenericAnnotation("thisOne"),
|
||||
* @GenericAnnotation("orThisOne")
|
||||
* })
|
||||
*/
|
||||
public $atLeastOneOf;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*
|
||||
* @Assert\Collection(
|
||||
* fields = {
|
||||
* "email" = @GenericAnnotation("email"),
|
||||
* "password" = @GenericAnnotation("password"),
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
public $collection;
|
||||
}
|
||||
?>
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\Attribute;
|
||||
|
||||
use Attribute;
|
||||
|
||||
/**
|
||||
* @annotation
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
|
||||
final class Path
|
||||
{
|
||||
public function __construct(
|
||||
private string $path
|
||||
) {
|
||||
}
|
||||
}
|
|
@ -10,4 +10,7 @@ namespace Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source;
|
|||
*/
|
||||
final class GenericAnnotation
|
||||
{
|
||||
public function __construct($some)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source;
|
||||
|
||||
final class Post
|
||||
{
|
||||
|
||||
}
|
|
@ -22,9 +22,6 @@ return static function (ContainerConfigurator $containerConfigurator): void {
|
|||
|
||||
new AnnotationToAttribute(Response::class),
|
||||
new AnnotationToAttribute('Symfony\Component\Routing\Annotation\Route'),
|
||||
new AnnotationToAttribute(
|
||||
'Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\Attribute\Path'
|
||||
),
|
||||
]),
|
||||
]]);
|
||||
};
|
||||
|
|
38
rules/Php80/NodeFactory/AttrGroupsFactory.php
Normal file
38
rules/Php80/NodeFactory/AttrGroupsFactory.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php80\NodeFactory;
|
||||
|
||||
use PhpParser\Node\AttributeGroup;
|
||||
use Rector\Php80\ValueObject\DoctrineTagAndAnnotationToAttribute;
|
||||
use Rector\PhpAttribute\Printer\PhpAttributeGroupFactory;
|
||||
|
||||
final class AttrGroupsFactory
|
||||
{
|
||||
public function __construct(
|
||||
private PhpAttributeGroupFactory $phpAttributeGroupFactory
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DoctrineTagAndAnnotationToAttribute[] $doctrineTagAndAnnotationToAttributes
|
||||
* @return AttributeGroup[]
|
||||
*/
|
||||
public function create(array $doctrineTagAndAnnotationToAttributes): array
|
||||
{
|
||||
$attributeGroups = [];
|
||||
|
||||
foreach ($doctrineTagAndAnnotationToAttributes as $doctrineTagAndAnnotationToAttribute) {
|
||||
$doctrineAnnotationTagValueNode = $doctrineTagAndAnnotationToAttribute->getDoctrineAnnotationTagValueNode();
|
||||
|
||||
// add attributes
|
||||
$attributeGroups[] = $this->phpAttributeGroupFactory->create(
|
||||
$doctrineAnnotationTagValueNode,
|
||||
$doctrineTagAndAnnotationToAttribute->getAnnotationToAttribute()
|
||||
);
|
||||
}
|
||||
|
||||
return $attributeGroups;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php80\PhpDocCleaner;
|
||||
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
|
||||
use Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode;
|
||||
use Rector\BetterPhpDocParser\ValueObject\PhpDoc\DoctrineAnnotation\CurlyListNode;
|
||||
use Rector\Php80\ValueObject\AnnotationToAttribute;
|
||||
use Symplify\SimplePhpDocParser\PhpDocNodeTraverser;
|
||||
|
||||
final class ConvertedAnnotationToAttributeParentRemover
|
||||
{
|
||||
/**
|
||||
* @param AnnotationToAttribute[] $annotationsToAttributes
|
||||
*/
|
||||
public function processPhpDocNode(PhpDocNode $phpDocNode, array $annotationsToAttributes): void
|
||||
{
|
||||
$phpDocNodeTraverser = new PhpDocNodeTraverser();
|
||||
|
||||
$phpDocNodeTraverser->traverseWithCallable($phpDocNode, '', function ($node) use ($annotationsToAttributes) {
|
||||
if (! $node instanceof DoctrineAnnotationTagValueNode) {
|
||||
return $node;
|
||||
}
|
||||
|
||||
// has only children of annotation to attribute? it will be removed
|
||||
if ($this->detect($node, $annotationsToAttributes)) {
|
||||
return PhpDocNodeTraverser::NODE_REMOVE;
|
||||
}
|
||||
|
||||
return $node;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AnnotationToAttribute[] $annotationsToAttributes
|
||||
*/
|
||||
private function detect(
|
||||
DoctrineAnnotationTagValueNode $doctrineAnnotationTagValueNode,
|
||||
array $annotationsToAttributes
|
||||
): bool {
|
||||
foreach ($doctrineAnnotationTagValueNode->getValues() as $nodeValue) {
|
||||
if (! $nodeValue instanceof CurlyListNode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->isCurlyListOfDoctrineAnnotationTagValueNodes($nodeValue, $annotationsToAttributes)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AnnotationToAttribute[] $annotationsToAttributes
|
||||
*/
|
||||
private function isCurlyListOfDoctrineAnnotationTagValueNodes(
|
||||
CurlyListNode $curlyListNode,
|
||||
array $annotationsToAttributes
|
||||
): bool {
|
||||
foreach ($curlyListNode->getOriginalValues() as $nodeValueValue) {
|
||||
foreach ($annotationsToAttributes as $annotationToAttribute) {
|
||||
if (! $nodeValueValue instanceof DoctrineAnnotationTagValueNode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// found it
|
||||
if ($nodeValueValue->hasClassName($annotationToAttribute->getTag())) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -19,7 +19,8 @@ use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTagRemover;
|
|||
use Rector\Core\Contract\Rector\ConfigurableRectorInterface;
|
||||
use Rector\Core\Rector\AbstractRector;
|
||||
use Rector\Core\ValueObject\PhpVersionFeature;
|
||||
use Rector\Php80\PhpDocNodeVisitor\AnnotationToAttributePhpDocNodeVisitor;
|
||||
use Rector\Php80\NodeFactory\AttrGroupsFactory;
|
||||
use Rector\Php80\PhpDocCleaner\ConvertedAnnotationToAttributeParentRemover;
|
||||
use Rector\Php80\ValueObject\AnnotationToAttribute;
|
||||
use Rector\Php80\ValueObject\DoctrineTagAndAnnotationToAttribute;
|
||||
use Rector\PhpAttribute\Printer\PhpAttributeGroupFactory;
|
||||
|
@ -59,7 +60,8 @@ final class AnnotationToAttributeRector extends AbstractRector implements Config
|
|||
public function __construct(
|
||||
private PhpAttributeGroupFactory $phpAttributeGroupFactory,
|
||||
private PhpDocTagRemover $phpDocTagRemover,
|
||||
//private AnnotationToAttributePhpDocNodeVisitor $annotationToAttributePhpDocNodeVisitor
|
||||
private ConvertedAnnotationToAttributeParentRemover $convertedAnnotationToAttributeParentRemover,
|
||||
private AttrGroupsFactory $attrGroupsFactory
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -204,6 +206,7 @@ CODE_SAMPLE
|
|||
if ($this->shouldSkip($phpDocInfo)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$doctrineTagAndAnnotationToAttributes = [];
|
||||
|
||||
$phpDocNodeTraverser = new PhpDocNodeTraverser();
|
||||
|
@ -234,18 +237,16 @@ CODE_SAMPLE
|
|||
return $node;
|
||||
});
|
||||
|
||||
foreach ($doctrineTagAndAnnotationToAttributes as $doctrineTagAndAnnotationToAttribute) {
|
||||
$doctrineAnnotationTagValueNode = $doctrineTagAndAnnotationToAttribute->getDoctrineAnnotationTagValueNode();
|
||||
|
||||
// 1. remove php-doc tag
|
||||
$this->phpDocTagRemover->removeTagValueFromNode($phpDocInfo, $doctrineAnnotationTagValueNode);
|
||||
|
||||
// 2. add attributes
|
||||
$node->attrGroups[] = $this->phpAttributeGroupFactory->create(
|
||||
$doctrineAnnotationTagValueNode,
|
||||
$doctrineTagAndAnnotationToAttribute->getAnnotationToAttribute()
|
||||
);
|
||||
$attrGroups = $this->attrGroupsFactory->create($doctrineTagAndAnnotationToAttributes);
|
||||
if ($attrGroups === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
$node->attrGroups = $attrGroups;
|
||||
$this->convertedAnnotationToAttributeParentRemover->processPhpDocNode(
|
||||
$phpDocInfo->getPhpDocNode(),
|
||||
$this->annotationsToAttributes
|
||||
);
|
||||
}
|
||||
|
||||
private function shouldSkip(PhpDocInfo $phpDocInfo): bool
|
||||
|
|
Loading…
Reference in New Issue
Block a user