Skip to content

Commit 3c25418

Browse files
committed
Support for promoted properties rules on AST level
1 parent 51aaf57 commit 3c25418

File tree

7 files changed

+83
-9
lines changed

7 files changed

+83
-9
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"nette/utils": "^3.1.3",
2424
"nikic/php-parser": "4.10.2",
2525
"ondram/ci-detector": "^3.4.0",
26-
"ondrejmirtes/better-reflection": "4.3.40",
26+
"ondrejmirtes/better-reflection": "4.3.41",
2727
"phpdocumentor/reflection-docblock": "4.3.4",
2828
"phpstan/php-8-stubs": "^0.1.6",
2929
"phpstan/phpdoc-parser": "^0.4.9",

composer.lock

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Analyser/NodeScopeResolver.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,27 @@ private function processStmtNode(
458458
$isFinal
459459
);
460460

461+
if ($stmt->name->toLowerString() === '__construct') {
462+
foreach ($stmt->params as $param) {
463+
if ($param->flags === 0) {
464+
continue;
465+
}
466+
467+
if (!$param->var instanceof Variable || !is_string($param->var->name)) {
468+
throw new \PHPStan\ShouldNotHappenException();
469+
}
470+
$nodeCallback(new ClassPropertyNode(
471+
$param->var->name,
472+
$param->flags,
473+
$param->type,
474+
$param->default,
475+
null, // todo
476+
true,
477+
$param
478+
), $methodScope);
479+
}
480+
}
481+
461482
if ($stmt->getAttribute('virtual', false) === false) {
462483
$nodeCallback(new InClassMethodNode($stmt), $methodScope);
463484
}
@@ -595,6 +616,7 @@ private function processStmtNode(
595616
$stmt->type,
596617
$prop->default,
597618
$docComment !== null ? $docComment->getText() : null,
619+
false,
598620
$prop
599621
),
600622
$scope

src/Node/ClassPropertyNode.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ class ClassPropertyNode extends NodeAbstract implements VirtualNode
2525

2626
private ?string $phpDoc;
2727

28+
private bool $isPromoted;
29+
2830
/**
2931
* @param int $flags
3032
* @param Identifier|Name|NullableType|UnionType|null $type
@@ -37,6 +39,7 @@ public function __construct(
3739
$type,
3840
?Expr $default,
3941
?string $phpDoc,
42+
bool $isPromoted,
4043
Node $originalNode
4144
)
4245
{
@@ -45,6 +48,7 @@ public function __construct(
4548
$this->flags = $flags;
4649
$this->type = $type;
4750
$this->default = $default;
51+
$this->isPromoted = $isPromoted;
4852
$this->phpDoc = $phpDoc;
4953
}
5054

@@ -63,6 +67,11 @@ public function getDefault(): ?Expr
6367
return $this->default;
6468
}
6569

70+
public function isPromoted(): bool
71+
{
72+
return $this->isPromoted;
73+
}
74+
6675
public function getPhpDoc(): ?string
6776
{
6877
return $this->phpDoc;

src/Node/ClassStatementsGatherer.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use PhpParser\Node\Expr\PropertyFetch;
1010
use PhpParser\Node\Expr\StaticCall;
1111
use PhpParser\Node\Expr\StaticPropertyFetch;
12+
use PhpParser\Node\Identifier;
1213
use PHPStan\Analyser\Scope;
1314
use PHPStan\Node\Constant\ClassConstantFetch;
1415
use PHPStan\Node\Property\PropertyRead;
@@ -119,6 +120,12 @@ private function gatherNodes(\PhpParser\Node $node, Scope $scope): void
119120
}
120121
if ($node instanceof ClassPropertyNode && !$scope->isInTrait()) {
121122
$this->properties[] = $node;
123+
if ($node->isPromoted()) {
124+
$this->propertyUsages[] = new PropertyWrite(
125+
new PropertyFetch(new Expr\Variable('this'), new Identifier($node->getName())),
126+
$scope
127+
);
128+
}
122129
return;
123130
}
124131
if ($node instanceof \PhpParser\Node\Stmt\ClassMethod && !$scope->isInTrait()) {

tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,4 +155,20 @@ public function testBug3636(): void
155155
]);
156156
}
157157

158+
public function testPromotedProperties(): void
159+
{
160+
if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) {
161+
$this->markTestSkipped('Test requires PHP 8.0.');
162+
}
163+
164+
$this->alwaysWrittenTags = [];
165+
$this->alwaysReadTags = [];
166+
$this->analyse([__DIR__ . '/data/unused-private-promoted-property.php'], [
167+
[
168+
'Property UnusedPrivatePromotedProperty\Foo::$lorem is never read, only written.',
169+
12,
170+
],
171+
]);
172+
}
173+
158174
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php // lint >= 8.0
2+
3+
namespace UnusedPrivatePromotedProperty;
4+
5+
class Foo
6+
{
7+
8+
public function __construct(
9+
public $foo,
10+
protected $bar,
11+
private $baz,
12+
private $lorem
13+
) { }
14+
15+
public function getBaz()
16+
{
17+
return $this->baz;
18+
}
19+
20+
}

0 commit comments

Comments
 (0)