1
0
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:
Matthew Brown 2017-12-30 08:47:00 -05:00
parent c0e923acb9
commit 752e99ad2e
4 changed files with 122 additions and 25 deletions

View File

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

View File

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

View File

@ -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 = [];
}

View File

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