1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-21 21:31:13 +01:00

Fix #36 - emit issues on deprecated properties

This commit is contained in:
Matthew Brown 2017-05-25 01:32:34 -04:00
parent 0fdf281896
commit 688a72c794
13 changed files with 170 additions and 61 deletions

View File

@ -88,6 +88,7 @@
<xs:element name="ConflictingReferenceConstraint" type="IssueHandlerType" minOccurs="0" />
<xs:element name="DeprecatedClass" type="IssueHandlerType" minOccurs="0" />
<xs:element name="DeprecatedMethod" type="IssueHandlerType" minOccurs="0" />
<xs:element name="DeprecatedProperty" type="IssueHandlerType" minOccurs="0" />
<xs:element name="DuplicateParam" type="IssueHandlerType" minOccurs="0" />
<xs:element name="DuplicateClass" type="IssueHandlerType" minOccurs="0" />
<xs:element name="FailedTypeResolution" type="IssueHandlerType" minOccurs="0" />

View File

@ -24,6 +24,7 @@
<MisplacedRequiredParam errorLevel="suppress" />
<PossiblyNullOperand errorLevel="suppress" />
<MissingConstructor errorLevel="suppress" />
<DeprecatedProperty errorLevel="suppress" />
<PossiblyUnusedVariable>
<errorLevel type="suppress">

View File

