Skip to content

Commit a3e3a84

Browse files
authored
Handle inline if list destructure (sirbrillig#268)
* Add tests for inline if list destructuring * Support treating T_OPEN_SQUARE_BRACKET as a list opener * Add test for foreach with list destructure * Better differentiate between list destructuring and array access * Use the same technique for finding list assignments as phpcs might See squizlabs/PHP_CodeSniffer#3632
1 parent 7c73f92 commit a3e3a84

File tree

4 files changed

+109
-2
lines changed

4 files changed

+109
-2
lines changed

Tests/VariableAnalysisSniff/IfConditionTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ public function testInlineIfConditionWarnings()
100100
136,
101101
152,
102102
154,
103+
165,
104+
175,
103105
];
104106
$this->assertSame($expectedWarnings, $lines);
105107
}
@@ -132,6 +134,8 @@ public function testInlineIfConditionWarningsWithValidUndefinedVariableNames()
132134
136,
133135
152,
134136
154,
137+
165,
138+
175,
135139
];
136140
$this->assertSame($expectedWarnings, $lines);
137141
}

Tests/VariableAnalysisSniff/fixtures/FunctionWithForeachFixture.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,19 @@ function doSomethingLoopy($receipts) {
8282
}
8383
}
8484
}
85+
86+
function foreachWithListDestructure($lines) {
87+
$passByRefFunctions = [];
88+
foreach ($lines as $line) {
89+
list ($function, $args) = explode(':', $line);
90+
$passByRefFunctions[$function] = explode(',', $args);
91+
}
92+
}
93+
94+
function foreachWithShortListDestructure($lines) {
95+
$passByRefFunctions = [];
96+
foreach ($lines as $line) {
97+
[$function, $args] = explode(':', $line);
98+
$passByRefFunctions[$function] = explode(',', $args);
99+
}
100+
}

Tests/VariableAnalysisSniff/fixtures/FunctionWithInlineIfConditionFixture.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,23 @@ function definedInsideElseIfBlockUndefinedInsideElseBlockDifferentName($first) {
154154
echo $third; // undefined variable $third
155155
echo $words;
156156
}
157+
158+
function inlineIfWithListDestructure() {
159+
if ( true )
160+
list( $a ) = [ 'hi' ];
161+
162+
echo $a;
163+
164+
if ( true )
165+
list( $b ) = [ 'hi' ]; // Unused variable $b
166+
}
167+
168+
function inlineIfWithShortListDestructure() {
169+
if ( true )
170+
[ $a ] = [ 'hi' ];
171+
172+
echo $a;
173+
174+
if ( true )
175+
[ $b ] = [ 'hi' ]; // Unused variable $b
176+
}

VariableAnalysis/Lib/Helpers.php

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public static function getIntOrNull($value)
4444
public static function findContainingOpeningSquareBracket(File $phpcsFile, $stackPtr)
4545
{
4646
$previousStatementPtr = self::getPreviousStatementPtr($phpcsFile, $stackPtr);
47-
return self::getIntOrNull($phpcsFile->findPrevious([T_OPEN_SHORT_ARRAY], $stackPtr - 1, $previousStatementPtr));
47+
return self::getIntOrNull($phpcsFile->findPrevious([T_OPEN_SHORT_ARRAY, T_OPEN_SQUARE_BRACKET], $stackPtr - 1, $previousStatementPtr));
4848
}
4949

5050
/**
@@ -654,7 +654,70 @@ public static function getArrowFunctionOpenClose(File $phpcsFile, $stackPtr)
654654
}
655655

656656
/**
657-
* Return a list of indices for variables assigned within a list assignment
657+
* Determine if a token is a list opener for list assignment/destructuring.
658+
*
659+
* The index provided can be either the opening square brace of a short list
660+
* assignment like the first character of `[$a] = $b;` or the `list` token of
661+
* an expression like `list($a) = $b;` or the opening parenthesis of that
662+
* expression.
663+
*
664+
* @param File $phpcsFile
665+
* @param int $listOpenerIndex
666+
*
667+
* @return bool
668+
*/
669+
private static function isListAssignment(File $phpcsFile, $listOpenerIndex)
670+
{
671+
$tokens = $phpcsFile->getTokens();
672+
// Match `[$a] = $b;` except for when the previous token is a parenthesis.
673+
if ($tokens[$listOpenerIndex]['code'] === T_OPEN_SHORT_ARRAY) {
674+
return true;
675+
}
676+
// Match `list($a) = $b;`
677+
if ($tokens[$listOpenerIndex]['code'] === T_LIST) {
678+
return true;
679+
}
680+
681+
// If $listOpenerIndex is the open parenthesis of `list($a) = $b;`, then
682+
// match that too.
683+
if ($tokens[$listOpenerIndex]['code'] === T_OPEN_PARENTHESIS) {
684+
$previousTokenPtr = $phpcsFile->findPrevious(Tokens::$emptyTokens, $listOpenerIndex - 1, null, true);
685+
if (
686+
isset($tokens[$previousTokenPtr])
687+
&& $tokens[$previousTokenPtr]['code'] === T_LIST
688+
) {
689+
return true;
690+
}
691+
return true;
692+
}
693+
694+
// If the list opener token is a square bracket that is preceeded by a
695+
// close parenthesis that has an owner which is a scope opener, then this
696+
// is a list assignment and not an array access.
697+
//
698+
// Match `if (true) [$a] = $b;`
699+
if ($tokens[$listOpenerIndex]['code'] === T_OPEN_SQUARE_BRACKET) {
700+
$previousTokenPtr = $phpcsFile->findPrevious(Tokens::$emptyTokens, $listOpenerIndex - 1, null, true);
701+
if (
702+
isset($tokens[$previousTokenPtr])
703+
&& $tokens[$previousTokenPtr]['code'] === T_CLOSE_PARENTHESIS
704+
&& isset($tokens[$previousTokenPtr]['parenthesis_owner'])
705+
&& isset(Tokens::$scopeOpeners[$tokens[$tokens[$previousTokenPtr]['parenthesis_owner']]['code']])
706+
) {
707+
return true;
708+
}
709+
}
710+
711+
return false;
712+
}
713+
714+
/**
715+
* Return a list of indices for variables assigned within a list assignment.
716+
*
717+
* The index provided can be either the opening square brace of a short list
718+
* assignment like the first character of `[$a] = $b;` or the `list` token of
719+
* an expression like `list($a) = $b;` or the opening parenthesis of that
720+
* expression.
658721
*
659722
* @param File $phpcsFile
660723
* @param int $listOpenerIndex
@@ -719,6 +782,10 @@ public static function getListAssignments(File $phpcsFile, $listOpenerIndex)
719782
++$currentPtr;
720783
}
721784

785+
if (! self::isListAssignment($phpcsFile, $listOpenerIndex)) {
786+
return null;
787+
}
788+
722789
return $variablePtrs;
723790
}
724791

0 commit comments

Comments
 (0)