mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
Reduce PossiblyUnusedParam false positives
This commit is contained in:
parent
c0e923acb9
commit
752e99ad2e
@ -26,7 +26,6 @@ use Psalm\Issue\MixedInferredReturnType;
|
||||
use Psalm\Issue\MoreSpecificImplementedReturnType;
|
||||
use Psalm\Issue\MoreSpecificReturnType;
|
||||
use Psalm\Issue\OverriddenMethodAccess;
|
||||
use Psalm\Issue\PossiblyUnusedParam;
|
||||
use Psalm\Issue\UntypedParam;
|
||||
use Psalm\Issue\UnusedParam;
|
||||
use Psalm\Issue\UnusedVariable;
|
||||
@ -512,25 +511,45 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
|
||||
$method_name_lc = strtolower((string)$this->function->name);
|
||||
$parent_method_id = end($class_storage->overridden_method_ids[$method_name_lc]);
|
||||
|
||||
$position = array_search(substr($var_name, 1), array_keys($storage->param_types), true);
|
||||
|
||||
if ($position === false) {
|
||||
throw new \UnexpectedValueException('$position should not be false here');
|
||||
}
|
||||
|
||||
if ($parent_method_id) {
|
||||
list($parent_fq_class_name) = explode('::', $parent_method_id);
|
||||
$parent_method_storage = MethodChecker::getStorage($project_checker, $parent_method_id);
|
||||
|
||||
$parent_method_class_storage = $classlike_storage_provider->get($parent_fq_class_name);
|
||||
|
||||
if (!$parent_method_class_storage->abstract) {
|
||||
// if the parent method has a param at that position and isn't abstract
|
||||
if (!$parent_method_storage->abstract
|
||||
&& isset($parent_method_storage->params[$position])
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (IssueBuffer::accepts(
|
||||
new PossiblyUnusedParam(
|
||||
'Param ' . $var_name . ' is never referenced in this method',
|
||||
$original_location
|
||||
),
|
||||
$this->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
$storage->unused_params[$position] = $original_location;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($storage instanceof MethodStorage && $class_storage) {
|
||||
foreach ($storage->params as $i => $_) {
|
||||
if (!isset($storage->unused_params[$i])) {
|
||||
$storage->used_params[$i] = true;
|
||||
|
||||
/** @var ClassMethod $this->function */
|
||||
$method_name_lc = strtolower((string)$this->function->name);
|
||||
|
||||
if (!isset($class_storage->overridden_method_ids[$method_name_lc])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($class_storage->overridden_method_ids[$method_name_lc] as $parent_method_id) {
|
||||
$parent_method_storage = MethodChecker::getStorage($project_checker, $parent_method_id);
|
||||
|
||||
$parent_method_storage->used_params[$i] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ use Psalm\Exception;
|
||||
use Psalm\FileManipulation\FunctionDocblockManipulator;
|
||||
use Psalm\Issue\CircularReference;
|
||||
use Psalm\Issue\PossiblyUnusedMethod;
|
||||
use Psalm\Issue\PossiblyUnusedParam;
|
||||
use Psalm\Issue\UnusedClass;
|
||||
use Psalm\Issue\UnusedMethod;
|
||||
use Psalm\IssueBuffer;
|
||||
@ -1117,7 +1118,7 @@ class ProjectChecker
|
||||
// fall through
|
||||
}
|
||||
} else {
|
||||
self::checkMethodReferences($classlike_storage);
|
||||
$this->checkMethodReferences($classlike_storage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1126,26 +1127,52 @@ class ProjectChecker
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
protected static function checkMethodReferences(\Psalm\Storage\ClassLikeStorage $classlike_storage)
|
||||
protected function checkMethodReferences(\Psalm\Storage\ClassLikeStorage $classlike_storage)
|
||||
{
|
||||
foreach ($classlike_storage->methods as $method_name => $method_storage) {
|
||||
if (($method_storage->referencing_locations === null
|
||||
|| count($method_storage->referencing_locations) === 0)
|
||||
&& !$classlike_storage->overridden_method_ids[$method_name]
|
||||
&& (substr($method_name, 0, 2) !== '__' || $method_name === '__construct')
|
||||
&& $method_storage->location
|
||||
) {
|
||||
$method_id = $classlike_storage->name . '::' . $method_storage->cased_name;
|
||||
|
||||
if ($method_storage->visibility === ClassLikeChecker::VISIBILITY_PUBLIC) {
|
||||
if (IssueBuffer::accepts(
|
||||
new PossiblyUnusedMethod(
|
||||
'Cannot find public calls to method ' . $method_id,
|
||||
$method_storage->location
|
||||
),
|
||||
$method_storage->suppressed_issues
|
||||
)) {
|
||||
// fall through
|
||||
$method_name_lc = strtolower($method_name);
|
||||
|
||||
$has_parent_references = false;
|
||||
|
||||
foreach ($classlike_storage->overridden_method_ids[$method_name_lc] as $parent_method_id) {
|
||||
$parent_method_storage = MethodChecker::getStorage($this, $parent_method_id);
|
||||
|
||||
if (!$parent_method_storage->abstract || $parent_method_storage->referencing_locations) {
|
||||
$has_parent_references = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($classlike_storage->class_implements as $fq_interface_name) {
|
||||
$interface_storage = $this->classlike_storage_provider->get($fq_interface_name);
|
||||
if (isset($interface_storage->methods[$method_name])) {
|
||||
$interface_method_storage = $interface_storage->methods[$method_name];
|
||||
|
||||
if ($interface_method_storage->referencing_locations) {
|
||||
$has_parent_references = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$has_parent_references) {
|
||||
if (IssueBuffer::accepts(
|
||||
new PossiblyUnusedMethod(
|
||||
'Cannot find public calls to method ' . $method_id,
|
||||
$method_storage->location
|
||||
),
|
||||
$method_storage->suppressed_issues
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
} elseif (!isset($classlike_storage->declaring_method_ids['__call'])) {
|
||||
if (IssueBuffer::accepts(
|
||||
@ -1157,6 +1184,35 @@ class ProjectChecker
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($method_storage->unused_params as $offset => $code_location) {
|
||||
$has_parent_references = false;
|
||||
|
||||
$method_name_lc = strtolower($method_name);
|
||||
|
||||
foreach ($classlike_storage->overridden_method_ids[$method_name_lc] as $parent_method_id) {
|
||||
$parent_method_storage = MethodChecker::getStorage($this, $parent_method_id);
|
||||
|
||||
if (!$parent_method_storage->abstract
|
||||
&& isset($parent_method_storage->used_params[$offset])
|
||||
) {
|
||||
$has_parent_references = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$has_parent_references && !isset($method_storage->used_params[$offset])) {
|
||||
if (IssueBuffer::accepts(
|
||||
new PossiblyUnusedParam(
|
||||
'Param #' . $offset . ' is never referenced in this method',
|
||||
$code_location
|
||||
),
|
||||
$method_storage->suppressed_issues
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
<?php
|
||||
namespace Psalm\Storage;
|
||||
|
||||
use Psalm\CodeLocation;
|
||||
|
||||
class MethodStorage extends FunctionLikeStorage
|
||||
{
|
||||
/**
|
||||
@ -27,4 +29,14 @@ class MethodStorage extends FunctionLikeStorage
|
||||
* @var bool
|
||||
*/
|
||||
public $final;
|
||||
|
||||
/**
|
||||
* @var array<int, CodeLocation>
|
||||
*/
|
||||
public $unused_params = [];
|
||||
|
||||
/**
|
||||
* @var array<int, bool>
|
||||
*/
|
||||
public $used_params = [];
|
||||
}
|
||||
|
@ -129,3 +129,13 @@ function reset(array &$arr) {}
|
||||
* @return TValue|false
|
||||
*/
|
||||
function end(array &$arr) {}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
*
|
||||
* @param mixed $needle
|
||||
* @param array<T, mixed> $haystack
|
||||
* @param bool $strict
|
||||
* @return T|false
|
||||
*/
|
||||
function array_search($needle, array $haystack, bool $strict = false) {}
|
||||
|
Loading…
Reference in New Issue
Block a user