Skip to content

Commit 15d7db5

Browse files
committed
Configuration for uncheckedExceptions
1 parent ef8827f commit 15d7db5

File tree

6 files changed

+90
-18
lines changed

6 files changed

+90
-18
lines changed

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,19 @@ parameters:
4848
- RuntimeException
4949
```
5050

51-
If some third-party code defines wrong throw types, you could override definitions like this:
51+
You could use `uncheckedExceptions` when you prefer a list of unchecked exceptions instead. It is a safer variant, but harder to adapt to the existing project.
52+
53+
```neon
54+
parameters:
55+
exceptionRules:
56+
uncheckedExceptions:
57+
- LogicException
58+
- PHPUnit\Framework\Exception
59+
```
60+
61+
> `checkedExceptions` and `uncheckedExceptions` cannot be configured at the same time
62+
63+
If some third-party code defines wrong throw types (or it doesn't use @throws annotations at all), you could override definitions like this:
5264

5365
```neon
5466
parameters:

extension.neon

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ parameters:
22
exceptionRules:
33
reportMaybes: true
44
checkedExceptions: []
5+
uncheckedExceptions: []
56
methodThrowTypeDeclarations: []
67
functionThrowTypeDeclarations: []
78

@@ -10,7 +11,7 @@ extensions:
1011

1112
services:
1213
-
13-
class: Pepakriz\PHPStanExceptionRules\CheckedExceptionService(%exceptionRules.checkedExceptions%)
14+
class: Pepakriz\PHPStanExceptionRules\CheckedExceptionService(%exceptionRules.checkedExceptions%, %exceptionRules.uncheckedExceptions%)
1415

1516
-
1617
class: Pepakriz\PHPStanExceptionRules\DefaultThrowTypeExtension(%exceptionRules.methodThrowTypeDeclarations%, %exceptionRules.functionThrowTypeDeclarations%)

phpstan.neon.dist

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,17 @@ parameters:
1212
- %rootDir%/../../../tests/*/data/*
1313

1414
exceptionRules:
15-
checkedExceptions:
16-
- Pepakriz\PHPStanExceptionRules\RuntimeException
15+
uncheckedExceptions:
16+
- LogicException
17+
- PHPStan\ShouldNotHappenException
18+
- PHPUnit\Framework\Exception
19+
- Nette\DI\MissingServiceException
20+
methodThrowTypeDeclarations:
21+
PHPStan\Broker\Broker:
22+
getClass:
23+
- PHPStan\Broker\ClassNotFoundException
24+
getFunction:
25+
- PHPStan\Broker\FunctionNotFoundException
26+
PHPStan\Reflection\ClassReflection:
27+
getMethod:
28+
- PHPStan\Reflection\MissingMethodFromReflectionException

src/CheckedExceptionService.php

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,41 @@
22

33
namespace Pepakriz\PHPStanExceptionRules;
44

5+
use LogicException;
56
use function array_filter;
7+
use function count;
68
use function is_a;
79

810
class CheckedExceptionService
911
{
1012

1113
/**
12-
* @var string[]
14+
* @var string[]|null
1315
*/
1416
private $checkedExceptions;
1517

18+
/**
19+
* @var string[]
20+
*/
21+
private $uncheckedExceptions;
22+
1623
/**
1724
* @param string[] $checkedExceptions
25+
* @param string[] $uncheckedExceptions
1826
*/
1927
public function __construct(
20-
array $checkedExceptions
28+
array $checkedExceptions,
29+
array $uncheckedExceptions = []
2130
)
2231
{
23-
$this->checkedExceptions = $checkedExceptions;
32+
$checkedExceptionsCounter = count($checkedExceptions);
33+
$uncheckedExceptionsCounter = count($uncheckedExceptions);
34+
if ($checkedExceptionsCounter > 0 && $uncheckedExceptionsCounter > 0) {
35+
throw new LogicException('$checkedExceptions and $uncheckedExceptions cannot be configured at the same time');
36+
}
37+
38+
$this->checkedExceptions = $checkedExceptionsCounter > 0 ? $checkedExceptions : null;
39+
$this->uncheckedExceptions = $uncheckedExceptions;
2440
}
2541

