Skip to content

Commit

Permalink
Merge pull request #392 from PHPCSStandards/utils/isshortlist-isshort…
Browse files Browse the repository at this point in the history
…array-refactor-improve-performance

IsShortArrayOrList: refactor for better accuracy and improved performance
  • Loading branch information
jrfnl committed Oct 25, 2022
2 parents 9a6d622 + 6cd5532 commit 75e13f4
Show file tree
Hide file tree
Showing 44 changed files with 4,306 additions and 1,579 deletions.
700 changes: 700 additions & 0 deletions PHPCSUtils/Internal/IsShortArrayOrList.php

Large diffs are not rendered by default.

269 changes: 269 additions & 0 deletions PHPCSUtils/Internal/IsShortArrayOrListWithCache.php
@@ -0,0 +1,269 @@
<?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
{

/**
* Key used for caching the return value of the short array/short list determination.
*
* @since 1.0.0-alpha4
*
* @var string
*/
const CACHE_KEY = __CLASS__;

/**
* The PHPCS file in which the current stackPtr was found.
*
* @since 1.0.0-alpha4
*
* @var \PHP_CodeSniffer\Files\File
*/
private $phpcsFile;

/**
* The token stack from the current file.
*
* @since 1.0.0-alpha4
*
* @var array
*/
private $tokens;

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

/**
* Stack pointer to the open bracket.
*
* @since 1.0.0-alpha4
*
* @var int
*/
private $opener;

/**
* 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($phpcsFile, $stackPtr))->process();
}

/**
* Constructor.
*
* @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 void
*/
private function __construct(File $phpcsFile, $stackPtr)
{
$this->phpcsFile = $phpcsFile;
$this->tokens = $phpcsFile->getTokens();
$this->stackPtr = $stackPtr;
}

/**
* 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
*
* @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()
{
if ($this->isValidStackPtr() === false) {
return false;
}

$this->opener = $this->getOpener();

/*
* Check the cache in case we've seen this token before.
*/
$type = $this->getFromCache();
if ($type !== false) {
return $type;
}

/*
* If we've not seen the token before, try and solve it and cache the results.
*
* Make sure to safeguard against unopened/unclosed square brackets (parse error),
* which should always be regarded as real square brackets.
*/
$type = IsShortArrayOrList::SQUARE_BRACKETS;
if (isset($this->tokens[$this->stackPtr]['bracket_opener'], $this->tokens[$this->stackPtr]['bracket_closer'])) {
$solver = new IsShortArrayOrList($this->phpcsFile, $this->opener);
$type = $solver->solve();
}

$this->updateCache($type);

return $type;
}

/**
* Verify the passed token could potentially be a short array or short list token.
*
* @since 1.0.0-alpha4
*
* @return bool
*/
private function isValidStackPtr()
{
return (isset($this->tokens[$this->stackPtr]) === true
&& isset(Collections::shortArrayTokensBC()[$this->tokens[$this->stackPtr]['code']]) === true);
}

/**
* Get the stack pointer to the short array/list opener.
*
* @since 1.0.0-alpha4
*
* @return int
*/
private function getOpener()
{
$opener = $this->stackPtr;
if (isset($this->tokens[$this->stackPtr]['bracket_opener'])) {
$opener = $this->tokens[$this->stackPtr]['bracket_opener'];
}

return $opener;
}

/**
* Retrieve the bracket "type" of a token from the cache.
*
* @since 1.0.0-alpha4
*
* @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()
{
if (Cache::isCached($this->phpcsFile, self::CACHE_KEY, $this->opener) === true) {
return Cache::get($this->phpcsFile, self::CACHE_KEY, $this->opener);
}

return false;
}

/**
* Update the cache with information about a particular bracket token.
*
* @since 1.0.0-alpha4
*
* @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($type)
{
Cache::set($this->phpcsFile, self::CACHE_KEY, $this->opener, $type);
}
}

0 comments on commit 75e13f4

Please sign in to comment.