Skip to content

Commit a4e23d6

Browse files
committed
Extract introduced Property Hooks-related functionalities into their own rules
1 parent c263971 commit a4e23d6

14 files changed

+242
-24
lines changed

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ lint:
7676
--exclude tests/PHPStan/Rules/Classes/data/extends-readonly-class.php \
7777
--exclude tests/PHPStan/Rules/Classes/data/instantiation-promoted-properties.php \
7878
--exclude tests/PHPStan/Rules/Classes/data/bug-11592.php \
79+
--exclude tests/PHPStan/Rules/Properties/data/non-public-property-hook.php \
80+
--exclude tests/PHPStan/Rules/Properties/data/non-empty-property-hook.php \
7981
src tests
8082

8183
cs:

build/collision-detector.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
"../tests/PHPStan/Rules/Names/data/no-namespace.php",
1212
"../tests/notAutoloaded",
1313
"../tests/PHPStan/Rules/Functions/data/define-bug-3349.php",
14+
"../tests/PHPStan/Rules/Properties/data/non-public-property-hook.php",
15+
"../tests/PHPStan/Rules/Properties/datanon-empty-property-hook.php",
1416
"../tests/PHPStan/Levels/data/stubs/function.php"
1517
]
1618
}

conf/config.level0.neon

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ rules:
9898
- PHPStan\Rules\Properties\PropertyAttributesRule
9999
- PHPStan\Rules\Properties\ReadOnlyPropertyRule
100100
- PHPStan\Rules\Properties\ReadOnlyByPhpDocPropertyRule
101+
- PHPStan\Rules\PropertyHooks\NonEmptyPropertyHookRule
102+
- PHPStan\Rules\PropertyHooks\NonPublicPropertyHookRule
101103
- PHPStan\Rules\Regexp\RegularExpressionPatternRule
102104
- PHPStan\Rules\Traits\ConflictingTraitConstantsRule
103105
- PHPStan\Rules\Traits\ConstantsInTraitsRule

src/Analyser/NodeScopeResolver.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -899,7 +899,6 @@ private function processStmtNode(
899899
}
900900
$propStmt = clone $stmt;
901901
$propStmt->setAttributes($prop->getAttributes());
902-
rd($propStmt);
903902
$nodeCallback(
904903
new ClassPropertyNode(
905904
$propertyName,

src/Node/ClassPropertyNode.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,4 +142,20 @@ public function getSubNodeNames(): array
142142
return [];
143143
}
144144

145+
public function isPropertyHook(): bool
146+
{
147+
return $this->originalNode->hooks !== [];
148+
}
149+
150+
public function areHooksBodiesEmpty(): bool
151+
{
152+
foreach ($this->originalNode->hooks as $hook) {
153+
if ($hook->body !== null) {
154+
return false;
155+
}
156+
}
157+
158+
return true;
159+
}
160+
145161
}

src/Php/PhpVersion.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ public function supportsPregCaptureOnlyNamedGroups(): bool
347347
return $this->versionId >= 80200;
348348
}
349349

350-
public function supportsPublicPropertiesInInterfaces(): bool
350+
public function supportsPropertyHooksInInterfaces(): bool
351351
{
352352
return $this->versionId >= 80400;
353353
}
@@ -379,4 +379,5 @@ public function substrReturnFalseInsteadOfEmptyString(): bool
379379
{
380380
return $this->versionId < 80000;
381381
}
382+
382383
}

src/Rules/Properties/PropertiesInInterfaceRule.php

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
use PhpParser\Node;
66
use PHPStan\Analyser\Scope;
77
use PHPStan\Node\ClassPropertyNode;
8-
use PHPStan\Php\PhpVersion;
98
use PHPStan\Rules\Rule;
109
use PHPStan\Rules\RuleErrorBuilder;
1110

