Skip to content

Commit

Permalink
Arrays::isShortArray()/Lists::isShortList(): consolidate the caching [2]
Browse files Browse the repository at this point in the history
This is step 2 in a refactor to improve short list/short array determination.

While caching had previously already been implemented, as things were, there could be four caches which would each refer to the same result, i.e.
1. A cache for an opener token passed to `Arrays::isShortArray()`.
2. A cache for an closer token of the same set of brackets passed to `Arrays::isShortArray()`.
3. A cache for an opener token of the same set of brackets passed to `Lists::isShortList()`.
4. A cache for an closer token of the same set of brackets passed to `Lists::isShortList()`.

This commit introduces a wrapper class around the `IsShortArrayOrList` class which will handle the caching and will only create one cache entry for all four potential starting points.

As this will now be the entry point to the `IsShortArrayOrList` class, we can rely upon the token being passed to the `IsShortArrayOrList` always being an _opener_ for the brackets and can remove some redundancy from the class.

Includes updating the tests to no longer pass closer tokens to the `IsShortArrayOrList` class and to use the correct cache key to test the caching functionality.
  • Loading branch information
jrfnl committed Oct 20, 2022
1 parent c04bdda commit bf14db9
Show file tree
Hide file tree
Showing 6 changed files with 273 additions and 74 deletions.
24 changes: 4 additions & 20 deletions PHPCSUtils/Internal/IsShortArrayOrList.php
Expand Up @@ -25,8 +25,8 @@
* This class is only intended for internal use by PHPCSUtils and is not part of the public API.
* This also means that it has no promise of backward compatibility.
*
* End-user should use the {@see \PHPCSUtils\Utils\Arrays::isShortArray()}
* or the {@see \PHPCSUtils\Utils\Lists::isShortList()} function instead.
* End-users should use the {@see \PHPCSUtils\Utils\Arrays::isShortArray()}
* or the {@see \PHPCSUtils\Utils\Lists::isShortList()} method instead.
* ---------------------------------------------------------------------------------------------
*
* @internal
Expand Down Expand Up @@ -72,15 +72,6 @@ final class IsShortArrayOrList
*/
private $phpcsFile;

/**
* The current stack pointer.
*
* @since 1.0.0-alpha4
*
* @var int
*/
private $stackPtr;

/**
* The token stack from the current file.
*
Expand Down Expand Up @@ -141,7 +132,7 @@ final class IsShortArrayOrList
* @since 1.0.0-alpha4
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the short array bracket token.
* @param int $stackPtr The position of the short array opener token.
*
* @return void
*/
Expand All @@ -155,15 +146,8 @@ public function __construct(File $phpcsFile, $stackPtr)
}

$this->phpcsFile = $phpcsFile;
$this->stackPtr = $stackPtr;
$this->tokens = $tokens;

$this->opener = $stackPtr;
if (isset($this->tokens[$stackPtr]['bracket_opener'])
&& $stackPtr !== $this->tokens[$stackPtr]['bracket_opener']
) {
$this->opener = $this->tokens[$stackPtr]['bracket_opener'];
}
$this->opener = $stackPtr;

