Skip to content

Commit

Permalink
ReadOnlyClassRule
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Nov 21, 2023
1 parent 7042805 commit 02b2e3d
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 0 deletions.
1 change: 1 addition & 0 deletions conf/config.level0.neon
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ rules:
- PHPStan\Rules\Classes\LocalTypeTraitAliasesRule
- PHPStan\Rules\Classes\NewStaticRule
- PHPStan\Rules\Classes\NonClassAttributeClassRule
- PHPStan\Rules\Classes\ReadOnlyClassRule
- PHPStan\Rules\Classes\TraitAttributeClassRule
- PHPStan\Rules\Constants\DynamicClassConstantFetchRule
- PHPStan\Rules\Constants\FinalConstantRule
Expand Down
10 changes: 10 additions & 0 deletions src/Php/PhpVersion.php
Original file line number Diff line number Diff line change
Expand Up @@ -262,4 +262,14 @@ public function supportsDynamicClassConstantFetch(): bool
return $this->versionId >= 80300;
}

public function supportsReadOnlyClasses(): bool
{
return $this->versionId >= 80200;
}

public function supportsReadOnlyAnonymousClasses(): bool
{
return $this->versionId >= 80300;
}

}
58 changes: 58 additions & 0 deletions src/Rules/Classes/ReadOnlyClassRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Classes;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Node\InClassNode;
use PHPStan\Php\PhpVersion;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;

/**
* @implements Rule<InClassNode>
*/
class ReadOnlyClassRule implements Rule
{

public function __construct(private PhpVersion $phpVersion)
{
}

public function getNodeType(): string
{
return InClassNode::class;
}

public function processNode(Node $node, Scope $scope): array
{
$classReflection = $node->getClassReflection();
if (!$classReflection->isReadOnly()) {
return [];
}
if ($classReflection->isAnonymous()) {
if ($this->phpVersion->supportsReadOnlyAnonymousClasses()) {
return [];
}

return [
RuleErrorBuilder::message('Anonymous readonly classes are supported only on PHP 8.3 and later.')
->identifier('classConstant.nativeTypeNotSupported')
->nonIgnorable()
->build(),
];
}

if ($this->phpVersion->supportsReadOnlyClasses()) {
return [];
}

return [
RuleErrorBuilder::message('Readonly classes are supported only on PHP 8.2 and later.')
->identifier('classConstant.nativeTypeNotSupported')
->nonIgnorable()
->build(),
];
}

}
42 changes: 42 additions & 0 deletions tests/PHPStan/Rules/Classes/ReadOnlyClassRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Classes;

use PHPStan\Php\PhpVersion;
use PHPStan\Rules\Rule as TRule;
use PHPStan\Testing\RuleTestCase;
use const PHP_VERSION_ID;

/**
* @extends RuleTestCase<ReadOnlyClassRule>
*/
class ReadOnlyClassRuleTest extends RuleTestCase
{

protected function getRule(): TRule
{
return new ReadOnlyClassRule(self::getContainer()->getByType(PhpVersion::class));
}

public function testRule(): void
{
$errors = [];
if (PHP_VERSION_ID < 80200) {
$errors = [
[
'Readonly classes are supported only on PHP 8.2 and later.',
5,
],
];
} elseif (PHP_VERSION_ID < 80300) {
$errors = [
[
'Anonymous readonly classes are supported only on PHP 8.3 and later.',
15,
],
];
}
$this->analyse([__DIR__ . '/data/readonly-class.php'], $errors);
}

}
20 changes: 20 additions & 0 deletions tests/PHPStan/Rules/Classes/data/readonly-class.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php // lint >= 8.3

namespace ReadonlyClass;

readonly class Foo
{

}

class Bar
{

public function doFoo(): void
{
$c = new readonly class () {

};
}

}

0 comments on commit 02b2e3d

Please sign in to comment.