diff --git a/conf/config.neon b/conf/config.neon index e2f0e6ace2..f19a3e5efc 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -750,12 +750,14 @@ services: treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% - - class: PHPStan\Rules\Exceptions\ExceptionTypeResolver + class: PHPStan\Rules\Exceptions\DefaultExceptionTypeResolver arguments: uncheckedExceptionRegexes: %exceptions.uncheckedExceptionRegexes% uncheckedExceptionClasses: %exceptions.uncheckedExceptionClasses% checkedExceptionRegexes: %exceptions.checkedExceptionRegexes% checkedExceptionClasses: %exceptions.checkedExceptionClasses% + autowired: + - PHPStan\Rules\Exceptions\DefaultExceptionTypeResolver - class: PHPStan\Rules\Exceptions\MissingCheckedExceptionInFunctionThrowsRule @@ -765,6 +767,8 @@ services: - class: PHPStan\Rules\Exceptions\MissingCheckedExceptionInThrowsCheck + arguments: + exceptionTypeResolver: @exceptionTypeResolver - class: PHPStan\Rules\Exceptions\TooWideFunctionThrowTypeRule @@ -1355,6 +1359,10 @@ services: - phpstan.broker.dynamicMethodReturnTypeExtension - phpstan.broker.dynamicStaticMethodReturnTypeExtension + exceptionTypeResolver: + class: PHPStan\Rules\Exceptions\ExceptionTypeResolver + factory: @PHPStan\Rules\Exceptions\DefaultExceptionTypeResolver + typeSpecifier: class: PHPStan\Analyser\TypeSpecifier factory: @typeSpecifierFactory::create diff --git a/src/Rules/Exceptions/DefaultExceptionTypeResolver.php b/src/Rules/Exceptions/DefaultExceptionTypeResolver.php new file mode 100644 index 0000000000..84e43fffec --- /dev/null +++ b/src/Rules/Exceptions/DefaultExceptionTypeResolver.php @@ -0,0 +1,115 @@ +reflectionProvider = $reflectionProvider; + $this->uncheckedExceptionRegexes = $uncheckedExceptionRegexes; + $this->uncheckedExceptionClasses = $uncheckedExceptionClasses; + $this->checkedExceptionRegexes = $checkedExceptionRegexes; + $this->checkedExceptionClasses = $checkedExceptionClasses; + } + + public function isCheckedException(string $className): bool + { + foreach ($this->uncheckedExceptionRegexes as $regex) { + if (Strings::match($className, $regex) !== null) { + return false; + } + } + + foreach ($this->uncheckedExceptionClasses as $uncheckedExceptionClass) { + if ($className === $uncheckedExceptionClass) { + return false; + } + } + + if (!$this->reflectionProvider->hasClass($className)) { + return $this->isCheckedExceptionInternal($className); + } + + $classReflection = $this->reflectionProvider->getClass($className); + foreach ($this->uncheckedExceptionClasses as $uncheckedExceptionClass) { + if ($classReflection->getName() === $uncheckedExceptionClass) { + return false; + } + + if (!$classReflection->isSubclassOf($uncheckedExceptionClass)) { + continue; + } + + return false; + } + + 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/src/Rules/Exceptions/ExceptionTypeResolver.php b/src/Rules/Exceptions/ExceptionTypeResolver.php index 600b70321e..3cb14a2fa1 100644 --- a/src/Rules/Exceptions/ExceptionTypeResolver.php +++ b/src/Rules/Exceptions/ExceptionTypeResolver.php @@ -2,114 +2,9 @@ namespace PHPStan\Rules\Exceptions; -use Nette\Utils\Strings; -use PHPStan\Reflection\ReflectionProvider; - -class ExceptionTypeResolver +interface ExceptionTypeResolver { - private ReflectionProvider $reflectionProvider; - - /** @var string[] */ - private array $uncheckedExceptionRegexes; - - /** @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 $checkedExceptionRegexes, - array $checkedExceptionClasses - ) - { - $this->reflectionProvider = $reflectionProvider; - $this->uncheckedExceptionRegexes = $uncheckedExceptionRegexes; - $this->uncheckedExceptionClasses = $uncheckedExceptionClasses; - $this->checkedExceptionRegexes = $checkedExceptionRegexes; - $this->checkedExceptionClasses = $checkedExceptionClasses; - } - - public function isCheckedException(string $className): bool - { - foreach ($this->uncheckedExceptionRegexes as $regex) { - if (Strings::match($className, $regex) !== null) { - return false; - } - } - - foreach ($this->uncheckedExceptionClasses as $uncheckedExceptionClass) { - if ($className === $uncheckedExceptionClass) { - return false; - } - } - - if (!$this->reflectionProvider->hasClass($className)) { - return $this->isCheckedExceptionInternal($className); - } - - $classReflection = $this->reflectionProvider->getClass($className); - foreach ($this->uncheckedExceptionClasses as $uncheckedExceptionClass) { - if ($classReflection->getName() === $uncheckedExceptionClass) { - return false; - } - - if (!$classReflection->isSubclassOf($uncheckedExceptionClass)) { - continue; - } - - return false; - } - - 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; - } + public function isCheckedException(string $className): bool; } diff --git a/tests/PHPStan/Rules/Exceptions/ExceptionTypeResolverTest.php b/tests/PHPStan/Rules/Exceptions/DefaultExceptionTypeResolverTest.php similarity index 90% rename from tests/PHPStan/Rules/Exceptions/ExceptionTypeResolverTest.php rename to tests/PHPStan/Rules/Exceptions/DefaultExceptionTypeResolverTest.php index 448e67a347..445b3249f3 100644 --- a/tests/PHPStan/Rules/Exceptions/ExceptionTypeResolverTest.php +++ b/tests/PHPStan/Rules/Exceptions/DefaultExceptionTypeResolverTest.php @@ -4,7 +4,7 @@ use PHPStan\Testing\TestCase; -class ExceptionTypeResolverTest extends TestCase +class DefaultExceptionTypeResolverTest extends TestCase { public function dataIsCheckedException(): array @@ -139,7 +139,7 @@ public function testIsCheckedException( bool $expectedResult ): void { - $resolver = new ExceptionTypeResolver($this->createBroker(), $uncheckedExceptionRegexes, $uncheckedExceptionClasses, $checkedExceptionRegexes, $checkedExceptionClasses); + $resolver = new DefaultExceptionTypeResolver($this->createBroker(), $uncheckedExceptionRegexes, $uncheckedExceptionClasses, $checkedExceptionRegexes, $checkedExceptionClasses); $this->assertSame($expectedResult, $resolver->isCheckedException($className)); } diff --git a/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInFunctionThrowsRuleTest.php b/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInFunctionThrowsRuleTest.php index 5e9613147d..67b7f7c96a 100644 --- a/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInFunctionThrowsRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInFunctionThrowsRuleTest.php @@ -14,7 +14,7 @@ class MissingCheckedExceptionInFunctionThrowsRuleTest extends RuleTestCase protected function getRule(): Rule { return new MissingCheckedExceptionInFunctionThrowsRule( - new MissingCheckedExceptionInThrowsCheck(new ExceptionTypeResolver( + new MissingCheckedExceptionInThrowsCheck(new DefaultExceptionTypeResolver( $this->createReflectionProvider(), [], [\PHPStan\ShouldNotHappenException::class], diff --git a/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInMethodThrowsRuleTest.php b/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInMethodThrowsRuleTest.php index 7fa2b53538..97797eef37 100644 --- a/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInMethodThrowsRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInMethodThrowsRuleTest.php @@ -14,7 +14,7 @@ class MissingCheckedExceptionInMethodThrowsRuleTest extends RuleTestCase protected function getRule(): Rule { return new MissingCheckedExceptionInMethodThrowsRule( - new MissingCheckedExceptionInThrowsCheck(new ExceptionTypeResolver( + new MissingCheckedExceptionInThrowsCheck(new DefaultExceptionTypeResolver( $this->createReflectionProvider(), [], [\PHPStan\ShouldNotHappenException::class],