Skip to content

Commit 89616c0

Browse files
committed
Only allow unused values in associative foreach loops with option (#167)
* Fix syntax of ThisWithinNestedClosedScopeFixture Weird that I never noticed that before. * Update tests to mark unused keys even with allowUnusedForeachVariables * Update description of allowUnusedForeachVariables to be accurate This makes it clear that it only operates on the values of a `key => value` expression. * Adjust more tests which use default allowUnusedForeachVariables * Only allow unused values in associative foreach loops with option * Fix unused foreach keys in sniff and helpers
1 parent f81ca4b commit 89616c0

File tree

7 files changed

+29
-15
lines changed

7 files changed

+29
-15
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ The available options are as follows:
6666
- `validUnusedVariableNames` (string, default `null`): a space-separated list of names of placeholder variables that you want to ignore from unused variable warnings. For example, to ignore the variables `$junk` and `$unused`, this could be set to `'junk unused'`.
6767
- `ignoreUnusedRegexp` (string, default `null`): a PHP regexp string (note that this requires explicit delimiters) for variables that you want to ignore from unused variable warnings. For example, to ignore the variables `$_junk` and `$_unused`, this could be set to `'/^_/'`.
6868
- `validUndefinedVariableNames` (string, default `null`): a space-separated list of names of placeholder variables that you want to ignore from undefined variable warnings. For example, to ignore the variables `$post` and `$undefined`, this could be set to `'post undefined'`.
69-
- `allowUnusedForeachVariables` (bool, default `true`): if set to true, unused keys or values created by the `as` statement in a `foreach` loop will never be marked as unused.
69+
- `allowUnusedForeachVariables` (bool, default `true`): if set to true, unused values from the `key => value` syntax in a `foreach` loop will never be marked as unused.
7070
- `sitePassByRefFunctions` (string, default `null`): a list of custom functions which pass in variables to be initialized by reference (eg `preg_match()`) and therefore should not require those variables to be defined ahead of time. The list is space separated and each entry is of the form `functionName:1,2`. The function name comes first followed by a colon and a comma-separated list of argument numbers (starting from 1) which should be considered variable definitions. The special value `...` in the arguments list will cause all arguments after the last number to be considered variable definitions.
7171
- `allowWordPressPassByRefFunctions` (bool, default `false`): if set to true, a list of common WordPress pass-by-reference functions will be added to the list of PHP ones so that passing undefined variables to these functions (to be initialized by reference) will be allowed.
7272

VariableAnalysis/Lib/Helpers.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public static function findParenthesisOwner(File $phpcsFile, $stackPtr) {
8282
* @return bool
8383
*/
8484
public static function areAnyConditionsAClass(array $conditions) {
85-
foreach (array_reverse($conditions, true) as $scopePtr => $scopeCode) {
85+
foreach (array_reverse($conditions, true) as $scopeCode) {
8686
if ($scopeCode === T_CLASS || $scopeCode === T_ANON_CLASS || $scopeCode === T_TRAIT) {
8787
return true;
8888
}
@@ -99,7 +99,7 @@ public static function areConditionsWithinFunctionBeforeClass(array $conditions)
9999
// Return true if the token conditions are within a function before
100100
// they are within a class.
101101
$classTypes = [T_CLASS, T_ANON_CLASS, T_TRAIT];
102-
foreach (array_reverse($conditions, true) as $scopePtr => $scopeCode) {
102+
foreach (array_reverse($conditions, true) as $scopeCode) {
103103
if (in_array($scopeCode, $classTypes)) {
104104
return false;
105105
}

VariableAnalysis/Lib/VariableInfo.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class VariableInfo {
6767
/**
6868
* @var bool
6969
*/
70-
public $isForeachLoopVar = false;
70+
public $isForeachLoopAssociativeValue = false;
7171

7272
/**
7373
* @var string[]

VariableAnalysis/Sniffs/CodeAnalysis/VariableAnalysisSniff.php

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ class VariableAnalysisSniff implements Sniff {
9898
public $allowUnusedParametersBeforeUsed = true;
9999

100100
/**
101-
* If set to true, unused keys or values created by the `as` statement
101+
* If set to true, unused values from the `key => value` syntax
102102
* in a `foreach` loop will never be marked as unused.
103103
*
104104
* @var bool
@@ -607,7 +607,7 @@ protected function checkForThisWithinClass(File $phpcsFile, $stackPtr, $varName)
607607
}
608608

609609
$inFunction = false;
610-
foreach (array_reverse($token['conditions'], true) as $scopePtr => $scopeCode) {
610+
foreach (array_reverse($token['conditions'], true) as $scopeCode) {
611611
// $this within a closure is valid
612612
if ($scopeCode === T_CLOSURE && $inFunction === false) {
613613
return true;
@@ -1015,7 +1015,11 @@ protected function checkForForeachLoopVar(File $phpcsFile, $stackPtr, $varName,
10151015
}
10161016
$this->markVariableAssignment($varName, $stackPtr, $currScope);
10171017
$varInfo = $this->getOrCreateVariableInfo($varName, $currScope);
1018-
$varInfo->isForeachLoopVar = true;
1018+
1019+
// Is this the value of a key => value foreach?
1020+
if ($phpcsFile->findPrevious(T_DOUBLE_ARROW, $stackPtr - 1, $openParenPtr) !== false) {
1021+
$varInfo->isForeachLoopAssociativeValue = true;
1022+
}
10191023

10201024
return true;
10211025
}
@@ -1401,7 +1405,7 @@ protected function processScopeCloseForVariable(File $phpcsFile, VariableInfo $v
14011405
if ($this->allowUnusedParametersBeforeUsed && $varInfo->scopeType === 'param' && $this->areFollowingArgumentsUsed($varInfo, $scopeInfo)) {
14021406
return;
14031407
}
1404-
if ($this->allowUnusedForeachVariables && $varInfo->isForeachLoopVar) {
1408+
if ($this->allowUnusedForeachVariables && $varInfo->isForeachLoopAssociativeValue) {
14051409
return;
14061410
}
14071411
if ($varInfo->passByReference && isset($varInfo->firstInitialized)) {

VariableAnalysis/Tests/CodeAnalysis/VariableAnalysisTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ public function testFunctionWithForeachWarnings() {
124124
22,
125125
24,
126126
26,
127+
48,
128+
50,
129+
52,
130+
54,
127131
// FIXME: this is an unused variable that needs to be fixed but for now
128132
// we will ignore it. See
129133
// https://github.com/sirbrillig/phpcs-variable-analysis/pull/36
@@ -832,12 +836,15 @@ public function testUnusedForeachVariablesAreIgnoredByDefault() {
832836
$phpcsFile->process();
833837
$lines = $this->getWarningLineNumbersFromFile($phpcsFile);
834838
$expectedWarnings = [
839+
5,
835840
7,
836841
8,
837842
16,
838843
17,
844+
23,
839845
25,
840846
26,
847+
32,
841848
33,
842849
34,
843850
];
@@ -882,12 +889,15 @@ public function testUnusedForeachVariablesAreIgnoredIfSet() {
882889
$phpcsFile->process();
883890
$lines = $this->getWarningLineNumbersFromFile($phpcsFile);
884891
$expectedWarnings = [
892+
5,
885893
7,
886894
8,
887895
16,
888896
17,
897+
23,
889898
25,
890899
26,
900+
32,
891901
33,
892902
34,
893903
];

VariableAnalysis/Tests/CodeAnalysis/fixtures/ThisWithinNestedClosedScopeFixture.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ function nestedFunctionDeclaration() {
3636
}
3737
}
3838
}
39-
}
39+
};
4040

41-
$anonClass = class() {
41+
$anonClass = new class() {
4242
public function bar() {
4343
// Using $this here is fine.
4444
if ($this->something) {
@@ -50,7 +50,7 @@ function nestedFunctionDeclaration() {
5050
}
5151
}
5252
}
53-
}
53+
};
5454

5555
trait FooTrait {
5656
public function bar() {

VariableAnalysis/Tests/CodeAnalysis/fixtures/UnusedForeachFixture.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
function loopWithUnusedKey() {
44
$array = [];
5-
foreach ( $array as $key => $value ) { // maybe marked as unused
5+
foreach ( $array as $key => $value ) { // key always marked as unused
66
echo $value;
77
$unused = 'foobar'; // should always be marked as unused
88
echo $undefined; // should always be marked as undefined
@@ -11,7 +11,7 @@ function loopWithUnusedKey() {
1111

1212
function loopWithUnusedValue() {
1313
$array = [];
14-
foreach ( $array as $key => $value ) { // maybe marked as unused
14+
foreach ( $array as $key => $value ) { // key maybe marked as unused
1515
echo $key;
1616
$unused = 'foobar'; // should always be marked as unused
1717
echo $undefined; // should always be marked as undefined
@@ -20,7 +20,7 @@ function loopWithUnusedValue() {
2020

2121
function loopWithUnusedKeyAndValue() {
2222
$array = [];
23-
foreach ( $array as $key => $value ) { // maybe marked as unused
23+
foreach ( $array as $key => $value ) { // key always marked as unused
2424
echo 'hello';
2525
$unused = 'foobar'; // should always be marked as unused
2626
echo $undefined; // should always be marked as undefined
@@ -29,7 +29,7 @@ function loopWithUnusedKeyAndValue() {
2929

3030
function loopWithUnusedValueOnly() {
3131
$array = [];
32-
foreach ( $array as $value ) { // maybe marked as unused
32+
foreach ( $array as $value ) { // value always marked as unused
3333
$unused = 'foobar'; // should always be marked as unused
3434
echo $undefined; // should always be marked as undefined
3535
}

0 commit comments

Comments
 (0)