diff --git a/extension.neon b/extension.neon index 9c60355..e2abb94 100644 --- a/extension.neon +++ b/extension.neon @@ -62,6 +62,11 @@ services: tags: - exceptionRules.dynamicConstructorThrowTypeExtension + - + class: Pepakriz\PHPStanExceptionRules\Extension\DOMDocumentExtension + tags: + - exceptionRules.dynamicMethodThrowTypeExtension + - class: Pepakriz\PHPStanExceptionRules\Extension\JsonEncodeDecodeExtension tags: diff --git a/src/Extension/DOMDocumentExtension.php b/src/Extension/DOMDocumentExtension.php new file mode 100644 index 0000000..7a8c9bf --- /dev/null +++ b/src/Extension/DOMDocumentExtension.php @@ -0,0 +1,65 @@ +getDeclaringClass()->getName(), DOMDocument::class, true)) { + throw new UnsupportedClassException(); + } + + if ($methodReflection->getName() === 'load' || $methodReflection->getName() === 'loadHTMLFile') { + return new ObjectType(ErrorException::class); + } + + if ($methodReflection->getName() === 'loadXML' || $methodReflection->getName() === 'loadHTML') { + return $this->resolveLoadSourceType($methodCall, $scope); + } + + throw new UnsupportedFunctionException(); + } + + private function resolveLoadSourceType(MethodCall $methodCall, Scope $scope): Type + { + $valueType = $scope->getType($methodCall->args[0]->value); + $exceptionType = new ObjectType(ErrorException::class); + + foreach (TypeUtils::getConstantStrings($valueType) as $constantString) { + if ($constantString->getValue() === '') { + return $exceptionType; + } + + $valueType = TypeCombinator::remove($valueType, $constantString); + } + + if (!$valueType instanceof NeverType) { + return $exceptionType; + } + + return new VoidType(); + } + +} diff --git a/tests/src/Rules/PhpInternalsTest.php b/tests/src/Rules/PhpInternalsTest.php index 0f7535b..d7706ba 100644 --- a/tests/src/Rules/PhpInternalsTest.php +++ b/tests/src/Rules/PhpInternalsTest.php @@ -6,6 +6,7 @@ use Pepakriz\PHPStanExceptionRules\DefaultThrowTypeService; use Pepakriz\PHPStanExceptionRules\DynamicThrowTypeService; use Pepakriz\PHPStanExceptionRules\Extension\DateTimeExtension; +use Pepakriz\PHPStanExceptionRules\Extension\DOMDocumentExtension; use Pepakriz\PHPStanExceptionRules\Extension\IntdivExtension; use Pepakriz\PHPStanExceptionRules\Extension\JsonEncodeDecodeExtension; use Pepakriz\PHPStanExceptionRules\Extension\ReflectionExtension; @@ -27,6 +28,8 @@ protected function getRule(): Rule $splFileObjectExtension = new SplFileObjectExtension(); $jsonEncodeDecodeExtension = new JsonEncodeDecodeExtension(); $intdivExtension = new IntdivExtension(); + $domDocumentExtension = new DOMDocumentExtension(); + return new ThrowsPhpDocRule( new CheckedExceptionService( [ @@ -34,7 +37,9 @@ protected function getRule(): Rule ] ), new DynamicThrowTypeService( - [], + [ + $domDocumentExtension, + ], [], [ $reflectionClassExtension, diff --git a/tests/src/Rules/data/throws-php-internal-functions.php b/tests/src/Rules/data/throws-php-internal-functions.php index 363a5ae..3a3d473 100644 --- a/tests/src/Rules/data/throws-php-internal-functions.php +++ b/tests/src/Rules/data/throws-php-internal-functions.php @@ -4,6 +4,7 @@ use DateTime; use DateTimeImmutable; +use DOMDocument; use ReflectionClass; use ReflectionFunction; use ReflectionMethod; @@ -130,5 +131,22 @@ public function testIntdiv(): void intdiv(rand(0, 1) === 0 ? 20 : 10, rand(0, 1) === 0 ? 5 : 10); } + public function testDOMDocumentObject(): void + { + $domDocument = new DOMDocument(); + + $domDocument->load(''); // error: Missing @throws ErrorException annotation + $domDocument->load('non empty string'); // error: Missing @throws ErrorException annotation + + $domDocument->loadXML(''); // error: Missing @throws ErrorException annotation + $domDocument->loadXML('non empty string'); + + $domDocument->loadHTML(''); // error: Missing @throws ErrorException annotation + $domDocument->loadHTML('non empty string'); + + $domDocument->loadHTMLFile(''); // error: Missing @throws ErrorException annotation + $domDocument->loadHTMLFile('non empty string'); // error: Missing @throws ErrorException annotation + } + }