1
0
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:
Matthew Brown 2019-08-11 16:01:37 -04:00
parent 5722512180
commit 7ed30cd5b0
6 changed files with 141 additions and 4 deletions

View File

@ -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;
}

View File

@ -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(

View File

@ -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;
}

View File

@ -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) {

View File

@ -69,6 +69,11 @@ class PropertyStorage
*/
public $internal = false;
/**
* @var bool
*/
public $readonly = false;
/**
* @var null|string
*/

View File

@ -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',
],
];
}
}