From 3ed3d1da3a6a2a574abfd8ccfb11821a8f4cf8fa Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Wed, 20 Nov 2024 16:40:55 +0100 Subject: [PATCH] [attributes] Add check that attribute class exists, to avoid upgrading early --- config/sets/annotations-to-attributes.php | 28 ------------------- ...ataProviderAnnotationToAttributeRector.php | 16 ++++++++--- ...dsAnnotationWithValueToAttributeRector.php | 13 ++++++++- .../TestWithAnnotationToAttributeRector.php | 16 +++++++++-- ...rsAnnotationWithValueToAttributeRector.php | 22 +++++++++++++-- .../TicketAnnotationToAttributeRector.php | 13 +++++++++ 6 files changed, 70 insertions(+), 38 deletions(-) diff --git a/config/sets/annotations-to-attributes.php b/config/sets/annotations-to-attributes.php index e4c6f802..74e28c08 100644 --- a/config/sets/annotations-to-attributes.php +++ b/config/sets/annotations-to-attributes.php @@ -52,34 +52,6 @@ new AnnotationWithValueToAttribute('uses', 'PHPUnit\Framework\Attributes\UsesClass'), new AnnotationWithValueToAttribute('testDox', 'PHPUnit\Framework\Attributes\TestDox'), new AnnotationWithValueToAttribute('testdox', 'PHPUnit\Framework\Attributes\TestDox'), - - // new AnnotationToAttribute('dataProvider', 'PHPUnit\Framework\Attributes\DataProviderExternal'), - - // depends - // new AnnotationToAttribute('depends', 'PHPUnit\Framework\Attributes\DependsExternal'), - // new AnnotationToAttribute('PHPUnit\Framework\Attributes\DependsExternalUsingDeepClone'), - // new AnnotationToAttribute('PHPUnit\Framework\Attributes\DependsExternalUsingShallowClone'), - // new AnnotationToAttribute('PHPUnit\Framework\Attributes\DependsOnClass'), - // new AnnotationToAttribute('PHPUnit\Framework\Attributes\DependsOnClassUsingDeepClone'), - // new AnnotationToAttribute('PHPUnit\Framework\Attributes\DependsOnClassUsingShallowClone'), - // new AnnotationToAttribute('PHPUnit\Framework\Attributes\DependsUsingDeepClone'), - // new AnnotationToAttribute('PHPUnit\Framework\Attributes\DependsUsingShallowClone'), - // new AnnotationToAttribute('PHPUnit\Framework\Attributes\ExcludeGlobalVariableFromBackup'), - // new AnnotationToAttribute('PHPUnit\Framework\Attributes\ExcludeStaticPropertyFromBackup'), - - // requires - // new AnnotationToAttribute('PHPUnit\Framework\Attributes\RequiresFunction'), - // new AnnotationToAttribute('PHPUnit\Framework\Attributes\RequiresMethod'), - // new AnnotationToAttribute('PHPUnit\Framework\Attributes\RequiresOperatingSystem'), - // new AnnotationToAttribute('PHPUnit\Framework\Attributes\RequiresOperatingSystemFamily'), - // new AnnotationToAttribute('PHPUnit\Framework\Attributes\RequiresPhp'), - // new AnnotationToAttribute('PHPUnit\Framework\Attributes\RequiresPhpExtension'), - // new AnnotationToAttribute('PHPUnit\Framework\Attributes\RequiresPhpunit'), - // new AnnotationToAttribute('PHPUnit\Framework\Attributes\RequiresSetting'), - // new AnnotationToAttribute('PHPUnit\Framework\Attributes\RunClassInSeparateProcess'), - - // new AnnotationToAttribute('PHPUnit\Framework\Attributes\TestWithJson'), - // new AnnotationToAttribute('PHPUnit\Framework\Attributes\UsesFunction'), ]); $rectorConfig->ruleWithConfiguration(AnnotationToAttributeRector::class, [ diff --git a/rules/AnnotationsToAttributes/Rector/ClassMethod/DataProviderAnnotationToAttributeRector.php b/rules/AnnotationsToAttributes/Rector/ClassMethod/DataProviderAnnotationToAttributeRector.php index 59770217..99eb2afe 100644 --- a/rules/AnnotationsToAttributes/Rector/ClassMethod/DataProviderAnnotationToAttributeRector.php +++ b/rules/AnnotationsToAttributes/Rector/ClassMethod/DataProviderAnnotationToAttributeRector.php @@ -10,6 +10,7 @@ use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ReflectionProvider; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTagRemover; @@ -28,6 +29,11 @@ */ final class DataProviderAnnotationToAttributeRector extends AbstractRector implements MinPhpVersionInterface { + /** + * @var string + */ + private const DATA_PROVIDER_CLASS = 'PHPUnit\Framework\Attributes\DataProvider'; + public function __construct( private readonly TestsNodeAnalyzer $testsNodeAnalyzer, private readonly PhpAttributeGroupFactory $phpAttributeGroupFactory, @@ -35,6 +41,7 @@ public function __construct( private readonly ReflectionResolver $reflectionResolver, private readonly DocBlockUpdater $docBlockUpdater, private readonly PhpDocInfoFactory $phpDocInfoFactory, + private readonly ReflectionProvider $reflectionProvider ) { } @@ -100,6 +107,10 @@ public function refactor(Node $node): ?Node return null; } + if (! $this->reflectionProvider->hasClass(self::DATA_PROVIDER_CLASS)) { + return null; + } + $phpDocInfo = $this->phpDocInfoFactory->createFromNode($node); if (! $phpDocInfo instanceof PhpDocInfo) { return null; @@ -158,9 +169,6 @@ private function createAttributeGroup(string $originalAttributeValue): Attribute ); } - return $this->phpAttributeGroupFactory->createFromClassWithItems( - 'PHPUnit\Framework\Attributes\DataProvider', - [$methodName] - ); + return $this->phpAttributeGroupFactory->createFromClassWithItems(self::DATA_PROVIDER_CLASS, [$methodName]); } } diff --git a/rules/AnnotationsToAttributes/Rector/ClassMethod/DependsAnnotationWithValueToAttributeRector.php b/rules/AnnotationsToAttributes/Rector/ClassMethod/DependsAnnotationWithValueToAttributeRector.php index b94dbe57..44df7bb7 100644 --- a/rules/AnnotationsToAttributes/Rector/ClassMethod/DependsAnnotationWithValueToAttributeRector.php +++ b/rules/AnnotationsToAttributes/Rector/ClassMethod/DependsAnnotationWithValueToAttributeRector.php @@ -9,6 +9,7 @@ use PhpParser\Node\Stmt\ClassMethod; use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; +use PHPStan\Reflection\ReflectionProvider; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTagRemover; @@ -26,12 +27,18 @@ */ final class DependsAnnotationWithValueToAttributeRector extends AbstractRector implements MinPhpVersionInterface { + /** + * @var string + */ + private const DEPENDS_ATTRIBUTE = 'PHPUnit\Framework\Attributes\Depends'; + public function __construct( private readonly TestsNodeAnalyzer $testsNodeAnalyzer, private readonly PhpAttributeGroupFactory $phpAttributeGroupFactory, private readonly PhpDocTagRemover $phpDocTagRemover, private readonly DocBlockUpdater $docBlockUpdater, private readonly PhpDocInfoFactory $phpDocInfoFactory, + private readonly ReflectionProvider $reflectionProvider ) { } @@ -95,6 +102,10 @@ public function refactor(Node $node): ?Node return null; } + if (! $this->reflectionProvider->hasClass(self::DEPENDS_ATTRIBUTE)) { + return null; + } + $hasChanged = false; foreach ($node->getMethods() as $classMethod) { $phpDocInfo = $this->phpDocInfoFactory->createFromNode($classMethod); @@ -178,7 +189,7 @@ private function resolveAttributeValueAndAttributeName( $originalAttributeValue ); - $attributeName = 'PHPUnit\Framework\Attributes\Depends'; + $attributeName = self::DEPENDS_ATTRIBUTE; if (! is_string($attributeValue)) { // other: depends other Class_ $attributeValue = $this->resolveDependsClass($originalAttributeValue); diff --git a/rules/AnnotationsToAttributes/Rector/ClassMethod/TestWithAnnotationToAttributeRector.php b/rules/AnnotationsToAttributes/Rector/ClassMethod/TestWithAnnotationToAttributeRector.php index 9389613d..874c438b 100644 --- a/rules/AnnotationsToAttributes/Rector/ClassMethod/TestWithAnnotationToAttributeRector.php +++ b/rules/AnnotationsToAttributes/Rector/ClassMethod/TestWithAnnotationToAttributeRector.php @@ -9,6 +9,7 @@ use PhpParser\Node\Stmt\ClassMethod; use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; +use PHPStan\Reflection\ReflectionProvider; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTagRemover; @@ -29,12 +30,18 @@ */ final class TestWithAnnotationToAttributeRector extends AbstractRector implements MinPhpVersionInterface { + /** + * @var string + */ + private const TEST_WITH_ATTRIBUTE = 'PHPUnit\Framework\Attributes\TestWith'; + public function __construct( private readonly TestsNodeAnalyzer $testsNodeAnalyzer, private readonly PhpAttributeGroupFactory $phpAttributeGroupFactory, private readonly PhpDocTagRemover $phpDocTagRemover, private readonly DocBlockUpdater $docBlockUpdater, - private readonly PhpDocInfoFactory $phpDocInfoFactory + private readonly PhpDocInfoFactory $phpDocInfoFactory, + private readonly ReflectionProvider $reflectionProvider ) { } @@ -94,6 +101,11 @@ public function refactor(Node $node): ?Node return null; } + // make sure the attribute class exists + if (! $this->reflectionProvider->hasClass(self::TEST_WITH_ATTRIBUTE)) { + return null; + } + $phpDocInfo = $this->phpDocInfoFactory->createFromNode($node); if (! $phpDocInfo instanceof PhpDocInfo) { return null; @@ -128,7 +140,7 @@ public function refactor(Node $node): ?Node $jsonArray = Json::decode(trim($testWithItem), Json::FORCE_ARRAY); $attributeGroups[] = $this->phpAttributeGroupFactory->createFromClassWithItems( - 'PHPUnit\Framework\Attributes\TestWith', + self::TEST_WITH_ATTRIBUTE, [$jsonArray] ); } diff --git a/rules/AnnotationsToAttributes/Rector/Class_/CoversAnnotationWithValueToAttributeRector.php b/rules/AnnotationsToAttributes/Rector/Class_/CoversAnnotationWithValueToAttributeRector.php index 2de26879..e8fae20a 100644 --- a/rules/AnnotationsToAttributes/Rector/Class_/CoversAnnotationWithValueToAttributeRector.php +++ b/rules/AnnotationsToAttributes/Rector/Class_/CoversAnnotationWithValueToAttributeRector.php @@ -10,6 +10,7 @@ use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode; +use PHPStan\Reflection\ReflectionProvider; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTagRemover; @@ -27,12 +28,23 @@ */ final class CoversAnnotationWithValueToAttributeRector extends AbstractRector implements MinPhpVersionInterface { + /** + * @var string + */ + private const COVERS_FUNCTION_ATTRIBUTE = 'PHPUnit\Framework\Attributes\CoversFunction'; + + /** + * @var string + */ + private const COVERTS_CLASS_ATTRIBUTE = 'PHPUnit\Framework\Attributes\CoversClass'; + public function __construct( private readonly PhpDocTagRemover $phpDocTagRemover, private readonly PhpAttributeGroupFactory $phpAttributeGroupFactory, private readonly TestsNodeAnalyzer $testsNodeAnalyzer, private readonly DocBlockUpdater $docBlockUpdater, - private readonly PhpDocInfoFactory $phpDocInfoFactory + private readonly PhpDocInfoFactory $phpDocInfoFactory, + private readonly ReflectionProvider $reflectionProvider ) { } @@ -98,6 +110,10 @@ public function refactor(Node $node): ?Node return null; } + if (! $this->reflectionProvider->hasClass(self::COVERS_FUNCTION_ATTRIBUTE)) { + return null; + } + if ($node instanceof Class_) { $coversAttributeGroups = $this->resolveClassAttributes($node); if ($coversAttributeGroups === []) { @@ -122,10 +138,10 @@ public function refactor(Node $node): ?Node private function createAttributeGroup(string $annotationValue): AttributeGroup { if (str_starts_with($annotationValue, '::')) { - $attributeClass = 'PHPUnit\Framework\Attributes\CoversFunction'; + $attributeClass = self::COVERS_FUNCTION_ATTRIBUTE; $attributeValue = trim($annotationValue, ':()'); } else { - $attributeClass = 'PHPUnit\Framework\Attributes\CoversClass'; + $attributeClass = self::COVERTS_CLASS_ATTRIBUTE; $attributeValue = trim($annotationValue) . '::class'; } diff --git a/rules/AnnotationsToAttributes/Rector/Class_/TicketAnnotationToAttributeRector.php b/rules/AnnotationsToAttributes/Rector/Class_/TicketAnnotationToAttributeRector.php index d15a43d5..01c34ec7 100644 --- a/rules/AnnotationsToAttributes/Rector/Class_/TicketAnnotationToAttributeRector.php +++ b/rules/AnnotationsToAttributes/Rector/Class_/TicketAnnotationToAttributeRector.php @@ -14,10 +14,12 @@ use PhpParser\Node\Stmt\ClassMethod; use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; +use PHPStan\Reflection\ReflectionProvider; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTagRemover; use Rector\Comments\NodeDocBlock\DocBlockUpdater; +use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer; use Rector\Rector\AbstractRector; use Rector\ValueObject\PhpVersionFeature; use Rector\VersionBonding\Contract\MinPhpVersionInterface; @@ -40,6 +42,8 @@ public function __construct( private readonly PhpDocTagRemover $phpDocTagRemover, private readonly DocBlockUpdater $docBlockUpdater, private readonly PhpDocInfoFactory $phpDocInfoFactory, + private readonly ReflectionProvider $reflectionProvider, + private readonly TestsNodeAnalyzer $testsNodeAnalyzer, ) { } @@ -90,6 +94,15 @@ public function provideMinPhpVersion(): int */ public function refactor(Node $node): ?Node { + if (! $this->testsNodeAnalyzer->isInTestClass($node)) { + return null; + } + + // make sure the attribute class exists + if (! $this->reflectionProvider->hasClass(self::TICKET_CLASS)) { + return null; + } + $phpDocInfo = $this->phpDocInfoFactory->createFromNode($node); if (! $phpDocInfo instanceof PhpDocInfo) { return null;