1111use VariableAnalysis \Lib \VariableInfo ;
1212use PHP_CodeSniffer \Util \Tokens ;
1313use PHPCSUtils \Utils \Context ;
14+ use PHPCSUtils \Utils \Lists ;
1415use PHPCSUtils \Utils \Parentheses ;
1516
1617class Helpers
@@ -733,64 +734,6 @@ public static function getArrowFunctionOpenClose(File $phpcsFile, $stackPtr)
733734 ];
734735 }
735736
736- /**
737- * Determine if a token is a list opener for list assignment/destructuring.
738- *
739- * The index provided can be either the opening square brace of a short list
740- * assignment like the first character of `[$a] = $b;` or the `list` token of
741- * an expression like `list($a) = $b;` or the opening parenthesis of that
742- * expression.
743- *
744- * @param File $phpcsFile
745- * @param int $listOpenerIndex
746- *
747- * @return bool
748- */
749- private static function isListAssignment (File $ phpcsFile , $ listOpenerIndex )
750- {
751- $ tokens = $ phpcsFile ->getTokens ();
752- // Match `[$a] = $b;` except for when the previous token is a parenthesis.
753- if ($ tokens [$ listOpenerIndex ]['code ' ] === T_OPEN_SHORT_ARRAY ) {
754- return true ;
755- }
756- // Match `list($a) = $b;`
757- if ($ tokens [$ listOpenerIndex ]['code ' ] === T_LIST ) {
758- return true ;
759- }
760-
761- // If $listOpenerIndex is the open parenthesis of `list($a) = $b;`, then
762- // match that too.
763- if ($ tokens [$ listOpenerIndex ]['code ' ] === T_OPEN_PARENTHESIS ) {
764- $ previousTokenPtr = $ phpcsFile ->findPrevious (Tokens::$ emptyTokens , $ listOpenerIndex - 1 , null , true );
765- if (
766- isset ($ tokens [$ previousTokenPtr ])
767- && $ tokens [$ previousTokenPtr ]['code ' ] === T_LIST
768- ) {
769- return true ;
770- }
771- return true ;
772- }
773-
774- // If the list opener token is a square bracket that is preceeded by a
775- // close parenthesis that has an owner which is a scope opener, then this
776- // is a list assignment and not an array access.
777- //
778- // Match `if (true) [$a] = $b;`
779- if ($ tokens [$ listOpenerIndex ]['code ' ] === T_OPEN_SQUARE_BRACKET ) {
780- $ previousTokenPtr = $ phpcsFile ->findPrevious (Tokens::$ emptyTokens , $ listOpenerIndex - 1 , null , true );
781- if (
782- isset ($ tokens [$ previousTokenPtr ])
783- && $ tokens [$ previousTokenPtr ]['code ' ] === T_CLOSE_PARENTHESIS
784- && isset ($ tokens [$ previousTokenPtr ]['parenthesis_owner ' ])
785- && isset (Tokens::$ scopeOpeners [$ tokens [$ tokens [$ previousTokenPtr ]['parenthesis_owner ' ]]['code ' ]])
786- ) {
787- return true ;
788- }
789- }
790-
791- return false ;
792- }
793-
794737 /**
795738 * Return a list of indices for variables assigned within a list assignment.
796739 *
@@ -806,74 +749,44 @@ private static function isListAssignment(File $phpcsFile, $listOpenerIndex)
806749 */
807750 public static function getListAssignments (File $ phpcsFile , $ listOpenerIndex )
808751 {
809- $ tokens = $ phpcsFile ->getTokens ();
810- self ::debug ('getListAssignments ' , $ listOpenerIndex , $ tokens [$ listOpenerIndex ]);
752+ self ::debug ('getListAssignments ' , $ listOpenerIndex , $ phpcsFile ->getTokens ()[$ listOpenerIndex ]);
811753
812- // First find the end of the list
813- $ closePtr = null ;
814- if (isset ($ tokens [$ listOpenerIndex ]['parenthesis_closer ' ])) {
815- $ closePtr = $ tokens [$ listOpenerIndex ]['parenthesis_closer ' ];
816- }
817- if (isset ($ tokens [$ listOpenerIndex ]['bracket_closer ' ])) {
818- $ closePtr = $ tokens [$ listOpenerIndex ]['bracket_closer ' ];
819- }
820- if (! $ closePtr ) {
754+ // Use PHPCSUtils to get detailed assignment information
755+ try {
756+ $ assignments = \PHPCSUtils \Utils \Lists::getAssignments ($ phpcsFile , $ listOpenerIndex );
757+ } catch (\PHPCSUtils \Exceptions \UnexpectedTokenType $ e ) {
758+ // Not a list token
821759 return null ;
822760 }
823761
824- // Find the assignment (equals sign) which, if this is a list assignment, should be the next non-space token
825- $ assignPtr = $ phpcsFile ->findNext (Tokens::$ emptyTokens , $ closePtr + 1 , null , true );
826-
827- // If the next token isn't an assignment, check for nested brackets because we might be a nested assignment
828- if (! is_int ($ assignPtr ) || $ tokens [$ assignPtr ]['code ' ] !== T_EQUAL ) {
829- // Collect the enclosing list open/close tokens ($parents is an assoc array keyed by opener index and the value is the closer index)
830- $ parents = isset ($ tokens [$ listOpenerIndex ]['nested_parenthesis ' ]) ? $ tokens [$ listOpenerIndex ]['nested_parenthesis ' ] : [];
831- // There's no record of nested brackets for short lists; we'll have to find the parent ourselves
832- if (empty ($ parents )) {
833- $ parentSquareBracketPtr = self ::findContainingOpeningSquareBracket ($ phpcsFile , $ listOpenerIndex );
834- if (is_int ($ parentSquareBracketPtr )) {
835- // Make sure that the parent is really a parent by checking that its
836- // closing index is outside of the current bracket's closing index.
837- $ parentSquareBracketToken = $ tokens [$ parentSquareBracketPtr ];
838- $ parentSquareBracketClosePtr = $ parentSquareBracketToken ['bracket_closer ' ];
839- if ($ parentSquareBracketClosePtr && $ parentSquareBracketClosePtr > $ closePtr ) {
840- self ::debug ("found enclosing bracket for {$ listOpenerIndex }: {$ parentSquareBracketPtr }" );
841- // Collect the opening index, but we don't actually need the closing paren index so just make that 0
842- $ parents = [$ parentSquareBracketPtr => 0 ];
843- }
844- }
845- }
846- // If we have no parents, this is not a nested assignment and therefore is not an assignment
847- if (empty ($ parents )) {
848- return null ;
849- }
850-
851- // Recursively check to see if the parent is a list assignment (we only need to check one level due to the recursion)
852- $ isNestedAssignment = null ;
853- $ parentListOpener = array_keys (array_reverse ($ parents , true ))[0 ];
854- $ isNestedAssignment = self ::getListAssignments ($ phpcsFile , $ parentListOpener );
855- if ($ isNestedAssignment === null ) {
856- return null ;
857- }
762+ if (empty ($ assignments )) {
763+ return null ;
858764 }
859765
766+ // Extract just the variable token positions for backward compatibility
860767 $ variablePtrs = [];
768+ foreach ($ assignments as $ assignment ) {
769+ // Skip empty list items like in: list($a, , $b)
770+ if ($ assignment ['is_empty ' ]) {
771+ continue ;
772+ }
861773
862- $ currentPtr = $ listOpenerIndex ;
863- $ variablePtr = 0 ;
864- while ($ currentPtr < $ closePtr && is_int ($ variablePtr )) {
865- $ variablePtr = $ phpcsFile ->findNext ([T_VARIABLE ], $ currentPtr + 1 , $ closePtr );
866- if (is_int ($ variablePtr )) {
867- $ variablePtrs [] = $ variablePtr ;
774+ // For nested lists, recursively get the assignments
775+ if ($ assignment ['is_nested_list ' ] && $ assignment ['assignment_token ' ] !== false ) {
776+ $ nestedVars = self ::getListAssignments ($ phpcsFile , $ assignment ['assignment_token ' ]);
777+ if (is_array ($ nestedVars )) {
778+ $ variablePtrs = array_merge ($ variablePtrs , $ nestedVars );
779+ }
780+ continue ;
868781 }
869- ++$ currentPtr ;
870- }
871782
872- if (! self ::isListAssignment ($ phpcsFile , $ listOpenerIndex )) {
873- return null ;
783+ // For regular variables, use the assignment_token which points to the T_VARIABLE
784+ if ($ assignment ['assignment_token ' ] !== false && $ assignment ['variable ' ] !== false ) {
785+ $ variablePtrs [] = $ assignment ['assignment_token ' ];
786+ }
874787 }
875788
876- return $ variablePtrs ;
789+ return empty ( $ variablePtrs ) ? null : $ variablePtrs ;
877790 }
878791
879792 /**
0 commit comments