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

declare(strict_types=1);

namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Expression\DecorateWillReturnMapWithExpectsMockRector\Fixture;

final class SkipPrivateHelper extends \PHPUnit\Framework\TestCase
{
private function createSomeMock()
{
$someMock = $this->createMock(\stdClass::class);
$someMock->method('some')->willReturnMap([1, 2]);

return $someMock;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Expression\DecorateWillReturnMapWithExpectsMockRector\Fixture;

final class SkipProtectedNonTestMethod extends \PHPUnit\Framework\TestCase
{
protected function prepareSomeMock()
{
$someMock = $this->createMock(\stdClass::class);
$someMock->method('some')->willReturnMap([1, 2]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Identifier;
use PhpParser\Node\Scalar\Int_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Expression;
use PHPStan\Type\ObjectType;
use Rector\PHPStan\ScopeFetcher;
use Rector\PhpParser\Node\BetterNodeFinder;
use Rector\PHPUnit\Enum\PHPUnitClassName;
use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer;
use Rector\Rector\AbstractRector;
use Rector\ValueObject\MethodName;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
Expand All @@ -25,6 +27,12 @@
*/
final class DecorateWillReturnMapWithExpectsMockRector extends AbstractRector
{
public function __construct(
private readonly TestsNodeAnalyzer $testsNodeAnalyzer,
private readonly BetterNodeFinder $betterNodeFinder,
) {
}

public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Decorate willReturnMap() calls with expects on the mock object if missing', [
Expand Down Expand Up @@ -78,54 +86,76 @@ protected function setUp(): void

public function getNodeTypes(): array
{
return [Expression::class];
return [ClassMethod::class];
}

/**
* @param Expression $node
* @return Expression|null
* @param ClassMethod $node
* @return ClassMethod|null
*/
public function refactor(Node $node)
{
if (! $node->expr instanceof MethodCall) {
// allowed as can be flexible
if ($this->isName($node, MethodName::SET_UP)) {
return null;
}

$methodCall = $node->expr;
if (! $this->isName($methodCall->name, 'willReturnMap')) {
// only decorate inside test methods, skip helpers and other non-test methods
if (! $this->testsNodeAnalyzer->isTestClassMethod($node)) {
return null;
}

$scope = ScopeFetcher::fetch($node);
$hasChanged = false;

// allowed as can be flexible
if ($scope->getFunctionName() === MethodName::SET_UP) {
/** @var Expression[] $expressions */
$expressions = $this->betterNodeFinder->findInstancesOf($node, [Expression::class]);
foreach ($expressions as $expression) {
if ($this->refactorExpression($expression)) {
$hasChanged = true;
}
}

if (! $hasChanged) {
return null;
}

return $node;
}

private function refactorExpression(Expression $expression): bool
{
if (! $expression->expr instanceof MethodCall) {
return false;
}

$methodCall = $expression->expr;
if (! $this->isName($methodCall->name, 'willReturnMap')) {
return false;
}

$topmostCall = $this->resolveTopmostCall($methodCall);

// already covered
if ($this->isName($topmostCall->name, 'expects')) {
return null;
return false;
}

if (! $this->isObjectType($topmostCall->var, new ObjectType(PHPUnitClassName::MOCK_OBJECT))) {
return null;
return false;
}

if ($methodCall->isFirstClassCallable()) {
return null;
return false;
}

// count values in will map arg
$willReturnMapArg = $methodCall->getArgs()[0] ?? null;
if (! $willReturnMapArg instanceof Arg) {
return null;
return false;
}

if (! $willReturnMapArg->value instanceof Array_) {
return null;
return false;
}

$array = $willReturnMapArg->value;
Expand All @@ -141,7 +171,7 @@ public function refactor(Node $node)
))]
);

return $node;
return true;
}

private function resolveTopmostCall(MethodCall $methodCall): MethodCall
Expand Down
Loading