Skip to content

Commit 1314ff5

Browse files
authored
Global scope support (fixed #65 #64) (#66)
1 parent 53ebd08 commit 1314ff5

File tree

8 files changed

+63
-13
lines changed

8 files changed

+63
-13
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ This extension provides following rules and features:
2323
* Unreachable catch statements
2424
* exception has been caught in some previous catch statement ([examples](https://github.com/pepakriz/phpstan-exception-rules/blob/master/tests/src/Rules/data/unreachable-catches.php))
2525
* checked exception is never thrown in the corresponding try block ([examples](https://github.com/pepakriz/phpstan-exception-rules/blob/master/tests/src/Rules/data/unused-catches.php))
26+
* Report throwing checked exceptions in the global scope ([examples](https://github.com/pepakriz/phpstan-exception-rules/blob/master/tests/src/Rules/data/throws-in-global-scope.php))
2627

2728
Features and rules provided by PHPStan core (we rely on):
2829

@@ -46,6 +47,7 @@ includes:
4647
parameters:
4748
exceptionRules:
4849
reportUnusedCatchesOfUncheckedExceptions: false
50+
reportCheckedThrowsInGlobalScope: false
4951
ignoreDescriptiveUncheckedExceptions: false
5052
checkedExceptions:
5153
- RuntimeException

extension.neon

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
parameters:
22
exceptionRules:
33
reportUnusedCatchesOfUncheckedExceptions: false
4+
reportCheckedThrowsInGlobalScope: false
45
ignoreDescriptiveUncheckedExceptions: false
56
checkedExceptions: []
67
uncheckedExceptions: []
@@ -46,6 +47,7 @@ services:
4647
class: Pepakriz\PHPStanExceptionRules\Rules\ThrowsPhpDocRule
4748
arguments:
4849
reportUnusedCatchesOfUncheckedExceptions: %exceptionRules.reportUnusedCatchesOfUncheckedExceptions%
50+
reportCheckedThrowsInGlobalScope: %exceptionRules.reportCheckedThrowsInGlobalScope%
4951
ignoreDescriptiveUncheckedExceptions: %exceptionRules.ignoreDescriptiveUncheckedExceptions%
5052
tags: [phpstan.rules.rule]
5153

phpstan.neon.dist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ parameters:
1313

1414
exceptionRules:
1515
reportUnusedCatchesOfUncheckedExceptions: true
16+
reportCheckedThrowsInGlobalScope: true
1617
uncheckedExceptions:
1718
- LogicException
1819
- PHPStan\ShouldNotHappenException

src/Rules/ThrowsPhpDocRule.php

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@ class ThrowsPhpDocRule implements Rule
9393
*/
9494
private $reportUnusedCatchesOfUncheckedExceptions;
9595

96+
/**
97+
* @var bool
98+
*/
99+
private $reportCheckedThrowsInGlobalScope;
100+
96101
/**
97102
* @var bool
98103
*/
@@ -105,6 +110,7 @@ public function __construct(
105110
ThrowsAnnotationReader $throwsAnnotationReader,
106111
Broker $broker,
107112
bool $reportUnusedCatchesOfUncheckedExceptions,
113+
bool $reportCheckedThrowsInGlobalScope,
108114
bool $ignoreDescriptiveUncheckedExceptions
109115
)
110116
{
@@ -115,6 +121,7 @@ public function __construct(
115121
$this->broker = $broker;
116122
$this->throwsScope = new ThrowsScope();
117123
$this->reportUnusedCatchesOfUncheckedExceptions = $reportUnusedCatchesOfUncheckedExceptions;
124+
$this->reportCheckedThrowsInGlobalScope = $reportCheckedThrowsInGlobalScope;
118125
$this->ignoreDescriptiveUncheckedExceptions = $ignoreDescriptiveUncheckedExceptions;
119126
}
120127

@@ -129,7 +136,7 @@ public function getNodeType(): string
129136
public function processNode(Node $node, Scope $scope): array
130137
{
131138
if ($node instanceof TryCatch) {
132-
return $this->processTryCatch($node, $scope);
139+
return $this->processTryCatch($node);
133140
}
134141

135142
if ($node instanceof TryCatchTryEnd) {
@@ -178,14 +185,8 @@ public function processNode(Node $node, Scope $scope): array
178185
/**
179186
* @return string[]
180187
*/
181-
private function processTryCatch(TryCatch $node, Scope $scope): array
188+
private function processTryCatch(TryCatch $node): array
182189
{
183-
$classReflection = $scope->getClassReflection();
184-
$methodReflection = $scope->getFunction();
185-
if ($classReflection === null || $methodReflection === null) {
186-
return [];
187-
}
188-
189190
$this->throwsScope->enterToTryCatch($node);
190191

191192
if (!$node->hasAttribute(self::ATTRIBUTE_HAS_TRY_CATCH_END)) {
@@ -216,7 +217,16 @@ private function processThrow(Throw_ $node, Scope $scope): array
216217
$exceptionClassNames = $this->throwsScope->filterExceptionsByUncaught($exceptionClassNames);
217218
$exceptionClassNames = $this->checkedExceptionService->filterCheckedExceptions($exceptionClassNames);
218219

219-
return array_map(static function (string $exceptionClassName): string {
220+
$isInGlobalScope = $this->throwsScope->isInGlobalScope();
221+
if (!$this->reportCheckedThrowsInGlobalScope && $isInGlobalScope) {
222+
return [];
223+
}
224+
225+
return array_map(static function (string $exceptionClassName) use ($isInGlobalScope): string {
226+
if ($isInGlobalScope) {
227+
return sprintf('Throwing checked exception %s in global scope is prohibited', $exceptionClassName);
228+
}
229+
220230
return sprintf('Missing @throws %s annotation', $exceptionClassName);
221231
}, $exceptionClassNames);
222232
}

src/Rules/ThrowsScope.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,22 @@ class ThrowsScope
2020
/**
2121
* @var int
2222
*/
23-
private $stackIndex = -1;
23+
private $stackIndex = 0;
2424

2525
/**
2626
* @var array<Type|null>
2727
*/
28-
private $throwsAnnotationBlockStack = [];
28+
private $throwsAnnotationBlockStack = [null];
2929

3030
/**
3131
* @var bool[][]
3232
*/
33-
private $usedThrowsAnnotationsStack = [];
33+
private $usedThrowsAnnotationsStack = [[]];
3434

3535
/**
3636
* @var TryCatch[][]
3737
*/
38-
private $tryCatchStack = [];
38+
private $tryCatchStack = [[]];
3939

4040
public function enterToThrowsAnnotationBlock(?Type $type): void
4141
{
@@ -62,6 +62,11 @@ public function exitFromThrowsAnnotationBlock(): array
6262
return array_keys($usedThrowsAnnotations);
6363
}
6464

65+
public function isInGlobalScope(): bool
66+
{
67+
return $this->stackIndex === 0;
68+
}
69+
6570
public function enterToTryCatch(TryCatch $tryCatch): void
6671
{
6772
$this->tryCatchStack[$this->stackIndex][] = $tryCatch;

tests/src/Rules/PhpInternalsTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ protected function getRule(): Rule
4141
$this->createThrowsAnnotationReader(),
4242
$this->createBroker(),
4343
true,
44+
true,
4445
true
4546
);
4647
}

tests/src/Rules/ThrowsPhpDocRuleTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ class ThrowsPhpDocRuleTest extends RuleTestCase
2929
*/
3030
private $ignoreDescriptiveUncheckedExceptions = false;
3131

32+
/**
33+
* @var bool
34+
*/
35+
private $reportCheckedThrowsInGlobalScope = false;
36+
3237
/**
3338
* @var mixed[]
3439
*/
@@ -69,6 +74,7 @@ protected function getRule(): Rule
6974
$this->createThrowsAnnotationReader(),
7075
$this->createBroker(),
7176
$this->reportUnusedCatchesOfUncheckedExceptions,
77+
$this->reportCheckedThrowsInGlobalScope,
7278
$this->ignoreDescriptiveUncheckedExceptions
7379
);
7480
}
@@ -149,4 +155,10 @@ public function testAnonymClass(): void
149155
$this->analyse(__DIR__ . '/data/throws-anonym-class.php');
150156
}
151157

158+
public function testThrowsInGlobalScope(): void
159+
{
160+
$this->reportCheckedThrowsInGlobalScope = true;
161+
$this->analyse(__DIR__ . '/data/throws-in-global-scope.php');
162+
}
163+
152164
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Pepakriz\PHPStanExceptionRules\Rules\ThrowsInGlobalScope;
4+
5+
use LogicException;
6+
use RuntimeException;
7+
8+
if (false) {
9+
throw new LogicException();
10+
throw new RuntimeException(); // error: Throwing checked exception RuntimeException in global scope is prohibited
11+
12+
try {
13+
throw new RuntimeException();
14+
} catch (RuntimeException $e) {
15+
// ignore
16+
}
17+
}

0 commit comments

Comments
 (0)