From 5ce46a84c0109a40d66fca030c82e6a1865d4d45 Mon Sep 17 00:00:00 2001 From: Volker Dusch <247397+edorian@users.noreply.github.com> Date: Tue, 11 Jul 2023 09:17:50 +0200 Subject: [PATCH] Allow array unpacking in ArrayDeclaration multiline Sniff (#3843) Ignore array unpacking when determining whether an array is a list- or dict-shaped. Includes tests. Fixes #3557 --- .../Sniffs/Arrays/ArrayDeclarationSniff.php | 23 +++++++-- .../Arrays/ArrayDeclarationUnitTest.1.inc | 47 ++++++++++++++++- .../ArrayDeclarationUnitTest.1.inc.fixed | 47 ++++++++++++++++- .../Arrays/ArrayDeclarationUnitTest.2.inc | 50 +++++++++++++++++++ .../ArrayDeclarationUnitTest.2.inc.fixed | 50 +++++++++++++++++++ .../Tests/Arrays/ArrayDeclarationUnitTest.php | 8 +++ 6 files changed, 216 insertions(+), 9 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php b/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php index b45a4709de..64d5d64da2 100644 --- a/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php +++ b/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php @@ -430,9 +430,13 @@ public function processMultiLineArray($phpcsFile, $stackPtr, $arrayStart, $array } if ($keyUsed === true && $tokens[$lastToken]['code'] === T_COMMA) { - $error = 'No key specified for array entry; first entry specifies key'; - $phpcsFile->addError($error, $nextToken, 'NoKeySpecified'); - return; + $nextToken = $phpcsFile->findNext(Tokens::$emptyTokens, ($lastToken + 1), null, true); + // Allow for PHP 7.4+ array unpacking within an array declaration. + if ($tokens[$nextToken]['code'] !== T_ELLIPSIS) { + $error = 'No key specified for array entry; first entry specifies key'; + $phpcsFile->addError($error, $nextToken, 'NoKeySpecified'); + return; + } } if ($keyUsed === false) { @@ -470,8 +474,17 @@ public function processMultiLineArray($phpcsFile, $stackPtr, $arrayStart, $array true ); - $indices[] = ['value' => $valueContent]; - $singleUsed = true; + $indices[] = ['value' => $valueContent]; + $usesArrayUnpacking = $phpcsFile->findPrevious( + Tokens::$emptyTokens, + ($nextToken - 2), + null, + true + ); + if ($tokens[$usesArrayUnpacking]['code'] !== T_ELLIPSIS) { + // Don't decide if an array is key => value indexed or not when PHP 7.4+ array unpacking is used. + $singleUsed = true; + } }//end if $lastToken = $nextToken; diff --git a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc index 2774660c0c..b96aec7bde 100644 --- a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc +++ b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc @@ -475,14 +475,57 @@ yield array( static fn () : string => '', ); -$foo = [ +$foo = array( 'foo' => match ($anything) { 'foo' => 'bar', default => null, }, - ]; + ); // Intentional syntax error. $a = array( 'a' => ); + +// Safeguard correct errors for key/no key when PHP 7.4+ array unpacking is encountered. +$x = array( + ...$a, + 'foo' => 'bar', + ); + +$x = array( + 'foo' => 'bar', + ...$a, + ); + +$x = array( + 'foo' => 'bar', + ...$a, + 'baz' => 'bar', + ); + +$x = array( + ...$a, + 'foo' => 'bar', // OK. + 'bar', // NoKeySpecified Error (based on second entry). + ); + +$x = array( + ...$a, + 'bar', // OK. + 'foo' => 'bar', // KeySpecified Error (based on second entry). + ); + +$x = array( + 'foo' => 'bar', + ...$a, + 'baz' => 'bar', + 'bar', // NoKeySpecified Error (based on first entry). + ); + +$x = array( + 'bar', + ...$a, + 'bar', + 'baz' => 'bar', // KeySpecified (based on first entry). + ); diff --git a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc.fixed b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc.fixed index b452006488..9da6a4279b 100644 --- a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc.fixed +++ b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc.fixed @@ -511,14 +511,57 @@ yield array( static fn () : string => '', ); -$foo = [ +$foo = array( 'foo' => match ($anything) { 'foo' => 'bar', default => null, }, - ]; + ); // Intentional syntax error. $a = array( 'a' => ); + +// Safeguard correct errors for key/no key when PHP 7.4+ array unpacking is encountered. +$x = array( + ...$a, + 'foo' => 'bar', + ); + +$x = array( + 'foo' => 'bar', + ...$a, + ); + +$x = array( + 'foo' => 'bar', + ...$a, + 'baz' => 'bar', + ); + +$x = array( + ...$a, + 'foo' => 'bar', // OK. + 'bar', // NoKeySpecified Error (based on second entry). + ); + +$x = array( + ...$a, + 'bar', // OK. + 'foo' => 'bar', // KeySpecified Error (based on second entry). + ); + +$x = array( + 'foo' => 'bar', + ...$a, + 'baz' => 'bar', + 'bar', // NoKeySpecified Error (based on first entry). + ); + +$x = array( + 'bar', + ...$a, + 'bar', + 'baz' => 'bar', // KeySpecified (based on first entry). + ); diff --git a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc index 621970fa1d..0c8b48fc89 100644 --- a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc +++ b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc @@ -464,7 +464,57 @@ yield [ static fn () : string => '', ]; +$foo = [ + 'foo' => match ($anything) { + 'foo' => 'bar', + default => null, + }, + ]; + // Intentional syntax error. $a = [ 'a' => ]; + +// Safeguard correct errors for key/no key when PHP 7.4+ array unpacking is encountered. +$x = [ + ...$a, + 'foo' => 'bar', + ]; + +$x = [ + 'foo' => 'bar', + ...$a, + ]; + +$x = [ + 'foo' => 'bar', + ...$a, + 'baz' => 'bar', + ]; + +$x = [ + ...$a, + 'foo' => 'bar', // OK. + 'bar', // NoKeySpecified Error (based on second entry). + ]; + +$x = [ + ...$a, + 'bar', // OK. + 'foo' => 'bar', // KeySpecified Error (based on second entry). + ]; + +$x = [ + 'foo' => 'bar', + ...$a, + 'baz' => 'bar', + 'bar', // NoKeySpecified Error (based on first entry). + ]; + +$x = [ + 'bar', + ...$a, + 'bar', + 'baz' => 'bar', // KeySpecified (based on first entry). + ]; diff --git a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc.fixed b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc.fixed index efe4c450e9..4b09e2f234 100644 --- a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc.fixed +++ b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc.fixed @@ -498,7 +498,57 @@ yield [ static fn () : string => '', ]; +$foo = [ + 'foo' => match ($anything) { + 'foo' => 'bar', + default => null, + }, + ]; + // Intentional syntax error. $a = [ 'a' => ]; + +// Safeguard correct errors for key/no key when PHP 7.4+ array unpacking is encountered. +$x = [ + ...$a, + 'foo' => 'bar', + ]; + +$x = [ + 'foo' => 'bar', + ...$a, + ]; + +$x = [ + 'foo' => 'bar', + ...$a, + 'baz' => 'bar', + ]; + +$x = [ + ...$a, + 'foo' => 'bar', // OK. + 'bar', // NoKeySpecified Error (based on second entry). + ]; + +$x = [ + ...$a, + 'bar', // OK. + 'foo' => 'bar', // KeySpecified Error (based on second entry). + ]; + +$x = [ + 'foo' => 'bar', + ...$a, + 'baz' => 'bar', + 'bar', // NoKeySpecified Error (based on first entry). + ]; + +$x = [ + 'bar', + ...$a, + 'bar', + 'baz' => 'bar', // KeySpecified (based on first entry). + ]; diff --git a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.php b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.php index 15ce0746ef..9d917b46b0 100644 --- a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.php +++ b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.php @@ -124,6 +124,10 @@ public function getErrorList($testFile='') 467 => 1, 471 => 1, 472 => 1, + 510 => 1, + 516 => 1, + 523 => 1, + 530 => 1, ]; case 'ArrayDeclarationUnitTest.2.inc': return [ @@ -210,6 +214,10 @@ public function getErrorList($testFile='') 456 => 1, 460 => 1, 461 => 1, + 499 => 1, + 505 => 1, + 512 => 1, + 519 => 1, ]; default: return [];