From fb065ed634b3707f12623cb7d95d1a9012e6db0a Mon Sep 17 00:00:00 2001 From: Andy Postnikov Date: Sun, 29 Oct 2023 03:09:58 +0100 Subject: [PATCH 1/9] WIP: Rule to convert action to attributes --- .../drupal-10/drupal-10-all-deprecations.php | 1 + config/drupal-10/drupal-10.2-deprecations.php | 11 ++ .../ActionAnnotationToAttributeRector.php | 171 ++++++++++++++++++ src/Set/Drupal10SetList.php | 1 + 4 files changed, 184 insertions(+) create mode 100644 config/drupal-10/drupal-10.2-deprecations.php create mode 100644 src/Rector/Deprecation/ActionAnnotationToAttributeRector.php diff --git a/config/drupal-10/drupal-10-all-deprecations.php b/config/drupal-10/drupal-10-all-deprecations.php index d7667917..43cbdd84 100644 --- a/config/drupal-10/drupal-10-all-deprecations.php +++ b/config/drupal-10/drupal-10-all-deprecations.php @@ -9,6 +9,7 @@ $rectorConfig->sets([ Drupal10SetList::DRUPAL_100, Drupal10SetList::DRUPAL_101, + Drupal10SetList::DRUPAL_102, ]); $rectorConfig->bootstrapFiles([ diff --git a/config/drupal-10/drupal-10.2-deprecations.php b/config/drupal-10/drupal-10.2-deprecations.php new file mode 100644 index 00000000..6267bacf --- /dev/null +++ b/config/drupal-10/drupal-10.2-deprecations.php @@ -0,0 +1,11 @@ +rule(\DrupalRector\Rector\Deprecation\ActionAnnotationToAttributeRector::class); +}; diff --git a/src/Rector/Deprecation/ActionAnnotationToAttributeRector.php b/src/Rector/Deprecation/ActionAnnotationToAttributeRector.php new file mode 100644 index 00000000..d4f975c6 --- /dev/null +++ b/src/Rector/Deprecation/ActionAnnotationToAttributeRector.php @@ -0,0 +1,171 @@ +phpDocTagRemover = $phpDocTagRemover; + $this->docBlockUpdater = $docBlockUpdater; + $this->phpDocInfoFactory = $phpDocInfoFactory; + $this->arrayParser = $arrayParser; + $this->tokenIteratorFactory = $tokenIteratorFactory; + } + public function getRuleDefinition() : RuleDefinition + { + return new RuleDefinition('Change annotations with value to attribute', [new CodeSample(<<<'CODE_SAMPLE' + +namespace Drupal\Core\Action\Plugin\Action; + +use Drupal\Core\Session\AccountInterface; + +/** + * Publishes an entity. + * + * @Action( + * id = "entity:publish_action", + * action_label = @Translation("Publish"), + * deriver = "Drupal\Core\Action\Plugin\Action\Derivative\EntityPublishedActionDeriver", + * ) + */ +class PublishAction extends EntityActionBase { +CODE_SAMPLE +, <<<'CODE_SAMPLE' + +namespace Drupal\Core\Action\Plugin\Action; + +use Drupal\Core\Action\Plugin\Action\Derivative\EntityPublishedActionDeriver; +use Drupal\Core\Action\Attribute\Action; +use Drupal\Core\Session\AccountInterface; +use Drupal\Core\StringTranslation\TranslatableMarkup; + +/** + * Publishes an entity. + */ +#[Action( + id: 'entity:publish_action', + action_label: new TranslatableMarkup('Publish'), + deriver: EntityPublishedActionDeriver::class +)] +class PublishAction extends EntityActionBase { +CODE_SAMPLE +)]); + } + /** + * @return array> + */ + public function getNodeTypes() : array + { + return [Class_::class]; + } + public function provideMinPhpVersion() : int + { + return PhpVersion::PHP_81; + } + /** + * @param Class_|ClassMethod $node + */ + public function refactor(Node $node) : ?Node + { + $phpDocInfo = $this->phpDocInfoFactory->createFromNode($node); + if (!$phpDocInfo instanceof PhpDocInfo) { + return null; + } + /** @var PhpDocTagNode[] $tagsByName */ + $tagsByName = $phpDocInfo->getTagsByName('Action'); + if ($tagsByName === []) { + return null; + } + $hasChanged = \false; + foreach ($tagsByName as $valueNode) { + if (!$valueNode->value instanceof GenericTagValueNode) { + continue; + } + $stringValue = $valueNode->value->value; + $stringValue = '{' . trim($stringValue, '()') . '}'; + $tokenIterator = $this->tokenIteratorFactory->create($stringValue); + $data = $this->arrayParser->parseCurlyArray($tokenIterator, $node); + $attribute = $this->createAttribute($data); + $node->attrGroups[] = new AttributeGroup([$attribute]); + // cleanup + $this->phpDocTagRemover->removeTagValueFromNode($phpDocInfo, $valueNode); + $hasChanged = \true; + } + if ($hasChanged) { + $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node); + return $node; + } + return null; + } + private function createAttribute(array $parsedArgs) : Attribute + { + $fullyQualified = new FullyQualified(Action::class); + $args = []; + foreach ($parsedArgs as $value) { + if ($value->key === 'label') { + $arg = new Node\Expr\New_(new Node\Name(TranslatableMarkup::class), [new Arg(new String_($value->value->values[0]->value->value))]); + } + else { + $arg = new String_($value->value->value); + } + $args[] = new Arg($arg, \false, \false, [], new Node\Identifier($value->key)); + } + return new Attribute($fullyQualified, $args); + } +} diff --git a/src/Set/Drupal10SetList.php b/src/Set/Drupal10SetList.php index aafeb6e5..9c9b1856 100644 --- a/src/Set/Drupal10SetList.php +++ b/src/Set/Drupal10SetList.php @@ -11,4 +11,5 @@ final class Drupal10SetList implements SetListInterface public const DRUPAL_10 = __DIR__.'/../../config/drupal-10/drupal-10-all-deprecations.php'; public const DRUPAL_100 = __DIR__.'/../../config/drupal-10/drupal-10.0-deprecations.php'; public const DRUPAL_101 = __DIR__.'/../../config/drupal-10/drupal-10.1-deprecations.php'; + public const DRUPAL_102 = __DIR__ . '/../../config/drupal-10/drupal-10.2-deprecations.php'; } From 0122ad92ed60b3f8b37ac00500823f1fa2df4ff6 Mon Sep 17 00:00:00 2001 From: bjorn Date: Sun, 10 Dec 2023 09:36:45 +0100 Subject: [PATCH 2/9] Rebase on main and move rector to Drupal10 namespace --- .../Rector/Deprecation/ActionAnnotationToAttributeRector.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/{ => Drupal10}/Rector/Deprecation/ActionAnnotationToAttributeRector.php (99%) diff --git a/src/Rector/Deprecation/ActionAnnotationToAttributeRector.php b/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector.php similarity index 99% rename from src/Rector/Deprecation/ActionAnnotationToAttributeRector.php rename to src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector.php index d4f975c6..42342fc5 100644 --- a/src/Rector/Deprecation/ActionAnnotationToAttributeRector.php +++ b/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector.php @@ -1,6 +1,6 @@ Date: Sun, 10 Dec 2023 09:37:14 +0100 Subject: [PATCH 3/9] Add basic unit test for ActionAnnotationToAttributeRector. --- .../ActionAnnotationToAttributeRectorTest.php | 35 +++++++++++++++++++ .../config/configured_rule.php | 12 +++++++ .../fixture/basic_fixture.php.inc | 27 ++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 tests/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector/ActionAnnotationToAttributeRectorTest.php create mode 100644 tests/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector/config/configured_rule.php create mode 100644 tests/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector/fixture/basic_fixture.php.inc diff --git a/tests/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector/ActionAnnotationToAttributeRectorTest.php b/tests/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector/ActionAnnotationToAttributeRectorTest.php new file mode 100644 index 00000000..f29f94cf --- /dev/null +++ b/tests/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector/ActionAnnotationToAttributeRectorTest.php @@ -0,0 +1,35 @@ +doTestFile($filePath); + } + + /** + * @return Iterator<> + */ + public static function provideData(): \Iterator + { + return self::yieldFilesFromDirectory(__DIR__.'/fixture'); + } + + public function provideConfigFilePath(): string + { + // must be implemented + return __DIR__.'/config/configured_rule.php'; + } +} diff --git a/tests/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector/config/configured_rule.php b/tests/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector/config/configured_rule.php new file mode 100644 index 00000000..f053fd2f --- /dev/null +++ b/tests/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector/config/configured_rule.php @@ -0,0 +1,12 @@ + + +----- + From 565a55f8b45c73fe48ae9ead4f87c7b927788992 Mon Sep 17 00:00:00 2001 From: bjorn Date: Sun, 10 Dec 2023 09:42:37 +0100 Subject: [PATCH 4/9] Fix codestyle --- config/drupal-10/drupal-10.2-deprecations.php | 1 - .../ActionAnnotationToAttributeRector.php | 45 ++++++++++++------- src/Set/Drupal10SetList.php | 2 +- .../config/configured_rule.php | 1 - 4 files changed, 31 insertions(+), 18 deletions(-) diff --git a/config/drupal-10/drupal-10.2-deprecations.php b/config/drupal-10/drupal-10.2-deprecations.php index 6267bacf..d7a1127a 100644 --- a/config/drupal-10/drupal-10.2-deprecations.php +++ b/config/drupal-10/drupal-10.2-deprecations.php @@ -2,7 +2,6 @@ declare(strict_types=1); -use DrupalRector\Rector\Deprecation\ActionAnnotationToAttributeRector; use Rector\Config\RectorConfig; return static function (RectorConfig $rectorConfig): void { diff --git a/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector.php b/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector.php index 42342fc5..2ba83478 100644 --- a/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector.php +++ b/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector.php @@ -1,4 +1,6 @@ -phpDocTagRemover = $phpDocTagRemover; @@ -67,7 +75,8 @@ public function __construct(PhpDocTagRemover $phpDocTagRemover, DocBlockUpdater $this->arrayParser = $arrayParser; $this->tokenIteratorFactory = $tokenIteratorFactory; } - public function getRuleDefinition() : RuleDefinition + + public function getRuleDefinition(): RuleDefinition { return new RuleDefinition('Change annotations with value to attribute', [new CodeSample(<<<'CODE_SAMPLE' @@ -86,7 +95,7 @@ public function getRuleDefinition() : RuleDefinition */ class PublishAction extends EntityActionBase { CODE_SAMPLE -, <<<'CODE_SAMPLE' + , <<<'CODE_SAMPLE' namespace Drupal\Core\Action\Plugin\Action; @@ -105,23 +114,26 @@ class PublishAction extends EntityActionBase { )] class PublishAction extends EntityActionBase { CODE_SAMPLE -)]); + )]); } + /** * @return array> */ - public function getNodeTypes() : array + public function getNodeTypes(): array { return [Class_::class]; } - public function provideMinPhpVersion() : int + + public function provideMinPhpVersion(): int { return PhpVersion::PHP_81; } + /** * @param Class_|ClassMethod $node */ - public function refactor(Node $node) : ?Node + public function refactor(Node $node): ?Node { $phpDocInfo = $this->phpDocInfoFactory->createFromNode($node); if (!$phpDocInfo instanceof PhpDocInfo) { @@ -138,7 +150,7 @@ public function refactor(Node $node) : ?Node continue; } $stringValue = $valueNode->value->value; - $stringValue = '{' . trim($stringValue, '()') . '}'; + $stringValue = '{'.trim($stringValue, '()').'}'; $tokenIterator = $this->tokenIteratorFactory->create($stringValue); $data = $this->arrayParser->parseCurlyArray($tokenIterator, $node); $attribute = $this->createAttribute($data); @@ -149,23 +161,26 @@ public function refactor(Node $node) : ?Node } if ($hasChanged) { $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node); + return $node; } + return null; } - private function createAttribute(array $parsedArgs) : Attribute + + private function createAttribute(array $parsedArgs): Attribute { $fullyQualified = new FullyQualified(Action::class); $args = []; foreach ($parsedArgs as $value) { if ($value->key === 'label') { $arg = new Node\Expr\New_(new Node\Name(TranslatableMarkup::class), [new Arg(new String_($value->value->values[0]->value->value))]); - } - else { + } else { $arg = new String_($value->value->value); } $args[] = new Arg($arg, \false, \false, [], new Node\Identifier($value->key)); } + return new Attribute($fullyQualified, $args); } } diff --git a/src/Set/Drupal10SetList.php b/src/Set/Drupal10SetList.php index 9c9b1856..768e2eba 100644 --- a/src/Set/Drupal10SetList.php +++ b/src/Set/Drupal10SetList.php @@ -11,5 +11,5 @@ final class Drupal10SetList implements SetListInterface public const DRUPAL_10 = __DIR__.'/../../config/drupal-10/drupal-10-all-deprecations.php'; public const DRUPAL_100 = __DIR__.'/../../config/drupal-10/drupal-10.0-deprecations.php'; public const DRUPAL_101 = __DIR__.'/../../config/drupal-10/drupal-10.1-deprecations.php'; - public const DRUPAL_102 = __DIR__ . '/../../config/drupal-10/drupal-10.2-deprecations.php'; + public const DRUPAL_102 = __DIR__.'/../../config/drupal-10/drupal-10.2-deprecations.php'; } diff --git a/tests/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector/config/configured_rule.php b/tests/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector/config/configured_rule.php index f053fd2f..f2d6a510 100644 --- a/tests/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector/config/configured_rule.php +++ b/tests/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector/config/configured_rule.php @@ -3,7 +3,6 @@ declare(strict_types=1); use DrupalRector\Drupal10\Rector\Deprecation\ActionAnnotationToAttributeRector; -use DrupalRector\Rector\ValueObject\DrupalIntroducedVersionConfiguration; use DrupalRector\Tests\Rector\Deprecation\DeprecationBase; use Rector\Config\RectorConfig; From 010e25636354a981d3865e4ef09333539be06df2 Mon Sep 17 00:00:00 2001 From: bjorn Date: Sun, 10 Dec 2023 09:47:16 +0100 Subject: [PATCH 5/9] Fix namespace in config for ActionAnnotationToAttributeRector --- config/drupal-10/drupal-10.2-deprecations.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/drupal-10/drupal-10.2-deprecations.php b/config/drupal-10/drupal-10.2-deprecations.php index d7a1127a..7b3b63e5 100644 --- a/config/drupal-10/drupal-10.2-deprecations.php +++ b/config/drupal-10/drupal-10.2-deprecations.php @@ -2,9 +2,10 @@ declare(strict_types=1); +use DrupalRector\Drupal10\Rector\Deprecation\ActionAnnotationToAttributeRector; use Rector\Config\RectorConfig; return static function (RectorConfig $rectorConfig): void { // @see https://www.drupal.org/node/3395575 - $rectorConfig->rule(\DrupalRector\Rector\Deprecation\ActionAnnotationToAttributeRector::class); + $rectorConfig->rule(ActionAnnotationToAttributeRector::class); }; From 65ce3d13cf9667a8cd9b9c2e8802626e66e9d38c Mon Sep 17 00:00:00 2001 From: bjorn Date: Sun, 10 Dec 2023 09:57:47 +0100 Subject: [PATCH 6/9] The label should be "action_label", --- .../Deprecation/ActionAnnotationToAttributeRector.php | 9 ++++----- .../fixture/basic_fixture.php.inc | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector.php b/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector.php index 2ba83478..59318144 100644 --- a/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector.php +++ b/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector.php @@ -4,8 +4,6 @@ namespace DrupalRector\Drupal10\Rector\Deprecation; -use Drupal\Core\Action\Attribute\Action; -use Drupal\Core\StringTranslation\TranslatableMarkup; use PhpParser\Node; use PhpParser\Node\Arg; use PhpParser\Node\Attribute; @@ -16,6 +14,7 @@ use PhpParser\Node\Stmt\ClassMethod; use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; +use Rector\BetterPhpDocParser\PhpDoc\ArrayItemNode; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\BetterPhpDocParser\PhpDocInfo\TokenIteratorFactory; @@ -170,11 +169,11 @@ public function refactor(Node $node): ?Node private function createAttribute(array $parsedArgs): Attribute { - $fullyQualified = new FullyQualified(Action::class); + $fullyQualified = new FullyQualified('Drupal\Core\Action\Attribute\Action'); $args = []; foreach ($parsedArgs as $value) { - if ($value->key === 'label') { - $arg = new Node\Expr\New_(new Node\Name(TranslatableMarkup::class), [new Arg(new String_($value->value->values[0]->value->value))]); + if ($value->key === 'action_label') { + $arg = new Node\Expr\New_(new Node\Name('Drupal\Core\StringTranslation\TranslatableMarkup'), [new Arg(new String_($value->value->values[0]->value->value))]); } else { $arg = new String_($value->value->value); } diff --git a/tests/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector/fixture/basic_fixture.php.inc b/tests/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector/fixture/basic_fixture.php.inc index 659e5203..4b4d78d9 100644 --- a/tests/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector/fixture/basic_fixture.php.inc +++ b/tests/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector/fixture/basic_fixture.php.inc @@ -5,7 +5,7 @@ * * @Action( * id = "action_example_basic_action", - * label = @Translation("Action Example: A basic example action that does nothing"), + * action_label = @Translation("Action Example: A basic example action that does nothing"), * type = "system" * ) */ @@ -20,7 +20,7 @@ class BasicExample extends ActionBase implements ContainerFactoryPluginInterface /** * A basic example action that does nothing. */ -#[\Drupal\Core\Action\Attribute\Action(id: 'action_example_basic_action', label: new Drupal\Core\StringTranslation\TranslatableMarkup('Action Example: A basic example action that does nothing'), type: 'system')] +#[\Drupal\Core\Action\Attribute\Action(id: 'action_example_basic_action', action_label: new Drupal\Core\StringTranslation\TranslatableMarkup('Action Example: A basic example action that does nothing'), type: 'system')] class BasicExample extends ActionBase implements ContainerFactoryPluginInterface { } From dedb8721a1df19d6a8f3c0a98e10c1e1bd89e7ce Mon Sep 17 00:00:00 2001 From: bjorn Date: Sun, 10 Dec 2023 09:57:54 +0100 Subject: [PATCH 7/9] Fix phpstan --- .../Rector/Deprecation/ActionAnnotationToAttributeRector.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector.php b/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector.php index 59318144..bd769964 100644 --- a/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector.php +++ b/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector.php @@ -167,6 +167,11 @@ public function refactor(Node $node): ?Node return null; } + /** + * @param array|ArrayItemNode[] $parsedArgs + * + * @return \PhpParser\Node\Attribute + */ private function createAttribute(array $parsedArgs): Attribute { $fullyQualified = new FullyQualified('Drupal\Core\Action\Attribute\Action'); From 842f593cb33c0d6de067125278a0ec85f6fd8c38 Mon Sep 17 00:00:00 2001 From: bjorn Date: Sun, 10 Dec 2023 10:39:40 +0100 Subject: [PATCH 8/9] Add test for other possible translation attribute arguments --- .../multiple_translation_arguments.php.inc | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 tests/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector/fixture/multiple_translation_arguments.php.inc diff --git a/tests/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector/fixture/multiple_translation_arguments.php.inc b/tests/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector/fixture/multiple_translation_arguments.php.inc new file mode 100644 index 00000000..bcc8d7ba --- /dev/null +++ b/tests/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector/fixture/multiple_translation_arguments.php.inc @@ -0,0 +1,27 @@ + + +----- + 'Argument'], ['context' => 'Validation']), type: 'system')] +class BasicExample extends ActionBase implements ContainerFactoryPluginInterface { + +} +?> From dcffdd9b65765d07b6a76c82fc6badee248d2d72 Mon Sep 17 00:00:00 2001 From: bjorn Date: Sun, 10 Dec 2023 10:39:58 +0100 Subject: [PATCH 9/9] Add test for situation where the attribute already exists --- .../existing_attribute_fixture.php.inc | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 tests/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector/fixture/existing_attribute_fixture.php.inc diff --git a/tests/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector/fixture/existing_attribute_fixture.php.inc b/tests/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector/fixture/existing_attribute_fixture.php.inc new file mode 100644 index 00000000..8b197dfe --- /dev/null +++ b/tests/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector/fixture/existing_attribute_fixture.php.inc @@ -0,0 +1,28 @@ + + +----- +