Skip to content

Commit 0c055c2

Browse files
Add DOMDocument Extension (#128)
1 parent 800c488 commit 0c055c2

File tree

4 files changed

+94
-1
lines changed

4 files changed

+94
-1
lines changed

extension.neon

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ services:
6262
tags:
6363
- exceptionRules.dynamicConstructorThrowTypeExtension
6464

65+
-
66+
class: Pepakriz\PHPStanExceptionRules\Extension\DOMDocumentExtension
67+
tags:
68+
- exceptionRules.dynamicMethodThrowTypeExtension
69+
6570
-
6671
class: Pepakriz\PHPStanExceptionRules\Extension\JsonEncodeDecodeExtension
6772
tags:
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Pepakriz\PHPStanExceptionRules\Extension;
4+
5+
use DOMDocument;
6+
use ErrorException;
7+
use Pepakriz\PHPStanExceptionRules\DynamicMethodThrowTypeExtension;
8+
use Pepakriz\PHPStanExceptionRules\UnsupportedClassException;
9+
use Pepakriz\PHPStanExceptionRules\UnsupportedFunctionException;
10+
use PhpParser\Node\Expr\MethodCall;
11+
use PHPStan\Analyser\Scope;
12+
use PHPStan\Reflection\MethodReflection;
13+
use PHPStan\Type\NeverType;
14+
use PHPStan\Type\ObjectType;
15+
use PHPStan\Type\Type;
16+
use PHPStan\Type\TypeCombinator;
17+
use PHPStan\Type\TypeUtils;
18+
use PHPStan\Type\VoidType;
19+
use function is_a;
20+
21+
class DOMDocumentExtension implements DynamicMethodThrowTypeExtension
22+
{
23+
24+
/**
25+
* @throws UnsupportedClassException
26+
* @throws UnsupportedFunctionException
27+
*/
28+
public function getThrowTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
29+
{
30+
if (!is_a($methodReflection->getDeclaringClass()->getName(), DOMDocument::class, true)) {
31+
throw new UnsupportedClassException();
32+
}
33+
34+
if ($methodReflection->getName() === 'load' || $methodReflection->getName() === 'loadHTMLFile') {
35+
return new ObjectType(ErrorException::class);
36+
}
37+
38+
if ($methodReflection->getName() === 'loadXML' || $methodReflection->getName() === 'loadHTML') {
39+
return $this->resolveLoadSourceType($methodCall, $scope);
40+
}
41+
42+
throw new UnsupportedFunctionException();
43+
}
44+
45+
private function resolveLoadSourceType(MethodCall $methodCall, Scope $scope): Type
46+
{
47+
$valueType = $scope->getType($methodCall->args[0]->value);
48+
$exceptionType = new ObjectType(ErrorException::class);
49+
50+
foreach (TypeUtils::getConstantStrings($valueType) as $constantString) {
51+
if ($constantString->getValue() === '') {
52+
return $exceptionType;
53+
}
54+
55+
$valueType = TypeCombinator::remove($valueType, $constantString);
56+
}
57+
58+
if (!$valueType instanceof NeverType) {
59+
return $exceptionType;
60+
}
61+
62+
return new VoidType();
63+
}
64+
65+
}

tests/src/Rules/PhpInternalsTest.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Pepakriz\PHPStanExceptionRules\DefaultThrowTypeService;
77
use Pepakriz\PHPStanExceptionRules\DynamicThrowTypeService;
88
use Pepakriz\PHPStanExceptionRules\Extension\DateTimeExtension;
9+
use Pepakriz\PHPStanExceptionRules\Extension\DOMDocumentExtension;
910
use Pepakriz\PHPStanExceptionRules\Extension\IntdivExtension;
1011
use Pepakriz\PHPStanExceptionRules\Extension\JsonEncodeDecodeExtension;
1112
use Pepakriz\PHPStanExceptionRules\Extension\ReflectionExtension;
@@ -27,14 +28,18 @@ protected function getRule(): Rule
2728
$splFileObjectExtension = new SplFileObjectExtension();
2829
$jsonEncodeDecodeExtension = new JsonEncodeDecodeExtension();
2930
$intdivExtension = new IntdivExtension();
31+
$domDocumentExtension = new DOMDocumentExtension();
32+
3033
return new ThrowsPhpDocRule(
3134
new CheckedExceptionService(
3235
[
3336
Throwable::class,
3437
]
3538
),
3639
new DynamicThrowTypeService(
37-
[],
40+
[
41+
$domDocumentExtension,
42+
],
3843
[],
3944
[
4045
$reflectionClassExtension,

tests/src/Rules/data/throws-php-internal-functions.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use DateTime;
66
use DateTimeImmutable;
7+
use DOMDocument;
78
use ReflectionClass;
89
use ReflectionFunction;
910
use ReflectionMethod;
@@ -130,5 +131,22 @@ public function testIntdiv(): void
130131
intdiv(rand(0, 1) === 0 ? 20 : 10, rand(0, 1) === 0 ? 5 : 10);
131132
}
132133

134+
public function testDOMDocumentObject(): void
135+
{
136+
$domDocument = new DOMDocument();
137+
138+
$domDocument->load(''); // error: Missing @throws ErrorException annotation
139+
$domDocument->load('non empty string'); // error: Missing @throws ErrorException annotation
140+
141+
$domDocument->loadXML(''); // error: Missing @throws ErrorException annotation
142+
$domDocument->loadXML('non empty string');
143+
144+
$domDocument->loadHTML(''); // error: Missing @throws ErrorException annotation
145+
$domDocument->loadHTML('non empty string');
146+
147+
$domDocument->loadHTMLFile(''); // error: Missing @throws ErrorException annotation
148+
$domDocument->loadHTMLFile('non empty string'); // error: Missing @throws ErrorException annotation
149+
}
150+
133151
}
134152

0 commit comments

Comments
 (0)