Skip to content

Commit

Permalink
ReadOnlyPropertyRule
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Aug 18, 2021
1 parent 0f4885a commit 502596f
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 0 deletions.
1 change: 1 addition & 0 deletions conf/config.level0.neon
Expand Up @@ -75,6 +75,7 @@ rules:
- PHPStan\Rules\Properties\AccessPropertiesInAssignRule
- PHPStan\Rules\Properties\AccessStaticPropertiesInAssignRule
- PHPStan\Rules\Properties\PropertyAttributesRule
- PHPStan\Rules\Properties\ReadOnlyPropertyRule
- PHPStan\Rules\Variables\UnsetRule

services:
Expand Down
5 changes: 5 additions & 0 deletions src/Php/PhpVersion.php
Expand Up @@ -137,4 +137,9 @@ public function supportsFinalConstants(): bool
return $this->versionId >= 80100;
}

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

}
52 changes: 52 additions & 0 deletions src/Rules/Properties/ReadOnlyPropertyRule.php
@@ -0,0 +1,52 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Properties;

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

/**
* @implements Rule<ClassPropertyNode>
*/
class ReadOnlyPropertyRule implements Rule
{

private PhpVersion $phpVersion;

public function __construct(PhpVersion $phpVersion)
{
$this->phpVersion = $phpVersion;
}

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

public function processNode(Node $node, Scope $scope): array
{
if (!$node->isReadOnly()) {
return [];
}

$errors = [];
if (!$this->phpVersion->supportsReadOnlyProperties()) {
$errors[] = RuleErrorBuilder::message('Readonly properties are supported only on PHP 8.1 and later.')->nonIgnorable()->build();
}

if ($node->getNativeType() === null) {
$errors[] = RuleErrorBuilder::message('Readonly property must have a native type.')->nonIgnorable()->build();
}

if ($node->getDefault() !== null) {
$errors[] = RuleErrorBuilder::message('Readonly property cannot have a default value.')->nonIgnorable()->build();
}

return $errors;
}

}
82 changes: 82 additions & 0 deletions tests/PHPStan/Rules/Properties/ReadOnlyPropertyRuleTest.php
@@ -0,0 +1,82 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Properties;

use PHPStan\Php\PhpVersion;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;

/**
* @extends RuleTestCase<ReadOnlyPropertyRule>
*/
class ReadOnlyPropertyRuleTest extends RuleTestCase
{

/** @var int */
private $phpVersionId;

protected function getRule(): Rule
{
return new ReadOnlyPropertyRule(new PhpVersion($this->phpVersionId));
}

public function dataRule(): array
{
return [
[
80000,
[
[
'Readonly properties are supported only on PHP 8.1 and later.',
8,
],
[
'Readonly properties are supported only on PHP 8.1 and later.',
9,
],
[
'Readonly property must have a native type.',
9,
],
[
'Readonly properties are supported only on PHP 8.1 and later.',
10,
],
[
'Readonly property cannot have a default value.',
10,
],
],
],
[
80100,
[
[
'Readonly property must have a native type.',
9,
],
[
'Readonly property cannot have a default value.',
10,
],
],
],
];
}

/**
* @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/read-only-property.php'], $errors);
}

}
12 changes: 12 additions & 0 deletions tests/PHPStan/Rules/Properties/data/read-only-property.php
@@ -0,0 +1,12 @@
<?php // lint >= 8.1

namespace ReadOnlyProperty;

class Foo
{

private readonly int $foo;
private readonly $bar;
private readonly int $baz = 0;

}

0 comments on commit 502596f

Please sign in to comment.