@@ -15,10 +14,6 @@
1514
final class PropertiesInInterfaceRule implements Rule
1615
{
1716

18-
public function __construct (private PhpVersion $phpVersion)
19-
{
20-
}
21-
2217
public function getNodeType(): string
2318
{
2419
return ClassPropertyNode::class;
@@ -30,7 +25,7 @@ public function processNode(Node $node, Scope $scope): array
3025
return [];
3126
}
3227

33-
if ($node->isPublic() && $this->phpVersion->supportsPublicPropertiesInInterfaces()) {
28+
if ($node->isPropertyHook()) {
3429
return [];
3530
}
3631

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\PropertyHooks;
4+
5+
use PhpParser\Node;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Node\ClassPropertyNode;
8+
use PHPStan\Php\PhpVersion;
9+
use PHPStan\Rules\Rule;
10+
use PHPStan\Rules\RuleErrorBuilder;
11+
12+
/**
13+
* @implements Rule<ClassPropertyNode>
14+
*/
15+
final class NonEmptyPropertyHookRule implements Rule
16+
{
17+
18+
public function __construct(private PhpVersion $phpVersion)
19+
{
20+
}
21+
22+
public function getNodeType(): string
23+
{
24+
return ClassPropertyNode::class;
25+
}
26+
27+
public function processNode(Node $node, Scope $scope): array
28+
{
29+
if (!$node->getClassReflection()->isInterface()) {
30+
return [];
31+
}
32+
33+
if (!$this->phpVersion->supportsPropertyHooksInInterfaces() || !$node->isPropertyHook()) {
34+
return [];
35+
}
36+
37+
if ($node->areHooksBodiesEmpty()) {
38+
return [];
39+
}
40+
41+
return [
42+
RuleErrorBuilder::message('Property hook must not be empty.')
43+
->nonIgnorable()
44+
->identifier('propertyHook.nonEmpty')
45+
->build(),
46+
];
47+
}
48+
49+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\PropertyHooks;
4+
5+
use PhpParser\Node;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Node\ClassPropertyNode;
8+
use PHPStan\Php\PhpVersion;
9+
use PHPStan\Rules\Rule;
10+
use PHPStan\Rules\RuleErrorBuilder;
11+
12+
/**
13+
* @implements Rule<ClassPropertyNode>
14+
*/
15+
final class NonPublicPropertyHookRule implements Rule
16+
{
17+
18+
public function __construct(private PhpVersion $phpVersion)
19+
{
20+
}
21+
22+
public function getNodeType(): string
23+
{
24+
return ClassPropertyNode::class;
25+
}
26+
27+
public function processNode(Node $node, Scope $scope): array
28+
{
29+
if (!$node->getClassReflection()->isInterface()) {
30+
return [];
31+
}
32+
33+
if (!$this->phpVersion->supportsPropertyHooksInInterfaces() || !$node->isPropertyHook()) {
34+
return [];
35+
}
36+
37+
if ($node->isPublic()) {
38+
return [];
39+
}
40+
41+
return [
42+
RuleErrorBuilder::message('Property hook must be public.')
43+
->nonIgnorable()
44+
->identifier('propertyHook.nonPublic')
45+
->build(),
46+
];
47+
}
48+
49+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Properties;
4+
5+
use PHPStan\Php\PhpVersion;
6+
use PHPStan\Rules\PropertyHooks\NonEmptyPropertyHookRule;
7+
use PHPStan\Rules\Rule;
8+
use PHPStan\Testing\RuleTestCase;
9+
use const PHP_VERSION_ID;
10+
11+
/**
12+
* @extends RuleTestCase<NonEmptyPropertyHookRule>
13+
*/
14+
class NonEmptyPropertyHookRuleTest extends RuleTestCase
15+
{
16+
17+
protected function getRule(): Rule
18+
{
19+
return new NonEmptyPropertyHookRule(new PhpVersion(PHP_VERSION_ID));
20+
}
21+
22+
public function testRule(): void
23+
{
24+
if (PHP_VERSION_ID < 80400) {
25+
$this->markTestSkipped('This test requires at least PHP 8.4.');
26+
}
27+
28+
$this->analyse([__DIR__ . '/data/non-empty-property-hook.php'], [
29+
[
30+
'Property hook must not be empty.',
31+
7,
32+
],
33+
[
34+
'Property hook must not be empty.',
35+
14,
36+
],
37+
]);
38+
}
39+
40+
}

0 commit comments

Comments
 (0)