1
0
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:
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(); $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;
} }

View File

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

View File

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

View File

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

View File

@ -69,6 +69,11 @@ class PropertyStorage
*/ */
public $internal = false; public $internal = false;
/**
* @var bool
*/
public $readonly = false;
/** /**
* @var null|string * @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' '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',
],
]; ];
} }
} }