Skip to content

Commit 4e80fc1

Browse files
authored
Handle nested destructuring assignment (#129)
* Add test for nested list() destructuring * Add phpcsutils as dependency * Use Lists::getAssignments to determine list assignment * Add short list nested destructuring test * Use Lists::getAssignments to determine short list assignments * Remove null coalesce to support php 5 * Wrap getAssignments in try/catch It shouldn't be able to fail since we already search for the opening bracket, but it seems to fail in CI. * Add phpcsutils to circleci dependencies * Add namespace to Exception * Prevent findPrevious from returning true This will never happen but the return type can include `true` so we must look for it to satisfy the static analysis. * Try another way to test for bool return value from findPrevious The comparison with true was sometimes breaking phpstan.
1 parent 1d12cbc commit 4e80fc1

File tree

5 files changed

+68
-14
lines changed

5 files changed

+68
-14
lines changed

VariableAnalysis/Sniffs/CodeAnalysis/VariableAnalysisSniff.php

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use PHP_CodeSniffer\Sniffs\Sniff;
1010
use PHP_CodeSniffer\Files\File;
1111
use PHP_CodeSniffer\Util\Tokens;
12+
use PHPCSUtils\Utils\Lists;
1213

1314
class VariableAnalysisSniff implements Sniff {
1415
/**
@@ -821,18 +822,23 @@ protected function checkForListShorthandAssignment(File $phpcsFile, $stackPtr, $
821822
}
822823

823824
// OK, we're a [ ... ] construct... are we being assigned to?
824-
$closePtr = Helpers::findContainingClosingSquareBracket($phpcsFile, $stackPtr);
825-
if (! is_int($closePtr)) {
825+
try {
826+
$assignments = Lists::getAssignments($phpcsFile, $openPtr);
827+
} catch (\Exception $error) {
826828
return false;
827829
}
828-
$assignPtr = Helpers::getNextAssignPointer($phpcsFile, $closePtr);
829-
if (! is_int($assignPtr)) {
830+
$matchingAssignment = array_reduce($assignments, function ($thisAssignment, array $assignment) use ($stackPtr) {
831+
if (isset($assignment['assignment_token']) && $assignment['assignment_token'] === $stackPtr) {
832+
return $assignment;
833+
}
834+
return $thisAssignment;
835+
});
836+
if (! $matchingAssignment) {
830837
return false;
831838
}
832839

833840
// Yes, we're being assigned.
834-
$writtenPtr = Helpers::findWhereAssignExecuted($phpcsFile, $assignPtr);
835-
$this->markVariableAssignment($varName, $writtenPtr, $currScope);
841+
$this->markVariableAssignment($varName, $stackPtr, $currScope);
836842
return true;
837843
}
838844

@@ -854,20 +860,28 @@ protected function checkForListAssignment(File $phpcsFile, $stackPtr, $varName,
854860
}
855861

856862
$prevPtr = $phpcsFile->findPrevious(Tokens::$emptyTokens, $openPtr - 1, null, true, null, true);
857-
if (($prevPtr === false) || ($tokens[$prevPtr]['code'] !== T_LIST)) {
863+
if ((is_bool($prevPtr)) || ($tokens[$prevPtr]['code'] !== T_LIST)) {
858864
return false;
859865
}
860866

861867
// OK, we're a list (...) construct... are we being assigned to?
862-
$closePtr = $tokens[$openPtr]['parenthesis_closer'];
863-
$assignPtr = Helpers::getNextAssignPointer($phpcsFile, $closePtr);
864-
if (! is_int($assignPtr)) {
868+
try {
869+
$assignments = Lists::getAssignments($phpcsFile, $prevPtr);
870+
} catch (\Exception $error) {
871+
return false;
872+
}
873+
$matchingAssignment = array_reduce($assignments, function ($thisAssignment, array $assignment) use ($stackPtr) {
874+
if (isset($assignment['assignment_token']) && $assignment['assignment_token'] === $stackPtr) {
875+
return $assignment;
876+
}
877+
return $thisAssignment;
878+
});
879+
if (! $matchingAssignment) {
865880
return false;
866881
}
867882

868883
// Yes, we're being assigned.
869-
$writtenPtr = Helpers::findWhereAssignExecuted($phpcsFile, $assignPtr);
870-
$this->markVariableAssignment($varName, $writtenPtr, $currScope);
884+
$this->markVariableAssignment($varName, $stackPtr, $currScope);
871885
return true;
872886
}
873887

VariableAnalysis/Tests/CodeAnalysis/VariableAnalysisTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,10 @@ public function testAllowDestructuringAssignment() {
739739
$expectedWarnings = [
740740
4,
741741
12,
742+
26,
743+
28,
744+
43,
745+
45,
742746
];
743747
$this->assertEquals($expectedWarnings, $lines);
744748
}

VariableAnalysis/Tests/CodeAnalysis/fixtures/DestructuringFixture.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,37 @@ function function_with_destructuring_assignment_using_list() {
1414
echo $b;
1515
echo $c;
1616
}
17+
18+
function function_with_nested_destructure_using_list() {
19+
list(
20+
$foo,
21+
list(
22+
$bar,
23+
)
24+
) = [ 'foo', [ 'bar' ] ];
25+
list(
26+
$baz, // unused
27+
list(
28+
$bap, //unused
29+
)
30+
) = [ 'foo', [ 'bar' ] ];
31+
echo $foo;
32+
echo $bar;
33+
}
34+
35+
function function_with_nested_destructure_using_short_list() {
36+
[
37+
$foo,
38+
[
39+
$bar,
40+
]
41+
] = [ 'foo', [ 'bar' ] ];
42+
[
43+
$baz, // unused
44+
[
45+
$bap, //unused
46+
]
47+
] = [ 'foo', [ 'bar' ] ];
48+
echo $foo;
49+
echo $bar;
50+
}

composer.circleci.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
},
3434
"require" : {
3535
"php" : ">=5.6.0",
36-
"squizlabs/php_codesniffer": "^3.1"
36+
"squizlabs/php_codesniffer": "^3.1",
37+
"phpcsstandards/phpcsutils": "^1.0"
3738
},
3839
"require-dev": {
3940
"phpunit/phpunit": "^5.0 || ^6.5 || ^7.0 || ^8.0"

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
},
3636
"require" : {
3737
"php" : ">=5.6.0",
38-
"squizlabs/php_codesniffer": "^3.1"
38+
"squizlabs/php_codesniffer": "^3.1",
39+
"phpcsstandards/phpcsutils": "^1.0"
3940
},
4041
"require-dev": {
4142
"dealerdirect/phpcodesniffer-composer-installer": "^0.4.4 || ^0.5 || ^0.6",

0 commit comments

Comments
 (0)