Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Arrays::isShortArray()/Lists::isShortList(): consolidate the logic [1]
This is step 1 in a refactor to improve short list/short array determination. In this commit, the pre-existing code from the `Arrays::isShortArray()` and `Lists::isShortList()` methods is consolidated into a class which handles both determinations. This initial refactor does not contain any functional changes other than to remove (most of) the code which handled BC with PHPCS < 3.7.1. Includes updating the `@covers` tags. Only the caching related tests are now still testing the `Arrays::isShortArray()` and `Lists::isShortList()` methods. Everything else is testing the new `PHPCSUtils\Internal\IsShortArrayOrList` class.
- Loading branch information
Showing
8 changed files
with
304 additions
and
318 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,286 @@ | ||
<?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\Exceptions\RuntimeException; | ||
use PHP_CodeSniffer\Files\File; | ||
use PHP_CodeSniffer\Util\Tokens; | ||
use PHPCSUtils\Tokens\Collections; | ||
use PHPCSUtils\Utils\Lists; | ||
use PHPCSUtils\Utils\Parentheses; | ||
|
||
/** | ||
* Determination of short array vs short list vs square brackets. | ||
* | ||
* --------------------------------------------------------------------------------------------- | ||
* 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. | ||
* --------------------------------------------------------------------------------------------- | ||
* | ||
* @internal | ||
* | ||
* @since 1.0.0-alpha4 | ||
*/ | ||
final class IsShortArrayOrList | ||
{ | ||
|
||
/** | ||
* Type annotation for short arrays. | ||
* | ||
* @since 1.0.0-alpha4 | ||
* | ||
* @var string | ||
*/ | ||
const SHORT_ARRAY = 'short array'; | ||
|
||
/** | ||
* Type annotation for short lists. | ||
* | ||
* @since 1.0.0-alpha4 | ||
* | ||
* @var string | ||
*/ | ||
const SHORT_LIST = 'short list'; | ||
|
||
/** | ||
* Type annotation for square brackets. | ||
* | ||
* @since 1.0.0-alpha4 | ||
* | ||
* @var string | ||
*/ | ||
const SQUARE_BRACKETS = 'square brackets'; | ||
|
||
/** | ||
* The PHPCS file in which the current stackPtr was found. | ||
* | ||
* @since 1.0.0-alpha4 | ||
* | ||
* @var \PHP_CodeSniffer\Files\File | ||
*/ | ||
private $phpcsFile; | ||
|
||
/** | ||
* The current stack pointer. | ||
* | ||
* @since 1.0.0-alpha4 | ||
* | ||
* @var int | ||
*/ | ||
private $stackPtr; | ||
|
||
/** | ||
* The token stack from the current file. | ||
* | ||
* @since 1.0.0-alpha4 | ||
* | ||
* @var array | ||
*/ | ||
private $tokens; | ||
|
||
/** | ||
* Stack pointer to the open bracket. | ||
* | ||
* @since 1.0.0-alpha4 | ||
* | ||
* @var int | ||
*/ | ||
private $opener; | ||
|
||
/** | ||
* Stack pointer to the close bracket. | ||
* | ||
* @since 1.0.0-alpha4 | ||
* | ||
* @var int | ||
*/ | ||
private $closer; | ||
|
||
/** | ||
* Stack pointer to the first non-empty token before the open bracket. | ||
* | ||
* @since 1.0.0-alpha4 | ||
* | ||
* @var int | ||
*/ | ||
private $beforeOpener; | ||
|
||
/** | ||
* Stack pointer to the first non-empty token after the close bracket. | ||
* | ||
* @since 1.0.0-alpha4 | ||
* | ||
* @var int|false Will be `false` if the close bracket is the last token in the file. | ||
*/ | ||
private $afterCloser; | ||
|
||
/** | ||
* 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 | ||
*/ | ||
public function __construct(File $phpcsFile, $stackPtr) | ||
{ | ||
$tokens = $phpcsFile->getTokens(); | ||
if (isset($tokens[$stackPtr]) === false | ||
|| isset(Collections::shortArrayTokensBC()[$tokens[$stackPtr]['code']]) === false | ||
) { | ||
throw new RuntimeException('Non-square bracket stack pointer passed'); | ||
} | ||
|
||
$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->closer = $stackPtr; | ||
if (isset($this->tokens[$stackPtr]['bracket_closer']) | ||
&& $stackPtr !== $this->tokens[$stackPtr]['bracket_closer'] | ||
) { | ||
$this->closer = $this->tokens[$stackPtr]['bracket_closer']; | ||
} | ||
|
||
$this->beforeOpener = $this->phpcsFile->findPrevious(Tokens::$emptyTokens, ($this->opener - 1), null, true); | ||
$this->afterCloser = $this->phpcsFile->findNext(Tokens::$emptyTokens, ($this->closer + 1), null, true); | ||
} | ||
|
||
/** | ||
* Determine whether the bracket is a short array, short list or real square bracket. | ||
* | ||
* @since 1.0.0-alpha4 | ||
* | ||
* @return string Either 'short array', 'short list' or 'square brackets'. | ||
*/ | ||
public function solve() | ||
{ | ||
if ($this->isSquareBracket() === true) { | ||
return self::SQUARE_BRACKETS; | ||
} | ||
|
||
// If the array closer is followed by an equals sign, it's always a short list. | ||
if ($this->afterCloser !== false && $this->tokens[$this->afterCloser]['code'] === \T_EQUAL) { | ||
return self::SHORT_LIST; | ||
} | ||
|
||
$type = $this->isInForeach(); | ||
if ($type !== false) { | ||
return $type; | ||
} | ||
|
||
// Maybe this is a short list syntax nested inside another short list syntax ? | ||
$parentOpen = $this->opener; | ||
do { | ||
$parentOpen = $this->phpcsFile->findPrevious( | ||
\T_OPEN_SHORT_ARRAY, | ||
($parentOpen - 1), | ||
null, | ||
false, | ||
null, | ||
true | ||
); | ||
|
||
if ($parentOpen === false) { | ||
return self::SHORT_ARRAY; | ||
} | ||
} while (isset($this->tokens[$parentOpen]['bracket_closer']) === true | ||
&& $this->tokens[$parentOpen]['bracket_closer'] < $this->opener | ||
); | ||
|
||
$recursedReturn = Lists::isShortList($this->phpcsFile, $parentOpen); | ||
if ($recursedReturn === true) { | ||
return self::SHORT_LIST; | ||
} | ||
|
||
return self::SHORT_ARRAY; | ||
} | ||
|
||
/** | ||
* Check if the brackets are in actual fact real square brackets. | ||
* | ||
* @since 1.0.0-alpha4 | ||
* | ||
* @return bool TRUE if these are real square brackets; FALSE otherwise. | ||
*/ | ||
private function isSquareBracket() | ||
{ | ||
if ($this->opener === $this->closer) { | ||
// Parse error (unclosed bracket) or live coding. Bow out. | ||
return true; | ||
} | ||
|
||
// Check if this is a bracket we need to examine or a mistokenization. | ||
if ($this->isShortArrayBracket() === false) { | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* Verify that the current set of brackets is not affected by known PHPCS cross-version tokenizer issues. | ||
* | ||
* At this time there are no known issues. | ||
* | ||
* List of previous tokenizer issues which affected the short array/short list tokenization for reference: | ||
* - {@link https://github.com/squizlabs/PHP_CodeSniffer/issues/1284 PHPCS#1284} (PHPCS < 2.8.1) | ||
* - {@link https://github.com/squizlabs/PHP_CodeSniffer/issues/1381 PHPCS#1381} (PHPCS < 2.9.0) | ||
* - {@link https://github.com/squizlabs/PHP_CodeSniffer/issues/1971 PHPCS#1971} (PHPCS 2.8.0 - 3.2.3) | ||
* - {@link https://github.com/squizlabs/PHP_CodeSniffer/pull/3013 PHPCS#3013} (PHPCS < 3.5.6) | ||
* - {@link https://github.com/squizlabs/PHP_CodeSniffer/pull/3172 PHPCS#3172} (PHPCS < 3.6.0) | ||
* | ||
* @since 1.0.0-alpha4 | ||
* | ||
* @return TRUE if this is actually a short array bracket which needs to be examined, | ||
* FALSE if it is an (incorrectly tokenized) square bracket. | ||
*/ | ||
private function isShortArrayBracket() | ||
{ | ||
if ($this->tokens[$this->opener]['code'] === \T_OPEN_SQUARE_BRACKET) { | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* Check is this set of brackets is used within a foreach expression. | ||
* | ||
* @since 1.0.0-alpha4 | ||
* | ||
* @return string|false The determined type or FALSE if undetermined. | ||
*/ | ||
private function isInForeach() | ||
{ | ||
if ($this->beforeOpener !== false | ||
&& ($this->tokens[$this->beforeOpener]['code'] === \T_AS | ||
|| $this->tokens[$this->beforeOpener]['code'] === \T_DOUBLE_ARROW) | ||
&& Parentheses::lastOwnerIn($this->phpcsFile, $this->beforeOpener, \T_FOREACH) !== false | ||
) { | ||
return self::SHORT_LIST; | ||
} | ||
|
||
return false; | ||
} | ||
} |
Oops, something went wrong.