Skip to content

Commit 749dc8b

Browse files
authored
[CodeQuality] Handle return throw and return null in void mock callbacks (#713)
1 parent 318378f commit 749dc8b

2 files changed

Lines changed: 77 additions & 4 deletions

File tree

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\RemoveReturnFromVoidMethodMockCallbackRector\Fixture;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\RemoveReturnFromVoidMethodMockCallbackRector\Source\SomeEntityManager;
7+
8+
final class ReturnThrowAndNull extends TestCase
9+
{
10+
public function test($matcher): void
11+
{
12+
$this->createMock(SomeEntityManager::class)
13+
->method('persist')
14+
->willReturnCallback(function ($entity) use ($matcher) {
15+
if (1 === $matcher->numberOfInvocations()) {
16+
return null;
17+
}
18+
19+
if (2 === $matcher->numberOfInvocations()) {
20+
return throw new \RuntimeException('boom');
21+
}
22+
});
23+
}
24+
}
25+
26+
?>
27+
-----
28+
<?php
29+
30+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\RemoveReturnFromVoidMethodMockCallbackRector\Fixture;
31+
32+
use PHPUnit\Framework\TestCase;
33+
use Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\RemoveReturnFromVoidMethodMockCallbackRector\Source\SomeEntityManager;
34+
35+
final class ReturnThrowAndNull extends TestCase
36+
{
37+
public function test($matcher): void
38+
{
39+
$this->createMock(SomeEntityManager::class)
40+
->method('persist')
41+
->willReturnCallback(function ($entity) use ($matcher): void {
42+
if (1 === $matcher->numberOfInvocations()) {
43+
return;
44+
}
45+
46+
if (2 === $matcher->numberOfInvocations()) {
47+
throw new \RuntimeException('boom');
48+
}
49+
});
50+
}
51+
}
52+
53+
?>

‎rules/CodeQuality/Rector/Class_/RemoveReturnFromVoidMethodMockCallbackRector.php‎

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@
1111
use PhpParser\Node\Expr\ConstFetch;
1212
use PhpParser\Node\Expr\MethodCall;
1313
use PhpParser\Node\Expr\PropertyFetch;
14+
use PhpParser\Node\Expr\Throw_;
1415
use PhpParser\Node\Expr\Variable;
1516
use PhpParser\Node\Identifier;
1617
use PhpParser\Node\Scalar;
1718
use PhpParser\Node\Scalar\String_;
1819
use PhpParser\Node\Stmt\Class_;
20+
use PhpParser\Node\Stmt\Expression;
1921
use PhpParser\Node\Stmt\Function_;
2022
use PhpParser\Node\Stmt\Return_;
2123
use PhpParser\NodeFinder;
@@ -275,21 +277,39 @@ private function refactorClosureToVoid(Closure $closure): bool
275277
return false;
276278
}
277279

278-
// only strip side-effect-free values, to not lose behavior
280+
// only strip side-effect-free values or a "return throw", to not lose behavior
279281
foreach ($valueReturns as $valueReturn) {
280282
$returnedExpr = $valueReturn->expr;
281283
if (! $returnedExpr instanceof Expr) {
282284
continue;
283285
}
284286

287+
// "return throw new X" becomes a bare "throw new X" statement below
288+
if ($returnedExpr instanceof Throw_) {
289+
continue;
290+
}
291+
285292
if (! $this->isPureValue($returnedExpr)) {
286293
return false;
287294
}
288295
}
289296

290-
foreach ($valueReturns as $valueReturn) {
291-
$valueReturn->expr = null;
292-
}
297+
$this->traverseNodesWithCallable($closure->stmts, static function (Node $subNode): ?Expression {
298+
if (! $subNode instanceof Return_) {
299+
return null;
300+
}
301+
302+
// "return throw new X;" is itself invalid in a void function, unwrap to "throw new X;"
303+
if ($subNode->expr instanceof Throw_) {
304+
return new Expression($subNode->expr);
305+
}
306+
307+
if ($subNode->expr instanceof Expr) {
308+
$subNode->expr = null;
309+
}
310+
311+
return null;
312+
});
293313

294314
// drop a now-empty trailing "return;"
295315
$lastStmt = $closure->stmts[array_key_last($closure->stmts)] ?? null;

0 commit comments

Comments
 (0)