Skip to content

Commit c52eb57

Browse files
committed
ReadOnlyPropertyRule - support for readonly classes
1 parent 3e383fc commit c52eb57

5 files changed

Lines changed: 49 additions & 1 deletion

File tree

src/Analyser/NodeScopeResolver.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,9 @@ private function processStmtNode(
503503
if ($param->getDocComment() !== null) {
504504
$phpDoc = $param->getDocComment()->getText();
505505
}
506+
if (!$scope->isInClass()) {
507+
throw new ShouldNotHappenException();
508+
}
506509
$nodeCallback(new ClassPropertyNode(
507510
$param->var->name,
508511
$param->flags,
@@ -513,6 +516,7 @@ private function processStmtNode(
513516
$param,
514517
false,
515518
$scope->isInTrait(),
519+
$scope->getClassReflection()->isReadOnly(),
516520
), $methodScope);
517521
}
518522
}
@@ -657,6 +661,9 @@ private function processStmtNode(
657661
foreach ($stmt->props as $prop) {
658662
$this->processStmtNode($prop, $scope, $nodeCallback);
659663
[,,,,,,,,,,$isReadOnly, $docComment] = $this->getPhpDocs($scope, $stmt);
664+
if (!$scope->isInClass()) {
665+
throw new ShouldNotHappenException();
666+
}
660667
$nodeCallback(
661668
new ClassPropertyNode(
662669
$prop->name->toString(),
@@ -668,6 +675,7 @@ private function processStmtNode(
668675
$prop,
669676
$isReadOnly,
670677
$scope->isInTrait(),
678+
$scope->getClassReflection()->isReadOnly(),
671679
),
672680
$scope,
673681
);

src/Node/ClassPropertyNode.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public function __construct(
2323
Node $originalNode,
2424
private bool $isReadonlyByPhpDoc,
2525
private bool $isDeclaredInTrait,
26+
private bool $isReadonlyClass,
2627
)
2728
{
2829
parent::__construct($originalNode->getAttributes());
@@ -76,7 +77,7 @@ public function isStatic(): bool
7677

7778
public function isReadOnly(): bool
7879
{
79-
return (bool) ($this->flags & Class_::MODIFIER_READONLY);
80+
return (bool) ($this->flags & Class_::MODIFIER_READONLY) || $this->isReadonlyClass;
8081
}
8182

8283
public function isReadOnlyByPhpDoc(): bool

src/Reflection/ClassReflection.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,11 @@ public function isEnum(): bool
584584
return $this->reflection->isEnum();
585585
}
586586

587+
public function isReadOnly(): bool
588+
{
589+
return $this->reflection->isReadOnly();
590+
}
591+
587592
public function isBackedEnum(): bool
588593
{
589594
if (!$this->reflection instanceof ReflectionEnum) {

tests/PHPStan/Rules/Properties/ReadOnlyPropertyRuleTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,14 @@ public function testRule(int $phpVersionId, array $errors): void
8989
$this->analyse([__DIR__ . '/data/read-only-property.php'], $errors);
9090
}
9191

92+
/**
93+
* @dataProvider dataRule
94+
* @param mixed[] $errors
95+
*/
96+
public function testRuleReadonlyClass(int $phpVersionId, array $errors): void
97+
{
98+
$this->phpVersionId = $phpVersionId;
99+
$this->analyse([__DIR__ . '/data/read-only-property-readonly-class.php'], $errors);
100+
}
101+
92102
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php // lint >= 8.2
2+
3+
namespace ReadOnlyPropertyReadonlyClass;
4+
5+
readonly class Foo
6+
{
7+
8+
private int $foo;
9+
private $bar;
10+
private int $baz = 0;
11+
12+
}
13+
14+
readonly final class ErrorResponse
15+
{
16+
public function __construct(public string $message = '')
17+
{
18+
}
19+
}
20+
21+
readonly class StaticReadonlyProperty
22+
{
23+
private static int $foo;
24+
}

0 commit comments

Comments
 (0)