@ -1044,18 +1044,17 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
Config $config
) {
$comment = $stmt->getDocComment();
$type_in_comment = null;
$var_comment = null;
$property_type_line_number = null;
$storage = self::$storage[strtolower($this->fq_class_name)];
if ($comment && $comment->getText() && $config->use_docblock_types) {
try {
$property_type_line_number = $comment->getLine();
$type_in_comment = CommentChecker::getTypeFromComment(
$var_comment = CommentChecker::getTypeFromComment(
$comment->getText(),
null,
$this,
null,
$storage->template_types,
$property_type_line_number
);
@ -1071,7 +1070,7 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
}
}
$property_group_type = $type_in_comment ?: null;
$property_group_type = $var_comment ? $var_comment->type : null;
foreach ($stmt->props as $property) {
$property_type_location = null;
@ -1098,20 +1097,21 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
$property_type = count($stmt->props) === 1 ? $property_group_type : clone $property_group_type;
}
$storage->properties[$property->name] = new PropertyStorage();
$storage->properties[$property->name]->is_static = (bool)$stmt->isStatic();
$storage->properties[$property->name]->type = $property_type;
$storage->properties[$property->name]->location = new CodeLocation($this, $property);
$storage->properties[$property->name]->type_location = $property_type_location;
$storage->properties[$property->name]->has_default = $property->default ? true : false;
$storage->properties[$property->name]->suggested_type = $property_group_type ? null : $default_type;
$property_storage = $storage->properties[$property->name] = new PropertyStorage();
$property_storage->is_static = (bool)$stmt->isStatic();
$property_storage->type = $property_type;
$property_storage->location = new CodeLocation($this, $property);
$property_storage->type_location = $property_type_location;
$property_storage->has_default = $property->default ? true : false;
$property_storage->suggested_type = $property_group_type ? null : $default_type;
$property_storage->deprecated = $var_comment ? $var_comment->deprecated : false;
if ($stmt->isPublic()) {
$storage->properties[$property->name]->visibility = self::VISIBILITY_PUBLIC;
$property_storage->visibility = self::VISIBILITY_PUBLIC;
} elseif ($stmt->isProtected()) {
$storage->properties[$property->name]->visibility = self::VISIBILITY_PROTECTED;
$property_storage->visibility = self::VISIBILITY_PROTECTED;
} elseif ($stmt->isPrivate()) {
$storage->properties[$property->name]->visibility = self::VISIBILITY_PRIVATE;
$property_storage->visibility = self::VISIBILITY_PRIVATE;
}
$property_id = $this->fq_class_name . '::$' . $property->name;
@ -1136,7 +1136,6 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
private function checkForMissingPropertyType(PhpParser\Node\Stmt\Property $stmt)
{
$comment = $stmt->getDocComment();
$type_in_comment = null;
$property_type_line_number = null;
$storage = self::$storage[strtolower($this->fq_class_name)];
@ -1194,14 +1193,14 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
Config $config
) {
$comment = $stmt->getDocComment();
$type_in_comment = null;
$var_comment = null;
$storage = self::$storage[strtolower((string)$class_context->self)];
if ($comment && $config->use_docblock_types && count($stmt->consts) === 1) {
$type_in_comment = CommentChecker::getTypeFromComment((string) $comment, null, $this);
$var_comment = CommentChecker::getTypeFromComment((string) $comment, null, $this);
}
$const_type = $type_in_comment ? $type_in_comment : Type::getMixed();
$const_type = $var_comment ? $var_comment->type : Type::getMixed();
foreach ($stmt->consts as $const) {
if ($stmt->isProtected()) {

View File

@ -8,6 +8,7 @@ use Psalm\Exception\TypeParseTreeException;
use Psalm\FunctionDocblockComment;
use Psalm\StatementsSource;
use Psalm\Type;
use Psalm\VarDocblockComment;
class CommentChecker
{
@ -17,11 +18,10 @@ class CommentChecker
* @param string $comment
* @param Context|null $context
* @param StatementsSource $source
* @param string $var_id
* @param array<string, string>|null $template_types
* @param int|null $var_line_number
* @param int|null $came_from_line_number what line number in $source that $comment came from
* @return Type\Union|null
* @return VarDocblockComment|null
* @throws DocblockParseException If there was a problem parsing the docblock.
* @psalm-suppress MixedArrayAccess
*/
@ -29,14 +29,13 @@ class CommentChecker
$comment,
$context,
StatementsSource $source,
$var_id = null,
array $template_types = null,
&$var_line_number = null,
$var_line_number = null,
$came_from_line_number = null
) {
$type_in_comments_var_id = null;
$var_id = null;
$type_in_comments = null;
$var_type_string = null;
$comments = self::parseDocComment($comment, $var_line_number);
@ -60,7 +59,7 @@ class CommentChecker
}
if ($line_parts && $line_parts[0]) {
$type_in_comments = FunctionLikeChecker::fixUpLocalType(
$var_type_string = FunctionLikeChecker::fixUpLocalType(
$line_parts[0],
$source,
$template_types
@ -71,7 +70,7 @@ class CommentChecker
// support PHPStorm-style docblocks like
// @var Type $variable
if (count($line_parts) > 1 && $line_parts[1][0] === '$') {
$type_in_comments_var_id = $line_parts[1];
$var_id = $line_parts[1];
}
break;
@ -79,16 +78,16 @@ class CommentChecker
}
}
if (!$type_in_comments) {
if (!$var_type_string) {
return null;
}
try {
$defined_type = Type::parseString($type_in_comments);
$defined_type = Type::parseString($var_type_string);
} catch (TypeParseTreeException $e) {
if (is_int($came_from_line_number)) {
throw new DocblockParseException(
$type_in_comments .
$var_type_string .
' is not a valid type' .
' (from ' .
$source->getCheckedFilePath() .
@ -97,18 +96,19 @@ class CommentChecker
')'
);
}
throw new DocblockParseException($type_in_comments . ' is not a valid type');
throw new DocblockParseException($var_type_string . ' is not a valid type');
}
$defined_type->setFromDocblock();
if ($context && $type_in_comments_var_id && $type_in_comments_var_id !== $var_id) {
$context->vars_in_scope[$type_in_comments_var_id] = $defined_type;
$var_comment = new VarDocblockComment();
$var_comment->type = $defined_type;
$var_comment->var_id = $var_id;
$var_comment->line_number = $var_line_number;
$var_comment->deprecated = isset($comments['specials']['deprecated']);
return null;
}
return $defined_type;
return $var_comment;
}
/**
@ -421,7 +421,7 @@ class CommentChecker
// Parse @specials.
$matches = [];
$have_specials = preg_match_all('/^\s?@([\w\-:]+)\s*([^\n]*)/m', $docblock, $matches, PREG_SET_ORDER);
$have_specials = preg_match_all('/^\s?@([\w\-:]+)[\t ]*([^\n]*)/m', $docblock, $matches, PREG_SET_ORDER);
if ($have_specials) {
$docblock = preg_replace('/^\s?@([\w\-:]+)\s*([^\n]*)/m', '', $docblock);
/** @var string[] $match */

View File

@ -222,12 +222,15 @@ class ForeachChecker
$doc_comment_text = (string)$stmt->getDocComment();
if ($doc_comment_text) {
CommentChecker::getTypeFromComment(
$var_comment = CommentChecker::getTypeFromComment(
$doc_comment_text,
$foreach_context,
$statements_checker->getSource(),
null
$statements_checker->getSource()
);
if ($var_comment && $var_comment->var_id) {
$context->vars_in_scope[$var_comment->var_id] = $var_comment->type;
}
}
$changed_vars = Context::getNewOrUpdatedVarIds($before_context, $foreach_context);

View File

@ -14,6 +14,7 @@ use Psalm\Checker\StatementsChecker;
use Psalm\Checker\TypeChecker;
use Psalm\CodeLocation;
use Psalm\Context;
use Psalm\Issue\DeprecatedProperty;
use Psalm\Issue\FailedTypeResolution;
use Psalm\Issue\InvalidArrayAssignment;
use Psalm\Issue\InvalidPropertyAssignment;
@ -73,19 +74,21 @@ class AssignmentChecker
$statements_checker
);
$var_comment = null;
if ($doc_comment) {
$null = null;
$type_in_comments = CommentChecker::getTypeFromComment(
$var_comment = CommentChecker::getTypeFromComment(
$doc_comment,
$context,
$statements_checker->getSource(),
$var_id,
null,
$null,
null,
$came_from_line_number
);
} else {
$type_in_comments = null;
if ($var_comment && $var_comment->var_id && $var_comment->var_id !== $var_id) {
$context->vars_in_scope[$var_comment->var_id] = $var_comment->type;
}
}
if ($assign_value && ExpressionChecker::analyze($statements_checker, $assign_value, $context) === false) {
@ -95,14 +98,17 @@ class AssignmentChecker
}
// if we're not exiting immediately, make everything mixed
$context->vars_in_scope[$var_id] = $type_in_comments ?: Type::getMixed();
$context->vars_in_scope[$var_id] =
$var_comment && (!$var_comment->var_id || $var_comment->var_id === $var_id)
? $var_comment->type
: Type::getMixed();
}
return false;
}
if ($type_in_comments) {
$assign_value_type = $type_in_comments;
if ($var_comment && (!$var_comment->var_id || $var_comment->var_id === $var_id)) {
$assign_value_type = $var_comment->type;
} elseif (!$assign_value_type) {
if (isset($assign_value->inferredType)) {
/** @var Type\Union */
@ -667,6 +673,18 @@ class AssignmentChecker
$property_storage =
ClassLikeChecker::$storage[strtolower((string)$declaring_property_class)]->properties[$stmt->name];
if ($property_storage->deprecated) {
if (IssueBuffer::accepts(
new DeprecatedProperty(
$property_id . ' is marked deprecated',
new CodeLocation($statements_checker->getSource(), $stmt)
),
$statements_checker->getSuppressedIssues()
)) {
// fall through
}
}
$class_property_type = $property_storage->type;
if ($class_property_type === false) {

View File

@ -11,6 +11,7 @@ use Psalm\Checker\StatementsChecker;
use Psalm\Checker\TraitChecker;
use Psalm\CodeLocation;
use Psalm\Context;
use Psalm\Issue\DeprecatedProperty;
use Psalm\Issue\InaccessibleClassConstant;
use Psalm\Issue\InvalidArrayAccess;
use Psalm\Issue\InvalidArrayAssignment;
@ -287,6 +288,18 @@ class FetchChecker
$property_storage = $declaring_class_storage->properties[$stmt->name];
if ($property_storage->deprecated) {
if (IssueBuffer::accepts(
new DeprecatedProperty(
$property_id . ' is marked deprecated',
new CodeLocation($statements_checker->getSource(), $stmt)
),
$statements_checker->getSuppressedIssues()
)) {
// fall through
}
}
$class_property_type = $property_storage->type;
if ($class_property_type === false) {

View File

@ -1623,14 +1623,18 @@ class ExpressionChecker
) {
$doc_comment_text = (string)$stmt->getDocComment();
$var_comment = null;
if ($doc_comment_text) {
$type_in_comments = CommentChecker::getTypeFromComment(
$var_comment = CommentChecker::getTypeFromComment(
$doc_comment_text,
$context,
$statements_checker->getSource()
$statements_checker
);
} else {
$type_in_comments = null;
if ($var_comment && $var_comment->var_id) {
$context->vars_in_scope[$var_comment->var_id] = $var_comment->type;
}
}
if ($stmt->key) {
@ -1644,8 +1648,8 @@ class ExpressionChecker
return false;
}
if ($type_in_comments) {
$stmt->inferredType = $type_in_comments;
if ($var_comment && !$var_comment->var_id) {
$stmt->inferredType = $var_comment->type;
} elseif (isset($stmt->value->inferredType)) {
$stmt->inferredType = $stmt->value->inferredType;
} else {

View File

@ -299,11 +299,15 @@ class StatementsChecker extends SourceChecker implements StatementsSource
$class_checker->analyze(null, $global_context);
} elseif ($stmt instanceof PhpParser\Node\Stmt\Nop) {
if ((string)$stmt->getDocComment()) {
CommentChecker::getTypeFromComment(
$var_comment = CommentChecker::getTypeFromComment(
(string)$stmt->getDocComment(),
$context,
$this->getSource()
);
if ($var_comment && $var_comment->var_id) {
$context->vars_in_scope[$var_comment->var_id] = $var_comment->type;
}
}
} elseif ($stmt instanceof PhpParser\Node\Stmt\Goto_) {
// do nothing
@ -673,14 +677,18 @@ class StatementsChecker extends SourceChecker implements StatementsSource
{
$doc_comment_text = (string)$stmt->getDocComment();
$var_comment = null;
if ($doc_comment_text) {
$type_in_comments = CommentChecker::getTypeFromComment(
$var_comment = CommentChecker::getTypeFromComment(
$doc_comment_text,
$context,
$this->source
);
} else {
$type_in_comments = null;
if ($var_comment && $var_comment->var_id) {
$context->vars_in_scope[$var_comment->var_id] = $var_comment->type;
}
}
if ($stmt->expr) {
@ -688,8 +696,8 @@ class StatementsChecker extends SourceChecker implements StatementsSource
return false;
}
if ($type_in_comments) {
$stmt->inferredType = $type_in_comments;
if ($var_comment && !$var_comment->var_id) {
$stmt->inferredType = $var_comment->type;
} elseif (isset($stmt->expr->inferredType)) {
$stmt->inferredType = $stmt->expr->inferredType;
} else {

View File

@ -0,0 +1,6 @@
<?php
namespace Psalm\Issue;
class DeprecatedProperty extends CodeError
{
}

View File

@ -40,4 +40,9 @@ class PropertyStorage
* @var bool
*/
public $has_default = false;
/**
* @var bool
*/
public $deprecated = false;
}

View File

@ -0,0 +1,26 @@
<?php
namespace Psalm;
class VarDocblockComment
{
/**
* @var Type\Union
*/
public $type;
/**
* @var string|null
*/
public $var_id = null;
/**
* @var int|null
*/
public $line_number;
/**
* Whether or not the function is deprecated
* @var boolean
*/
public $deprecated = false;
}

View File

@ -219,6 +219,31 @@ class AnnotationTest extends TestCase
$a = new Foo();',
'error_message' => 'DeprecatedClass'
],
'deprecatedPropertyGet' => [
'<?php
class A{
/**
* @deprecated
* @var ?int
*/
public $foo;
}
echo (new A)->foo;',
'error_message' => 'DeprecatedProperty'
],
'deprecatedPropertySet' => [
'<?php
class A{
/**
* @deprecated
* @var ?int
*/
public $foo;
}
$a = new A;
$a->foo = 5;',
'error_message' => 'DeprecatedProperty'
],
'invalidDocblockParam' => [
'<?php
/**