Skip to content

Commit bc7473c

Browse files
authored
[CodeQuality] Skip never-returning fall-through in WillReturnCallbackFallbackToReturnFalseRector (#801)
1 parent 8c960f3 commit bc7473c

3 files changed

Lines changed: 65 additions & 0 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\MethodCall\WillReturnCallbackFallbackToReturnFalseRector\Fixture;
4+
5+
use PHPUnit\Framework\TestCase;
6+
7+
final class SkipFailFallthrough extends TestCase
8+
{
9+
public function test()
10+
{
11+
$eventMock = $this->createMock(\stdClass::class);
12+
$eventMock->method('has')->willReturnCallback(function (string $id): bool {
13+
if ('service' === $id) {
14+
return true;
15+
}
16+
if ('other' === $id) {
17+
return false;
18+
}
19+
20+
self::fail('Unknown ID requested: ' . $id);
21+
});
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\MethodCall\WillReturnCallbackFallbackToReturnFalseRector\Fixture;
4+
5+
use PHPUnit\Framework\TestCase;
6+
7+
final class SkipThrowFallthrough extends TestCase
8+
{
9+
public function test()
10+
{
11+
$eventMock = $this->createMock(\stdClass::class);
12+
$eventMock->method('has')->willReturnCallback(function (string $id): bool {
13+
if ('service' === $id) {
14+
return true;
15+
}
16+
if ('other' === $id) {
17+
return false;
18+
}
19+
20+
throw new \Exception('Unknown ID requested: ' . $id);
21+
});
22+
}
23+
}

‎rules/CodeQuality/Rector/MethodCall/WillReturnCallbackFallbackToReturnFalseRector.php‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@
55
namespace Rector\PHPUnit\CodeQuality\Rector\MethodCall;
66

77
use PhpParser\Node;
8+
use PhpParser\Node\Expr;
89
use PhpParser\Node\Expr\Closure;
910
use PhpParser\Node\Expr\ConstFetch;
1011
use PhpParser\Node\Expr\MethodCall;
12+
use PhpParser\Node\Expr\Throw_;
13+
use PhpParser\Node\Stmt\Expression;
1114
use PhpParser\Node\Stmt\Nop;
1215
use PhpParser\Node\Stmt\Return_;
16+
use PHPStan\Type\NeverType;
1317
use Rector\PhpParser\Node\BetterNodeFinder;
1418
use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer;
1519
use Rector\Rector\AbstractRector;
@@ -132,6 +136,11 @@ public function refactor(Node $node): ?MethodCall
132136
return null;
133137
}
134138

139+
// closure cannot fall through: last statement throws or calls a never-returning method
140+
if ($lastStmt instanceof Expression && $this->isNeverReturningExpression($lastStmt->expr)) {
141+
return null;
142+
}
143+
135144
if (! $this->hasOnlyBoolReturns($closure)) {
136145
return null;
137146
}
@@ -161,4 +170,14 @@ private function hasOnlyBoolReturns(Closure $closure): bool
161170

162171
return true;
163172
}
173+
174+
private function isNeverReturningExpression(Expr $expr): bool
175+
{
176+
// cheap structural check first, before reflection-based type resolution
177+
if ($expr instanceof Throw_) {
178+
return true;
179+
}
180+
181+
return $this->getType($expr) instanceof NeverType;
182+
}
164183
}

0 commit comments

Comments
 (0)