Skip to content

Commit 02b2e3d

Browse files
committed
ReadOnlyClassRule
1 parent 7042805 commit 02b2e3d

File tree

5 files changed

+131
-0
lines changed

5 files changed

+131
-0
lines changed

conf/config.level0.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ rules:
5656
- PHPStan\Rules\Classes\LocalTypeTraitAliasesRule
5757
- PHPStan\Rules\Classes\NewStaticRule
5858
- PHPStan\Rules\Classes\NonClassAttributeClassRule
59+
- PHPStan\Rules\Classes\ReadOnlyClassRule
5960
- PHPStan\Rules\Classes\TraitAttributeClassRule
6061
- PHPStan\Rules\Constants\DynamicClassConstantFetchRule
6162
- PHPStan\Rules\Constants\FinalConstantRule

src/Php/PhpVersion.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,4 +262,14 @@ public function supportsDynamicClassConstantFetch(): bool
262262
return $this->versionId >= 80300;
263263
}
264264

265+
public function supportsReadOnlyClasses(): bool
266+
{
267+
return $this->versionId >= 80200;
268+
}
269+
270+
public function supportsReadOnlyAnonymousClasses(): bool
271+
{
272+
return $this->versionId >= 80300;
273+
}
274+
265275
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Classes;
4+
5+
use PhpParser\Node;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Node\InClassNode;
8+
use PHPStan\Php\PhpVersion;
9+
use PHPStan\Rules\Rule;
10+
use PHPStan\Rules\RuleErrorBuilder;
11+
12+
/**
13+
* @implements Rule<InClassNode>
14+
*/
15+
class ReadOnlyClassRule implements Rule
16+
{
17+
18+
public function __construct(private PhpVersion $phpVersion)
19+
{
20+
}
21+
22+
public function getNodeType(): string
23+
{
24+
return InClassNode::class;
25+
}
26+
27+
public function processNode(Node $node, Scope $scope): array
28+
{
29+
$classReflection = $node->getClassReflection();
30+
if (!$classReflection->isReadOnly()) {
31+
return [];
32+
}
33+
if ($classReflection->isAnonymous()) {
34+
if ($this->phpVersion->supportsReadOnlyAnonymousClasses()) {
35+
return [];
36+
}
37+
38+
return [
39+
RuleErrorBuilder::message('Anonymous readonly classes are supported only on PHP 8.3 and later.')
40+
->identifier('classConstant.nativeTypeNotSupported')
41+
->nonIgnorable()
42+
->build(),
43+
];
44+
}
45+
46+
if ($this->phpVersion->supportsReadOnlyClasses()) {
47+
return [];
48+
}
49+
50+
return [
51+
RuleErrorBuilder::message('Readonly classes are supported only on PHP 8.2 and later.')
52+
->identifier('classConstant.nativeTypeNotSupported')
53+
->nonIgnorable()
54+
->build(),
55+
];
56+
}
57+
58+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Classes;
4+
5+
use PHPStan\Php\PhpVersion;
6+
use PHPStan\Rules\Rule as TRule;
7+
use PHPStan\Testing\RuleTestCase;
8+
use const PHP_VERSION_ID;
9+
10+
/**
11+
* @extends RuleTestCase<ReadOnlyClassRule>
12+
*/
13+
class ReadOnlyClassRuleTest extends RuleTestCase
14+
{
15+
16+
protected function getRule(): TRule
17+
{
18+
return new ReadOnlyClassRule(self::getContainer()->getByType(PhpVersion::class));
19+
}
20+
21+
public function testRule(): void
22+
{
23+
$errors = [];
24+
if (PHP_VERSION_ID < 80200) {
25+
$errors = [
26+
[
27+
'Readonly classes are supported only on PHP 8.2 and later.',
28+
5,
29+
],
30+
];
31+
} elseif (PHP_VERSION_ID < 80300) {
32+
$errors = [
33+
[
34+
'Anonymous readonly classes are supported only on PHP 8.3 and later.',
35+
15,
36+
],
37+
];
38+
}
39+
$this->analyse([__DIR__ . '/data/readonly-class.php'], $errors);
40+
}
41+
42+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php // lint >= 8.3
2+
3+
namespace ReadonlyClass;
4+
5+
readonly class Foo
6+
{
7+
8+
}
9+
10+
class Bar
11+
{
12+
13+
public function doFoo(): void
14+
{
15+
$c = new readonly class () {
16+
17+
};
18+
}
19+
20+
}

0 commit comments

Comments
 (0)