From 68026636bf67e3e63bfc5f5f6556da70e8112c5f Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sun, 21 Mar 2021 00:16:21 +0100 Subject: [PATCH] [PHP 8.0] Refactor attributes from magic interface to explicit list (#5926) --- .../PhpDocInfo/PhpDocInfo.php | 2 - .../PhpDoc/SymfonyRequiredTagNode.php | 17 +-- .../PhpDocNode/AbstractTagValueNode.php | 8 ++ .../Doctrine/AbstractDoctrineTagValueNode.php | 7 -- .../Doctrine/Class_/EntityTagValueNode.php | 17 +-- .../Doctrine/Property_/ColumnTagValueNode.php | 9 +- .../Property_/GeneratedValueTagValueNode.php | 9 +- .../Doctrine/Property_/IdTagValueNode.php | 9 +- .../Property_/JoinColumnTagValueNode.php | 9 +- .../Property_/JoinTableTagValueNode.php | 46 +------- .../Property_/ManyToManyTagValueNode.php | 9 +- .../Symfony/SymfonyRouteTagValueNode.php | 16 +-- .../Constraints/AssertEmailTagValueNode.php | 16 +-- .../Constraints/AssertRangeTagValueNode.php | 16 +-- .../AnnotationToAttributeConverter.php | 111 ------------------ .../ManyPhpAttributableTagNodeInterface.php | 13 -- .../PhpAttributableTagNodeInterface.php | 19 --- .../Printer/PhpAttributeGroupFactory.php | 81 ++----------- .../RFC/id_column_generated_value.php.inc | 31 ----- .../Fixture/RFC/join_table.php.inc | 34 ------ .../Fixture/RFC/just_id.php.inc | 29 ----- .../Fixture/RFC/many_to_many.php.inc | 29 ----- ...ert_email.php.inc => assert_email.php.inc} | 4 - .../Fixture/{RFC => }/assert_range.php.inc | 8 +- .../Fixture/entity_with_repository.php.inc | 27 ----- .../Fixture/fixture.php.inc | 27 ----- .../Fixture/nette_cross_origin.php.inc | 29 ----- .../config/auto_import.php | 6 +- .../config/configured_rule.php | 38 +++++- .../DoctrineItemDefaultValueManipulator.php | 6 +- .../Class_/AnnotationToAttributeRector.php | 95 +++++++++++++-- .../ValueObject/AnnotationToAttribute.php | 46 ++++++++ src/ValueObject/PhpVersionFeature.php | 6 + 33 files changed, 213 insertions(+), 616 deletions(-) delete mode 100644 packages/PhpAttribute/AnnotationToAttributeConverter.php delete mode 100644 packages/PhpAttribute/Contract/ManyPhpAttributableTagNodeInterface.php delete mode 100644 packages/PhpAttribute/Contract/PhpAttributableTagNodeInterface.php delete mode 100644 rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/RFC/id_column_generated_value.php.inc delete mode 100644 rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/RFC/join_table.php.inc delete mode 100644 rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/RFC/just_id.php.inc delete mode 100644 rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/RFC/many_to_many.php.inc rename rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/{RFC/entity_column_and_assert_email.php.inc => assert_email.php.inc} (79%) rename rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/{RFC => }/assert_range.php.inc (90%) delete mode 100644 rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/entity_with_repository.php.inc delete mode 100644 rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/fixture.php.inc delete mode 100644 rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/nette_cross_origin.php.inc create mode 100644 rules/Php80/ValueObject/AnnotationToAttribute.php diff --git a/packages/BetterPhpDocParser/PhpDocInfo/PhpDocInfo.php b/packages/BetterPhpDocParser/PhpDocInfo/PhpDocInfo.php index 74fd905ad682..c208a71c9c21 100644 --- a/packages/BetterPhpDocParser/PhpDocInfo/PhpDocInfo.php +++ b/packages/BetterPhpDocParser/PhpDocInfo/PhpDocInfo.php @@ -27,7 +27,6 @@ use Rector\Core\Exception\NotImplementedYetException; use Rector\Core\Exception\ShouldNotHappenException; use Rector\Core\Util\StaticInstanceOf; -use Rector\PhpAttribute\Contract\PhpAttributableTagNodeInterface; use Rector\StaticTypeMapper\StaticTypeMapper; /** @@ -493,7 +492,6 @@ private function ensureTypeIsTagValueNode(string $type, string $location): void $desiredTypes = array_merge([ PhpDocTagValueNode::class, PhpDocTagNode::class, - PhpAttributableTagNodeInterface::class, ], NodeTypes::TYPE_AWARE_NODES); if (StaticInstanceOf::isOneOf($type, $desiredTypes)) { diff --git a/packages/BetterPhpDocParser/ValueObject/PhpDoc/SymfonyRequiredTagNode.php b/packages/BetterPhpDocParser/ValueObject/PhpDoc/SymfonyRequiredTagNode.php index 1dfcfe78804a..76697c6e86a4 100644 --- a/packages/BetterPhpDocParser/ValueObject/PhpDoc/SymfonyRequiredTagNode.php +++ b/packages/BetterPhpDocParser/ValueObject/PhpDoc/SymfonyRequiredTagNode.php @@ -7,13 +7,11 @@ use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; -use Rector\PhpAttribute\Contract\PhpAttributableTagNodeInterface; - /** * Use by Symfony to autowire dependencies outside constructor, * @see https://symfony.com/doc/current/service_container/autowiring.html#autowiring-other-methods-e-g-setters-and-public-typed-properties */ -final class SymfonyRequiredTagNode extends PhpDocTagNode implements PhpAttributableTagNodeInterface +final class SymfonyRequiredTagNode extends PhpDocTagNode { /** * @var string @@ -34,17 +32,4 @@ public function getShortName(): string { return self::NAME; } - - public function getAttributeClassName(): string - { - return 'Symfony\Contracts\Service\Attribute\Required'; - } - - /** - * @return mixed[] - */ - public function getAttributableItems(): array - { - return []; - } } diff --git a/packages/BetterPhpDocParser/ValueObject/PhpDocNode/AbstractTagValueNode.php b/packages/BetterPhpDocParser/ValueObject/PhpDocNode/AbstractTagValueNode.php index 656c57f4652d..b9bca84c8549 100644 --- a/packages/BetterPhpDocParser/ValueObject/PhpDocNode/AbstractTagValueNode.php +++ b/packages/BetterPhpDocParser/ValueObject/PhpDocNode/AbstractTagValueNode.php @@ -73,6 +73,14 @@ public function getItems(): array return $this->items; } + /** + * @return mixed[] + */ + public function getItemsWithoutDefaults(): array + { + return $this->filterOutMissingItems($this->items); + } + /** * @param mixed $value */ diff --git a/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Doctrine/AbstractDoctrineTagValueNode.php b/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Doctrine/AbstractDoctrineTagValueNode.php index cfe38b9a2c24..b3e1e66a546c 100644 --- a/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Doctrine/AbstractDoctrineTagValueNode.php +++ b/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Doctrine/AbstractDoctrineTagValueNode.php @@ -10,11 +10,4 @@ abstract class AbstractDoctrineTagValueNode extends AbstractTagValueNode implements DoctrineTagNodeInterface, ShortNameAwareTagInterface { - /** - * @return mixed[] - */ - public function getAttributableItems(): array - { - return $this->filterOutMissingItems($this->items); - } } diff --git a/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Doctrine/Class_/EntityTagValueNode.php b/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Doctrine/Class_/EntityTagValueNode.php index 2b2129ee5c3c..4f2f7150c97f 100644 --- a/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Doctrine/Class_/EntityTagValueNode.php +++ b/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Doctrine/Class_/EntityTagValueNode.php @@ -5,10 +5,8 @@ namespace Rector\BetterPhpDocParser\ValueObject\PhpDocNode\Doctrine\Class_; use Rector\BetterPhpDocParser\ValueObject\PhpDocNode\Doctrine\AbstractDoctrineTagValueNode; -use Rector\PhpAttribute\Contract\PhpAttributableTagNodeInterface; -use Rector\PhpAttribute\Printer\PhpAttributeGroupFactory; -final class EntityTagValueNode extends AbstractDoctrineTagValueNode implements PhpAttributableTagNodeInterface +final class EntityTagValueNode extends AbstractDoctrineTagValueNode { /** * @var string @@ -34,17 +32,4 @@ public function getShortName(): string { return '@ORM\Entity'; } - - /** - * @return mixed[] - */ - public function getAttributableItems(): array - { - return $this->filterOutMissingItems($this->items); - } - - public function getAttributeClassName(): string - { - return PhpAttributeGroupFactory::TO_BE_ANNOUNCED; - } } diff --git a/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Doctrine/Property_/ColumnTagValueNode.php b/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Doctrine/Property_/ColumnTagValueNode.php index 9a9097d03859..10454744c356 100644 --- a/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Doctrine/Property_/ColumnTagValueNode.php +++ b/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Doctrine/Property_/ColumnTagValueNode.php @@ -5,10 +5,8 @@ namespace Rector\BetterPhpDocParser\ValueObject\PhpDocNode\Doctrine\Property_; use Rector\BetterPhpDocParser\ValueObject\PhpDocNode\Doctrine\AbstractDoctrineTagValueNode; -use Rector\PhpAttribute\Contract\PhpAttributableTagNodeInterface; -use Rector\PhpAttribute\Printer\PhpAttributeGroupFactory; -final class ColumnTagValueNode extends AbstractDoctrineTagValueNode implements PhpAttributableTagNodeInterface +final class ColumnTagValueNode extends AbstractDoctrineTagValueNode { public function changeType(string $type): void { @@ -37,9 +35,4 @@ public function getOptions(): array { return $this->items['options'] ?? []; } - - public function getAttributeClassName(): string - { - return PhpAttributeGroupFactory::TO_BE_ANNOUNCED; - } } diff --git a/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Doctrine/Property_/GeneratedValueTagValueNode.php b/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Doctrine/Property_/GeneratedValueTagValueNode.php index 0aebada7f634..6c8338b009e9 100644 --- a/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Doctrine/Property_/GeneratedValueTagValueNode.php +++ b/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Doctrine/Property_/GeneratedValueTagValueNode.php @@ -6,13 +6,11 @@ use Rector\BetterPhpDocParser\Contract\PhpDocNode\SilentKeyNodeInterface; use Rector\BetterPhpDocParser\ValueObject\PhpDocNode\Doctrine\AbstractDoctrineTagValueNode; -use Rector\PhpAttribute\Contract\PhpAttributableTagNodeInterface; -use Rector\PhpAttribute\Printer\PhpAttributeGroupFactory; /** * @see \Rector\Tests\BetterPhpDocParser\PhpDocParser\TagValueNodeReprint\TagValueNodeReprintTest */ -final class GeneratedValueTagValueNode extends AbstractDoctrineTagValueNode implements PhpAttributableTagNodeInterface, SilentKeyNodeInterface +final class GeneratedValueTagValueNode extends AbstractDoctrineTagValueNode implements SilentKeyNodeInterface { public function getShortName(): string { @@ -23,9 +21,4 @@ public function getSilentKey(): string { return 'strategy'; } - - public function getAttributeClassName(): string - { - return PhpAttributeGroupFactory::TO_BE_ANNOUNCED; - } } diff --git a/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Doctrine/Property_/IdTagValueNode.php b/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Doctrine/Property_/IdTagValueNode.php index a70a765e404d..64ce67d60547 100644 --- a/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Doctrine/Property_/IdTagValueNode.php +++ b/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Doctrine/Property_/IdTagValueNode.php @@ -5,18 +5,11 @@ namespace Rector\BetterPhpDocParser\ValueObject\PhpDocNode\Doctrine\Property_; use Rector\BetterPhpDocParser\ValueObject\PhpDocNode\Doctrine\AbstractDoctrineTagValueNode; -use Rector\PhpAttribute\Contract\PhpAttributableTagNodeInterface; -use Rector\PhpAttribute\Printer\PhpAttributeGroupFactory; -final class IdTagValueNode extends AbstractDoctrineTagValueNode implements PhpAttributableTagNodeInterface +final class IdTagValueNode extends AbstractDoctrineTagValueNode { public function getShortName(): string { return '@ORM\Id'; } - - public function getAttributeClassName(): string - { - return PhpAttributeGroupFactory::TO_BE_ANNOUNCED; - } } diff --git a/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Doctrine/Property_/JoinColumnTagValueNode.php b/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Doctrine/Property_/JoinColumnTagValueNode.php index 0dd90877d02a..f0c50295c944 100644 --- a/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Doctrine/Property_/JoinColumnTagValueNode.php +++ b/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Doctrine/Property_/JoinColumnTagValueNode.php @@ -8,10 +8,8 @@ use Rector\BetterPhpDocParser\Printer\ArrayPartPhpDocTagPrinter; use Rector\BetterPhpDocParser\Printer\TagValueNodePrinter; use Rector\BetterPhpDocParser\ValueObject\PhpDocNode\Doctrine\AbstractDoctrineTagValueNode; -use Rector\PhpAttribute\Contract\PhpAttributableTagNodeInterface; -use Rector\PhpAttribute\Printer\PhpAttributeGroupFactory; -final class JoinColumnTagValueNode extends AbstractDoctrineTagValueNode implements TagAwareNodeInterface, PhpAttributableTagNodeInterface +final class JoinColumnTagValueNode extends AbstractDoctrineTagValueNode implements TagAwareNodeInterface { /** * @var string @@ -64,9 +62,4 @@ public function changeShortName(string $shortName): void { $this->shortName = $shortName; } - - public function getAttributeClassName(): string - { - return PhpAttributeGroupFactory::TO_BE_ANNOUNCED; - } } diff --git a/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Doctrine/Property_/JoinTableTagValueNode.php b/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Doctrine/Property_/JoinTableTagValueNode.php index 81b07a7b5e43..c011b24925b1 100644 --- a/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Doctrine/Property_/JoinTableTagValueNode.php +++ b/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Doctrine/Property_/JoinTableTagValueNode.php @@ -9,11 +9,8 @@ use Rector\BetterPhpDocParser\ValueObject\AroundSpaces; use Rector\BetterPhpDocParser\ValueObject\PhpDocNode\Doctrine\AbstractDoctrineTagValueNode; use Rector\Core\Exception\ShouldNotHappenException; -use Rector\PhpAttribute\Contract\ManyPhpAttributableTagNodeInterface; -use Rector\PhpAttribute\Contract\PhpAttributableTagNodeInterface; -use Rector\PhpAttribute\Printer\PhpAttributeGroupFactory; -final class JoinTableTagValueNode extends AbstractDoctrineTagValueNode implements PhpAttributableTagNodeInterface, ManyPhpAttributableTagNodeInterface +final class JoinTableTagValueNode extends AbstractDoctrineTagValueNode { /** * @var string @@ -96,47 +93,6 @@ public function getShortName(): string return '@ORM\JoinTable'; } - /** - * @return mixed[] - */ - public function getAttributableItems(): array - { - $items = []; - - if ($this->name !== null) { - $items['name'] = $this->name; - } - - if ($this->schema !== null) { - $items['schema'] = $this->schema; - } - - return $items; - } - - /** - * @return array - */ - public function provide(): array - { - $items = []; - - foreach ($this->joinColumns as $joinColumn) { - $items[$joinColumn->getShortName()] = $joinColumn->getAttributableItems(); - } - - foreach ($this->inverseJoinColumns as $inverseJoinColumn) { - $items['@ORM\InverseJoinColumn'] = $inverseJoinColumn->getAttributableItems(); - } - - return $items; - } - - public function getAttributeClassName(): string - { - return PhpAttributeGroupFactory::TO_BE_ANNOUNCED; - } - /** * @return string[] */ diff --git a/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Doctrine/Property_/ManyToManyTagValueNode.php b/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Doctrine/Property_/ManyToManyTagValueNode.php index 407faa59a32f..f9ee2be91dea 100644 --- a/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Doctrine/Property_/ManyToManyTagValueNode.php +++ b/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Doctrine/Property_/ManyToManyTagValueNode.php @@ -10,10 +10,8 @@ use Rector\BetterPhpDocParser\Printer\ArrayPartPhpDocTagPrinter; use Rector\BetterPhpDocParser\Printer\TagValueNodePrinter; use Rector\BetterPhpDocParser\ValueObject\PhpDocNode\Doctrine\AbstractDoctrineTagValueNode; -use Rector\PhpAttribute\Contract\PhpAttributableTagNodeInterface; -use Rector\PhpAttribute\Printer\PhpAttributeGroupFactory; -final class ManyToManyTagValueNode extends AbstractDoctrineTagValueNode implements ToManyTagNodeInterface, MappedByNodeInterface, InversedByNodeInterface, PhpAttributableTagNodeInterface +final class ManyToManyTagValueNode extends AbstractDoctrineTagValueNode implements ToManyTagNodeInterface, MappedByNodeInterface, InversedByNodeInterface { /** * @var string @@ -76,9 +74,4 @@ public function getShortName(): string { return '@ORM\ManyToMany'; } - - public function getAttributeClassName(): string - { - return PhpAttributeGroupFactory::TO_BE_ANNOUNCED; - } } diff --git a/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Symfony/SymfonyRouteTagValueNode.php b/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Symfony/SymfonyRouteTagValueNode.php index 5c95f37cec92..13016443861d 100644 --- a/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Symfony/SymfonyRouteTagValueNode.php +++ b/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Symfony/SymfonyRouteTagValueNode.php @@ -8,12 +8,11 @@ use Rector\BetterPhpDocParser\Contract\PhpDocNode\ShortNameAwareTagInterface; use Rector\BetterPhpDocParser\Contract\PhpDocNode\SilentKeyNodeInterface; use Rector\BetterPhpDocParser\ValueObject\PhpDocNode\AbstractTagValueNode; -use Rector\PhpAttribute\Contract\PhpAttributableTagNodeInterface; /** * @see \Rector\Tests\BetterPhpDocParser\PhpDocParser\TagValueNodeReprint\TagValueNodeReprintTest */ -final class SymfonyRouteTagValueNode extends AbstractTagValueNode implements ShortNameAwareTagInterface, SilentKeyNodeInterface, PhpAttributableTagNodeInterface, ClassNameAwareTagInterface +final class SymfonyRouteTagValueNode extends AbstractTagValueNode implements ShortNameAwareTagInterface, SilentKeyNodeInterface, ClassNameAwareTagInterface { /** * @var string @@ -59,19 +58,6 @@ public function mimicTagValueNodeConfiguration(AbstractTagValueNode $abstractTag $this->tagValueNodeConfiguration->mimic($abstractTagValueNode->tagValueNodeConfiguration); } - /** - * @return mixed[] - */ - public function getAttributableItems(): array - { - return $this->filterOutMissingItems($this->items); - } - - public function getAttributeClassName(): string - { - return self::CLASS_NAME; - } - public function getClassName(): string { return self::CLASS_NAME; diff --git a/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Symfony/Validator/Constraints/AssertEmailTagValueNode.php b/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Symfony/Validator/Constraints/AssertEmailTagValueNode.php index 57cedbeb8420..dd7bf9b24e64 100644 --- a/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Symfony/Validator/Constraints/AssertEmailTagValueNode.php +++ b/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Symfony/Validator/Constraints/AssertEmailTagValueNode.php @@ -7,12 +7,11 @@ use Rector\BetterPhpDocParser\Contract\PhpDocNode\ShortNameAwareTagInterface; use Rector\BetterPhpDocParser\Contract\PhpDocNode\SilentKeyNodeInterface; use Rector\BetterPhpDocParser\ValueObject\PhpDocNode\AbstractTagValueNode; -use Rector\PhpAttribute\Contract\PhpAttributableTagNodeInterface; /** * @see \Rector\Tests\BetterPhpDocParser\PhpDocParser\TagValueNodeReprint\TagValueNodeReprintTest */ -final class AssertEmailTagValueNode extends AbstractTagValueNode implements ShortNameAwareTagInterface, PhpAttributableTagNodeInterface, SilentKeyNodeInterface +final class AssertEmailTagValueNode extends AbstractTagValueNode implements ShortNameAwareTagInterface, SilentKeyNodeInterface { public function getShortName(): string { @@ -23,17 +22,4 @@ public function getSilentKey(): string { return 'choices'; } - - /** - * @return mixed[] - */ - public function getAttributableItems(): array - { - return $this->filterOutMissingItems($this->items); - } - - public function getAttributeClassName(): string - { - return 'Symfony\Component\Validator\Constraints\Email'; - } } diff --git a/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Symfony/Validator/Constraints/AssertRangeTagValueNode.php b/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Symfony/Validator/Constraints/AssertRangeTagValueNode.php index ae32815ad03f..cb6a4c6c9a74 100644 --- a/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Symfony/Validator/Constraints/AssertRangeTagValueNode.php +++ b/packages/BetterPhpDocParser/ValueObject/PhpDocNode/Symfony/Validator/Constraints/AssertRangeTagValueNode.php @@ -6,25 +6,11 @@ use Rector\BetterPhpDocParser\Contract\PhpDocNode\ShortNameAwareTagInterface; use Rector\BetterPhpDocParser\ValueObject\PhpDocNode\AbstractTagValueNode; -use Rector\PhpAttribute\Contract\PhpAttributableTagNodeInterface; -final class AssertRangeTagValueNode extends AbstractTagValueNode implements ShortNameAwareTagInterface, PhpAttributableTagNodeInterface +final class AssertRangeTagValueNode extends AbstractTagValueNode implements ShortNameAwareTagInterface { public function getShortName(): string { return '@Assert\Range'; } - - /** - * @return mixed[] - */ - public function getAttributableItems(): array - { - return $this->filterOutMissingItems($this->items); - } - - public function getAttributeClassName(): string - { - return 'Symfony\Component\Validator\Constraints\Range'; - } } diff --git a/packages/PhpAttribute/AnnotationToAttributeConverter.php b/packages/PhpAttribute/AnnotationToAttributeConverter.php deleted file mode 100644 index 1346572e3409..000000000000 --- a/packages/PhpAttribute/AnnotationToAttributeConverter.php +++ /dev/null @@ -1,111 +0,0 @@ -phpAttributeGroupFactory = $phpAttributeGroupFactory; - $this->phpDocInfoFactory = $phpDocInfoFactory; - $this->phpDocTagRemover = $phpDocTagRemover; - $this->reflectionProvider = $reflectionProvider; - } - - /** - * @param Class_|Property|ClassMethod|Function_|Closure|ArrowFunction $node - */ - public function convertNode(Node $node): ?Node - { - $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node); - - // 0. has 0 nodes, nothing to change - /** @var PhpAttributableTagNodeInterface[] $phpAttributableTagNodes */ - $phpAttributableTagNodes = $phpDocInfo->findAllByType(PhpAttributableTagNodeInterface::class); - if ($phpAttributableTagNodes === []) { - return null; - } - - $hasNewAttrGroups = false; - - // 1. keep only those, whom's attribute class exists - $phpAttributableTagNodes = $this->filterOnlyExistingAttributes($phpAttributableTagNodes); - if ($phpAttributableTagNodes !== []) { - $hasNewAttrGroups = true; - } - - // 2. remove tags - foreach ($phpAttributableTagNodes as $phpAttributableTagNode) { - $this->phpDocTagRemover->removeTagValueFromNode($phpDocInfo, $phpAttributableTagNode); - } - - // 3. convert annotations to attributes - $newAttrGroups = $this->phpAttributeGroupFactory->create($phpAttributableTagNodes); - $node->attrGroups = array_merge($node->attrGroups, $newAttrGroups); - - if ($hasNewAttrGroups) { - return $node; - } - - return null; - } - - /** - * @param PhpAttributableTagNodeInterface[] $phpAttributableTagNodes - * @return PhpAttributableTagNodeInterface[] - */ - private function filterOnlyExistingAttributes(array $phpAttributableTagNodes): array - { - if (StaticPHPUnitEnvironment::isPHPUnitRun()) { - return $phpAttributableTagNodes; - } - - return array_filter( - $phpAttributableTagNodes, - function (PhpAttributableTagNodeInterface $phpAttributableTagNode): bool { - return $this->reflectionProvider->hasClass($phpAttributableTagNode->getAttributeClassName()); - } - ); - } -} diff --git a/packages/PhpAttribute/Contract/ManyPhpAttributableTagNodeInterface.php b/packages/PhpAttribute/Contract/ManyPhpAttributableTagNodeInterface.php deleted file mode 100644 index ab2a4e8cbb26..000000000000 --- a/packages/PhpAttribute/Contract/ManyPhpAttributableTagNodeInterface.php +++ /dev/null @@ -1,13 +0,0 @@ - - */ - public function provide(): array; -} diff --git a/packages/PhpAttribute/Contract/PhpAttributableTagNodeInterface.php b/packages/PhpAttribute/Contract/PhpAttributableTagNodeInterface.php deleted file mode 100644 index 1d5675eec5f5..000000000000 --- a/packages/PhpAttribute/Contract/PhpAttributableTagNodeInterface.php +++ /dev/null @@ -1,19 +0,0 @@ -printPhpAttributableTagNode($phpAttributableTagNode); - $attributeGroups = array_merge($attributeGroups, $currentAttributeGroups); - } - - return $attributeGroups; - } - - /** - * @return Arg[] - */ - public function printItemsToAttributeArgs(PhpAttributableTagNodeInterface $phpAttributableTagNode): array - { - $items = $phpAttributableTagNode->getAttributableItems(); - return $this->createArgsFromItems($items); - } - - /** - * @return AttributeGroup[] - */ - private function printPhpAttributableTagNode(PhpAttributableTagNodeInterface $phpAttributableTagNode): array + public function create(Node $node, AnnotationToAttribute $annotationToAttribute): AttributeGroup { - $args = $this->printItemsToAttributeArgs($phpAttributableTagNode); - - $attributeClassName = $this->resolveAttributeClassName($phpAttributableTagNode); - - $attributeGroups = []; - $attributeGroups[] = $this->createAttributeGroupFromNameAndArgs($attributeClassName, $args); + $fullyQualified = new FullyQualified($annotationToAttribute->getAttributeClass()); - if ($phpAttributableTagNode instanceof ManyPhpAttributableTagNodeInterface) { - foreach ($phpAttributableTagNode->provide() as $shortName => $items) { - $args = $this->createArgsFromItems($items); - $name = new Name($shortName); - $attributeGroups[] = $this->createAttributeGroupFromNameAndArgs($name, $args); - } + if ($node instanceof AbstractTagValueNode) { + $args = $this->createArgsFromItems($node->getItemsWithoutDefaults()); + } else { + $args = []; } - return $attributeGroups; + $attribute = new Attribute($fullyQualified, $args); + return new AttributeGroup([$attribute]); } /** @@ -101,24 +60,6 @@ private function createArgsFromItems(array $items, ?string $silentKey = null): a return $args; } - private function resolveAttributeClassName(PhpAttributableTagNodeInterface $phpAttributableTagNode): Name - { - if ($phpAttributableTagNode->getAttributeClassName() !== self::TO_BE_ANNOUNCED) { - return new FullyQualified($phpAttributableTagNode->getAttributeClassName()); - } - - return new Name($phpAttributableTagNode->getShortName()); - } - - /** - * @param Arg[] $args - */ - private function createAttributeGroupFromNameAndArgs(Name $name, array $args): AttributeGroup - { - $attribute = new Attribute($name, $args); - return new AttributeGroup([$attribute]); - } - /** * @param mixed[] $items */ diff --git a/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/RFC/id_column_generated_value.php.inc b/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/RFC/id_column_generated_value.php.inc deleted file mode 100644 index 6777079791a7..000000000000 --- a/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/RFC/id_column_generated_value.php.inc +++ /dev/null @@ -1,31 +0,0 @@ - ------ - diff --git a/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/RFC/join_table.php.inc b/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/RFC/join_table.php.inc deleted file mode 100644 index 7aa120494ef3..000000000000 --- a/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/RFC/join_table.php.inc +++ /dev/null @@ -1,34 +0,0 @@ - ------ - diff --git a/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/RFC/just_id.php.inc b/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/RFC/just_id.php.inc deleted file mode 100644 index aa69d16b4d58..000000000000 --- a/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/RFC/just_id.php.inc +++ /dev/null @@ -1,29 +0,0 @@ - ------ - diff --git a/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/RFC/many_to_many.php.inc b/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/RFC/many_to_many.php.inc deleted file mode 100644 index 2d91fde0e303..000000000000 --- a/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/RFC/many_to_many.php.inc +++ /dev/null @@ -1,29 +0,0 @@ - ------ - diff --git a/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/RFC/entity_column_and_assert_email.php.inc b/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/assert_email.php.inc similarity index 79% rename from rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/RFC/entity_column_and_assert_email.php.inc rename to rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/assert_email.php.inc index 873f4a626dcc..720e26ae1b94 100644 --- a/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/RFC/entity_column_and_assert_email.php.inc +++ b/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/assert_email.php.inc @@ -2,13 +2,11 @@ namespace Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Fixture\RFC; -use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; class EntityColumnAndAssertEmail { /** - * @ORM\Column(type="string", unique=true) * @Assert\Email(message="The email '{{ value }}' is not a valid email.") */ public $name; @@ -20,12 +18,10 @@ class EntityColumnAndAssertEmail namespace Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Fixture\RFC; -use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; class EntityColumnAndAssertEmail { - #[@ORM\Column(type: 'string', unique: true)] #[\Symfony\Component\Validator\Constraints\Email(message: 'This value is not a valid email address.')] public $name; } diff --git a/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/RFC/assert_range.php.inc b/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/assert_range.php.inc similarity index 90% rename from rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/RFC/assert_range.php.inc rename to rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/assert_range.php.inc index 187a6f843c9c..409611336b90 100644 --- a/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/RFC/assert_range.php.inc +++ b/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/assert_range.php.inc @@ -1,10 +1,10 @@ ------ - diff --git a/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/fixture.php.inc b/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/fixture.php.inc deleted file mode 100644 index 3cdd76b49ae2..000000000000 --- a/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/fixture.php.inc +++ /dev/null @@ -1,27 +0,0 @@ - ------ - diff --git a/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/nette_cross_origin.php.inc b/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/nette_cross_origin.php.inc deleted file mode 100644 index 5440c384a6f9..000000000000 --- a/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Fixture/nette_cross_origin.php.inc +++ /dev/null @@ -1,29 +0,0 @@ - ------ - diff --git a/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/config/auto_import.php b/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/config/auto_import.php index 0fe7ae535838..fbcf03a42235 100644 --- a/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/config/auto_import.php +++ b/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/config/auto_import.php @@ -3,13 +3,11 @@ declare(strict_types=1); use Rector\Core\Configuration\Option; -use Rector\Php80\Rector\Class_\AnnotationToAttributeRector; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; return static function (ContainerConfigurator $containerConfigurator): void { + $containerConfigurator->import(__DIR__ . '/configured_rule.php'); + $parameters = $containerConfigurator->parameters(); $parameters->set(Option::AUTO_IMPORT_NAMES, true); - - $services = $containerConfigurator->services(); - $services->set(AnnotationToAttributeRector::class); }; diff --git a/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/config/configured_rule.php b/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/config/configured_rule.php index 984c46330fd0..b8dec0349096 100644 --- a/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/config/configured_rule.php +++ b/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/config/configured_rule.php @@ -2,11 +2,47 @@ declare(strict_types=1); +use Rector\BetterPhpDocParser\ValueObject\PhpDoc\SymfonyRequiredTagNode; +use Rector\BetterPhpDocParser\ValueObject\PhpDocNode\Symfony\SymfonyRouteTagValueNode; +use Rector\BetterPhpDocParser\ValueObject\PhpDocNode\Symfony\Validator\Constraints\AssertEmailTagValueNode; +use Rector\BetterPhpDocParser\ValueObject\PhpDocNode\Symfony\Validator\Constraints\AssertRangeTagValueNode; +use Rector\Nette\PhpDoc\Node\NetteCrossOriginTagNode; +use Rector\Nette\PhpDoc\Node\NetteInjectTagNode; +use Rector\Nette\PhpDoc\Node\NettePersistentTagNode; use Rector\Php80\Rector\Class_\AnnotationToAttributeRector; +use Rector\Php80\ValueObject\AnnotationToAttribute; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; +use Symplify\SymfonyPhpConfig\ValueObjectInliner; return static function (ContainerConfigurator $containerConfigurator): void { $services = $containerConfigurator->services(); + $services->set(AnnotationToAttributeRector::class) + ->call('configure', [[ + AnnotationToAttributeRector::ANNOTATION_TO_ATTRIBUTE => ValueObjectInliner::inline([ + // nette 3.0+, see https://github.com/nette/application/commit/d2471134ed909210de8a3e8559931902b1bee67b#diff-457507a8bdc046dd4f3a4aa1ca51794543fbb1e06f03825ab69ee864549a570c + new AnnotationToAttribute(NetteInjectTagNode::class, 'Nette\DI\Attributes\Inject'), + new AnnotationToAttribute(NettePersistentTagNode::class, 'Nette\Application\Attributes\Persistent'), + new AnnotationToAttribute(NetteCrossOriginTagNode::class, 'Nette\Application\Attributes\CrossOrigin'), - $services->set(AnnotationToAttributeRector::class); + // symfony + new AnnotationToAttribute( + SymfonyRequiredTagNode::class, + 'Symfony\Contracts\Service\Attribute\Required' + ), + new AnnotationToAttribute( + SymfonyRouteTagValueNode::class, + 'Symfony\Component\Routing\Annotation\Route' + ), + + // symfony/validation + new AnnotationToAttribute( + AssertEmailTagValueNode::class, + 'Symfony\Component\Validator\Constraints\Email' + ), + new AnnotationToAttribute( + AssertRangeTagValueNode::class, + 'Symfony\Component\Validator\Constraints\Range' + ), + ]), + ]]); }; diff --git a/rules/DoctrineCodeQuality/NodeManipulator/DoctrineItemDefaultValueManipulator.php b/rules/DoctrineCodeQuality/NodeManipulator/DoctrineItemDefaultValueManipulator.php index 508728c51d91..b1dd1b77e154 100644 --- a/rules/DoctrineCodeQuality/NodeManipulator/DoctrineItemDefaultValueManipulator.php +++ b/rules/DoctrineCodeQuality/NodeManipulator/DoctrineItemDefaultValueManipulator.php @@ -34,11 +34,11 @@ private function hasItemWithDefaultValue( string $item, $defaultValue ): bool { - $attributableItems = $doctrineTagValueNode->getAttributableItems(); - if (! isset($attributableItems[$item])) { + $items = $doctrineTagValueNode->getItems(); + if (! isset($items[$item])) { return false; } - return $attributableItems[$item] === $defaultValue; + return $items[$item] === $defaultValue; } } diff --git a/rules/Php80/Rector/Class_/AnnotationToAttributeRector.php b/rules/Php80/Rector/Class_/AnnotationToAttributeRector.php index aa96ff25dbbb..a6a9466a5c93 100644 --- a/rules/Php80/Rector/Class_/AnnotationToAttributeRector.php +++ b/rules/Php80/Rector/Class_/AnnotationToAttributeRector.php @@ -11,10 +11,17 @@ use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Function_; use PhpParser\Node\Stmt\Property; +use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; +use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTagRemover; +use Rector\BetterPhpDocParser\ValueObject\PhpDocNode\Symfony\SymfonyRouteTagValueNode; +use Rector\Core\Contract\Rector\ConfigurableRectorInterface; use Rector\Core\Rector\AbstractRector; -use Rector\PhpAttribute\AnnotationToAttributeConverter; -use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; +use Rector\Core\ValueObject\PhpVersionFeature; +use Rector\Php80\ValueObject\AnnotationToAttribute; +use Rector\PhpAttribute\Printer\PhpAttributeGroupFactory; +use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; +use Webmozart\Assert\Assert; /** * @see https://wiki.php.net/rfc/attributes_v2 @@ -23,22 +30,40 @@ * * @see \Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\AnnotationToAttributeRectorTest */ -final class AnnotationToAttributeRector extends AbstractRector +final class AnnotationToAttributeRector extends AbstractRector implements ConfigurableRectorInterface { /** - * @var AnnotationToAttributeConverter + * @var string */ - private $annotationToAttributeConverter; + public const ANNOTATION_TO_ATTRIBUTE = 'annotation_to_attribute'; - public function __construct(AnnotationToAttributeConverter $annotationToAttributeConverter) - { - $this->annotationToAttributeConverter = $annotationToAttributeConverter; + /** + * @var AnnotationToAttribute[] + */ + private $annotationsToAttributes = []; + + /** + * @var PhpAttributeGroupFactory + */ + private $phpAttributeGroupFactory; + + /** + * @var PhpDocTagRemover + */ + private $phpDocTagRemover; + + public function __construct( + PhpAttributeGroupFactory $phpAttributeGroupFactory, + PhpDocTagRemover $phpDocTagRemover + ) { + $this->phpAttributeGroupFactory = $phpAttributeGroupFactory; + $this->phpDocTagRemover = $phpDocTagRemover; } public function getRuleDefinition(): RuleDefinition { return new RuleDefinition('Change annotation to attribute', [ - new CodeSample( + new ConfiguredCodeSample( <<<'CODE_SAMPLE' use Symfony\Component\Routing\Annotation\Route; @@ -64,6 +89,14 @@ public function action() } } CODE_SAMPLE + , [ + self::ANNOTATION_TO_ATTRIBUTE => [ + new AnnotationToAttribute( + SymfonyRouteTagValueNode::class, + 'Symfony\Component\Routing\Annotation\Route' + ), + ], + ] ), ]); } @@ -88,6 +121,48 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - return $this->annotationToAttributeConverter->convertNode($node); + if (! $this->isAtLeastPhpVersion(PhpVersionFeature::ATTRIBUTES)) { + return null; + } + + $phpDocInfo = $this->phpDocInfoFactory->createFromNode($node); + if (! $phpDocInfo instanceof PhpDocInfo) { + return null; + } + + $hasNewAttrGroups = false; + + foreach ($this->annotationsToAttributes as $annotationToAttribute) { + $tagNodeType = $annotationToAttribute->getTagNodeClass(); + $phpDocTagNode = $phpDocInfo->getByType($tagNodeType); + if (! $phpDocTagNode instanceof \PHPStan\PhpDocParser\Ast\Node) { + continue; + } + + // 1. remove php-doc tag + $this->phpDocTagRemover->removeTagValueFromNode($phpDocInfo, $phpDocTagNode); + + // 2. add attributes + $node->attrGroups[] = $this->phpAttributeGroupFactory->create($phpDocTagNode, $annotationToAttribute); + + $hasNewAttrGroups = true; + } + + if ($hasNewAttrGroups) { + return $node; + } + + return null; + } + + /** + * @param array $configuration + */ + public function configure(array $configuration): void + { + $annotationsToAttributes = $configuration[self::ANNOTATION_TO_ATTRIBUTE] ?? []; + Assert::allIsInstanceOf($annotationsToAttributes, AnnotationToAttribute::class); + + $this->annotationsToAttributes = $annotationsToAttributes; } } diff --git a/rules/Php80/ValueObject/AnnotationToAttribute.php b/rules/Php80/ValueObject/AnnotationToAttribute.php new file mode 100644 index 000000000000..29a0db7baec2 --- /dev/null +++ b/rules/Php80/ValueObject/AnnotationToAttribute.php @@ -0,0 +1,46 @@ + + */ + private $tagNodeClass; + + /** + * @var class-string + */ + private $attributeClass; + + /** + * @param class-string $tagNodeClass + * @param class-string $attributeClass + */ + public function __construct(string $tagNodeClass, string $attributeClass) + { + $this->tagNodeClass = $tagNodeClass; + $this->attributeClass = $attributeClass; + } + + /** + * @return class-string + */ + public function getTagNodeClass(): string + { + return $this->tagNodeClass; + } + + /** + * @return class-string + */ + public function getAttributeClass(): string + { + return $this->attributeClass; + } +} diff --git a/src/ValueObject/PhpVersionFeature.php b/src/ValueObject/PhpVersionFeature.php index 60ae8ebf3a70..baf6ed4a3ebe 100644 --- a/src/ValueObject/PhpVersionFeature.php +++ b/src/ValueObject/PhpVersionFeature.php @@ -188,4 +188,10 @@ final class PhpVersionFeature * @var int */ public const PROPERTY_PROMOTION = PhpVersion::PHP_80; + + /** + * @see https://wiki.php.net/rfc/attributes_v2 + * @var int + */ + public const ATTRIBUTES = PhpVersion::PHP_80; }