Skip to content

Commit 502596f

Browse files
committed
ReadOnlyPropertyRule
1 parent 0f4885a commit 502596f

File tree

5 files changed

+152
-0
lines changed

5 files changed

+152
-0
lines changed

conf/config.level0.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ rules:
7575
- PHPStan\Rules\Properties\AccessPropertiesInAssignRule
7676
- PHPStan\Rules\Properties\AccessStaticPropertiesInAssignRule
7777
- PHPStan\Rules\Properties\PropertyAttributesRule
78+
- PHPStan\Rules\Properties\ReadOnlyPropertyRule
7879
- PHPStan\Rules\Variables\UnsetRule
7980

8081
services:

src/Php/PhpVersion.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,4 +137,9 @@ public function supportsFinalConstants(): bool
137137
return $this->versionId >= 80100;
138138
}
139139

140+
public function supportsReadOnlyProperties(): bool
141+
{
142+
return $this->versionId >= 80100;
143+
}
144+
140145
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Properties;
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+
class ReadOnlyPropertyRule implements Rule
16+
{
17+
18+
private PhpVersion $phpVersion;
19+
20+
public function __construct(PhpVersion $phpVersion)
21+
{
22+
$this->phpVersion = $phpVersion;
23+
}
24+
25+
public function getNodeType(): string
26+
{
27+
return ClassPropertyNode::class;
28+
}
29+
30+
public function processNode(Node $node, Scope $scope): array
31+
{
32+
if (!$node->isReadOnly()) {
33+
return [];
34+
}
35+
36+
$errors = [];
37+
if (!$this->phpVersion->supportsReadOnlyProperties()) {
38+
$errors[] = RuleErrorBuilder::message('Readonly properties are supported only on PHP 8.1 and later.')->nonIgnorable()->build();
39+
}
40+
41+
if ($node->getNativeType() === null) {
42+
$errors[] = RuleErrorBuilder::message('Readonly property must have a native type.')->nonIgnorable()->build();
43+
}
44+
45+
if ($node->getDefault() !== null) {
46+
$errors[] = RuleErrorBuilder::message('Readonly property cannot have a default value.')->nonIgnorable()->build();
47+
}
48+
49+
return $errors;
50+
}
51+
52+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Properties;
4+
5+
use PHPStan\Php\PhpVersion;
6+
use PHPStan\Rules\Rule;
7+
use PHPStan\Testing\RuleTestCase;
8+
9+
/**
10+
* @extends RuleTestCase<ReadOnlyPropertyRule>
11+
*/
12+
class ReadOnlyPropertyRuleTest extends RuleTestCase
13+
{
14+
15+
/** @var int */
16+
private $phpVersionId;
17+
18+
protected function getRule(): Rule
19+
{
20+
return new ReadOnlyPropertyRule(new PhpVersion($this->phpVersionId));
21+
}
22+
23+
public function dataRule(): array
24+
{
25+
return [
26+
[
27+
80000,
28+
[
29+
[
30+
'Readonly properties are supported only on PHP 8.1 and later.',
31+
8,
32+
],
33+
[
34+
'Readonly properties are supported only on PHP 8.1 and later.',
35+
9,
36+
],
37+
[
38+
'Readonly property must have a native type.',
39+
9,
40+
],
41+
[
42+
'Readonly properties are supported only on PHP 8.1 and later.',
43+
10,
44+
],
45+
[
46+
'Readonly property cannot have a default value.',
47+
10,
48+
],
49+
],
50+
],
51+
[
52+
80100,
53+
[
54+
[
55+
'Readonly property must have a native type.',
56+
9,
57+
],
58+
[
59+
'Readonly property cannot have a default value.',
60+
10,
61+
],
62+
],
63+
],
64+
];
65+
}
66+
67+
/**
68+
* @dataProvider dataRule
69+
* @param int $phpVersionId
70+
* @param mixed[] $errors
71+
*/
72+
public function testRule(int $phpVersionId, array $errors): void
73+
{
74+
if (!self::$useStaticReflectionProvider) {
75+
$this->markTestSkipped('Test requires static reflection.');
76+
}
77+
78+
$this->phpVersionId = $phpVersionId;
79+
$this->analyse([__DIR__ . '/data/read-only-property.php'], $errors);
80+
}
81+
82+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php // lint >= 8.1
2+
3+
namespace ReadOnlyProperty;
4+
5+
class Foo
6+
{
7+
8+
private readonly int $foo;
9+
private readonly $bar;
10+
private readonly int $baz = 0;
11+
12+
}

0 commit comments

Comments
 (0)