From 349b3b301b5bc4c8625473dd4f9ca104b62edcf2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 14 May 2021 15:14:22 +0200 Subject: [PATCH] ExceptionTypeResolver - support listing checked exception types --- conf/config.neon | 6 ++ .../Exceptions/ExceptionTypeResolver.php | 52 +++++++++++++- .../Exceptions/ExceptionTypeResolverTest.php | 68 ++++++++++++++++++- 3 files changed, 122 insertions(+), 4 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index bb0fa077c1..781d70e08c 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -14,6 +14,8 @@ parameters: exceptions: uncheckedExceptionRegexes: [] uncheckedExceptionClasses: [] + checkedExceptionRegexes: [] + checkedExceptionClasses: [] check: missingCheckedExceptionInThrows: false tooWideThrowType: false @@ -179,6 +181,8 @@ parametersSchema: exceptions: structure([ uncheckedExceptionRegexes: listOf(string()), uncheckedExceptionClasses: listOf(string()), + checkedExceptionRegexes: listOf(string()), + checkedExceptionClasses: listOf(string()), check: structure([ missingCheckedExceptionInThrows: bool(), tooWideThrowType: bool() @@ -748,6 +752,8 @@ services: arguments: uncheckedExceptionRegexes: %exceptions.uncheckedExceptionRegexes% uncheckedExceptionClasses: %exceptions.uncheckedExceptionClasses% + checkedExceptionRegexes: %exceptions.checkedExceptionRegexes% + checkedExceptionClasses: %exceptions.checkedExceptionClasses% - class: PHPStan\Rules\Exceptions\MissingCheckedExceptionInFunctionThrowsRule diff --git a/src/Rules/Exceptions/ExceptionTypeResolver.php b/src/Rules/Exceptions/ExceptionTypeResolver.php index ec6731adcb..600b70321e 100644 --- a/src/Rules/Exceptions/ExceptionTypeResolver.php +++ b/src/Rules/Exceptions/ExceptionTypeResolver.php @@ -16,20 +16,32 @@ class ExceptionTypeResolver /** @var string[] */ private array $uncheckedExceptionClasses; + /** @var string[] */ + private array $checkedExceptionRegexes; + + /** @var string[] */ + private array $checkedExceptionClasses; + /** * @param ReflectionProvider $reflectionProvider * @param string[] $uncheckedExceptionRegexes * @param string[] $uncheckedExceptionClasses + * @param string[] $checkedExceptionRegexes + * @param string[] $checkedExceptionClasses */ public function __construct( ReflectionProvider $reflectionProvider, array $uncheckedExceptionRegexes, - array $uncheckedExceptionClasses + array $uncheckedExceptionClasses, + array $checkedExceptionRegexes, + array $checkedExceptionClasses ) { $this->reflectionProvider = $reflectionProvider; $this->uncheckedExceptionRegexes = $uncheckedExceptionRegexes; $this->uncheckedExceptionClasses = $uncheckedExceptionClasses; + $this->checkedExceptionRegexes = $checkedExceptionRegexes; + $this->checkedExceptionClasses = $checkedExceptionClasses; } public function isCheckedException(string $className): bool @@ -47,7 +59,7 @@ public function isCheckedException(string $className): bool } if (!$this->reflectionProvider->hasClass($className)) { - return true; + return $this->isCheckedExceptionInternal($className); } $classReflection = $this->reflectionProvider->getClass($className); @@ -63,7 +75,41 @@ public function isCheckedException(string $className): bool return false; } - return true; + return $this->isCheckedExceptionInternal($className); + } + + private function isCheckedExceptionInternal(string $className): bool + { + foreach ($this->checkedExceptionRegexes as $regex) { + if (Strings::match($className, $regex) !== null) { + return true; + } + } + + foreach ($this->checkedExceptionClasses as $checkedExceptionClass) { + if ($className === $checkedExceptionClass) { + return true; + } + } + + if (!$this->reflectionProvider->hasClass($className)) { + return count($this->checkedExceptionRegexes) === 0 && count($this->checkedExceptionClasses) === 0; + } + + $classReflection = $this->reflectionProvider->getClass($className); + foreach ($this->checkedExceptionClasses as $checkedExceptionClass) { + if ($classReflection->getName() === $checkedExceptionClass) { + return true; + } + + if (!$classReflection->isSubclassOf($checkedExceptionClass)) { + continue; + } + + return true; + } + + return count($this->checkedExceptionRegexes) === 0 && count($this->checkedExceptionClasses) === 0; } } diff --git a/tests/PHPStan/Rules/Exceptions/ExceptionTypeResolverTest.php b/tests/PHPStan/Rules/Exceptions/ExceptionTypeResolverTest.php index ce28a09ecc..448e67a347 100644 --- a/tests/PHPStan/Rules/Exceptions/ExceptionTypeResolverTest.php +++ b/tests/PHPStan/Rules/Exceptions/ExceptionTypeResolverTest.php @@ -11,6 +11,8 @@ public function dataIsCheckedException(): array { return [ [ + [], + [], [], [], \InvalidArgumentException::class, @@ -21,6 +23,8 @@ public function dataIsCheckedException(): array '#^InvalidArgumentException$#', ], [], + [], + [], \InvalidArgumentException::class, false, ], @@ -29,6 +33,8 @@ public function dataIsCheckedException(): array [ \InvalidArgumentException::class, ], + [], + [], \InvalidArgumentException::class, false, ], @@ -37,6 +43,8 @@ public function dataIsCheckedException(): array [ \LogicException::class, ], + [], + [], \LogicException::class, false, ], @@ -45,6 +53,8 @@ public function dataIsCheckedException(): array [ \LogicException::class, ], + [], + [], \DomainException::class, false, ], @@ -53,9 +63,61 @@ public function dataIsCheckedException(): array [ \DomainException::class, ], + [], + [], \LogicException::class, true, ], + [ + [], + [], + [ + '#^Exception$#', + ], + [], + \InvalidArgumentException::class, + false, + ], + [ + [], + [], + [ + '#^InvalidArgumentException#', + ], + [], + \InvalidArgumentException::class, + true, + ], + [ + [], + [], + [], + [ + \DomainException::class, + ], + \InvalidArgumentException::class, + false, + ], + [ + [], + [], + [], + [ + \InvalidArgumentException::class, + ], + \InvalidArgumentException::class, + true, + ], + [ + [], + [], + [], + [ + \LogicException::class, + ], + \InvalidArgumentException::class, + true, + ], ]; } @@ -63,17 +125,21 @@ public function dataIsCheckedException(): array * @dataProvider dataIsCheckedException * @param string[] $uncheckedExceptionRegexes * @param string[] $uncheckedExceptionClasses + * @param string[] $checkedExceptionRegexes + * @param string[] $checkedExceptionClasses * @param string $className * @param bool $expectedResult */ public function testIsCheckedException( array $uncheckedExceptionRegexes, array $uncheckedExceptionClasses, + array $checkedExceptionRegexes, + array $checkedExceptionClasses, string $className, bool $expectedResult ): void { - $resolver = new ExceptionTypeResolver($this->createBroker(), $uncheckedExceptionRegexes, $uncheckedExceptionClasses); + $resolver = new ExceptionTypeResolver($this->createBroker(), $uncheckedExceptionRegexes, $uncheckedExceptionClasses, $checkedExceptionRegexes, $checkedExceptionClasses); $this->assertSame($expectedResult, $resolver->isCheckedException($className)); }