Skip to content

Commit 810e504

Browse files
committed
MissingCheckedExceptionInThrowsCheck - report new catch position
1 parent 179b213 commit 810e504

3 files changed

Lines changed: 51 additions & 4 deletions

File tree

src/Rules/Exceptions/MissingCheckedExceptionInFunctionThrowsRule.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public function processNode(Node $node, Scope $scope): array
3636
}
3737

3838
$errors = [];
39-
foreach ($this->check->check($functionReflection->getThrowType(), $statementResult->getThrowPoints()) as [$className, $throwPointNode]) {
39+
foreach ($this->check->check($functionReflection->getThrowType(), $statementResult->getThrowPoints()) as [$className, $throwPointNode, $newCatchPosition]) {
4040
$errors[] = RuleErrorBuilder::message(sprintf(
4141
'Function %s() throws checked exception %s but it\'s missing from the PHPDoc @throws tag.',
4242
$functionReflection->getName(),
@@ -46,6 +46,7 @@ public function processNode(Node $node, Scope $scope): array
4646
->identifier('exceptions.missingThrowsTag')
4747
->metadata([
4848
'exceptionName' => $className,
49+
'newCatchPosition' => $newCatchPosition,
4950
'statementDepth' => $throwPointNode->getAttribute('statementDepth'),
5051
'statementOrder' => $throwPointNode->getAttribute('statementOrder'),
5152
])

src/Rules/Exceptions/MissingCheckedExceptionInMethodThrowsRule.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public function processNode(Node $node, Scope $scope): array
3636
}
3737

3838
$errors = [];
39-
foreach ($this->check->check($methodReflection->getThrowType(), $statementResult->getThrowPoints()) as [$className, $throwPointNode]) {
39+
foreach ($this->check->check($methodReflection->getThrowType(), $statementResult->getThrowPoints()) as [$className, $throwPointNode, $newCatchPosition]) {
4040
$errors[] = RuleErrorBuilder::message(sprintf(
4141
'Method %s::%s() throws checked exception %s but it\'s missing from the PHPDoc @throws tag.',
4242
$methodReflection->getDeclaringClass()->getDisplayName(),
@@ -47,6 +47,7 @@ public function processNode(Node $node, Scope $scope): array
4747
->identifier('exceptions.missingThrowsTag')
4848
->metadata([
4949
'exceptionName' => $className,
50+
'newCatchPosition' => $newCatchPosition,
5051
'statementDepth' => $throwPointNode->getAttribute('statementDepth'),
5152
'statementOrder' => $throwPointNode->getAttribute('statementOrder'),
5253
])

src/Rules/Exceptions/MissingCheckedExceptionInThrowsCheck.php

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use PHPStan\Type\NeverType;
88
use PHPStan\Type\ObjectType;
99
use PHPStan\Type\Type;
10+
use PHPStan\Type\TypeCombinator;
1011
use PHPStan\Type\TypeUtils;
1112
use PHPStan\Type\TypeWithClassName;
1213
use PHPStan\Type\VerbosityLevel;
@@ -24,7 +25,7 @@ public function __construct(ExceptionTypeResolver $exceptionTypeResolver)
2425
/**
2526
* @param Type|null $throwType
2627
* @param ThrowPoint[] $throwPoints
27-
* @return array<int, array{string, Node\Expr|Node\Stmt}>
28+
* @return array<int, array{string, Node\Expr|Node\Stmt, int|null}>
2829
*/
2930
public function check(?Type $throwType, array $throwPoints): array
3031
{
@@ -53,11 +54,55 @@ public function check(?Type $throwType, array $throwPoints): array
5354
continue;
5455
}
5556

56-
$classes[] = [$throwPointType->describe(VerbosityLevel::typeOnly()), $throwPoint->getNode()];
57+
$classes[] = [$throwPointType->describe(VerbosityLevel::typeOnly()), $throwPoint->getNode(), $this->getNewCatchPosition($throwPointType, $throwPoint->getNode())];
5758
}
5859
}
5960

6061
return $classes;
6162
}
6263

64+
private function getNewCatchPosition(Type $throwPointType, Node $throwPointNode): ?int
65+
{
66+
if ($throwPointType instanceof TypeWithClassName) {
67+
// to get rid of type subtraction
68+
$throwPointType = new ObjectType($throwPointType->getClassName());
69+
}
70+
$tryCatch = $this->findTryCatch($throwPointNode);
71+
if ($tryCatch === null) {
72+
return null;
73+
}
74+
75+
$position = 0;
76+
foreach ($tryCatch->catches as $catch) {
77+
$type = TypeCombinator::union(...array_map(static function (Node\Name $class): ObjectType {
78+
return new ObjectType($class->toString());
79+
}, $catch->types));
80+
if (!$throwPointType->isSuperTypeOf($type)->yes()) {
81+
continue;
82+
}
83+
84+
$position++;
85+
}
86+
87+
return $position;
88+
}
89+
90+
private function findTryCatch(Node $node): ?Node\Stmt\TryCatch
91+
{
92+
if ($node instanceof Node\FunctionLike) {
93+
return null;
94+
}
95+
96+
if ($node instanceof Node\Stmt\TryCatch) {
97+
return $node;
98+
}
99+
100+
$parent = $node->getAttribute('parent');
101+
if ($parent === null) {
102+
return null;
103+
}
104+
105+
return $this->findTryCatch($parent);
106+
}
107+
63108
}

0 commit comments

Comments
 (0)