Skip to content

Commit

Permalink
[attributes] Add check that attribute class exists, to avoid upgradin…
Browse files Browse the repository at this point in the history
…g early (#411)
  • Loading branch information
TomasVotruba authored Nov 20, 2024
1 parent 07f8fde commit a506b2c
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 38 deletions.
28 changes: 0 additions & 28 deletions config/sets/annotations-to-attributes.php
Original file line number Diff line number Diff line change
Expand Up @@ -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, [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -28,13 +29,19 @@
*/
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,
private readonly PhpDocTagRemover $phpDocTagRemover,
private readonly ReflectionResolver $reflectionResolver,
private readonly DocBlockUpdater $docBlockUpdater,
private readonly PhpDocInfoFactory $phpDocInfoFactory,
private readonly ReflectionProvider $reflectionProvider
) {
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
) {
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
) {
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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]
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
) {
}

Expand Down Expand Up @@ -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 === []) {
Expand All @@ -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';
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
) {
}

Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit a506b2c

Please sign in to comment.