'', 'assignment' => '', 'is_empty' => false, 'is_nested_list' => false, 'variable' => false, 'assignment_token' => false, 'assignment_end_token' => false, 'assign_by_reference' => false, 'reference_token' => false, ]; /** * Determine whether a T_OPEN/CLOSE_SHORT_ARRAY token is a short list() construct. * * This method also accepts `T_OPEN/CLOSE_SQUARE_BRACKET` tokens to allow it to be * PHPCS cross-version compatible as the short array tokenizing has been plagued by * a number of bugs over time, which affects the short list determination. * * @since 1.0.0 * * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. * @param int $stackPtr The position of the short array bracket token. * * @return bool `TRUE` if the token passed is the open/close bracket of a short list. * `FALSE` if the token is a short array bracket or plain square bracket * or not one of the accepted tokens. */ public static function isShortList(File $phpcsFile, $stackPtr) { return IsShortArrayOrListWithCache::isShortList($phpcsFile, $stackPtr); } /** * Find the list opener and closer based on a T_LIST or T_OPEN_SHORT_ARRAY token. * * This method also accepts `T_OPEN_SQUARE_BRACKET` tokens to allow it to be * PHPCS cross-version compatible as the short array tokenizing has been plagued by * a number of bugs over time, which affects the short list determination. * * @since 1.0.0 * * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. * @param int $stackPtr The position of the T_LIST or T_OPEN_SHORT_ARRAY * token in the stack. * @param true|null $isShortList Short-circuit the short list check for T_OPEN_SHORT_ARRAY * tokens if it isn't necessary. * Efficiency tweak for when this has already been established, * i.e. when encountering a nested list while walking the * tokens in a list. * Use with care. * * @return array|false An array with the token pointers; or `FALSE` if this is not a (short) list * token or if the opener/closer could not be determined. * The format of the array return value is: * ```php * array( * 'opener' => integer, // Stack pointer to the list open bracket. * 'closer' => integer, // Stack pointer to the list close bracket. * ) * ``` */ public static function getOpenClose(File $phpcsFile, $stackPtr, $isShortList = null) { $tokens = $phpcsFile->getTokens(); // Is this one of the tokens this function handles ? if (isset($tokens[$stackPtr]) === false || isset(Collections::listOpenTokensBC()[$tokens[$stackPtr]['code']]) === false ) { return false; } switch ($tokens[ $stackPtr ]['code']) { case \T_LIST: if (isset($tokens[$stackPtr]['parenthesis_opener'])) { $opener = $tokens[$stackPtr]['parenthesis_opener']; if (isset($tokens[$opener]['parenthesis_closer'])) { $closer = $tokens[$opener]['parenthesis_closer']; } } break; case \T_OPEN_SHORT_ARRAY: case \T_OPEN_SQUARE_BRACKET: if ($isShortList === true || self::isShortList($phpcsFile, $stackPtr) === true) { $opener = $stackPtr; $closer = $tokens[$stackPtr]['bracket_closer']; } break; } if (isset($opener, $closer)) { return [ 'opener' => $opener, 'closer' => $closer, ]; } return false; } /** * Retrieves information on the assignments made in the specified (long/short) list. * * This method also accepts `T_OPEN_SQUARE_BRACKET` tokens to allow it to be * PHPCS cross-version compatible as the short array tokenizing has been plagued by * a number of bugs over time, which affects the short list determination. * * The returned array will contain the following basic information for each assignment: * * ```php * 0 => array( * 'raw' => string, // The full content of the variable definition, * // including whitespace and comments. * // This may be an empty string when a list * // item is being skipped. * 'assignment' => string, // The content of the assignment part, * // cleaned of comments. * // This may be an empty string for an empty * // list item; it could also be a nested list * // represented as a string. * 'is_empty' => bool, // Whether this is an empty list item, i.e. * // the second item in `list($a, , $b)`. * 'is_nested_list' => bool, // Whether this is a nested list. * 'variable' => string|false, // The base variable being assigned to; or * // FALSE in case of a nested list or * // a variable variable. * // I.e. `$a` in `list($a['key'])`. * 'assignment_token' => int|false, // The start pointer for the assignment. * // For a nested list, this will be the pointer * // to the `list` keyword or the open square * // bracket in case of a short list. * 'assignment_end_token' => int|false, // The end pointer for the assignment. * 'assign_by_reference' => bool, // Is the variable assigned by reference? * 'reference_token' => int|false, // The stack pointer to the reference operator; * // or FALSE when not a reference assignment. * ) * ``` * * Assignments with keys will have the following additional array indexes set: * ```php * 'key' => string, // The content of the key, cleaned of comments. * 'key_token' => int, // The stack pointer to the start of the key. * 'key_end_token' => int, // The stack pointer to the end of the key. * 'double_arrow_token' => int, // The stack pointer to the double arrow. * ``` * * @since 1.0.0 * * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. * @param int $stackPtr The position in the stack of the function token * to acquire the parameters for. * * @return array An array with information on each assignment made, including skipped assignments (empty), * or an empty array if no assignments are made at all (fatal error in PHP >= 7.0). * * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified $stackPtr is not of * type T_LIST, T_OPEN_SHORT_ARRAY or * T_OPEN_SQUARE_BRACKET. */ public static function getAssignments(File $phpcsFile, $stackPtr) { $openClose = self::getOpenClose($phpcsFile, $stackPtr); if ($openClose === false) { // The `getOpenClose()` method does the $stackPtr validation. throw new RuntimeException('The Lists::getAssignments() method expects a long/short list token.'); } if (Cache::isCached($phpcsFile, __METHOD__, $stackPtr) === true) { return Cache::get($phpcsFile, __METHOD__, $stackPtr); } $opener = $openClose['opener']; $closer = $openClose['closer']; $tokens = $phpcsFile->getTokens(); $vars = []; $start = null; $lastNonEmpty = null; $reference = null; $list = null; $lastComma = $opener; $keys = []; for ($i = ($opener + 1); $i <= $closer; $i++) { if (isset(Tokens::$emptyTokens[$tokens[$i]['code']])) { continue; } switch ($tokens[$i]['code']) { case \T_DOUBLE_ARROW: $keys['key'] = GetTokensAsString::compact($phpcsFile, $start, $lastNonEmpty, true); $keys['key_token'] = $start; $keys['key_end_token'] = $lastNonEmpty; $keys['double_arrow_token'] = $i; // Partial reset. $start = null; $lastNonEmpty = null; $list = null; // Prevent confusion when short array was used as the key. $reference = null; break; case \T_COMMA: case $tokens[$closer]['code']: // Check if this is the end of the list or only a token with the same type as the list closer. if ($tokens[$i]['code'] === $tokens[$closer]['code']) { if ($i !== $closer) { /* * Shouldn't be possible anymore now nested brackets are being skipped over, * but keep it just in case. */ // @codeCoverageIgnoreStart $lastNonEmpty = $i; break; // @codeCoverageIgnoreEnd } elseif ($start === null && $lastComma === $opener) { // This is an empty list. break 2; } } // Ok, so this is actually the end of the list item. $current = self::$listItemDefaults; $current['raw'] = \trim(GetTokensAsString::normal($phpcsFile, ($lastComma + 1), ($i - 1))); if ($start === null) { $current['is_empty'] = true; } else { $current['assignment'] = \trim( GetTokensAsString::compact($phpcsFile, $start, $lastNonEmpty, true) ); $current['is_nested_list'] = isset($list); $current['variable'] = false; if (isset($list) === false && $tokens[$start]['code'] === \T_VARIABLE) { $current['variable'] = $tokens[$start]['content']; } $current['assignment_token'] = $start; $current['assignment_end_token'] = $lastNonEmpty; if (isset($reference)) { $current['assign_by_reference'] = true; $current['reference_token'] = $reference; } } if (empty($keys) === false) { $current += $keys; } $vars[] = $current; // Reset. $start = null; $lastNonEmpty = null; $reference = null; $list = null; $lastComma = $i; $keys = []; break; case \T_LIST: case \T_OPEN_SHORT_ARRAY: if ($start === null) { $start = $i; } /* * As the top level list has an open/close, we know we don't have a parse error and * any nested (short) arrays/lists will be tokenized correctly, so no need for extra checks here. */ $nestedOpenClose = self::getOpenClose($phpcsFile, $i, true); $list = $i; $i = $nestedOpenClose['closer']; $lastNonEmpty = $i; break; case \T_BITWISE_AND: $reference = $i; $lastNonEmpty = $i; break; default: if ($start === null) { $start = $i; } // Skip over everything within all types of brackets which may be used in keys. if (isset($tokens[$i]['bracket_opener'], $tokens[$i]['bracket_closer']) && $i === $tokens[$i]['bracket_opener'] ) { $i = $tokens[$i]['bracket_closer']; } elseif ($tokens[$i]['code'] === \T_OPEN_PARENTHESIS && isset($tokens[$i]['parenthesis_closer']) ) { $i = $tokens[$i]['parenthesis_closer']; } elseif (isset($tokens[$i]['scope_condition'], $tokens[$i]['scope_closer']) && $tokens[$i]['scope_condition'] === $i ) { $i = $tokens[$i]['scope_closer']; } elseif ($tokens[$i]['code'] === \T_ATTRIBUTE && isset($tokens[$i]['attribute_closer']) ) { $i = $tokens[$i]['attribute_closer']; } $lastNonEmpty = $i; break; } } Cache::set($phpcsFile, __METHOD__, $stackPtr, $vars); return $vars; } }