wishthis/vendor/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Arrays.php

228 lines
8.5 KiB
PHP
Raw Normal View History

2023-09-19 17:26:46 +00:00
<?php
/**
* PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers.
*
* @package PHPCSUtils
* @copyright 2019-2020 PHPCSUtils Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCSStandards/PHPCSUtils
*/
namespace PHPCSUtils\Utils;
use PHP_CodeSniffer\Exceptions\RuntimeException;
use PHP_CodeSniffer\Files\File;
use PHPCSUtils\Internal\Cache;
use PHPCSUtils\Internal\IsShortArrayOrListWithCache;
use PHPCSUtils\Tokens\Collections;
/**
* Utility functions for use when examining arrays.
*
* @since 1.0.0
*/
final class Arrays
{
/**
* The tokens to target to find the double arrow in an array item.
*
* @since 1.0.0
*
* @var array <int|string> => <int|string>
*/
private static $doubleArrowTargets = [
\T_DOUBLE_ARROW => \T_DOUBLE_ARROW,
// Nested arrays.
\T_ARRAY => \T_ARRAY,
\T_OPEN_SHORT_ARRAY => \T_OPEN_SHORT_ARRAY,
// Inline function, control structures and other things to skip over.
\T_LIST => \T_LIST,
\T_FN => \T_FN,
\T_MATCH => \T_MATCH,
\T_ATTRIBUTE => \T_ATTRIBUTE,
];
/**
* Determine whether a T_OPEN/CLOSE_SHORT_ARRAY token is a short array construct
* and not a short list.
*
* 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.
*
* @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 array.
* `FALSE` if the token is a short list bracket, a plain square bracket
* or not one of the accepted tokens.
*/
public static function isShortArray(File $phpcsFile, $stackPtr)
{
return IsShortArrayOrListWithCache::isShortArray($phpcsFile, $stackPtr);
}
/**
* Find the array opener and closer based on a T_ARRAY 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 array determination.
*
* @since 1.0.0
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the `T_ARRAY` or `T_OPEN_SHORT_ARRAY`
* token in the stack.
* @param true|null $isShortArray Short-circuit the short array 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 array while walking the
* tokens in an array.
* Use with care.
*
* @return array|false An array with the token pointers; or `FALSE` if this is not a
* (short) array 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 array open bracket.
* 'closer' => integer, // Stack pointer to the array close bracket.
* )
* ```
*/
public static function getOpenClose(File $phpcsFile, $stackPtr, $isShortArray = null)
{
$tokens = $phpcsFile->getTokens();
// Is this one of the tokens this function handles ?
if (isset($tokens[$stackPtr]) === false
|| isset(Collections::arrayOpenTokensBC()[$tokens[$stackPtr]['code']]) === false
) {
return false;
}
switch ($tokens[$stackPtr]['code']) {
case \T_ARRAY:
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 ($isShortArray === true || self::isShortArray($phpcsFile, $stackPtr) === true) {
$opener = $stackPtr;
$closer = $tokens[$stackPtr]['bracket_closer'];
}
break;
}
if (isset($opener, $closer)) {
return [
'opener' => $opener,
'closer' => $closer,
];
}
return false;
}
/**
* Get the stack pointer position of the double arrow within an array item.
*
* Expects to be passed the array item start and end tokens as retrieved via
* {@see \PHPCSUtils\Utils\PassedParameters::getParameters()}.
*
* @since 1.0.0
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being examined.
* @param int $start Stack pointer to the start of the array item.
* @param int $end Stack pointer to the last token in the array item.
*
* @return int|false Stack pointer to the double arrow if this array item has a key; or `FALSE` otherwise.
*
* @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the start or end positions are invalid.
*/
public static function getDoubleArrowPtr(File $phpcsFile, $start, $end)
{
$tokens = $phpcsFile->getTokens();
if (isset($tokens[$start], $tokens[$end]) === false || $start > $end) {
throw new RuntimeException(
'Invalid start and/or end position passed to getDoubleArrowPtr().'
. ' Received: $start ' . $start . ', $end ' . $end
);
}
$cacheId = "$start-$end";
if (Cache::isCached($phpcsFile, __METHOD__, $cacheId) === true) {
return Cache::get($phpcsFile, __METHOD__, $cacheId);
}
$targets = self::$doubleArrowTargets;
$targets += Collections::closedScopes();
$doubleArrow = ($start - 1);
$returnValue = false;
++$end;
do {
$doubleArrow = $phpcsFile->findNext(
$targets,
($doubleArrow + 1),
$end
);
if ($doubleArrow === false) {
break;
}
if ($tokens[$doubleArrow]['code'] === \T_DOUBLE_ARROW) {
$returnValue = $doubleArrow;
break;
}
// Skip over closed scopes which may contain foreach structures or generators.
if ((isset(Collections::closedScopes()[$tokens[$doubleArrow]['code']]) === true
|| $tokens[$doubleArrow]['code'] === \T_FN
|| $tokens[$doubleArrow]['code'] === \T_MATCH)
&& isset($tokens[$doubleArrow]['scope_closer']) === true
) {
$doubleArrow = $tokens[$doubleArrow]['scope_closer'];
continue;
}
// Skip over attributes which may contain arrays as a passed parameters.
if ($tokens[$doubleArrow]['code'] === \T_ATTRIBUTE
&& isset($tokens[$doubleArrow]['attribute_closer'])
) {
$doubleArrow = $tokens[$doubleArrow]['attribute_closer'];
continue;
}
// Skip over potentially keyed long lists.
if ($tokens[$doubleArrow]['code'] === \T_LIST
&& isset($tokens[$doubleArrow]['parenthesis_closer'])
) {
$doubleArrow = $tokens[$doubleArrow]['parenthesis_closer'];
continue;
}
// Start of nested long/short array.
break;
} while ($doubleArrow < $end);
Cache::set($phpcsFile, __METHOD__, $cacheId, $returnValue);
return $returnValue;
}
}