Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Rector\PHPUnit\Tests\PHPUnit120\Rector\ClassMethod\ExpressionCreateMockToCreateStubRector\Fixture;

use PHPUnit\Framework\TestCase;
use Rector\PHPUnit\Tests\PHPUnit120\Rector\ClassMethod\ExpressionCreateMockToCreateStubRector\Source\ClassWithDependency;

final class SkipUsedAsNextArgMockObjectType extends TestCase
{
public function test()
{
$mock = $this->createMock(ClassWithDependency::class);
$this->configureMock($mock);
}

private function configureMock(\PHPUnit\Framework\MockObject\MockObject $mock)
{
$mock->method('someMethod')->willReturn(1);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Rector\PHPUnit\Tests\PHPUnit120\Rector\ClassMethod\ExpressionCreateMockToCreateStubRector\Fixture;

use PHPUnit\Framework\TestCase;
use Rector\PHPUnit\Tests\PHPUnit120\Rector\ClassMethod\ExpressionCreateMockToCreateStubRector\Source\ClassWithDependency;

final class SkipUsedAsNextArgMockObjectTypeNamed extends TestCase
{
public function test()
{
$mock = $this->createMock(ClassWithDependency::class);
$this->configureMock(mock: $mock, otherParam: 1);
}

private function configureMock($otherParam, \PHPUnit\Framework\MockObject\MockObject $mock)
{
$mock->method('someMethod')->willReturn(1);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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));

Expand Down
49 changes: 49 additions & 0 deletions rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,24 @@
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
{
public function __construct(
private BetterNodeFinder $betterNodeFinder,
private NodeNameResolver $nodeNameResolver,
private VariableFinder $variableFinder,
private ReflectionResolver $reflectionResolver,
) {
}

Expand Down Expand Up @@ -67,6 +73,8 @@ public function isUsedForMocking(Expr $expr, ClassMethod $classMethod): bool
/** @var array<Expr\MethodCall> $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;
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ private function shouldSkipClass(Class_ $class): bool
continue;
}

/** @var GenericTagValueNode $genericTagValueNode */
$genericTagValueNode = $seePhpDocTagNode->value;

$seeTagClass = ltrim($genericTagValueNode->value, '\\');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ public function refactor(Node $node): ?ClassMethod
continue;
}

/** @var Assign $assign */
$assign = $stmt->expr;

if (! $assign->var instanceof Variable) {
Expand Down
Loading