mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
Add support for @readonly annotation
This commit is contained in:
parent
5722512180
commit
7ed30cd5b0
@ -96,10 +96,6 @@ class CommentAnalyzer
|
||||
|
||||
$comment_text = $comment->getText();
|
||||
|
||||
if (!isset($parsed_docblock['specials']['var']) && !isset($parsed_docblock['specials']['psalm-var'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$var_line_number = $comment->getLine();
|
||||
|
||||
if ($parsed_docblock) {
|
||||
@ -184,6 +180,7 @@ class CommentAnalyzer
|
||||
$var_comment->type_end = $type_end;
|
||||
$var_comment->deprecated = isset($parsed_docblock['specials']['deprecated']);
|
||||
$var_comment->internal = isset($parsed_docblock['specials']['internal']);
|
||||
$var_comment->readonly = isset($parsed_docblock['specials']['readonly']);
|
||||
if (isset($parsed_docblock['specials']['psalm-internal'])) {
|
||||
$psalm_internal = reset($parsed_docblock['specials']['psalm-internal']);
|
||||
if ($psalm_internal) {
|
||||
@ -202,6 +199,19 @@ class CommentAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
if (!$var_comments
|
||||
&& (isset($parsed_docblock['specials']['deprecated'])
|
||||
|| isset($parsed_docblock['specials']['internal'])
|
||||
|| isset($parsed_docblock['specials']['readonly']))
|
||||
) {
|
||||
$var_comment = new VarDocblockComment();
|
||||
$var_comment->deprecated = isset($parsed_docblock['specials']['deprecated']);
|
||||
$var_comment->internal = isset($parsed_docblock['specials']['internal']);
|
||||
$var_comment->readonly = isset($parsed_docblock['specials']['readonly']);
|
||||
|
||||
$var_comments[] = $var_comment;
|
||||
}
|
||||
|
||||
return $var_comments;
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@ use Psalm\CodeLocation;
|
||||
use Psalm\Context;
|
||||
use Psalm\Issue\DeprecatedProperty;
|
||||
use Psalm\Issue\ImplicitToStringCast;
|
||||
use Psalm\Issue\InaccessibleProperty;
|
||||
use Psalm\Issue\InternalProperty;
|
||||
use Psalm\Issue\InvalidPropertyAssignment;
|
||||
use Psalm\Issue\InvalidPropertyAssignmentValue;
|
||||
@ -614,6 +615,32 @@ class PropertyAssignmentAnalyzer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($property_storage->readonly) {
|
||||
$appearing_property_class = $codebase->properties->getAppearingClassForProperty(
|
||||
$property_id,
|
||||
true
|
||||
);
|
||||
|
||||
if ($appearing_property_class
|
||||
&& !($context->self
|
||||
&& ($appearing_property_class === $context->self
|
||||
|| $codebase->classExtends($context->self, $appearing_property_class))
|
||||
&& (!$context->calling_method_id
|
||||
|| \strpos($context->calling_method_id, '::__construct'))
|
||||
)
|
||||
) {
|
||||
if (IssueBuffer::accepts(
|
||||
new InaccessibleProperty(
|
||||
$property_id . ' is marked readonly',
|
||||
new CodeLocation($statements_analyzer->getSource(), $stmt)
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$class_property_type = $codebase->properties->getPropertyType(
|
||||
|
@ -58,4 +58,11 @@ class VarDocblockComment
|
||||
* @var null|string
|
||||
*/
|
||||
public $psalm_internal = null;
|
||||
|
||||
/**
|
||||
* Whether or not the property is readonly
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $readonly = false;
|
||||
}
|
||||
|
@ -2967,6 +2967,7 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
|
||||
$property_storage->deprecated = $var_comment ? $var_comment->deprecated : false;
|
||||
$property_storage->internal = $var_comment ? $var_comment->internal : false;
|
||||
$property_storage->psalm_internal = $var_comment ? $var_comment->psalm_internal : null;
|
||||
$property_storage->readonly = $var_comment ? $var_comment->readonly : false;
|
||||
|
||||
if (!$signature_type && !$doc_var_group_type) {
|
||||
if ($property->default) {
|
||||
|
@ -69,6 +69,11 @@ class PropertyStorage
|
||||
*/
|
||||
public $internal = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $readonly = false;
|
||||
|
||||
/**
|
||||
* @var null|string
|
||||
*/
|
||||
|
@ -1661,6 +1661,38 @@ class PropertyTypeTest extends TestCase
|
||||
}
|
||||
}',
|
||||
],
|
||||
'readonlyPropertySetInConstructor' => [
|
||||
'<?php
|
||||
class A {
|
||||
/**
|
||||
* @readonly
|
||||
*/
|
||||
public string $bar;
|
||||
|
||||
public function __construct() {
|
||||
$this->bar = "hello";
|
||||
}
|
||||
}
|
||||
|
||||
echo (new A)->bar;'
|
||||
],
|
||||
'readonlyPropertySetChildClass' => [
|
||||
'<?php
|
||||
abstract class A {
|
||||
/**
|
||||
* @readonly
|
||||
*/
|
||||
public string $bar;
|
||||
}
|
||||
|
||||
class B extends A {
|
||||
public function __construct() {
|
||||
$this->bar = "hello";
|
||||
}
|
||||
}
|
||||
|
||||
echo (new B)->bar;'
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@ -2592,6 +2624,61 @@ class PropertyTypeTest extends TestCase
|
||||
}',
|
||||
'error_message' => 'InvalidPropertyAssignmentValue'
|
||||
],
|
||||
'readonlyPropertySetInConstructorAndAlsoAnotherMethodInsideClass' => [
|
||||
'<?php
|
||||
class A {
|
||||
/**
|
||||
* @readonly
|
||||
*/
|
||||
public string $bar;
|
||||
|
||||
public function __construct() {
|
||||
$this->bar = "hello";
|
||||
}
|
||||
|
||||
public function setBar() : void {
|
||||
$this->bar = "goodbye";
|
||||
}
|
||||
}',
|
||||
'error_message' => 'InaccessibleProperty',
|
||||
],
|
||||
'readonlyPropertySetInConstructorAndAlsoAnotherMethodInSublass' => [
|
||||
'<?php
|
||||
class A {
|
||||
/**
|
||||
* @readonly
|
||||
*/
|
||||
public string $bar;
|
||||
|
||||
public function __construct() {
|
||||
$this->bar = "hello";
|
||||
}
|
||||
}
|
||||
|
||||
class B extends A {
|
||||
public function setBar() : void {
|
||||
$this->bar = "hello";
|
||||
}
|
||||
}',
|
||||
'error_message' => 'InaccessibleProperty',
|
||||
],
|
||||
'readonlyPropertySetInConstructorAndAlsoOutsideClass' => [
|
||||
'<?php
|
||||
class A {
|
||||
/**
|
||||
* @readonly
|
||||
*/
|
||||
public string $bar;
|
||||
|
||||
public function __construct() {
|
||||
$this->bar = "hello";
|
||||
}
|
||||
}
|
||||
|
||||
$a = new A();
|
||||
$a->bar = "goodbye";',
|
||||
'error_message' => 'InaccessibleProperty',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user