mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +01:00
Emit separate type of issue when foreach value is unused (#5932)
* Emit separate type of issue when foreach value is unused Fixes vimeo/psalm#5929 * Fixed var name case sensitivity
This commit is contained in:
parent
b1b1072f41
commit
e552925af6
@ -452,6 +452,7 @@
|
||||
<xs:element name="UnusedClass" type="ClassIssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="UnusedClosureParam" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="UnusedConstructor" type="MethodIssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="UnusedForeachValue" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="UnusedFunctionCall" type="FunctionIssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="UnusedMethod" type="MethodIssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="UnusedMethodCall" type="MethodIssueHandlerType" minOccurs="0" />
|
||||
|
@ -310,6 +310,7 @@ These issues are treated as errors at level 7 and below.
|
||||
- [UnusedClass](issues/UnusedClass.md)
|
||||
- [UnusedClosureParam](issues/UnusedClosureParam.md)
|
||||
- [UnusedConstructor](issues/UnusedConstructor.md)
|
||||
- [UnusedForeachValue](issues/UnusedForeachValue.md)
|
||||
- [UnusedMethod](issues/UnusedMethod.md)
|
||||
- [UnusedParam](issues/UnusedParam.md)
|
||||
- [UnusedProperty](issues/UnusedProperty.md)
|
||||
|
27
docs/running_psalm/issues/UnusedForeachValue.md
Normal file
27
docs/running_psalm/issues/UnusedForeachValue.md
Normal file
@ -0,0 +1,27 @@
|
||||
# UnusedForeachValue
|
||||
|
||||
Emitted when `--find-dead-code` is turned on and Psalm cannot find any
|
||||
references to the foreach value
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
/** @param non-empty-array<string, int> $a */
|
||||
function foo(array $a) : void {
|
||||
foreach ($a as $key => $value) { // $value is unused
|
||||
break;
|
||||
}
|
||||
echo $key;
|
||||
}
|
||||
```
|
||||
|
||||
Can be suppressed by prefixing the variable name with an underscore or naming
|
||||
it `$_`:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
foreach ([1, 2, 3] as $key => $_val) {}
|
||||
|
||||
foreach ([1, 2, 3] as $key => $_) {}
|
||||
```
|
@ -1606,6 +1606,10 @@ class Config
|
||||
return 'UndefinedClass';
|
||||
}
|
||||
|
||||
if ($issue_type === 'UnusedForeachValue') {
|
||||
return 'UnusedVariable';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -93,6 +93,10 @@ class ForeachAnalyzer
|
||||
|
||||
if ($stmt->valueVar instanceof PhpParser\Node\Expr\Variable && is_string($stmt->valueVar->name)) {
|
||||
$safe_var_ids['$' . $stmt->valueVar->name] = true;
|
||||
$statements_analyzer->foreach_var_locations['$' . $stmt->valueVar->name][] = new CodeLocation(
|
||||
$statements_analyzer,
|
||||
$stmt->valueVar
|
||||
);
|
||||
} elseif ($stmt->valueVar instanceof PhpParser\Node\Expr\List_) {
|
||||
foreach ($stmt->valueVar->items as $list_item) {
|
||||
if (!$list_item) {
|
||||
|
@ -38,6 +38,7 @@ use Psalm\Issue\Trace;
|
||||
use Psalm\Issue\UndefinedTrace;
|
||||
use Psalm\Issue\UnevaluatedCode;
|
||||
use Psalm\Issue\UnrecognizedStatement;
|
||||
use Psalm\Issue\UnusedForeachValue;
|
||||
use Psalm\Issue\UnusedVariable;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\Plugin\EventHandler\Event\AfterStatementAnalysisEvent;
|
||||
@ -129,6 +130,16 @@ class StatementsAnalyzer extends SourceAnalyzer
|
||||
/** @var ?DataFlowGraph */
|
||||
public $data_flow_graph;
|
||||
|
||||
/**
|
||||
* Locations of foreach values
|
||||
*
|
||||
* Used to discern ordinary UnusedVariables from UnusedForeachValues
|
||||
*
|
||||
* @var array<string, list<CodeLocation>>
|
||||
* @psalm-internal Psalm\Internal\Analyzer
|
||||
*/
|
||||
public $foreach_var_locations = [];
|
||||
|
||||
public function __construct(SourceAnalyzer $source, \Psalm\Internal\Provider\NodeDataProvider $node_data)
|
||||
{
|
||||
$this->source = $source;
|
||||
@ -769,12 +780,31 @@ class StatementsAnalyzer extends SourceAnalyzer
|
||||
&& $this->data_flow_graph instanceof VariableUseGraph
|
||||
&& !$this->data_flow_graph->isVariableUsed($assignment_node)
|
||||
) {
|
||||
$issue = new UnusedVariable(
|
||||
$var_id . ' is never referenced or the value is not used',
|
||||
$original_location
|
||||
);
|
||||
$is_foreach_var = false;
|
||||
|
||||
if (isset($this->foreach_var_locations[$var_id])) {
|
||||
foreach ($this->foreach_var_locations[$var_id] as $location) {
|
||||
if ($location->raw_file_start === $original_location->raw_file_start) {
|
||||
$is_foreach_var = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($is_foreach_var) {
|
||||
$issue = new UnusedForeachValue(
|
||||
$var_id . ' is never referenced or the value is not used',
|
||||
$original_location
|
||||
);
|
||||
} else {
|
||||
$issue = new UnusedVariable(
|
||||
$var_id . ' is never referenced or the value is not used',
|
||||
$original_location
|
||||
);
|
||||
}
|
||||
|
||||
if ($codebase->alter_code
|
||||
&& $issue instanceof UnusedVariable
|
||||
&& !$unused_var_remover->checkIfVarRemoved($var_id, $original_location)
|
||||
&& isset($project_analyzer->getIssuesToFix()['UnusedVariable'])
|
||||
&& !IssueBuffer::isSuppressed($issue, $this->getSuppressedIssues())
|
||||
|
9
src/Psalm/Issue/UnusedForeachValue.php
Normal file
9
src/Psalm/Issue/UnusedForeachValue.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Psalm\Issue;
|
||||
|
||||
class UnusedForeachValue extends CodeIssue
|
||||
{
|
||||
public const ERROR_LEVEL = -2;
|
||||
public const SHORTCODE = 275;
|
||||
}
|
@ -2968,7 +2968,7 @@ class UnusedVariableTest extends TestCase
|
||||
$i = $i;
|
||||
}
|
||||
}',
|
||||
'error_message' => 'UnusedVariable',
|
||||
'error_message' => 'UnusedForeachValue',
|
||||
],
|
||||
'detectUnusedVariableInsideLoopAfterAssignmentWithAddition' => [
|
||||
'<?php
|
||||
@ -2977,7 +2977,7 @@ class UnusedVariableTest extends TestCase
|
||||
$i = $i + 1;
|
||||
}
|
||||
}',
|
||||
'error_message' => 'UnusedVariable',
|
||||
'error_message' => 'UnusedForeachValue',
|
||||
],
|
||||
'detectUnusedVariableInsideLoopCalledInFunction' => [
|
||||
'<?php
|
||||
@ -3078,7 +3078,30 @@ class UnusedVariableTest extends TestCase
|
||||
}
|
||||
return 4;
|
||||
}',
|
||||
'error_message' => 'UnusedVariable',
|
||||
'error_message' => 'UnusedForeachValue',
|
||||
],
|
||||
'conditionalForeachWithUnusedValue' => [
|
||||
'<?php
|
||||
if (rand(0, 1) > 0) {
|
||||
foreach ([1, 2, 3] as $val) {}
|
||||
}
|
||||
',
|
||||
'error_message' => 'UnusedForeachValue',
|
||||
],
|
||||
'doubleForeachWithInnerUnusedValue' => [
|
||||
'<?php
|
||||
/**
|
||||
* @param non-empty-list<list<int>> $arr
|
||||
* @return list<int>
|
||||
*/
|
||||
function f(array $arr): array {
|
||||
foreach ($arr as $elt) {
|
||||
foreach ($elt as $subelt) {}
|
||||
}
|
||||
return $elt;
|
||||
}
|
||||
',
|
||||
'error_message' => 'UnusedForeachValue'
|
||||
],
|
||||
'defineInBothBranchesOfConditional' => [
|
||||
'<?php
|
||||
|
Loading…
Reference in New Issue
Block a user