Skip to content

Commit 9fc483d

Browse files
committed
Made TooWideMethodThrowTypeRule behaviour similar to TooWideMethodReturnTypehintRule + new config option checkTooWideThrowTypesInProtectedAndPublicMethods
1 parent 31e52fa commit 9fc483d

11 files changed

+138
-36
lines changed

conf/config.level4.neon

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ services:
2222

2323
-
2424
class: PHPStan\Rules\Exceptions\TooWideMethodThrowTypeRule
25+
arguments:
26+
checkProtectedAndPublicMethods: %checkTooWideThrowTypesInProtectedAndPublicMethods%
2527

2628
-
2729
class: PHPStan\Rules\Exceptions\TooWidePropertyHookThrowTypeRule

conf/config.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ parameters:
6565
checkMissingTypehints: false
6666
checkTooWideParameterOutInProtectedAndPublicMethods: false
6767
checkTooWideReturnTypesInProtectedAndPublicMethods: false
68+
checkTooWideThrowTypesInProtectedAndPublicMethods: false
6869
checkUninitializedProperties: false
6970
checkDynamicProperties: false
7071
strictRulesInstalled: false

conf/parametersSchema.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ parametersSchema:
6868
checkMissingTypehints: bool()
6969
checkTooWideParameterOutInProtectedAndPublicMethods: bool()
7070
checkTooWideReturnTypesInProtectedAndPublicMethods: bool()
71+
checkTooWideThrowTypesInProtectedAndPublicMethods: bool()
7172
checkUninitializedProperties: bool()
7273
checkDynamicProperties: bool()
7374
strictRulesInstalled: bool()

src/Rules/Exceptions/TooWideMethodThrowTypeRule.php

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
use PHPStan\Node\MethodReturnStatementsNode;
88
use PHPStan\Rules\Rule;
99
use PHPStan\Rules\RuleErrorBuilder;
10-
use PHPStan\Type\FileTypeMapper;
1110
use function sprintf;
1211

1312
/**
@@ -16,7 +15,10 @@
1615
final class TooWideMethodThrowTypeRule implements Rule
1716
{
1817

19-
public function __construct(private FileTypeMapper $fileTypeMapper, private TooWideThrowTypeCheck $check)
18+
public function __construct(
19+
private TooWideThrowTypeCheck $check,
20+
private bool $checkProtectedAndPublicMethods,
21+
)
2022
{
2123
}
2224

@@ -27,34 +29,32 @@ public function getNodeType(): string
2729

2830
public function processNode(Node $node, Scope $scope): array
2931
{
30-
$docComment = $node->getDocComment();
31-
if ($docComment === null) {
32-
return [];
33-
}
34-
3532
$statementResult = $node->getStatementResult();
36-
$methodReflection = $node->getMethodReflection();
37-
$classReflection = $node->getClassReflection();
38-
$resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc(
39-
$scope->getFile(),
40-
$classReflection->getName(),
41-
$scope->isInTrait() ? $scope->getTraitReflection()->getName() : null,
42-
$methodReflection->getName(),
43-
$docComment->getText(),
44-
);
33+
$method = $node->getMethodReflection();
34+
$isFirstDeclaration = $method->getPrototype()->getDeclaringClass() === $method->getDeclaringClass();
35+
if (!$method->isPrivate()) {
36+
if (!$method->getDeclaringClass()->isFinal() && !$method->isFinal()->yes()) {
37+
if (!$this->checkProtectedAndPublicMethods) {
38+
return [];
39+
}
4540

46-
if ($resolvedPhpDoc->getThrowsTag() === null) {
47-
return [];
41+
if ($isFirstDeclaration) {
42+
return [];
43+
}
44+
}
4845
}
4946

50-
$throwType = $resolvedPhpDoc->getThrowsTag()->getType();
47+
$throwType = $method->getThrowType();
48+
if ($throwType === null) {
49+
return [];
50+
}
5151

5252
$errors = [];
5353
foreach ($this->check->check($throwType, $statementResult->getThrowPoints()) as $throwClass) {
5454
$errors[] = RuleErrorBuilder::message(sprintf(
5555
'Method %s::%s() has %s in PHPDoc @throws tag but it\'s not thrown.',
56-
$methodReflection->getDeclaringClass()->getDisplayName(),
57-
$methodReflection->getName(),
56+
$method->getDeclaringClass()->getDisplayName(),
57+
$method->getName(),
5858
$throwClass,
5959
))
6060
->identifier('throws.unusedType')

tests/PHPStan/Rules/Exceptions/TooWideMethodThrowTypeRuleTest.php

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
use PHPStan\Rules\Rule;
66
use PHPStan\Testing\RuleTestCase;
7-
use PHPStan\Type\FileTypeMapper;
87
use PHPUnit\Framework\Attributes\DataProvider;
98
use PHPUnit\Framework\Attributes\RequiresPhp;
109

@@ -16,9 +15,11 @@ class TooWideMethodThrowTypeRuleTest extends RuleTestCase
1615

1716
private bool $implicitThrows = true;
1817

18+
private bool $checkProtectedAndPublicMethods = true;
19+
1920
protected function getRule(): Rule
2021
{
21-
return new TooWideMethodThrowTypeRule(self::getContainer()->getByType(FileTypeMapper::class), new TooWideThrowTypeCheck($this->implicitThrows));
22+
return new TooWideMethodThrowTypeRule(new TooWideThrowTypeCheck($this->implicitThrows), $this->checkProtectedAndPublicMethods);
2223
}
2324

2425
public function testRule(): void
@@ -45,8 +46,8 @@ public function testRule(): void
4546
66,
4647
],
4748
[
48-
'Method TooWideThrowsMethod\ParentClass::doFoo() has LogicException in PHPDoc @throws tag but it\'s not thrown.',
49-
77,
49+
'Method TooWideThrowsMethod\ChildClass::doFoo() has LogicException in PHPDoc @throws tag but it\'s not thrown.',
50+
87,
5051
],
5152
[
5253
'Method TooWideThrowsMethod\ImmediatelyCalledCallback::doFoo2() has InvalidArgumentException in PHPDoc @throws tag but it\'s not thrown.',
@@ -103,4 +104,50 @@ public function testRuleLookOnlyForExplicitThrowPoints(bool $implicitThrows, arr
103104
$this->analyse([__DIR__ . '/data/too-wide-throws-explicit.php'], $errors);
104105
}
105106

107+
public static function dataAlwaysCheckFinal(): iterable
108+
{
109+
yield [
110+
false,
111+
[
112+
[
113+
'Method TooWideThrowTypeAlwaysCheckFinal\Foo::doFoo() has RuntimeException in PHPDoc @throws tag but it\'s not thrown.',
114+
14,
115+
],
116+
[
117+
'Method TooWideThrowTypeAlwaysCheckFinal\Baz::doBar() has RuntimeException in PHPDoc @throws tag but it\'s not thrown.',
118+
46,
119+
],
120+
],
121+
];
122+
123+
yield [
124+
true,
125+
[
126+
[
127+
'Method TooWideThrowTypeAlwaysCheckFinal\Foo::doFoo() has RuntimeException in PHPDoc @throws tag but it\'s not thrown.',
128+
14,
129+
],
130+
[
131+
'Method TooWideThrowTypeAlwaysCheckFinal\Baz::doFoo() has RuntimeException in PHPDoc @throws tag but it\'s not thrown.',
132+
38,
133+
],
134+
[
135+
'Method TooWideThrowTypeAlwaysCheckFinal\Baz::doBar() has RuntimeException in PHPDoc @throws tag but it\'s not thrown.',
136+
46,
137+
],
138+
],
139+
];
140+
}
141+
142+
/**
143+
* @param list<array{0: string, 1: int, 2?: string|null}> $expectedErrors
144+
*/
145+
#[DataProvider('dataAlwaysCheckFinal')]
146+
public function testAlwaysCheckFinal(bool $checkProtectedAndPublicMethods, array $expectedErrors): void
147+
{
148+
$this->implicitThrows = true;
149+
$this->checkProtectedAndPublicMethods = $checkProtectedAndPublicMethods;
150+
$this->analyse([__DIR__ . '/data/too-wide-throw-type-always-check-final.php'], $expectedErrors);
151+
}
152+
106153
}

