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,32 @@
<?php

namespace Rector\PHPUnit\Tests\PHPUnit60\Rector\ClassMethod\AddDoesNotPerformAssertionToNonAssertingTestRector\Fixture;

use Rector\PHPUnit\Tests\PHPUnit60\Rector\ClassMethod\AddDoesNotPerformAssertionToNonAssertingTestRector\Source\AbstractClassWithNonAssertingMethod;

class ParentTestCallWithoutAssert extends AbstractClassWithNonAssertingMethod
{
public function testSomething()
{
parent::testSomething();
}
}

?>
-----
<?php

namespace Rector\PHPUnit\Tests\PHPUnit60\Rector\ClassMethod\AddDoesNotPerformAssertionToNonAssertingTestRector\Fixture;

use Rector\PHPUnit\Tests\PHPUnit60\Rector\ClassMethod\AddDoesNotPerformAssertionToNonAssertingTestRector\Source\AbstractClassWithNonAssertingMethod;

class ParentTestCallWithoutAssert extends AbstractClassWithNonAssertingMethod
{
#[\PHPUnit\Framework\Attributes\DoesNotPerformAssertions]
public function testSomething()
{
parent::testSomething();
}
}

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

namespace Rector\PHPUnit\Tests\PHPUnit60\Rector\ClassMethod\AddDoesNotPerformAssertionToNonAssertingTestRector\Fixture;

use PHPUnit\Framework\TestCase;

final class SiblingHelperNonAsserting extends TestCase
{
public function testHelper()
{
$this->helper();
}

private function helper()
{
$value = 1;
}
}

final class SiblingHelperAsserting extends TestCase
{
public function testHelper()
{
$this->helper();
}

private function helper()
{
$this->assertTrue(true);
}
}

?>
-----
<?php

namespace Rector\PHPUnit\Tests\PHPUnit60\Rector\ClassMethod\AddDoesNotPerformAssertionToNonAssertingTestRector\Fixture;

use PHPUnit\Framework\TestCase;

final class SiblingHelperNonAsserting extends TestCase
{
#[\PHPUnit\Framework\Attributes\DoesNotPerformAssertions]
public function testHelper()
{
$this->helper();
}

private function helper()
{
$value = 1;
}
}

final class SiblingHelperAsserting extends TestCase
{
public function testHelper()
{
$this->helper();
}

private function helper()
{
$this->assertTrue(true);
}
}

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

namespace Rector\PHPUnit\Tests\PHPUnit60\Rector\ClassMethod\AddDoesNotPerformAssertionToNonAssertingTestRector\Fixture;

use Rector\PHPUnit\Tests\PHPUnit60\Rector\ClassMethod\AddDoesNotPerformAssertionToNonAssertingTestRector\Source\AbstractClassWithAssertingMethod;

class SkipParentTestCallWithAssert extends AbstractClassWithAssertingMethod
{
public function testSomething()
{
parent::testSomething();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Rector\PHPUnit\Tests\PHPUnit60\Rector\ClassMethod\AddDoesNotPerformAssertionToNonAssertingTestRector\Source;

use PHPUnit\Framework\TestCase;

abstract class AbstractClassWithAssertingMethod extends TestCase
{
public function testSomething()
{
$this->assertTrue(true);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Rector\PHPUnit\Tests\PHPUnit60\Rector\ClassMethod\AddDoesNotPerformAssertionToNonAssertingTestRector\Source;

use PHPUnit\Framework\TestCase;

abstract class AbstractClassWithNonAssertingMethod extends TestCase
{
public function testSomething()
{
$value = 1 + 1;
}
}
70 changes: 60 additions & 10 deletions src/NodeAnalyzer/AssertCallAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,17 @@
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Identifier;
use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Type\TypeWithClassName;
use Rector\Enum\ObjectReference;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\NodeTypeResolver;
use Rector\PhpParser\AstResolver;
use Rector\PhpParser\Node\BetterNodeFinder;
use Rector\PhpParser\Printer\BetterStandardPrinter;
use Rector\PHPUnit\Enum\PHPUnitClassName;
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;

final class AssertCallAnalyzer
Expand Down Expand Up @@ -74,7 +79,10 @@ public function containsAssertCall(ClassMethod $classMethod): bool
return false;
}

$cacheHash = md5($this->betterStandardPrinter->prettyPrint([$classMethod]));
// the same body means a different method per class, e.g. "$this->helper()" or "parent::test()"
$cacheHash = md5(
$this->betterStandardPrinter->prettyPrint([$classMethod]) . $this->resolveOwnClassName($classMethod)
);

if (isset($this->containsAssertCallByClassMethod[$cacheHash])) {
return $this->containsAssertCallByClassMethod[$cacheHash];
Expand All @@ -83,7 +91,7 @@ public function containsAssertCall(ClassMethod $classMethod): bool
// A. try "->assert" shallow search first for performance
$hasDirectAssertOrMockCall = $this->hasDirectAssertOrMockCall($classMethod);
if ($hasDirectAssertOrMockCall) {
$this->containsAssertCallByClassMethod[$cacheHash] = $hasDirectAssertOrMockCall;
$this->containsAssertCallByClassMethod[$cacheHash] = true;
return true;
}

Expand Down Expand Up @@ -203,22 +211,64 @@ private function hasNestedAssertCall(ClassMethod $classMethod): bool

private function resolveClassMethodFromCall(StaticCall|MethodCall $call): ?ClassMethod
{
if ($call instanceof MethodCall) {
$objectType = $this->nodeTypeResolver->getType($call->var);
} else {
// StaticCall
$objectType = $this->nodeTypeResolver->getType($call->class);
$methodName = $this->nodeNameResolver->getName($call->name);
if ($methodName === null) {
return null;
}

$className = $this->resolveClassName($call);
if ($className === null) {
return null;
}

return $this->astResolver->resolveClassMethod($className, $methodName);
}

private function resolveOwnClassName(ClassMethod $classMethod): string
{
$classReflection = $this->resolveClassReflection($classMethod);
if (! $classReflection instanceof ClassReflection) {
return '';
}

return $classReflection->getName();
}

private function resolveClassName(StaticCall|MethodCall $call): ?string
{
if ($call instanceof StaticCall && $this->nodeNameResolver->isName($call->class, ObjectReference::PARENT)) {
// "parent" resolves to a union of all ancestors in type resolver, so use the direct parent class instead
$parentClassReflection = $this->resolveClassReflection($call)?->getParentClass();
if (! $parentClassReflection instanceof ClassReflection) {
return null;
}

// PHPUnit itself has no asserting test methods, no need to parse its huge classes
if ($parentClassReflection->getName() === PHPUnitClassName::TEST_CASE) {
return null;
}

return $parentClassReflection->getName();
}

$objectType = $call instanceof MethodCall
? $this->nodeTypeResolver->getType($call->var)
: $this->nodeTypeResolver->getType($call->class);

if (! $objectType instanceof TypeWithClassName) {
return null;
}

$methodName = $this->nodeNameResolver->getName($call->name);
if ($methodName === null) {
return $objectType->getClassName();
}

private function resolveClassReflection(Node $node): ?ClassReflection
{
$scope = $node->getAttribute(AttributeKey::SCOPE);
if (! $scope instanceof Scope) {
return null;
}

return $this->astResolver->resolveClassMethod($objectType->getClassName(), $methodName);
return $scope->getClassReflection();
}
}
Loading