1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-27 04:45:20 +01:00

Merge pull request #9444 from weirdan/readonly-classes

This commit is contained in:
Bruce Weirdan 2023-03-03 04:14:04 -04:00 committed by GitHub
commit 864b25150e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 84 additions and 2 deletions

View File

@ -33,7 +33,7 @@
"felixfbecker/language-server-protocol": "^1.5.2",
"fidry/cpu-core-counter": "^0.4.1 || ^0.5.1",
"netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0",
"nikic/php-parser": "^4.13",
"nikic/php-parser": "^4.14",
"sebastian/diff": "^4.0 || ^5.0",
"spatie/array-to-xml": "^2.17.0 || ^3.0",
"symfony/console": "^4.1.6 || ^5.0 || ^6.0",

View File

@ -2360,6 +2360,18 @@ class ClassAnalyzer extends ClassLikeAnalyzer
);
}
if ($parent_class_storage->readonly && !$storage->readonly) {
IssueBuffer::maybeAdd(
new InvalidExtendClass(
'Non-readonly class ' . $fq_class_name . ' may not inherit from '
. 'readonly class ' . $parent_fq_class_name,
$code_location,
$fq_class_name,
),
$storage->suppressed_issues + $this->getSuppressedIssues(),
);
}
if ($parent_class_storage->deprecated) {
IssueBuffer::maybeAdd(
new DeprecatedClass(

View File

@ -42,11 +42,13 @@ use Psalm\Issue\ConstantDeclarationInTrait;
use Psalm\Issue\DuplicateClass;
use Psalm\Issue\DuplicateConstant;
use Psalm\Issue\DuplicateEnumCase;
use Psalm\Issue\InvalidAttribute;
use Psalm\Issue\InvalidDocblock;
use Psalm\Issue\InvalidEnumBackingType;
use Psalm\Issue\InvalidEnumCaseValue;
use Psalm\Issue\InvalidTypeImport;
use Psalm\Issue\MissingDocblockType;
use Psalm\Issue\MissingPropertyType;
use Psalm\Issue\ParseError;
use Psalm\IssueBuffer;
use Psalm\Storage\AttributeStorage;
@ -256,6 +258,7 @@ class ClassLikeNodeScanner
if ($node instanceof PhpParser\Node\Stmt\Class_) {
$storage->abstract = $node->isAbstract();
$storage->final = $node->isFinal();
$storage->readonly = $node->isReadonly();
$this->codebase->classlikes->addFullyQualifiedClassName($fq_classlike_name, $this->file_path);
@ -765,6 +768,14 @@ class ClassLikeNodeScanner
$storage->external_mutation_free = true;
}
if ($attribute->fq_class_name === 'AllowDynamicProperties' && $storage->readonly) {
IssueBuffer::maybeAdd(new InvalidAttribute(
'Readonly classes cannot have dynamic properties',
new CodeLocation($this->file_scanner, $attr, null, true),
));
continue;
}
$storage->attributes[] = $attribute;
}
}
@ -1586,10 +1597,22 @@ class ClassLikeNodeScanner
if (count($property_storage->internal) === 0 && $var_comment && $var_comment->internal) {
$property_storage->internal = [NamespaceAnalyzer::getNameSpaceRoot($fq_classlike_name)];
}
$property_storage->readonly = $stmt->isReadonly() || ($var_comment && $var_comment->readonly);
$property_storage->readonly = $storage->readonly
|| $stmt->isReadonly()
|| ($var_comment && $var_comment->readonly);
$property_storage->allow_private_mutation = $var_comment ? $var_comment->allow_private_mutation : false;
$property_storage->description = $var_comment ? $var_comment->description : null;
if (!$signature_type && $storage->readonly) {
IssueBuffer::maybeAdd(
new MissingPropertyType(
'Properties of readonly classes must have a type',
new CodeLocation($this->file_scanner, $stmt, null, true),
$fq_classlike_name . '::$' . $property->name->name,
),
);
}
if (!$signature_type && !$doc_var_group_type) {
if ($property->default) {
$property_storage->suggested_type = SimpleTypeInferer::infer(

View File

@ -470,6 +470,8 @@ final class ClassLikeStorage implements HasAttributesInterface
public bool $public_api = false;
public bool $readonly = false;
public function __construct(string $name)
{
$this->name = $name;

View File

@ -1248,6 +1248,51 @@ class ClassTest extends TestCase
PHP,
'error_message' => 'PrivateFinalMethod',
],
'readonlyClass' => [
'code' => <<<'PHP'
<?php
readonly class Foo {
public int $a = 22;
}
$foo = new Foo;
$foo->a = 33;
PHP,
'error_message' => 'InaccessibleProperty',
'ignored_issues' => [],
'php_version' => '8.2',
],
'readonlyClassRequiresTypedProperties' => [
'code' => <<<'PHP'
<?php
readonly class Foo {
/** @var int */
public $a = 22;
}
PHP,
'error_message' => 'MissingPropertyType',
'ignored_issues' => [],
'php_version' => '8.2',
],
'readonlyClassCannotHaveDynamicProperties' => [
'code' => <<<'PHP'
<?php
#[AllowDynamicProperties]
readonly class Foo {}
PHP,
'error_message' => 'InvalidAttribute',
'ignored_issues' => [],
'php_version' => '8.2',
],
'readonlyClassesCannotBeExtendedByNonReadonlyOnes' => [
'code' => <<<'PHP'
<?php
readonly class Foo {}
class Bar extends Foo {}
PHP,
'error_message' => 'InvalidExtendClass',
'ignored_issues' => [],
'php_version' => '8.2',
],
];
}
}