tests/PHPStan/Rules/Exceptions/data/bug-6233.php

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

33
namespace Bug6233;
44

5-
class TestClass {
5+
final class TestClass {
66
/**
77
* @throws \Exception
88
**/
@@ -11,7 +11,7 @@ public function __invoke() {
1111
}
1212
}
1313

14-
class TestClass2 {
14+
final class TestClass2 {
1515
/**
1616
* @throws \Exception
1717
**/
@@ -20,7 +20,7 @@ public function __invoke() {
2020
}
2121
}
2222

23-
class Container {
23+
final class Container {
2424
/**
2525
* @throws \Exception
2626
**/

tests/PHPStan/Rules/Exceptions/data/immediately-called-arrow-function.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace ImmediatelyCalledArrowFunction;
44

5-
class ImmediatelyCalledCallback
5+
final class ImmediatelyCalledCallback
66
{
77

88
/**

tests/PHPStan/Rules/Exceptions/data/immediately-called-fcc.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace ImmediatelyCalledFcc;
44

5-
class Foo
5+
final class Foo
66
{
77

88
/**
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
namespace TooWideThrowTypeAlwaysCheckFinal;
4+
5+
use LogicException;
6+
use RuntimeException;
7+
8+
final class Foo
9+
{
10+
11+
/**
12+
* @throws LogicException|RuntimeException
13+
*/
14+
public function doFoo(): void
15+
{
16+
throw new LogicException();
17+
}
18+
19+
}
20+
21+
class Bar
22+
{
23+
24+
/**
25+
* @throws LogicException|RuntimeException
26+
*/
27+
public function doFoo(): void
28+
{
29+
// first declaration
30+
throw new LogicException();
31+
}
32+
33+
}
34+
35+
class Baz extends Bar
36+
{
37+
38+
public function doFoo(): void
39+
{
40+
throw new LogicException();
41+
}
42+
43+
/**
44+
* @throws LogicException|RuntimeException
45+
*/
46+
private function doBar(): void
47+
{
48+
throw new LogicException();
49+
}
50+
51+
}

tests/PHPStan/Rules/Exceptions/data/too-wide-throws-explicit.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace TooWideThrowsExplicit;
44

5-
class Foo
5+
final class Foo
66
{
77

88
/**

0 commit comments

Comments
 (0)