diff --git a/conf/config.level0.neon b/conf/config.level0.neon index cb91d6c769..c9361622fa 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -50,6 +50,7 @@ rules: - PHPStan\Rules\Classes\NewStaticRule - PHPStan\Rules\Classes\NonClassAttributeClassRule - PHPStan\Rules\Classes\TraitAttributeClassRule + - PHPStan\Rules\Constants\FinalConstantRule - PHPStan\Rules\Exceptions\ThrowExpressionRule - PHPStan\Rules\Functions\ArrowFunctionAttributesRule - PHPStan\Rules\Functions\ArrowFunctionReturnNullsafeByRefRule diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index 688fae28ae..6bf3aa7589 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -132,4 +132,9 @@ public function isInterfaceConstantImplicitlyFinal(): bool return $this->versionId < 80100; } + public function supportsFinalConstants(): bool + { + return $this->versionId >= 80100; + } + } diff --git a/src/Rules/Constants/FinalConstantRule.php b/src/Rules/Constants/FinalConstantRule.php new file mode 100644 index 0000000000..ffbcc7d491 --- /dev/null +++ b/src/Rules/Constants/FinalConstantRule.php @@ -0,0 +1,43 @@ + */ +class FinalConstantRule implements Rule +{ + + private PhpVersion $phpVersion; + + public function __construct(PhpVersion $phpVersion) + { + $this->phpVersion = $phpVersion; + } + + public function getNodeType(): string + { + return ClassConst::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->isFinal()) { + return []; + } + + if ($this->phpVersion->supportsFinalConstants()) { + return []; + } + + return [ + RuleErrorBuilder::message('Final class constants are supported only on PHP 8.1 and later.')->nonIgnorable()->build(), + ]; + } + +} diff --git a/tests/PHPStan/Rules/Constants/FinalConstantRuleTest.php b/tests/PHPStan/Rules/Constants/FinalConstantRuleTest.php new file mode 100644 index 0000000000..df7f57f92a --- /dev/null +++ b/tests/PHPStan/Rules/Constants/FinalConstantRuleTest.php @@ -0,0 +1,57 @@ + + */ +class FinalConstantRuleTest extends RuleTestCase +{ + + /** @var int */ + private $phpVersionId; + + protected function getRule(): Rule + { + return new FinalConstantRule(new PhpVersion($this->phpVersionId)); + } + + public function dataRule(): array + { + return [ + [ + 80000, + [ + [ + 'Final class constants are supported only on PHP 8.1 and later.', + 9, + ], + ], + ], + [ + 80100, + [], + ], + ]; + } + + /** + * @dataProvider dataRule + * @param int $phpVersionId + * @param mixed[] $errors + */ + public function testRule(int $phpVersionId, array $errors): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection.'); + } + + $this->phpVersionId = $phpVersionId; + $this->analyse([__DIR__ . '/data/final-constant.php'], $errors); + } + +} diff --git a/tests/PHPStan/Rules/Constants/data/final-constant.php b/tests/PHPStan/Rules/Constants/data/final-constant.php new file mode 100644 index 0000000000..9cb37a0cb8 --- /dev/null +++ b/tests/PHPStan/Rules/Constants/data/final-constant.php @@ -0,0 +1,11 @@ +