Skip to content

Commit 33f96bf

Browse files
committed
IncompatiblePropertyHookPhpDocTypeRule - level 2
1 parent 3dc8717 commit 33f96bf

File tree

4 files changed

+236
-0
lines changed

4 files changed

+236
-0
lines changed

conf/config.level2.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ rules:
5252
- PHPStan\Rules\PhpDoc\IncompatibleSelfOutTypeRule
5353
- PHPStan\Rules\PhpDoc\IncompatibleClassConstantPhpDocTypeRule
5454
- PHPStan\Rules\PhpDoc\IncompatiblePhpDocTypeRule
55+
- PHPStan\Rules\PhpDoc\IncompatiblePropertyHookPhpDocTypeRule
5556
- PHPStan\Rules\PhpDoc\IncompatiblePropertyPhpDocTypeRule
5657
- PHPStan\Rules\PhpDoc\InvalidThrowsPhpDocValueRule
5758
- PHPStan\Rules\PhpDoc\IncompatibleParamImmediatelyInvokedCallableRule
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\PhpDoc;
4+
5+
use PhpParser\Node;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Node\InPropertyHookNode;
8+
use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection;
9+
use PHPStan\Rules\Rule;
10+
use PHPStan\Type\FileTypeMapper;
11+
use PHPStan\Type\Type;
12+
13+
/**
14+
* @implements Rule<InPropertyHookNode>
15+
*/
16+
final class IncompatiblePropertyHookPhpDocTypeRule implements Rule
17+
{
18+
19+
public function __construct(
20+
private FileTypeMapper $fileTypeMapper,
21+
private IncompatiblePhpDocTypeCheck $check,
22+
)
23+
{
24+
}
25+
26+
public function getNodeType(): string
27+
{
28+
return InPropertyHookNode::class;
29+
}
30+
31+
public function processNode(Node $node, Scope $scope): array
32+
{
33+
$docComment = $node->getDocComment();
34+
if ($docComment === null) {
35+
return [];
36+
}
37+
38+
$hookReflection = $node->getHookReflection();
39+
40+
$resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc(
41+
$scope->getFile(),
42+
$node->getClassReflection()->getName(),
43+
$scope->isInTrait() ? $scope->getTraitReflection()->getName() : null,
44+
$hookReflection->getName(),
45+
$docComment->getText(),
46+
);
47+
48+
return $this->check->check(
49+
$scope,
50+
$node,
51+
$resolvedPhpDoc,
52+
$hookReflection->getName(),
53+
$this->getNativeParameterTypes($hookReflection),
54+
$this->getByRefParameters($hookReflection),
55+
$hookReflection->getNativeReturnType(),
56+
);
57+
}
58+
59+
/**
60+
* @return array<string, Type>
61+
*/
62+
private function getNativeParameterTypes(PhpMethodFromParserNodeReflection $node): array
63+
{
64+
$parameters = [];
65+
foreach ($node->getParameters() as $parameter) {
66+
$parameters[$parameter->getName()] = $parameter->getNativeType();
67+
}
68+
69+
return $parameters;
70+
}
71+
72+
/**
73+
* @return array<string, false>
74+
*/
75+
private function getByRefParameters(PhpMethodFromParserNodeReflection $node): array
76+
{
77+
$parameters = [];
78+
foreach ($node->getParameters() as $parameter) {
79+
$parameters[$parameter->getName()] = false;
80+
}
81+
82+
return $parameters;
83+
}
84+
85+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\PhpDoc;
4+
5+
use PHPStan\Rules\ClassCaseSensitivityCheck;
6+
use PHPStan\Rules\ClassForbiddenNameCheck;
7+
use PHPStan\Rules\ClassNameCheck;
8+
use PHPStan\Rules\Generics\GenericObjectTypeCheck;
9+
use PHPStan\Rules\Generics\TemplateTypeCheck;
10+
use PHPStan\Rules\Rule;
11+
use PHPStan\Testing\RuleTestCase;
12+
use PHPStan\Type\FileTypeMapper;
13+
14+
/**
15+
* @extends RuleTestCase<IncompatiblePropertyHookPhpDocTypeRule>
16+
*/
17+
class IncompatiblePropertyHookPhpDocTypeRuleTest extends RuleTestCase
18+
{
19+
20+
protected function getRule(): Rule
21+
{
22+
$reflectionProvider = $this->createReflectionProvider();
23+
$typeAliasResolver = $this->createTypeAliasResolver([], $reflectionProvider);
24+
25+
return new IncompatiblePropertyHookPhpDocTypeRule(
26+
self::getContainer()->getByType(FileTypeMapper::class),
27+
new IncompatiblePhpDocTypeCheck(
28+
new GenericObjectTypeCheck(),
29+
new UnresolvableTypeHelper(),
30+
new GenericCallableRuleHelper(
31+
new TemplateTypeCheck(
32+
$reflectionProvider,
33+
new ClassNameCheck(
34+
new ClassCaseSensitivityCheck($reflectionProvider, true),
35+
new ClassForbiddenNameCheck(self::getContainer()),
36+
),
37+
new GenericObjectTypeCheck(),
38+
$typeAliasResolver,
39+
true,
40+
),
41+
),
42+
),
43+
);
44+
}
45+
46+
public function testRule(): void
47+
{
48+
$this->analyse([__DIR__ . '/data/incompatible-property-hook-phpdoc-types.php'], [
49+
[
50+
'PHPDoc tag @return with type string is incompatible with native type int.',
51+
10,
52+
],
53+
[
54+
'PHPDoc tag @return with type string is incompatible with native type void.',
55+
17,
56+
],
57+
[
58+
'PHPDoc tag @param for parameter $value with type string is incompatible with native type int.',
59+
27,
60+
],
61+
[
62+
'Parameter $value for PHPDoc tag @param-out is not passed by reference.',
63+
27,
64+
],
65+
[
66+
'PHPDoc tag @param for parameter $value contains unresolvable type.',
67+
34,
68+
],
69+
[
70+
'PHPDoc tag @param for parameter $value contains generic type Exception<int> but class Exception is not generic.',
71+
41,
72+
],
73+
[
74+
'PHPDoc tag @param for parameter $value template T of callable<T of mixed>(T): T shadows @template T for class IncompatiblePropertyHookPhpDocTypes\GenericFoo.',
75+
54,
76+
],
77+
[
78+
'PHPDoc tag @param for parameter $value template of callable<\stdClass of mixed>(T): T cannot have existing class \stdClass as its name.',
79+
61,
80+
],
81+
]);
82+
}
83+
84+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php // lint >= 8.4
2+
3+
namespace IncompatiblePropertyHookPhpDocTypes;
4+
5+
class Foo
6+
{
7+
8+
public int $i {
9+
/** @return string */
10+
get {
11+
return $this->i;
12+
}
13+
}
14+
15+
public int $j {
16+
/** @return string */
17+
set {
18+
$this->j = 1;
19+
}
20+
}
21+
22+
public int $k {
23+
/**
24+
* @param string $value
25+
* @param-out int $value
26+
*/
27+
set {
28+
$this->k = 1;
29+
}
30+
}
31+
32+
public int $l {
33+
/** @param \stdClass&\Exception $value */
34+
set {
35+
36+
}
37+
}
38+
39+
public \Exception $m {
40+
/** @param \Exception<int> $value */
41+
set {
42+
43+
}
44+
}
45+
46+
}
47+
48+
/** @template T */
49+
class GenericFoo
50+
{
51+
52+
public int $n {
53+
/** @param int|callable<T>(T): T $value */
54+
set (int|callable $value) {
55+
56+
}
57+
}
58+
59+
public int $o {
60+
/** @param int|callable<\stdClass>(T): T $value */
61+
set (int|callable $value) {
62+
63+
}
64+
}
65+
66+
}

0 commit comments

Comments
 (0)