diff --git a/rules-tests/PHPUnit120/Rector/ClassMethod/ExpressionCreateMockToCreateStubRector/Fixture/skip_used_as_next_arg_mockobject_type.php.inc b/rules-tests/PHPUnit120/Rector/ClassMethod/ExpressionCreateMockToCreateStubRector/Fixture/skip_used_as_next_arg_mockobject_type.php.inc new file mode 100644 index 00000000..db78935b --- /dev/null +++ b/rules-tests/PHPUnit120/Rector/ClassMethod/ExpressionCreateMockToCreateStubRector/Fixture/skip_used_as_next_arg_mockobject_type.php.inc @@ -0,0 +1,20 @@ +createMock(ClassWithDependency::class); + $this->configureMock($mock); + } + + private function configureMock(\PHPUnit\Framework\MockObject\MockObject $mock) + { + $mock->method('someMethod')->willReturn(1); + } +} diff --git a/rules-tests/PHPUnit120/Rector/ClassMethod/ExpressionCreateMockToCreateStubRector/Fixture/skip_used_as_next_arg_mockobject_type_named.php.inc b/rules-tests/PHPUnit120/Rector/ClassMethod/ExpressionCreateMockToCreateStubRector/Fixture/skip_used_as_next_arg_mockobject_type_named.php.inc new file mode 100644 index 00000000..a3cd3c63 --- /dev/null +++ b/rules-tests/PHPUnit120/Rector/ClassMethod/ExpressionCreateMockToCreateStubRector/Fixture/skip_used_as_next_arg_mockobject_type_named.php.inc @@ -0,0 +1,20 @@ +createMock(ClassWithDependency::class); + $this->configureMock(mock: $mock, otherParam: 1); + } + + private function configureMock($otherParam, \PHPUnit\Framework\MockObject\MockObject $mock) + { + $mock->method('someMethod')->willReturn(1); + } +} diff --git a/rules/AnnotationsToAttributes/Rector/ClassMethod/TestWithAnnotationToAttributeRector.php b/rules/AnnotationsToAttributes/Rector/ClassMethod/TestWithAnnotationToAttributeRector.php index 30f59c69..763cfda7 100644 --- a/rules/AnnotationsToAttributes/Rector/ClassMethod/TestWithAnnotationToAttributeRector.php +++ b/rules/AnnotationsToAttributes/Rector/ClassMethod/TestWithAnnotationToAttributeRector.php @@ -129,7 +129,6 @@ public function refactor(Node $node): ?Node // test from doc blocks $this->phpDocTagRemover->removeTagValueFromNode($phpDocInfo, $testWithPhpDocTagNode); - /** @var GenericTagValueNode $genericTagValueNode */ $genericTagValueNode = $testWithPhpDocTagNode->value; $testWithItems = explode("\n", trim($genericTagValueNode->value)); diff --git a/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php b/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php index 5bd81179..88573763 100644 --- a/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php +++ b/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php @@ -8,11 +8,16 @@ use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Expr\Variable; +use PhpParser\Node\Identifier; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; +use PHPStan\Reflection\MethodReflection; +use PHPStan\Type\ObjectType; use Rector\NodeNameResolver\NodeNameResolver; use Rector\PhpParser\Node\BetterNodeFinder; use Rector\PHPUnit\CodeQuality\NodeFinder\VariableFinder; +use Rector\PHPUnit\Enum\PHPUnitClassName; +use Rector\Reflection\ReflectionResolver; final readonly class MockObjectExprDetector { @@ -20,6 +25,7 @@ public function __construct( private BetterNodeFinder $betterNodeFinder, private NodeNameResolver $nodeNameResolver, private VariableFinder $variableFinder, + private ReflectionResolver $reflectionResolver, ) { } @@ -67,6 +73,8 @@ public function isUsedForMocking(Expr $expr, ClassMethod $classMethod): bool /** @var array $methodCalls */ $methodCalls = $this->betterNodeFinder->findInstancesOfScoped((array) $classMethod->stmts, [MethodCall::class]); + $mockObjectType = new ObjectType(PHPUnitClassName::MOCK_OBJECT); + foreach ($methodCalls as $methodCall) { if (! $methodCall->var instanceof Variable) { continue; @@ -76,6 +84,47 @@ public function isUsedForMocking(Expr $expr, ClassMethod $classMethod): bool // variable is being called on, most like mocking, lets skip return true; } + + if ($methodCall->isFirstClassCallable()) { + continue; + } + + // check if variable is passed as arg to a method that declares MockObject type parameter + foreach ($methodCall->getArgs() as $argIndex => $arg) { + if (! $arg->value instanceof Variable) { + continue; + } + + if (! $this->nodeNameResolver->isName($arg->value, $variableName)) { + continue; + } + + $methodReflection = $this->reflectionResolver->resolveMethodReflectionFromMethodCall($methodCall); + if (! $methodReflection instanceof MethodReflection) { + continue; + } + + $variants = $methodReflection->getVariants(); + foreach ($variants as $variant) { + $parameters = $variant->getParameters(); + foreach ($parameters as $parameter) { + $paramType = $parameter->getType(); + if ($arg->name instanceof Identifier + && $this->nodeNameResolver->isName($arg->name, $parameter->getName()) + && $mockObjectType->isSuperTypeOf($paramType) + ->yes()) { + return true; + } + } + + if (isset($parameters[$argIndex])) { + $paramType = $parameters[$argIndex]->getType(); + if ($mockObjectType->isSuperTypeOf($paramType)->yes()) { + return true; + } + } + } + } } return false; diff --git a/rules/CodeQuality/Rector/Class_/AddSeeTestAnnotationRector.php b/rules/CodeQuality/Rector/Class_/AddSeeTestAnnotationRector.php index 2e8708b6..43306e35 100644 --- a/rules/CodeQuality/Rector/Class_/AddSeeTestAnnotationRector.php +++ b/rules/CodeQuality/Rector/Class_/AddSeeTestAnnotationRector.php @@ -134,7 +134,6 @@ private function shouldSkipClass(Class_ $class): bool continue; } - /** @var GenericTagValueNode $genericTagValueNode */ $genericTagValueNode = $seePhpDocTagNode->value; $seeTagClass = ltrim($genericTagValueNode->value, '\\'); diff --git a/rules/PHPUnit120/Rector/ClassMethod/ExpressionCreateMockToCreateStubRector.php b/rules/PHPUnit120/Rector/ClassMethod/ExpressionCreateMockToCreateStubRector.php index 5c1df1b1..acf54efc 100644 --- a/rules/PHPUnit120/Rector/ClassMethod/ExpressionCreateMockToCreateStubRector.php +++ b/rules/PHPUnit120/Rector/ClassMethod/ExpressionCreateMockToCreateStubRector.php @@ -106,7 +106,6 @@ public function refactor(Node $node): ?ClassMethod continue; } - /** @var Assign $assign */ $assign = $stmt->expr; if (! $assign->var instanceof Variable) {