mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +01:00
Emit issue if property type is missing
This commit is contained in:
parent
e8fa46149e
commit
c2f63c392e
@ -3,6 +3,7 @@
|
||||
namespace Psalm\Checker;
|
||||
|
||||
use Psalm\Issue\InvalidClass;
|
||||
use Psalm\Issue\MissingPropertyType;
|
||||
use Psalm\Issue\UndefinedClass;
|
||||
use Psalm\Issue\UndefinedTrait;
|
||||
use Psalm\Issue\UnimplementedInterfaceMethod;
|
||||
@ -102,42 +103,42 @@ abstract class ClassLikeChecker implements StatementsSource
|
||||
/**
|
||||
* A lookup table for public class properties
|
||||
*
|
||||
* @var array<string,array<string,Type\Union>>
|
||||
* @var array<string,array<string,Type\Union|false>>
|
||||
*/
|
||||
protected static $public_class_properties = [];
|
||||
|
||||
/**
|
||||
* A lookup table for protected class properties
|
||||
*
|
||||
* @var array<string,array<string,Type\Union>>
|
||||
* @var array<string,array<string,Type\Union|false>>
|
||||
*/
|
||||
protected static $protected_class_properties = [];
|
||||
|
||||
/**
|
||||
* A lookup table for protected class properties
|
||||
*
|
||||
* @var array<string,array<string,Type\Union>>
|
||||
* @var array<string,array<string,Type\Union|false>>
|
||||
*/
|
||||
protected static $private_class_properties = [];
|
||||
|
||||
/**
|
||||
* A lookup table for public static class properties
|
||||
*
|
||||
* @var array<string,array<string,Type\Union>>
|
||||
* @var array<string,array<string,Type\Union|false>>
|
||||
*/
|
||||
protected static $public_static_class_properties = [];
|
||||
|
||||
/**
|
||||
* A lookup table for protected static class properties
|
||||
*
|
||||
* @var array<string,array<string,Type\Union>>
|
||||
* @var array<string,array<string,Type\Union|false>>
|
||||
*/
|
||||
protected static $protected_static_class_properties = [];
|
||||
|
||||
/**
|
||||
* A lookup table for private static class properties
|
||||
*
|
||||
* @var array<string,array<string,Type\Union>>
|
||||
* @var array<string,array<string,Type\Union|false>>
|
||||
*/
|
||||
protected static $private_static_class_properties = [];
|
||||
|
||||
@ -379,23 +380,35 @@ abstract class ClassLikeChecker implements StatementsSource
|
||||
$comment = $stmt->getDocComment();
|
||||
$type_in_comment = null;
|
||||
|
||||
if ($comment && $config->use_docblock_types && count($stmt->props) === 1) {
|
||||
if ($comment && $config->use_docblock_types) {
|
||||
$type_in_comment = CommentChecker::getTypeFromComment((string) $comment, null, $this);
|
||||
}
|
||||
elseif (!$comment && $check_methods) {
|
||||
if (IssueBuffer::accepts(
|
||||
new MissingPropertyType(
|
||||
'Property ' . $this->absolute_class . '::$' . $stmt->props[0]->name . ' does not have a declared type',
|
||||
$this->file_name,
|
||||
$stmt->getLine()
|
||||
),
|
||||
$this->suppressed_issues
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
|
||||
$property_group_type = $type_in_comment ? $type_in_comment : null;
|
||||
|
||||
foreach ($stmt->props as $property) {
|
||||
if (!$property_group_type) {
|
||||
if (!$property->default) {
|
||||
$property_type = Type::getMixed();
|
||||
$property_type = false;
|
||||
}
|
||||
else {
|
||||
$property_type = StatementsChecker::getSimpleType($property->default) ?: Type::getMixed();
|
||||
}
|
||||
}
|
||||
else {
|
||||
$property_type = $property_group_type;
|
||||
$property_type = count($stmt->props) === 1 ? $property_group_type : clone $property_group_type;
|
||||
}
|
||||
|
||||
if ($stmt->isStatic()) {
|
||||
@ -456,7 +469,7 @@ abstract class ClassLikeChecker implements StatementsSource
|
||||
);
|
||||
|
||||
foreach ($all_instance_properties as $property_name => $property_type) {
|
||||
$class_context->vars_in_scope['$this->' . $property_name] = $property_type;
|
||||
$class_context->vars_in_scope['$this->' . $property_name] = $property_type ?: Type::getMixed();
|
||||
}
|
||||
|
||||
$all_static_properties = array_merge(
|
||||
@ -466,7 +479,7 @@ abstract class ClassLikeChecker implements StatementsSource
|
||||
);
|
||||
|
||||
foreach ($all_static_properties as $property_name => $property_type) {
|
||||
$class_context->vars_in_scope[$this->absolute_class . '::$' . $property_name] = $property_type;
|
||||
$class_context->vars_in_scope[$this->absolute_class . '::$' . $property_name] = $property_type ?: Type::getMixed();
|
||||
}
|
||||
|
||||
$config = Config::getInstance();
|
||||
@ -948,7 +961,7 @@ abstract class ClassLikeChecker implements StatementsSource
|
||||
/**
|
||||
* @param string $class_name
|
||||
* @param mixed $visibility
|
||||
* @return array<string,Type\Union>
|
||||
* @return array<string,Type\Union|false>
|
||||
*/
|
||||
public static function getInstancePropertiesForClass($class_name, $visibility)
|
||||
{
|
||||
|
@ -27,13 +27,14 @@ use Psalm\Issue\InvalidScope;
|
||||
use Psalm\Issue\InvalidStaticVariable;
|
||||
use Psalm\Issue\InvalidPropertyFetch;
|
||||
use Psalm\Issue\InvisibleProperty;
|
||||
use Psalm\Issue\MissingPropertyDeclaration;
|
||||
use Psalm\Issue\MissingPropertyType;
|
||||
use Psalm\Issue\MixedArgument;
|
||||
use Psalm\Issue\MixedArrayOffset;
|
||||
use Psalm\Issue\MixedMethodCall;
|
||||
use Psalm\Issue\MixedPropertyAssignment;
|
||||
use Psalm\Issue\MixedPropertyFetch;
|
||||
use Psalm\Issue\MixedStringOffsetAssignment;
|
||||
use Psalm\Issue\MissingPropertyDeclaration;
|
||||
use Psalm\Issue\NoInterfaceProperties;
|
||||
use Psalm\Issue\NullPropertyAssignment;
|
||||
use Psalm\Issue\NullPropertyFetch;
|
||||
@ -800,11 +801,29 @@ class ExpressionChecker
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($stmt->inferredType)) {
|
||||
$stmt->inferredType = Type::combineUnionTypes(clone $class_properties[$stmt->name], $stmt->inferredType);
|
||||
if ($class_properties[$stmt->name] === false) {
|
||||
if (IssueBuffer::accepts(
|
||||
new MissingPropertyType(
|
||||
'Property ' . $lhs_type_part->value . '::$' . $stmt->name . ' does not have a declared type',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
|
||||
$class_property_type = Type::getMixed();
|
||||
}
|
||||
else {
|
||||
$stmt->inferredType = $class_properties[$stmt->name];
|
||||
$class_property_type = clone $class_properties[$stmt->name];
|
||||
}
|
||||
|
||||
if (isset($stmt->inferredType)) {
|
||||
$stmt->inferredType = Type::combineUnionTypes($class_property_type, $stmt->inferredType);
|
||||
}
|
||||
else {
|
||||
$stmt->inferredType = $class_property_type;
|
||||
}
|
||||
}
|
||||
|
||||
@ -836,7 +855,7 @@ class ExpressionChecker
|
||||
|
||||
$class_properties = ClassLikeChecker::getInstancePropertiesForClass($context->self, \ReflectionProperty::IS_PRIVATE);
|
||||
|
||||
$class_property_types[] = clone $class_properties[$prop_name];
|
||||
$class_property_types[] = $class_properties[$prop_name] ? clone $class_properties[$prop_name] : Type::getMixed();
|
||||
|
||||
$var_id = '$this->' . $prop_name;
|
||||
}
|
||||
@ -1027,7 +1046,23 @@ class ExpressionChecker
|
||||
continue;
|
||||
}
|
||||
|
||||
$class_property_types[] = clone $class_properties[$prop_name];
|
||||
if ($class_properties[$prop_name] === false) {
|
||||
if (IssueBuffer::accepts(
|
||||
new MissingPropertyType(
|
||||
'Property ' . $lhs_type_part->value . '::$' . $stmt->name . ' does not have a declared type',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
|
||||
$class_property_types[] = Type::getMixed();
|
||||
}
|
||||
else {
|
||||
$class_property_types[] = clone $class_properties[$prop_name];
|
||||
}
|
||||
}
|
||||
|
||||
if (!$has_regular_setter) {
|
||||
@ -1178,14 +1213,28 @@ class ExpressionChecker
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$context->vars_in_scope[$var_id] = $assignment_type;
|
||||
|
||||
$class_property_type = clone $class_properties[$prop_name];
|
||||
if ($class_properties[$prop_name] === false) {
|
||||
if (IssueBuffer::accepts(
|
||||
new MissingPropertyType(
|
||||
'Property ' . $absolute_class . '::$' . $prop_name . ' does not have a declared type',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
|
||||
$class_property_type = Type::getMixed();
|
||||
}
|
||||
else {
|
||||
$class_property_type = clone $class_properties[$prop_name];
|
||||
}
|
||||
|
||||
if ($assignment_type->isMixed()) {
|
||||
return;
|
||||
|
@ -1060,7 +1060,7 @@ class TypeChecker
|
||||
return null;
|
||||
}
|
||||
|
||||
$class_property_type = clone $class_properties[$key_parts[$i]];
|
||||
$class_property_type = $class_properties[$key_parts[$i]] ? clone $class_properties[$key_parts[$i]] : Type::getMixed();
|
||||
}
|
||||
|
||||
if (!$new_base_type) {
|
||||
|
7
src/Psalm/Issue/MissingPropertyType.php
Normal file
7
src/Psalm/Issue/MissingPropertyType.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Psalm\Issue;
|
||||
|
||||
class MissingPropertyType extends CodeError
|
||||
{
|
||||
}
|
Loading…
Reference in New Issue
Block a user