Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace Rector\PHPUnit\Tests\AnnotationsToAttributes\Rector\ClassMethod\DataProviderAnnotationToAttributeRector\Fixture;

trait SomeTestTrait
{
/**
* @dataProvider someMethod()
*/
public function test(): void
{
}
}

?>
-----
<?php

namespace Rector\PHPUnit\Tests\AnnotationsToAttributes\Rector\ClassMethod\DataProviderAnnotationToAttributeRector\Fixture;

trait SomeTestTrait
{
#[\PHPUnit\Framework\Attributes\DataProvider('someMethod')]
public function test(): void
{
}
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Rector\PHPUnit\Tests\AnnotationsToAttributes\Rector\ClassMethod\DataProviderAnnotationToAttributeRector\Fixture;

trait SkipNonPublicTraitMethod
{
/**
* @dataProvider someMethod()
*/
protected function test(): void
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Rector\PHPUnit\Tests\AnnotationsToAttributes\Rector\ClassMethod\DataProviderAnnotationToAttributeRector\Fixture;

trait SkipStaticTraitMethod
{
/**
* @dataProvider someMethod()
*/
public static function test(): void
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ReflectionProvider;
use Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
Expand All @@ -20,7 +19,6 @@
use Rector\PHPUnit\Enum\PHPUnitAttribute;
use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer;
use Rector\Rector\AbstractRector;
use Rector\Reflection\ReflectionResolver;
use Rector\ValueObject\PhpVersion;
use Rector\VersionBonding\Contract\ComposerPackageConstraintInterface;
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
Expand All @@ -37,7 +35,6 @@ 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 @@ -126,15 +123,6 @@ public function refactor(Node $node): ?Node
return null;
}

$classReflection = $this->reflectionResolver->resolveClassReflection($node);
if (! $classReflection instanceof ClassReflection) {
return null;
}

if (! $classReflection->isClass()) {
return null;
}

foreach ($desiredTagValueNodes as $desiredTagValueNode) {
if (! $desiredTagValueNode->value instanceof GenericTagValueNode && ! $desiredTagValueNode->value instanceof DoctrineAnnotationTagValueNode) {
continue;
Expand Down
44 changes: 44 additions & 0 deletions src/NodeAnalyzer/TestsNodeAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ public function isInTestClass(Node $node): bool
return false;
}

// traits have no parent, so the test case check below can never match them
if ($classReflection->isTrait()) {
return $this->isInTestTrait($classReflection, $node);
}

return array_any(
PHPUnitClassName::TEST_CLASSES,
fn (string $testCaseObjectClass): bool => $classReflection->is($testCaseObjectClass)
Expand Down Expand Up @@ -83,6 +88,45 @@ public function isAssertMethodCallName(Node $node, string $name): bool
return $this->nodeNameResolver->isName($node->name, $name);
}

/**
* Test traits live next to the test cases that use them, so the namespace is the main hint.
* Only public non-static methods can be test methods, and a "test" prefixed one is a test
* method even outside a tests namespace.
*/
private function isInTestTrait(ClassReflection $classReflection, Node $node): bool
{
if (! $node instanceof ClassMethod) {
return $this->isInTestsNamespace($classReflection);
}

if (! $node->isPublic()) {
return false;
}

if ($node->isStatic()) {
return false;
}

if ($this->isInTestsNamespace($classReflection)) {
return true;
}

return str_starts_with($node->name->toString(), 'test');
}

private function isInTestsNamespace(ClassReflection $classReflection): bool
{
$nameParts = explode('\\', $classReflection->getName());

// drop the short trait name, only the namespace matters here
array_pop($nameParts);

return array_any(
$nameParts,
static fn (string $namePart): bool => in_array($namePart, ['Test', 'Tests'], true)
);
}

/**
* @param string[] $names
*/
Expand Down
Loading