mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +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();
|
$comment_text = $comment->getText();
|
||||||
|
|
||||||
if (!isset($parsed_docblock['specials']['var']) && !isset($parsed_docblock['specials']['psalm-var'])) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$var_line_number = $comment->getLine();
|
$var_line_number = $comment->getLine();
|
||||||
|
|
||||||
if ($parsed_docblock) {
|
if ($parsed_docblock) {
|
||||||
@ -184,6 +180,7 @@ class CommentAnalyzer
|
|||||||
$var_comment->type_end = $type_end;
|
$var_comment->type_end = $type_end;
|
||||||
$var_comment->deprecated = isset($parsed_docblock['specials']['deprecated']);
|
$var_comment->deprecated = isset($parsed_docblock['specials']['deprecated']);
|
||||||
$var_comment->internal = isset($parsed_docblock['specials']['internal']);
|
$var_comment->internal = isset($parsed_docblock['specials']['internal']);
|
||||||
|
$var_comment->readonly = isset($parsed_docblock['specials']['readonly']);
|
||||||
if (isset($parsed_docblock['specials']['psalm-internal'])) {
|
if (isset($parsed_docblock['specials']['psalm-internal'])) {
|
||||||
$psalm_internal = reset($parsed_docblock['specials']['psalm-internal']);
|
$psalm_internal = reset($parsed_docblock['specials']['psalm-internal']);
|
||||||
if ($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;
|
return $var_comments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ use Psalm\CodeLocation;
|
|||||||
use Psalm\Context;
|
use Psalm\Context;
|
||||||
use Psalm\Issue\DeprecatedProperty;
|
use Psalm\Issue\DeprecatedProperty;
|
||||||
use Psalm\Issue\ImplicitToStringCast;
|
use Psalm\Issue\ImplicitToStringCast;
|
||||||
|
use Psalm\Issue\InaccessibleProperty;
|
||||||
use Psalm\Issue\InternalProperty;
|
use Psalm\Issue\InternalProperty;
|
||||||
use Psalm\Issue\InvalidPropertyAssignment;
|
use Psalm\Issue\InvalidPropertyAssignment;
|
||||||
use Psalm\Issue\InvalidPropertyAssignmentValue;
|
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(
|
$class_property_type = $codebase->properties->getPropertyType(
|
||||||
|
@ -58,4 +58,11 @@ class VarDocblockComment
|
|||||||
* @var null|string
|
* @var null|string
|
||||||
*/
|
*/
|
||||||
public $psalm_internal = null;
|
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->deprecated = $var_comment ? $var_comment->deprecated : false;
|
||||||
$property_storage->internal = $var_comment ? $var_comment->internal : false;
|
$property_storage->internal = $var_comment ? $var_comment->internal : false;
|
||||||
$property_storage->psalm_internal = $var_comment ? $var_comment->psalm_internal : null;
|
$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 (!$signature_type && !$doc_var_group_type) {
|
||||||
if ($property->default) {
|
if ($property->default) {
|
||||||
|
@ -69,6 +69,11 @@ class PropertyStorage
|
|||||||
*/
|
*/
|
||||||
public $internal = false;
|
public $internal = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public $readonly = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var null|string
|
* @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'
|
'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