Skip to content

Commit 67ef20b

Browse files
committed
Adjust TooWidePropertyHookThrowTypeRule to be similar to TooWideMethodReturnTypehintRule
1 parent 00ea4d2 commit 67ef20b

File tree

4 files changed

+146
-19
lines changed

4 files changed

+146
-19
lines changed

conf/config.level4.neon

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,5 @@ services:
2727

2828
-
2929
class: PHPStan\Rules\Exceptions\TooWidePropertyHookThrowTypeRule
30+
arguments:
31+
checkProtectedAndPublicMethods: %checkTooWideThrowTypesInProtectedAndPublicMethods%

src/Rules/Exceptions/TooWidePropertyHookThrowTypeRule.php

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
use PHPStan\Rules\Rule;
99
use PHPStan\Rules\RuleErrorBuilder;
1010
use PHPStan\ShouldNotHappenException;
11-
use PHPStan\Type\FileTypeMapper;
1211
use function sprintf;
1312
use function ucfirst;
1413

@@ -18,7 +17,10 @@
1817
final class TooWidePropertyHookThrowTypeRule implements Rule
1918
{
2019

21-
public function __construct(private FileTypeMapper $fileTypeMapper, private TooWideThrowTypeCheck $check)
20+
public function __construct(
21+
private TooWideThrowTypeCheck $check,
22+
private bool $checkProtectedAndPublicMethods,
23+
)
2224
{
2325
}
2426

@@ -29,31 +31,28 @@ public function getNodeType(): string
2931

3032
public function processNode(Node $node, Scope $scope): array
3133
{
32-
$docComment = $node->getDocComment();
33-
if ($docComment === null) {
34-
return [];
35-
}
36-
3734
$statementResult = $node->getStatementResult();
3835
$hookReflection = $node->getHookReflection();
3936
if ($hookReflection->getPropertyHookName() === null) {
4037
throw new ShouldNotHappenException();
4138
}
4239

43-
$classReflection = $node->getClassReflection();
44-
$resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc(
45-
$scope->getFile(),
46-
$classReflection->getName(),
47-
$scope->isInTrait() ? $scope->getTraitReflection()->getName() : null,
48-
$hookReflection->getName(),
49-
$docComment->getText(),
50-
);
40+
$propertyReflection = $node->getPropertyReflection();
5141

52-
if ($resolvedPhpDoc->getThrowsTag() === null) {
42+
$throwType = $hookReflection->getThrowType();
43+
if ($throwType === null) {
5344
return [];
5445
}
5546

56-
$throwType = $resolvedPhpDoc->getThrowsTag()->getType();
47+
if (
48+
!$propertyReflection->isPrivate()
49+
&& !$propertyReflection->getDeclaringClass()->isFinal()
50+
&& !$propertyReflection->isFinal()->yes()
51+
&& !$hookReflection->isFinal()->yes()
52+
&& !$this->checkProtectedAndPublicMethods
53+
) {
54+
return [];
55+
}
5756

5857
$errors = [];
5958
foreach ($this->check->check($throwType, $statementResult->getThrowPoints()) as $throwClass) {

tests/PHPStan/Rules/Exceptions/TooWidePropertyHookThrowTypeRuleTest.php

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

55
use PHPStan\Rules\Rule;
66
use PHPStan\Testing\RuleTestCase;
7-
use PHPStan\Type\FileTypeMapper;
7+
use PHPUnit\Framework\Attributes\DataProvider;
88
use PHPUnit\Framework\Attributes\RequiresPhp;
99

1010
/**
@@ -13,9 +13,11 @@
1313
class TooWidePropertyHookThrowTypeRuleTest extends RuleTestCase
1414
{
1515

16+
private bool $checkProtectedAndPublicMethods = true;
17+
1618
protected function getRule(): Rule
1719
{
18-
return new TooWidePropertyHookThrowTypeRule(self::getContainer()->getByType(FileTypeMapper::class), new TooWideThrowTypeCheck(true));
20+
return new TooWidePropertyHookThrowTypeRule(new TooWideThrowTypeCheck(true), $this->checkProtectedAndPublicMethods);
1921
}
2022

2123
#[RequiresPhp('>= 8.4')]
@@ -49,4 +51,70 @@ public function testRule(): void
4951
]);
5052
}
5153

54+
public static function dataAlwaysCheckFinal(): iterable
55+
{
56+
yield [
57+
false,
58+
[
59+
[
60+
'Get hook for property TooWideThrowsPropertyHookAlwaysCheckFinal\Foo::$foo has RuntimeException in PHPDoc @throws tag but it\'s not thrown.',
61+
13,
62+
],
63+
[
64+
'Get hook for property TooWideThrowsPropertyHookAlwaysCheckFinal\Foo::$baz2 has RuntimeException in PHPDoc @throws tag but it\'s not thrown.',
65+
34,
66+
],
67+
[
68+
'Get hook for property TooWideThrowsPropertyHookAlwaysCheckFinal\Foo::$baz3 has RuntimeException in PHPDoc @throws tag but it\'s not thrown.',
69+
41,
70+
],
71+
[
72+
'Get hook for property TooWideThrowsPropertyHookAlwaysCheckFinal\FinalFoo::$baz has RuntimeException in PHPDoc @throws tag but it\'s not thrown.',
73+
53,
74+
],
75+
],
76+
];
77+
78+
yield [
79+
true,
80+
[
81+
[
82+
'Get hook for property TooWideThrowsPropertyHookAlwaysCheckFinal\Foo::$foo has RuntimeException in PHPDoc @throws tag but it\'s not thrown.',
83+
13,
84+
],
85+
[
86+
'Get hook for property TooWideThrowsPropertyHookAlwaysCheckFinal\Foo::$bar has RuntimeException in PHPDoc @throws tag but it\'s not thrown.',
87+
20,
88+
],
89+
[
90+
'Get hook for property TooWideThrowsPropertyHookAlwaysCheckFinal\Foo::$baz has RuntimeException in PHPDoc @throws tag but it\'s not thrown.',
91+
27,
92+
],
93+
[
94+
'Get hook for property TooWideThrowsPropertyHookAlwaysCheckFinal\Foo::$baz2 has RuntimeException in PHPDoc @throws tag but it\'s not thrown.',
95+
34,
96+
],
97+
[
98+
'Get hook for property TooWideThrowsPropertyHookAlwaysCheckFinal\Foo::$baz3 has RuntimeException in PHPDoc @throws tag but it\'s not thrown.',
99+
41,
100+
],
101+
[
102+
'Get hook for property TooWideThrowsPropertyHookAlwaysCheckFinal\FinalFoo::$baz has RuntimeException in PHPDoc @throws tag but it\'s not thrown.',
103+
53,
104+
],
105+
],
106+
];
107+
}
108+
109+
/**
110+
* @param list<array{0: string, 1: int, 2?: string|null}> $expectedErrors
111+
*/
112+
#[DataProvider('dataAlwaysCheckFinal')]
113+
#[RequiresPhp('>= 8.4')]
114+
public function testAlwaysCheckFinal(bool $checkProtectedAndPublicMethods, array $expectedErrors): void
115+
{
116+
$this->checkProtectedAndPublicMethods = $checkProtectedAndPublicMethods;
117+
$this->analyse([__DIR__ . '/data/too-wide-throw-type-always-check-final-property-hooks.php'], $expectedErrors);
118+
}
119+
52120
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php // lint >= 8.4
2+
3+
namespace TooWideThrowsPropertyHookAlwaysCheckFinal;
4+
5+
use LogicException;
6+
use RuntimeException;
7+
8+
class Foo
9+
{
10+
11+
private int $foo {
12+
/** @throws LogicException|RuntimeException */
13+
get {
14+
throw new LogicException();
15+
}
16+
}
17+
18+
protected int $bar {
19+
/** @throws LogicException|RuntimeException */
20+
get {
21+
throw new LogicException();
22+
}
23+
}
24+
25+
public int $baz {
26+
/** @throws LogicException|RuntimeException */
27+
get {
28+
throw new LogicException();
29+
}
30+
}
31+
32+
final public int $baz2 {
33+
/** @throws LogicException|RuntimeException */
34+
get {
35+
throw new LogicException();
36+
}
37+
}
38+
39+
public int $baz3 {
40+
/** @throws LogicException|RuntimeException */
41+
final get {
42+
throw new LogicException();
43+
}
44+
}
45+
46+
}
47+
48+
final class FinalFoo
49+
{
50+
51+
public int $baz {
52+
/** @throws LogicException|RuntimeException */
53+
get {
54+
throw new LogicException();
55+
}
56+
}
57+
58+
}

0 commit comments

Comments
 (0)