$this->closer = $stackPtr;
if (isset($this->tokens[$stackPtr]['bracket_closer'])
Expand Down
251 changes: 251 additions & 0 deletions PHPCSUtils/Internal/IsShortArrayOrListWithCache.php
@@ -0,0 +1,251 @@
<?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\Internal;

use PHP_CodeSniffer\Files\File;
use PHPCSUtils\Internal\Cache;
use PHPCSUtils\Internal\IsShortArrayOrList;
use PHPCSUtils\Tokens\Collections;

/**
* Determination of short array vs short list vs square brackets.
*
* Uses caching of previous results to mitigate performance issues.
*
* ---------------------------------------------------------------------------------------------
* This class is only intended for internal use by PHPCSUtils and is not part of the public API.
* This also means that it has no promise of backward compatibility.
*
* End-users should use the {@see \PHPCSUtils\Utils\Arrays::isShortArray()}
* or the {@see \PHPCSUtils\Utils\Lists::isShortList()} method instead.
* ---------------------------------------------------------------------------------------------
*
* @internal
*
* @since 1.0.0-alpha4
*/
final class IsShortArrayOrListWithCache
{

/**
* Tokens which are used for short arrays (PHPCS cross-version compatible).
*
* @since 1.0.0-alpha4
*
* @var array <int|string> => <int|string>
*/
private $shortArrayTokensBC;

/**
* 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-alpha4
*
* @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 (self::getType($phpcsFile, $stackPtr) === IsShortArrayOrList::SHORT_ARRAY);
}

/**
* Determine whether a T_OPEN/CLOSE_SHORT_ARRAY token is a short list construct
* in contrast to a short array.
*
* 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-alpha4
*
* @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 (self::getType($phpcsFile, $stackPtr) === IsShortArrayOrList::SHORT_LIST);
}

/**
* Determine whether a T_OPEN/CLOSE_SHORT_ARRAY token is a short array or 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-alpha4
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the short array bracket token.
*
* @return string|false The type of construct this bracket was determined to be.
* Either 'short array', 'short list' or 'square brackets'.
* Or FALSE is this was not a bracket token.
*/
public static function getType(File $phpcsFile, $stackPtr)
{
return (new self())->process($phpcsFile, $stackPtr);
}

/**
* Constructor.
*
* @since 1.0.0-alpha4
*
* @return void
*/
public function __construct()
{
$this->shortArrayTokensBC = Collections::shortArrayTokensBC();
}

/**
* Determine whether a T_[OPEN|CLOSE}_[SHORT_ARRAY|SQUARE_BRACKET] token is a short array
* or short list construct using previously cached results whenever possible.
*
* @since 1.0.0-alpha4
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the short array bracket token.
*
* @return string|false The type of construct this bracket was determined to be.
* Either 'short array', 'short list' or 'square brackets'.
* Or FALSE is this was not a bracket token.
*/
private function process(File $phpcsFile, $stackPtr)
{
if ($this->isValidStackPtr($phpcsFile, $stackPtr) === false) {
return false;
}

$opener = $this->getOpener($phpcsFile, $stackPtr);

/*
* Check the cache in case we've seen this token before.
* The cache _may_ return an empty string.
*/
$type = $this->getFromCache($phpcsFile, $opener);
if ($type !== false) {
return $type;
}

/*
* If we've not seen the token before, try and solve it and cache the results.
*/
$solver = new IsShortArrayOrList($phpcsFile, $opener);
$type = $solver->solve();

$this->updateCache($phpcsFile, $opener, $type);

return $type;
}

/**
* Verify the passed token could potentially be a short array or short list token.
*
* @since 1.0.0-alpha4
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the short array bracket token.
*
* @return bool
*/
private function isValidStackPtr(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();

return (isset($tokens[$stackPtr]) === true
&& isset($this->shortArrayTokensBC[$tokens[$stackPtr]['code']]) === true);
}

/**
* Get the stack pointer to the short array/list opener.
*
* @since 1.0.0-alpha4
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of a short array bracket token.
*
* @return bool
*/
private function getOpener(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();

$opener = $stackPtr;
if (isset($tokens[$stackPtr]['bracket_opener'])
&& $stackPtr !== $tokens[$stackPtr]['bracket_opener']
) {
$opener = $tokens[$stackPtr]['bracket_opener'];
}

return $opener;
}

/**
* Retrieve the bracket "type" of a token from the cache.
*
* @since 1.0.0-alpha4
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the short array OPEN bracket token.
*
* @return string|false The previously determined type (which could be an empty string)
* or FALSE if no cache entry was found for this token.
*/
private function getFromCache(File $phpcsFile, $stackPtr)
{
if (Cache::isCached($phpcsFile, __CLASS__, $stackPtr) === true) {
return Cache::get($phpcsFile, __CLASS__, $stackPtr);
}

return false;
}

/**
* Update the cache with information about a particular bracket token.
*
* @since 1.0.0-alpha4
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the short array OPEN bracket token.
* @param string $type The type this bracket has been determined to be.
* Either 'short array', 'short list' or 'square brackets'.
*
* @return void
*/
private function updateCache(File $phpcsFile, $stackPtr, $type)
{
$result = false;
if ($type === IsShortArrayOrList::SHORT_ARRAY
|| $type === IsShortArrayOrList::SHORT_LIST
|| $type === IsShortArrayOrList::SQUARE_BRACKETS
) {
$result = $type;
}

Cache::set($phpcsFile, __CLASS__, $stackPtr, $result);
}
}
22 changes: 2 additions & 20 deletions PHPCSUtils/Utils/Arrays.php
Expand Up @@ -13,7 +13,7 @@
use PHP_CodeSniffer\Exceptions\RuntimeException;
use PHP_CodeSniffer\Files\File;
use PHPCSUtils\Internal\Cache;
use PHPCSUtils\Internal\IsShortArrayOrList;
use PHPCSUtils\Internal\IsShortArrayOrListWithCache;
use PHPCSUtils\Tokens\Collections;

/**
Expand Down Expand Up @@ -64,25 +64,7 @@ final class Arrays
*/
public static function isShortArray(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();

// Is this one of the tokens this function handles ?
if (isset($tokens[$stackPtr]) === false
|| isset(Collections::shortArrayTokensBC()[$tokens[$stackPtr]['code']]) === false
) {
return false;
}

if (Cache::isCached($phpcsFile, __METHOD__, $stackPtr) === true) {
return Cache::get($phpcsFile, __METHOD__, $stackPtr);
}

$solver = new IsShortArrayOrList($phpcsFile, $stackPtr);
$type = $solver->solve();
$returnValue = ($type === IsShortArrayOrList::SHORT_ARRAY);

Cache::set($phpcsFile, __METHOD__, $stackPtr, $returnValue);
return $returnValue;
return IsShortArrayOrListWithCache::isShortArray($phpcsFile, $stackPtr);
}

/**
Expand Down
22 changes: 2 additions & 20 deletions PHPCSUtils/Utils/Lists.php
Expand Up @@ -14,7 +14,7 @@
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Tokens;
use PHPCSUtils\Internal\Cache;
use PHPCSUtils\Internal\IsShortArrayOrList;
use PHPCSUtils\Internal\IsShortArrayOrListWithCache;
use PHPCSUtils\Tokens\Collections;
use PHPCSUtils\Utils\GetTokensAsString;

Expand Down Expand Up @@ -66,25 +66,7 @@ final class Lists
*/
public static function isShortList(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();

// Is this one of the tokens this function handles ?
if (isset($tokens[$stackPtr]) === false
|| isset(Collections::shortListTokensBC()[$tokens[$stackPtr]['code']]) === false
) {
return false;
}

if (Cache::isCached($phpcsFile, __METHOD__, $stackPtr) === true) {
return Cache::get($phpcsFile, __METHOD__, $stackPtr);
}

$solver = new IsShortArrayOrList($phpcsFile, $stackPtr);
$type = $solver->solve();
$returnValue = ($type === IsShortArrayOrList::SHORT_LIST);

Cache::set($phpcsFile, __METHOD__, $stackPtr, $returnValue);
return $returnValue;
return IsShortArrayOrListWithCache::isShortList($phpcsFile, $stackPtr);
}

/**
Expand Down
Expand Up @@ -70,7 +70,7 @@ echo 'just here to prevent the below test running into a tokenizer issue tested
/* testShortList */
[$b] = $c;

/* testShortListDetectOnCloseBracket */
/* testShortListMultiItem */
[$a, $b] = $c;

/* testShortListWithKeys */
Expand Down

0 comments on commit bf14db9

Please sign in to comment.