Skip to content

Commit b8b93b8

Browse files
marcospassospepakriz
authored andcommitted
Consider default exceptions as possibly thrown exceptions (#59)
1 parent 2300a67 commit b8b93b8

File tree

4 files changed

+71
-14
lines changed

4 files changed

+71
-14
lines changed

src/Rules/ThrowsPhpDocRule.php

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@
55
use Iterator;
66
use IteratorAggregate;
77
use Pepakriz\PHPStanExceptionRules\CheckedExceptionService;
8+
use Pepakriz\PHPStanExceptionRules\DefaultThrowTypeService;
89
use Pepakriz\PHPStanExceptionRules\DynamicThrowTypeService;
910
use Pepakriz\PHPStanExceptionRules\Node\FunctionEnd;
1011
use Pepakriz\PHPStanExceptionRules\Node\TryCatchTryEnd;
1112
use Pepakriz\PHPStanExceptionRules\ThrowsAnnotationReader;
13+
use Pepakriz\PHPStanExceptionRules\UnsupportedClassException;
14+
use Pepakriz\PHPStanExceptionRules\UnsupportedFunctionException;
1215
use PhpParser\Node;
1316
use PhpParser\Node\Expr;
1417
use PhpParser\Node\Expr\FuncCall;
@@ -65,6 +68,11 @@ class ThrowsPhpDocRule implements Rule
6568
*/
6669
private $dynamicThrowTypeService;
6770

71+
/**
72+
* @var DefaultThrowTypeService
73+
*/
74+
private $defaultThrowTypeService;
75+
6876
/**
6977
* @var ThrowsAnnotationReader
7078
*/
@@ -93,6 +101,7 @@ class ThrowsPhpDocRule implements Rule
93101
public function __construct(
94102
CheckedExceptionService $checkedExceptionService,
95103
DynamicThrowTypeService $dynamicThrowTypeService,
104+
DefaultThrowTypeService $defaultThrowTypeService,
96105
ThrowsAnnotationReader $throwsAnnotationReader,
97106
Broker $broker,
98107
bool $reportUnusedCatchesOfUncheckedExceptions,
@@ -101,6 +110,7 @@ public function __construct(
101110
{
102111
$this->checkedExceptionService = $checkedExceptionService;
103112
$this->dynamicThrowTypeService = $dynamicThrowTypeService;
113+
$this->defaultThrowTypeService = $defaultThrowTypeService;
104114
$this->throwsAnnotationReader = $throwsAnnotationReader;
105115
$this->broker = $broker;
106116
$this->throwsScope = new ThrowsScope();
@@ -438,26 +448,41 @@ private function filterUnusedExceptions(array $declaredThrows, array $usedThrows
438448
$checkedThrowsAnnotations = $this->checkedExceptionService->filterCheckedExceptions($usedThrowsAnnotations);
439449
$unusedThrows = array_diff($declaredThrows, $checkedThrowsAnnotations);
440450

441-
if (!$this->ignoreDescriptiveUncheckedExceptions) {
442-
return $unusedThrows;
443-
}
444-
445451
$functionReflection = $scope->getFunction();
446452
if ($functionReflection === null) {
447453
return $unusedThrows;
448454
}
449455

456+
try {
457+
if ($functionReflection instanceof MethodReflection) {
458+
$defaultThrowsType = $functionReflection->getName() === '__construct' ?
459+
$this->defaultThrowTypeService->getConstructorThrowType($functionReflection) :
460+
$this->defaultThrowTypeService->getMethodThrowType($functionReflection);
461+
} else {
462+
$defaultThrowsType = $this->defaultThrowTypeService->getFunctionThrowType($functionReflection);
463+
}
464+
} catch (UnsupportedClassException | UnsupportedFunctionException $exception) {
465+
$defaultThrowsType = new VoidType();
466+
}
467+
468+
$unusedThrows = array_diff($unusedThrows, TypeUtils::getDirectClassNames($defaultThrowsType));
469+
450470
try {
451471
if ($functionReflection instanceof MethodReflection) {
452472
$nativeClassReflection = $functionReflection->getDeclaringClass()->getNativeReflection();
453473
$nativeFunctionReflection = $nativeClassReflection->getMethod($functionReflection->getName());
474+
454475
} else {
455476
$nativeFunctionReflection = new ReflectionFunction($functionReflection->getName());
456477
}
457478
} catch (ReflectionException $exception) {
458479
return $unusedThrows;
459480
}
460481

482+
if (!$this->ignoreDescriptiveUncheckedExceptions) {
483+
return $unusedThrows;
484+
}
485+
461486
$throwsAnnotations = $this->throwsAnnotationReader->read($nativeFunctionReflection);
462487

463488
return array_filter($unusedThrows, static function (string $type) use ($throwsAnnotations, $usedThrowsAnnotations): bool {

tests/src/Rules/PhpInternalsTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Pepakriz\PHPStanExceptionRules\Rules;
44

55
use Pepakriz\PHPStanExceptionRules\CheckedExceptionService;
6+
use Pepakriz\PHPStanExceptionRules\DefaultThrowTypeService;
67
use Pepakriz\PHPStanExceptionRules\DynamicThrowTypeService;
78
use Pepakriz\PHPStanExceptionRules\Extension\DateTimeExtension;
89
use Pepakriz\PHPStanExceptionRules\Extension\JsonEncodeDecodeExtension;
@@ -36,6 +37,7 @@ protected function getRule(): Rule
3637
$jsonEncodeDecodeExtension,
3738
]
3839
),
40+
new DefaultThrowTypeService([], []),
3941
$this->createThrowsAnnotationReader(),
4042
$this->createBroker(),
4143
true,

tests/src/Rules/ThrowsPhpDocRuleTest.php

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
use Pepakriz\PHPStanExceptionRules\DynamicThrowTypeService;
99
use Pepakriz\PHPStanExceptionRules\Rules\Data\CheckedException;
1010
use Pepakriz\PHPStanExceptionRules\Rules\DynamicExtension\DynamicExtension;
11+
use Pepakriz\PHPStanExceptionRules\Rules\UnusedCatches\FooException;
12+
use Pepakriz\PHPStanExceptionRules\Rules\UnusedCatches\UnusedCatches;
1113
use Pepakriz\PHPStanExceptionRules\RuleTestCase;
1214
use PharData;
1315
use PHPStan\Rules\Rule;
@@ -27,19 +29,26 @@ class ThrowsPhpDocRuleTest extends RuleTestCase
2729
*/
2830
private $ignoreDescriptiveUncheckedExceptions = false;
2931

32+
/**
33+
* @var mixed[]
34+
*/
35+
private $methodThrowTypes = [];
36+
37+
/**
38+
* @var mixed[]
39+
*/
40+
private $functionThrowTypes = [];
41+
3042
protected function getRule(): Rule
3143
{
44+
$defaultThrowTypeService = new DefaultThrowTypeService(
45+
$this->methodThrowTypes,
46+
$this->functionThrowTypes
47+
);
48+
3249
$extensions = [
3350
new DynamicExtension(),
34-
new DefaultThrowTypeExtension(
35-
new DefaultThrowTypeService([
36-
PharData::class => [
37-
'extractTo' => [
38-
RuntimeException::class,
39-
],
40-
],
41-
], [])
42-
),
51+
new DefaultThrowTypeExtension($defaultThrowTypeService),
4352
];
4453

4554
return new ThrowsPhpDocRule(
@@ -56,6 +65,7 @@ protected function getRule(): Rule
5665
$extensions,
5766
$extensions
5867
),
68+
$defaultThrowTypeService,
5969
$this->createThrowsAnnotationReader(),
6070
$this->createBroker(),
6171
$this->reportUnusedCatchesOfUncheckedExceptions,
@@ -80,6 +90,19 @@ public function testUnusedThrows(): void
8090

8191
public function testUnusedCatches(): void
8292
{
93+
$this->methodThrowTypes = [
94+
PharData::class => [
95+
'extractTo' => [
96+
RuntimeException::class,
97+
],
98+
],
99+
UnusedCatches::class => [
100+
'methodWithDefaultThrowType' => [
101+
FooException::class,
102+
],
103+
],
104+
];
105+
83106
$this->analyse(__DIR__ . '/data/unused-catches.php');
84107
}
85108

tests/src/Rules/data/unused-catches.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,5 +115,12 @@ private function dynamicThrowType(): void
115115
}
116116
}
117117

118-
}
118+
/**
119+
* @throws FooException
120+
*/
121+
private function methodWithDefaultThrowType(callable $callable): void
122+
{
123+
$callable();
124+
}
119125

126+
}

0 commit comments

Comments
 (0)