2642
/**
@@ -36,13 +52,23 @@ public function filterCheckedExceptions(array $classes): array
3652

3753
public function isCheckedException(string $exceptionClassName): bool
3854
{
39-
foreach ($this->checkedExceptions as $whitelistedException) {
40-
if (is_a($exceptionClassName, $whitelistedException, true)) {
41-
return true;
55+
if ($this->checkedExceptions !== null) {
56+
foreach ($this->checkedExceptions as $checkedException) {
57+
if (is_a($exceptionClassName, $checkedException, true)) {
58+
return true;
59+
}
60+
}
61+
62+
return false;
63+
}
64+
65+
foreach ($this->uncheckedExceptions as $uncheckedException) {
66+
if (is_a($exceptionClassName, $uncheckedException, true)) {
67+
return false;
4268
}
4369
}
4470

45-
return false;
71+
return true;
4672
}
4773

4874
}

src/Rules/ThrowsPhpDocInheritanceRule.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@
77
use PhpParser\Node\Stmt\ClassMethod;
88
use PHPStan\Analyser\Scope;
99
use PHPStan\Broker\Broker;
10+
use PHPStan\Broker\ClassNotFoundException;
11+
use PHPStan\Reflection\MissingMethodFromReflectionException;
1012
use PHPStan\Reflection\ThrowableReflection;
1113
use PHPStan\Rules\Rule;
14+
use PHPStan\ShouldNotHappenException;
1215
use PHPStan\Type\FileTypeMapper;
1316
use PHPStan\Type\ObjectType;
1417
use PHPStan\Type\Type;
@@ -97,12 +100,18 @@ public function processNode(Node $node, Scope $scope): array
97100

98101
$messages = [];
99102
foreach ($parentClasses as $parentClass) {
100-
$parentClassReflection = $this->broker->getClass($parentClass->getName());
101-
if (!$parentClassReflection->hasMethod($methodName)) {
103+
try {
104+
$parentClassReflection = $this->broker->getClass($parentClass->getName());
105+
} catch (ClassNotFoundException $e) {
106+
throw new ShouldNotHappenException();
107+
}
108+
109+
try {
110+
$methodReflection = $parentClassReflection->getMethod($methodName, $scope);
111+
} catch (MissingMethodFromReflectionException $e) {
102112
continue;
103113
}
104114

105-
$methodReflection = $parentClassReflection->getMethod($methodName, $scope);
106115
if (!$methodReflection instanceof ThrowableReflection) {
107116
continue;
108117
}

src/Rules/ThrowsPhpDocRule.php

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
use PHPStan\Reflection\MissingMethodFromReflectionException;
3131
use PHPStan\Reflection\ThrowableReflection;
3232
use PHPStan\Rules\Rule;
33+
use PHPStan\ShouldNotHappenException;
3334
use PHPStan\Type\ObjectType;
3435
use PHPStan\Type\Type;
3536
use PHPStan\Type\TypeCombinator;
@@ -193,12 +194,18 @@ private function processMethodCall(MethodCall $node, Scope $scope): array
193194

194195
$throwTypes = [];
195196
foreach ($targetClassNames as $targetClassName) {
196-
$targetClassReflection = $this->broker->getClass($targetClassName);
197-
if (!$targetClassReflection->hasMethod($methodName->toString())) {
197+
try {
198+
$targetClassReflection = $this->broker->getClass($targetClassName);
199+
} catch (ClassNotFoundException $e) {
200+
throw new ShouldNotHappenException();
201+
}
202+
203+
try {
204+
$targetMethodReflection = $targetClassReflection->getMethod($methodName->toString(), $scope);
205+
} catch (MissingMethodFromReflectionException $e) {
198206
continue;
199207
}
200208

201-
$targetMethodReflection = $targetClassReflection->getMethod($methodName->toString(), $scope);
202209
if (!$targetMethodReflection instanceof ThrowableReflection) {
203210
continue;
204211
}
@@ -302,7 +309,12 @@ private function processClassMethod(ClassMethod $node, Scope $scope): array
302309
return [];
303310
}
304311

305-
$methodReflection = $classReflection->getMethod($node->name->toString(), $scope);
312+
try {
313+
$methodReflection = $classReflection->getMethod($node->name->toString(), $scope);
314+
} catch (MissingMethodFromReflectionException $e) {
315+
throw new ShouldNotHappenException();
316+
}
317+
306318
if ($methodReflection instanceof ThrowableReflection) {
307319
$this->throwsScope->enterToThrowsAnnotationBlock($methodReflection->getThrowType());
308320
}

0 commit comments

Comments
 (0)