From 2cad0c6143544f07eea4714ef379d9dd5d88bcce Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Wed, 29 Apr 2026 15:10:15 +0700 Subject: [PATCH 01/13] [PHPUnit12] Skip used as next arg with MockObject Type on ExpressionCreateMockToCreateStubRector --- ...p_used_as_next_arg_mockobject_type.php.inc | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 rules-tests/PHPUnit120/Rector/ClassMethod/ExpressionCreateMockToCreateStubRector/Fixture/skip_used_as_next_arg_mockobject_type.php.inc 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); + } +} From 79bf991aa31644646aefd40f492c83ac2901591b Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Wed, 29 Apr 2026 15:11:20 +0700 Subject: [PATCH 02/13] Fix --- .../NodeAnalyser/MockObjectExprDetector.php | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php b/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php index 5bd81179..3c7064ee 100644 --- a/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php +++ b/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php @@ -4,15 +4,20 @@ namespace Rector\PHPUnit\CodeQuality\NodeAnalyser; +use PhpParser\Node\Arg; use PhpParser\Node\Expr; use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Expr\Variable; 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,41 @@ 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 $position => $arg) { + if (! $arg instanceof Arg) { + continue; + } + + if (! $arg->value instanceof Variable) { + continue; + } + + if (! $this->nodeNameResolver->isName($arg->value, $variableName)) { + continue; + } + + $methodReflection = $this->reflectionResolver->resolveMethodReflectionFromMethodCall($methodCall); + if (! $methodReflection instanceof MethodReflection) { + continue; + } + + $parameters = $methodReflection->getVariants()[0] + ->getParameters(); + if (! isset($parameters[$position])) { + continue; + } + + $paramType = $parameters[$position]->getType(); + if ($mockObjectType->isSuperTypeOf($paramType)->yes()) { + return true; + } + } } return false; From e11f9cf8129545aa6dbb15c8c9817116fad3862a Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Wed, 29 Apr 2026 08:11:55 +0000 Subject: [PATCH 03/13] [rector] Rector fixes --- .../Rector/ClassMethod/TestWithAnnotationToAttributeRector.php | 1 - rules/CodeQuality/Rector/Class_/AddSeeTestAnnotationRector.php | 1 - .../ClassMethod/ExpressionCreateMockToCreateStubRector.php | 1 - 3 files changed, 3 deletions(-) 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/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) { From 1805bf2c775884963a85a5c2831c193ed76dd9ee Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Wed, 29 Apr 2026 15:18:48 +0700 Subject: [PATCH 04/13] fix --- .../NodeAnalyser/MockObjectExprDetector.php | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php b/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php index 3c7064ee..3c526a53 100644 --- a/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php +++ b/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php @@ -9,6 +9,7 @@ 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; @@ -90,7 +91,7 @@ public function isUsedForMocking(Expr $expr, ClassMethod $classMethod): bool } // check if variable is passed as arg to a method that declares MockObject type parameter - foreach ($methodCall->getArgs() as $position => $arg) { + foreach ($methodCall->getArgs() as $arg) { if (! $arg instanceof Arg) { continue; } @@ -108,15 +109,30 @@ public function isUsedForMocking(Expr $expr, ClassMethod $classMethod): bool continue; } - $parameters = $methodReflection->getVariants()[0] - ->getParameters(); - if (! isset($parameters[$position])) { + $variants = $methodReflection->getVariants(); + if (! isset($variants[0])) { continue; } - $paramType = $parameters[$position]->getType(); - if ($mockObjectType->isSuperTypeOf($paramType)->yes()) { - return true; + $parameters = $variants[0]->getParameters(); + + foreach ($parameters as $index => $parameterReflection) { + $paramType = $parameters[$index]->getType(); + if ($arg->name instanceof Identifier + && $this->nodeNameResolver->isName($arg->name, $parameterReflection->getName()) + && $mockObjectType->isSuperTypeOf($paramType) + ->yes()) { + return true; + } + + if (! isset($parameters[$index])) { + continue; + } + + if ($mockObjectType->isSuperTypeOf($paramType)->yes()) { + return true; + } + } } } From ab9595aeb807353bf97f7573565fcbcf4a152416 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Wed, 29 Apr 2026 15:19:34 +0700 Subject: [PATCH 05/13] fix --- rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php | 1 - 1 file changed, 1 deletion(-) diff --git a/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php b/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php index 3c526a53..a545dffc 100644 --- a/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php +++ b/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php @@ -132,7 +132,6 @@ public function isUsedForMocking(Expr $expr, ClassMethod $classMethod): bool if ($mockObjectType->isSuperTypeOf($paramType)->yes()) { return true; } - } } } From c965ca0a5b5811438bcbee3cbac2860b194eb253 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Wed, 29 Apr 2026 15:21:46 +0700 Subject: [PATCH 06/13] fix --- rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php b/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php index a545dffc..12864196 100644 --- a/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php +++ b/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php @@ -91,7 +91,7 @@ public function isUsedForMocking(Expr $expr, ClassMethod $classMethod): bool } // check if variable is passed as arg to a method that declares MockObject type parameter - foreach ($methodCall->getArgs() as $arg) { + foreach ($methodCall->getArgs() as $argIndex => $arg) { if (! $arg instanceof Arg) { continue; } @@ -117,7 +117,7 @@ public function isUsedForMocking(Expr $expr, ClassMethod $classMethod): bool $parameters = $variants[0]->getParameters(); foreach ($parameters as $index => $parameterReflection) { - $paramType = $parameters[$index]->getType(); + $paramType = $parameterReflection->getType(); if ($arg->name instanceof Identifier && $this->nodeNameResolver->isName($arg->name, $parameterReflection->getName()) && $mockObjectType->isSuperTypeOf($paramType) @@ -128,7 +128,10 @@ public function isUsedForMocking(Expr $expr, ClassMethod $classMethod): bool if (! isset($parameters[$index])) { continue; } + } + if (isset($parameters[$argIndex])) { + $paramType = $parameters[$argIndex]->getType(); if ($mockObjectType->isSuperTypeOf($paramType)->yes()) { return true; } From e5c1304f02499f5318ef3df46f48df55d4a50428 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Wed, 29 Apr 2026 15:21:56 +0700 Subject: [PATCH 07/13] fix --- rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php b/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php index 12864196..fdfd60e5 100644 --- a/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php +++ b/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php @@ -124,10 +124,6 @@ public function isUsedForMocking(Expr $expr, ClassMethod $classMethod): bool ->yes()) { return true; } - - if (! isset($parameters[$index])) { - continue; - } } if (isset($parameters[$argIndex])) { From 82792cd2a91708b6536d5533b063c30c0ae1925a Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Wed, 29 Apr 2026 15:22:06 +0700 Subject: [PATCH 08/13] fix --- rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php b/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php index fdfd60e5..024b3990 100644 --- a/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php +++ b/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php @@ -116,7 +116,7 @@ public function isUsedForMocking(Expr $expr, ClassMethod $classMethod): bool $parameters = $variants[0]->getParameters(); - foreach ($parameters as $index => $parameterReflection) { + foreach ($parameters as $parameterReflection) { $paramType = $parameterReflection->getType(); if ($arg->name instanceof Identifier && $this->nodeNameResolver->isName($arg->name, $parameterReflection->getName()) From 52cde6861118df7f673cb9f862be876f8bfd267f Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Wed, 29 Apr 2026 08:22:42 +0000 Subject: [PATCH 09/13] [rector] Rector fixes --- rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php b/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php index 024b3990..01348a6b 100644 --- a/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php +++ b/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php @@ -116,10 +116,10 @@ public function isUsedForMocking(Expr $expr, ClassMethod $classMethod): bool $parameters = $variants[0]->getParameters(); - foreach ($parameters as $parameterReflection) { - $paramType = $parameterReflection->getType(); + foreach ($parameters as $parameter) { + $paramType = $parameter->getType(); if ($arg->name instanceof Identifier - && $this->nodeNameResolver->isName($arg->name, $parameterReflection->getName()) + && $this->nodeNameResolver->isName($arg->name, $parameter->getName()) && $mockObjectType->isSuperTypeOf($paramType) ->yes()) { return true; From 3d68eee528c2f05922d0a670383ff48e97b9a64c Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Wed, 29 Apr 2026 15:23:58 +0700 Subject: [PATCH 10/13] fix --- rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php b/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php index 01348a6b..f39d2edf 100644 --- a/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php +++ b/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php @@ -92,10 +92,6 @@ public function isUsedForMocking(Expr $expr, ClassMethod $classMethod): bool // check if variable is passed as arg to a method that declares MockObject type parameter foreach ($methodCall->getArgs() as $argIndex => $arg) { - if (! $arg instanceof Arg) { - continue; - } - if (! $arg->value instanceof Variable) { continue; } From 095f7ea3e3000783370d3302637fb12e8bc74c4d Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Wed, 29 Apr 2026 08:25:09 +0000 Subject: [PATCH 11/13] [rector] Rector fixes --- rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php | 1 - 1 file changed, 1 deletion(-) diff --git a/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php b/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php index f39d2edf..834b62f3 100644 --- a/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php +++ b/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php @@ -4,7 +4,6 @@ namespace Rector\PHPUnit\CodeQuality\NodeAnalyser; -use PhpParser\Node\Arg; use PhpParser\Node\Expr; use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\PropertyFetch; From 607dfdd5bdcd3f9d404eb0ab67d09e1812da920c Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Wed, 29 Apr 2026 15:33:00 +0700 Subject: [PATCH 12/13] final touch: cleanup --- .../NodeAnalyser/MockObjectExprDetector.php | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php b/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php index 834b62f3..88573763 100644 --- a/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php +++ b/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php @@ -105,26 +105,23 @@ public function isUsedForMocking(Expr $expr, ClassMethod $classMethod): bool } $variants = $methodReflection->getVariants(); - if (! isset($variants[0])) { - continue; - } - - $parameters = $variants[0]->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; + 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; + if (isset($parameters[$argIndex])) { + $paramType = $parameters[$argIndex]->getType(); + if ($mockObjectType->isSuperTypeOf($paramType)->yes()) { + return true; + } } } } From 4e9d2675ef855aef3bbea7d6eae531f5ea1edc9b Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Wed, 29 Apr 2026 15:34:55 +0700 Subject: [PATCH 13/13] final touch: more fixture --- ..._as_next_arg_mockobject_type_named.php.inc | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 rules-tests/PHPUnit120/Rector/ClassMethod/ExpressionCreateMockToCreateStubRector/Fixture/skip_used_as_next_arg_mockobject_type_named.php.inc 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); + } +}