Skip to content

Commit 3eaad7f

Browse files
committed
Bleeding edge - report implicit (inherited) too-wide @throws type
1 parent 844f569 commit 3eaad7f

File tree

7 files changed

+99
-2
lines changed

7 files changed

+99
-2
lines changed

conf/bleedingEdge.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ parameters:
1515
reportNestedTooWideType: false
1616
assignToByRefForeachExpr: true
1717
curlSetOptArrayTypes: true
18+
tooWideImplicitThrows: true

conf/config.level4.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ services:
2424
class: PHPStan\Rules\Exceptions\TooWideMethodThrowTypeRule
2525
arguments:
2626
checkProtectedAndPublicMethods: %checkTooWideThrowTypesInProtectedAndPublicMethods%
27+
tooWideImplicitThrows: %featureToggles.tooWideImplicitThrows%
2728

2829
-
2930
class: PHPStan\Rules\Exceptions\TooWidePropertyHookThrowTypeRule

conf/config.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ parameters:
4040
reportNestedTooWideType: false
4141
assignToByRefForeachExpr: false
4242
curlSetOptArrayTypes: false
43+
tooWideImplicitThrows: false
4344
fileExtensions:
4445
- php
4546
checkAdvancedIsset: false

conf/parametersSchema.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ parametersSchema:
4343
reportNestedTooWideType: bool()
4444
assignToByRefForeachExpr: bool()
4545
curlSetOptArrayTypes: bool()
46+
tooWideImplicitThrows: bool()
4647
])
4748
fileExtensions: listOf(string())
4849
checkAdvancedIsset: bool()

src/Rules/Exceptions/TooWideMethodThrowTypeRule.php

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use PHPStan\Node\MethodReturnStatementsNode;
88
use PHPStan\Rules\Rule;
99
use PHPStan\Rules\RuleErrorBuilder;
10+
use PHPStan\Type\FileTypeMapper;
1011
use function sprintf;
1112

1213
/**
@@ -16,8 +17,10 @@ final class TooWideMethodThrowTypeRule implements Rule
1617
{
1718

1819
public function __construct(
20+
private FileTypeMapper $fileTypeMapper,
1921
private TooWideThrowTypeCheck $check,
2022
private bool $checkProtectedAndPublicMethods,
23+
private bool $tooWideImplicitThrows,
2124
)
2225
{
2326
}
@@ -49,8 +52,34 @@ public function processNode(Node $node, Scope $scope): array
4952
return [];
5053
}
5154

55+
$unusedThrowClasses = $this->check->check($throwType, $statementResult->getThrowPoints());
56+
if (!$this->tooWideImplicitThrows) {
57+
$docComment = $node->getDocComment();
58+
if ($docComment === null) {
59+
return [];
60+
}
61+
62+
$classReflection = $node->getClassReflection();
63+
$resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc(
64+
$scope->getFile(),
65+
$classReflection->getName(),
66+
$scope->isInTrait() ? $scope->getTraitReflection()->getName() : null,
67+
$method->getName(),
68+
$docComment->getText(),
69+
);
70+
71+
if ($resolvedPhpDoc->getThrowsTag() === null) {
72+
return [];
73+
}
74+
75+
$explicitThrowType = $resolvedPhpDoc->getThrowsTag()->getType();
76+
if ($explicitThrowType->equals($throwType)) {
77+
return [];
78+
}
79+
}
80+
5281
$errors = [];
53-
foreach ($this->check->check($throwType, $statementResult->getThrowPoints()) as $throwClass) {
82+
foreach ($unusedThrowClasses as $throwClass) {
5483
$errors[] = RuleErrorBuilder::message(sprintf(
5584
'Method %s::%s() has %s in PHPDoc @throws tag but it\'s not thrown.',
5685
$method->getDeclaringClass()->getDisplayName(),

tests/PHPStan/Rules/Exceptions/TooWideMethodThrowTypeRuleTest.php

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use PHPStan\Rules\Rule;
66
use PHPStan\Testing\RuleTestCase;
7+
use PHPStan\Type\FileTypeMapper;
78
use PHPUnit\Framework\Attributes\DataProvider;
89
use PHPUnit\Framework\Attributes\RequiresPhp;
910

@@ -17,9 +18,16 @@ class TooWideMethodThrowTypeRuleTest extends RuleTestCase
1718

1819
private bool $checkProtectedAndPublicMethods = true;
1920

21+
private bool $tooWideImplicitThrows = true;
22+
2023
protected function getRule(): Rule
2124
{
22-
return new TooWideMethodThrowTypeRule(new TooWideThrowTypeCheck($this->implicitThrows), $this->checkProtectedAndPublicMethods);
25+
return new TooWideMethodThrowTypeRule(
26+
self::getContainer()->getByType(FileTypeMapper::class),
27+
new TooWideThrowTypeCheck($this->implicitThrows),
28+
$this->checkProtectedAndPublicMethods,
29+
$this->tooWideImplicitThrows,
30+
);
2331
}
2432

2533
public function testRule(): void
@@ -150,4 +158,32 @@ public function testAlwaysCheckFinal(bool $checkProtectedAndPublicMethods, array
150158
$this->analyse([__DIR__ . '/data/too-wide-throw-type-always-check-final.php'], $expectedErrors);
151159
}
152160

161+
public static function dataTooWideImplicitThrows(): iterable
162+
{
163+
yield [
164+
false,
165+
[],
166+
];
167+
168+
yield [
169+
true,
170+
[
171+
[
172+
'Method TooWideImplicitThrows\Bar::doFoo() has LogicException in PHPDoc @throws tag but it\'s not thrown.',
173+
23,
174+
],
175+
],
176+
];
177+
}
178+
179+
/**
180+
* @param list<array{0: string, 1: int, 2?: string|null}> $expectedErrors
181+
*/
182+
#[DataProvider('dataTooWideImplicitThrows')]
183+
public function testTooWideImplicitThrows(bool $tooWideImplicitThrows, array $expectedErrors): void
184+
{
185+
$this->tooWideImplicitThrows = $tooWideImplicitThrows;
186+
$this->analyse([__DIR__ . '/data/too-wide-implicit-throws.php'], $expectedErrors);
187+
}
188+
153189
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace TooWideImplicitThrows;
4+
5+
use LogicException;
6+
7+
class Foo
8+
{
9+
10+
/**
11+
* @throws LogicException
12+
*/
13+
public function doFoo(): void
14+
{
15+
throw new LogicException();
16+
}
17+
18+
}
19+
20+
final class Bar extends Foo
21+
{
22+
23+
public function doFoo(): void
24+
{
25+
26+
}
27+
28+
}

0 commit comments

Comments
 (0)