From 650867fced71446dfb3083df2e9e20b984c63f60 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Wed, 20 Aug 2025 16:35:10 +0300 Subject: [PATCH] [PHP 8.5] Convert `@deprecated` to `#[\Deprecated]` on constants Based on the PHP 8.4 rule from #6923, logic was moved to a new `DeprecatedAnnotationToDeprecatedAttributeConverter` service to avoid duplication. --- config/set/php85.php | 8 +- ...otationToDeprecatedAttributeRectorTest.php | 28 ++++ .../Fixture/basic.php.inc | 27 ++++ .../config/configured_rule.php | 12 ++ ...dAnnotationToDeprecatedAttributeRector.php | 112 +-------------- ...dAnnotationToDeprecatedAttributeRector.php | 63 +++++++++ ...notationToDeprecatedAttributeConverter.php | 131 ++++++++++++++++++ src/ValueObject/PhpVersionFeature.php | 6 + 8 files changed, 277 insertions(+), 110 deletions(-) create mode 100644 rules-tests/Php85/Rector/Const_/DeprecatedAnnotationToDeprecatedAttributeRector/DeprecatedAnnotationToDeprecatedAttributeRectorTest.php create mode 100644 rules-tests/Php85/Rector/Const_/DeprecatedAnnotationToDeprecatedAttributeRector/Fixture/basic.php.inc create mode 100644 rules-tests/Php85/Rector/Const_/DeprecatedAnnotationToDeprecatedAttributeRector/config/configured_rule.php create mode 100644 rules/Php85/Rector/Const_/DeprecatedAnnotationToDeprecatedAttributeRector.php create mode 100644 src/PhpAttribute/DeprecatedAnnotationToDeprecatedAttributeConverter.php diff --git a/config/set/php85.php b/config/set/php85.php index b298658f0b6..7536382f3b3 100644 --- a/config/set/php85.php +++ b/config/set/php85.php @@ -7,6 +7,7 @@ use PhpParser\Node\Expr\Cast\Int_; use PhpParser\Node\Expr\Cast\String_; use Rector\Config\RectorConfig; +use Rector\Php85\Rector\Const_\DeprecatedAnnotationToDeprecatedAttributeRector; use Rector\Php85\Rector\ArrayDimFetch\ArrayFirstLastRector; use Rector\Php85\Rector\ClassMethod\NullDebugInfoReturnRector; use Rector\Php85\Rector\FuncCall\RemoveFinfoBufferContextArgRector; @@ -22,7 +23,12 @@ return static function (RectorConfig $rectorConfig): void { $rectorConfig->rules( - [ArrayFirstLastRector::class, RemoveFinfoBufferContextArgRector::class, NullDebugInfoReturnRector::class] + [ + ArrayFirstLastRector::class, + RemoveFinfoBufferContextArgRector::class, + NullDebugInfoReturnRector::class, + DeprecatedAnnotationToDeprecatedAttributeRector::class, + ] ); $rectorConfig->ruleWithConfiguration( diff --git a/rules-tests/Php85/Rector/Const_/DeprecatedAnnotationToDeprecatedAttributeRector/DeprecatedAnnotationToDeprecatedAttributeRectorTest.php b/rules-tests/Php85/Rector/Const_/DeprecatedAnnotationToDeprecatedAttributeRector/DeprecatedAnnotationToDeprecatedAttributeRectorTest.php new file mode 100644 index 00000000000..122b522c627 --- /dev/null +++ b/rules-tests/Php85/Rector/Const_/DeprecatedAnnotationToDeprecatedAttributeRector/DeprecatedAnnotationToDeprecatedAttributeRectorTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/rules-tests/Php85/Rector/Const_/DeprecatedAnnotationToDeprecatedAttributeRector/Fixture/basic.php.inc b/rules-tests/Php85/Rector/Const_/DeprecatedAnnotationToDeprecatedAttributeRector/Fixture/basic.php.inc new file mode 100644 index 00000000000..7556a2caed6 --- /dev/null +++ b/rules-tests/Php85/Rector/Const_/DeprecatedAnnotationToDeprecatedAttributeRector/Fixture/basic.php.inc @@ -0,0 +1,27 @@ + +----- + diff --git a/rules-tests/Php85/Rector/Const_/DeprecatedAnnotationToDeprecatedAttributeRector/config/configured_rule.php b/rules-tests/Php85/Rector/Const_/DeprecatedAnnotationToDeprecatedAttributeRector/config/configured_rule.php new file mode 100644 index 00000000000..78c55cdee0f --- /dev/null +++ b/rules-tests/Php85/Rector/Const_/DeprecatedAnnotationToDeprecatedAttributeRector/config/configured_rule.php @@ -0,0 +1,12 @@ +rule(DeprecatedAnnotationToDeprecatedAttributeRector::class); + $rectorConfig->phpVersion(PhpVersion::PHP_85); +}; diff --git a/rules/Php84/Rector/Class_/DeprecatedAnnotationToDeprecatedAttributeRector.php b/rules/Php84/Rector/Class_/DeprecatedAnnotationToDeprecatedAttributeRector.php index 5913af34c46..c8bffbb5e74 100644 --- a/rules/Php84/Rector/Class_/DeprecatedAnnotationToDeprecatedAttributeRector.php +++ b/rules/Php84/Rector/Class_/DeprecatedAnnotationToDeprecatedAttributeRector.php @@ -4,25 +4,11 @@ namespace Rector\Php84\Rector\Class_; -use Nette\Utils\Strings; use PhpParser\Node; -use PhpParser\Node\Arg; -use PhpParser\Node\Attribute; -use PhpParser\Node\AttributeGroup; -use PhpParser\Node\Identifier; -use PhpParser\Node\Name\FullyQualified; -use PhpParser\Node\Scalar\String_; use PhpParser\Node\Stmt\ClassConst; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Function_; -use PHPStan\PhpDocParser\Ast\PhpDoc\DeprecatedTagValueNode; -use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode; -use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; -use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; -use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTagRemover; -use Rector\Comments\NodeDocBlock\DocBlockUpdater; -use Rector\NodeTypeResolver\Node\AttributeKey; -use Rector\PhpAttribute\NodeFactory\PhpAttributeGroupFactory; +use Rector\PhpAttribute\DeprecatedAnnotationToDeprecatedAttributeConverter; use Rector\Rector\AbstractRector; use Rector\ValueObject\PhpVersionFeature; use Rector\VersionBonding\Contract\MinPhpVersionInterface; @@ -34,23 +20,8 @@ */ final class DeprecatedAnnotationToDeprecatedAttributeRector extends AbstractRector implements MinPhpVersionInterface { - /** - * @see https://regex101.com/r/qNytVk/1 - * @var string - */ - private const VERSION_MATCH_REGEX = '/^(?:(\d+\.\d+\.\d+)\s+)?(.*)$/'; - - /** - * @see https://regex101.com/r/SVDPOB/1 - * @var string - */ - private const START_STAR_SPACED_REGEX = '#^ *\*#ms'; - public function __construct( - private readonly PhpDocTagRemover $phpDocTagRemover, - private readonly PhpAttributeGroupFactory $phpAttributeGroupFactory, - private readonly DocBlockUpdater $docBlockUpdater, - private readonly PhpDocInfoFactory $phpDocInfoFactory, + private readonly DeprecatedAnnotationToDeprecatedAttributeConverter $converter, ) { } @@ -104,88 +75,11 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - $hasChanged = false; - $phpDocInfo = $this->phpDocInfoFactory->createFromNode($node); - if ($phpDocInfo instanceof PhpDocInfo) { - $deprecatedAttributeGroup = $this->handleDeprecated($phpDocInfo); - if ($deprecatedAttributeGroup instanceof AttributeGroup) { - $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node); - $node->attrGroups = array_merge($node->attrGroups, [$deprecatedAttributeGroup]); - $this->removeDeprecatedAnnotations($phpDocInfo); - $hasChanged = true; - } - } - - return $hasChanged ? $node : null; + return $this->converter->convert($node); } public function provideMinPhpVersion(): int { return PhpVersionFeature::DEPRECATED_ATTRIBUTE; } - - private function handleDeprecated(PhpDocInfo $phpDocInfo): ?AttributeGroup - { - $attributeGroup = null; - $desiredTagValueNodes = $phpDocInfo->getTagsByName('deprecated'); - foreach ($desiredTagValueNodes as $desiredTagValueNode) { - if (! $desiredTagValueNode->value instanceof DeprecatedTagValueNode) { - continue; - } - - $attributeGroup = $this->createAttributeGroup($desiredTagValueNode->value->description); - $this->phpDocTagRemover->removeTagValueFromNode($phpDocInfo, $desiredTagValueNode); - - break; - } - - return $attributeGroup; - } - - private function createAttributeGroup(string $annotationValue): AttributeGroup - { - $matches = Strings::match($annotationValue, self::VERSION_MATCH_REGEX); - - if ($matches === null) { - $annotationValue = Strings::replace($annotationValue, self::START_STAR_SPACED_REGEX, ''); - - return new AttributeGroup([ - new Attribute( - new FullyQualified('Deprecated'), - [new Arg( - value: new String_($annotationValue, [ - AttributeKey::KIND => String_::KIND_NOWDOC, - AttributeKey::DOC_LABEL => 'TXT', - ]), - name: new Identifier('message') - )] - ), - ]); - } - - $since = $matches[1] ?? null; - $message = $matches[2] ?? null; - - return $this->phpAttributeGroupFactory->createFromClassWithItems('Deprecated', array_filter([ - 'message' => $message, - 'since' => $since, - ])); - } - - private function removeDeprecatedAnnotations(PhpDocInfo $phpDocInfo): bool - { - $hasChanged = false; - - $desiredTagValueNodes = $phpDocInfo->getTagsByName('deprecated'); - foreach ($desiredTagValueNodes as $desiredTagValueNode) { - if (! $desiredTagValueNode->value instanceof GenericTagValueNode) { - continue; - } - - $this->phpDocTagRemover->removeTagValueFromNode($phpDocInfo, $desiredTagValueNode); - $hasChanged = true; - } - - return $hasChanged; - } } diff --git a/rules/Php85/Rector/Const_/DeprecatedAnnotationToDeprecatedAttributeRector.php b/rules/Php85/Rector/Const_/DeprecatedAnnotationToDeprecatedAttributeRector.php new file mode 100644 index 00000000000..b72c6925bc5 --- /dev/null +++ b/rules/Php85/Rector/Const_/DeprecatedAnnotationToDeprecatedAttributeRector.php @@ -0,0 +1,63 @@ +converter->convert($node); + } + + public function provideMinPhpVersion(): int + { + return PhpVersionFeature::DEPRECATED_ATTRIBUTE_ON_CONSTANT; + } +} diff --git a/src/PhpAttribute/DeprecatedAnnotationToDeprecatedAttributeConverter.php b/src/PhpAttribute/DeprecatedAnnotationToDeprecatedAttributeConverter.php new file mode 100644 index 00000000000..19401e28b09 --- /dev/null +++ b/src/PhpAttribute/DeprecatedAnnotationToDeprecatedAttributeConverter.php @@ -0,0 +1,131 @@ +phpDocInfoFactory->createFromNode($node); + if ($phpDocInfo instanceof PhpDocInfo) { + $deprecatedAttributeGroup = $this->handleDeprecated($phpDocInfo); + if ($deprecatedAttributeGroup instanceof AttributeGroup) { + $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node); + $node->attrGroups = array_merge($node->attrGroups, [$deprecatedAttributeGroup]); + $this->removeDeprecatedAnnotations($phpDocInfo); + $hasChanged = true; + } + } + + return $hasChanged ? $node : null; + } + + private function handleDeprecated(PhpDocInfo $phpDocInfo): ?AttributeGroup + { + $attributeGroup = null; + $desiredTagValueNodes = $phpDocInfo->getTagsByName('deprecated'); + foreach ($desiredTagValueNodes as $desiredTagValueNode) { + if (! $desiredTagValueNode->value instanceof DeprecatedTagValueNode) { + continue; + } + + $attributeGroup = $this->createAttributeGroup($desiredTagValueNode->value->description); + $this->phpDocTagRemover->removeTagValueFromNode($phpDocInfo, $desiredTagValueNode); + + break; + } + + return $attributeGroup; + } + + private function createAttributeGroup(string $annotationValue): AttributeGroup + { + $matches = Strings::match($annotationValue, self::VERSION_MATCH_REGEX); + + if ($matches === null) { + $annotationValue = Strings::replace($annotationValue, self::START_STAR_SPACED_REGEX, ''); + + return new AttributeGroup([ + new Attribute( + new FullyQualified('Deprecated'), + [new Arg( + value: new String_($annotationValue, [ + AttributeKey::KIND => String_::KIND_NOWDOC, + AttributeKey::DOC_LABEL => 'TXT', + ]), + name: new Identifier('message') + )] + ), + ]); + } + + $since = $matches[1] ?? null; + $message = $matches[2] ?? null; + + return $this->phpAttributeGroupFactory->createFromClassWithItems('Deprecated', array_filter([ + 'message' => $message, + 'since' => $since, + ])); + } + + private function removeDeprecatedAnnotations(PhpDocInfo $phpDocInfo): bool + { + $hasChanged = false; + + $desiredTagValueNodes = $phpDocInfo->getTagsByName('deprecated'); + foreach ($desiredTagValueNodes as $desiredTagValueNode) { + if (! $desiredTagValueNode->value instanceof GenericTagValueNode) { + continue; + } + + $this->phpDocTagRemover->removeTagValueFromNode($phpDocInfo, $desiredTagValueNode); + $hasChanged = true; + } + + return $hasChanged; + } +} diff --git a/src/ValueObject/PhpVersionFeature.php b/src/ValueObject/PhpVersionFeature.php index ec67f7703e1..4cde90e8564 100644 --- a/src/ValueObject/PhpVersionFeature.php +++ b/src/ValueObject/PhpVersionFeature.php @@ -780,4 +780,10 @@ final class PhpVersionFeature * @var int */ public const DEPRECATED_NULL_DEBUG_INFO_RETURN = PhpVersion::PHP_85; + + /** + * @see https://wiki.php.net/rfc/attributes-on-constants + * @var int + */ + public const DEPRECATED_ATTRIBUTE_ON_CONSTANT = PhpVersion::PHP_85; }