419 lines
18 KiB
PHP
419 lines
18 KiB
PHP
<?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\Files\File;
|
|
use PHP_CodeSniffer\Util\Tokens;
|
|
|
|
/**
|
|
* Utility functions for use when examining parenthesis tokens and arbitrary tokens wrapped
|
|
* in parentheses.
|
|
*
|
|
* In contrast to PHPCS natively, `isset()`, `unset()`, `empty()`, `exit()`, `die()` and `eval()`
|
|
* will be considered parentheses owners by the functions in this class.
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
final class Parentheses
|
|
{
|
|
|
|
/**
|
|
* Extra tokens which should be considered parentheses owners.
|
|
*
|
|
* - `T_ISSET`, `T_UNSET`, `T_EMPTY`, `T_EXIT` and `T_EVAL` are not PHPCS native parentheses
|
|
* owners, but are considered such for the purposes of this class.
|
|
* Also see {@link https://github.com/squizlabs/PHP_CodeSniffer/issues/3118 PHPCS#3118}.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @var array <int|string> => <int|string>
|
|
*/
|
|
private static $extraParenthesesOwners = [
|
|
\T_ISSET => \T_ISSET,
|
|
\T_UNSET => \T_UNSET,
|
|
\T_EMPTY => \T_EMPTY,
|
|
\T_EXIT => \T_EXIT,
|
|
\T_EVAL => \T_EVAL,
|
|
];
|
|
|
|
/**
|
|
* Get the stack pointer to the parentheses owner of an open/close parenthesis.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
|
|
* @param int $stackPtr The position of `T_OPEN/CLOSE_PARENTHESIS` token.
|
|
*
|
|
* @return int|false Integer stack pointer to the parentheses owner; or `FALSE` if the
|
|
* parenthesis does not have a (direct) owner or if the token passed
|
|
* was not a parenthesis.
|
|
*/
|
|
public static function getOwner(File $phpcsFile, $stackPtr)
|
|
{
|
|
$tokens = $phpcsFile->getTokens();
|
|
|
|
if (isset($tokens[$stackPtr]['parenthesis_owner'])) {
|
|
return $tokens[$stackPtr]['parenthesis_owner'];
|
|
}
|
|
|
|
/*
|
|
* As the 'parenthesis_owner' index is only set on parentheses, we didn't need to do any
|
|
* input validation before, but now we do.
|
|
*/
|
|
if (isset($tokens[$stackPtr]) === false
|
|
|| ($tokens[$stackPtr]['code'] !== \T_OPEN_PARENTHESIS
|
|
&& $tokens[$stackPtr]['code'] !== \T_CLOSE_PARENTHESIS)
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
if ($tokens[$stackPtr]['code'] === \T_CLOSE_PARENTHESIS) {
|
|
$stackPtr = $tokens[$stackPtr]['parenthesis_opener'];
|
|
}
|
|
|
|
$prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
|
|
if ($prevNonEmpty !== false
|
|
&& isset(self::$extraParenthesesOwners[$tokens[$prevNonEmpty]['code']]) === true
|
|
) {
|
|
return $prevNonEmpty;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check whether the parenthesis owner of an open/close parenthesis is within a limited
|
|
* set of valid owners.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
|
|
* @param int $stackPtr The position of `T_OPEN/CLOSE_PARENTHESIS` token.
|
|
* @param int|string|array $validOwners Array of token constants for the owners
|
|
* which should be considered valid.
|
|
*
|
|
* @return bool `TRUE` if the owner is within the list of `$validOwners`; `FALSE` if not and
|
|
* if the parenthesis does not have a (direct) owner.
|
|
*/
|
|
public static function isOwnerIn(File $phpcsFile, $stackPtr, $validOwners)
|
|
{
|
|
$owner = self::getOwner($phpcsFile, $stackPtr);
|
|
if ($owner === false) {
|
|
return false;
|
|
}
|
|
|
|
$tokens = $phpcsFile->getTokens();
|
|
$validOwners = (array) $validOwners;
|
|
|
|
return \in_array($tokens[$owner]['code'], $validOwners, true);
|
|
}
|
|
|
|
/**
|
|
* Check whether the passed token is nested within parentheses owned by one of the valid owners.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
|
|
* @param int $stackPtr The position of the token we are checking.
|
|
* @param int|string|array $validOwners Array of token constants for the owners
|
|
* which should be considered valid.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public static function hasOwner(File $phpcsFile, $stackPtr, $validOwners)
|
|
{
|
|
return (self::nestedParensWalker($phpcsFile, $stackPtr, $validOwners) !== false);
|
|
}
|
|
|
|
/**
|
|
* Retrieve the stack pointer to the parentheses opener of the first (outer) set of parentheses
|
|
* an arbitrary token is wrapped in.
|
|
*
|
|
* If the optional `$validOwners` parameter is passed, the stack pointer to the opener to
|
|
* the first set of parentheses, which has an owner which is in the list of valid owners,
|
|
* will be returned. This may be a nested set of parentheses.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
|
|
* @param int $stackPtr The position of the token we are checking.
|
|
* @param int|string|array $validOwners Array of token constants for the owners
|
|
* which should be considered valid.
|
|
*
|
|
* @return int|false Integer stack pointer to the parentheses opener; or `FALSE` if the token
|
|
* does not have parentheses owned by any of the valid owners or if
|
|
* the token is not nested in parentheses at all.
|
|
*/
|
|
public static function getFirstOpener(File $phpcsFile, $stackPtr, $validOwners = [])
|
|
{
|
|
return self::nestedParensWalker($phpcsFile, $stackPtr, $validOwners, false);
|
|
}
|
|
|
|
/**
|
|
* Retrieve the stack pointer to the parentheses closer of the first (outer) set of parentheses
|
|
* an arbitrary token is wrapped in.
|
|
*
|
|
* If the optional `$validOwners` parameter is passed, the stack pointer to the closer to
|
|
* the first set of parentheses, which has an owner which is in the list of valid owners,
|
|
* will be returned. This may be a nested set of parentheses.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
|
|
* @param int $stackPtr The position of the token we are checking.
|
|
* @param int|string|array $validOwners Array of token constants for the owners
|
|
* which should be considered valid.
|
|
*
|
|
* @return int|false Integer stack pointer to the parentheses closer; or `FALSE` if the token
|
|
* does not have parentheses owned by any of the valid owners or if
|
|
* the token is not nested in parentheses at all.
|
|
*/
|
|
public static function getFirstCloser(File $phpcsFile, $stackPtr, $validOwners = [])
|
|
{
|
|
$opener = self::getFirstOpener($phpcsFile, $stackPtr, $validOwners);
|
|
$tokens = $phpcsFile->getTokens();
|
|
if ($opener !== false && isset($tokens[$opener]['parenthesis_closer']) === true) {
|
|
return $tokens[$opener]['parenthesis_closer'];
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the stack pointer to the parentheses owner of the first (outer) set of parentheses
|
|
* an arbitrary token is wrapped in.
|
|
*
|
|
* If the optional `$validOwners` parameter is passed, the stack pointer to the owner of
|
|
* the first set of parentheses, which has an owner which is in the list of valid owners,
|
|
* will be returned. This may be a nested set of parentheses.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
|
|
* @param int $stackPtr The position of the token we are checking.
|
|
* @param int|string|array $validOwners Array of token constants for the owners
|
|
* which should be considered valid.
|
|
*
|
|
* @return int|false Integer stack pointer to the parentheses owner; or `FALSE` if the token
|
|
* does not have parentheses owned by any of the valid owners or if
|
|
* the token is not nested in parentheses at all.
|
|
*/
|
|
public static function getFirstOwner(File $phpcsFile, $stackPtr, $validOwners = [])
|
|
{
|
|
$opener = self::getFirstOpener($phpcsFile, $stackPtr, $validOwners);
|
|
if ($opener !== false) {
|
|
return self::getOwner($phpcsFile, $opener);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the stack pointer to the parentheses opener of the last (inner) set of parentheses
|
|
* an arbitrary token is wrapped in.
|
|
*
|
|
* If the optional `$validOwners` parameter is passed, the stack pointer to the opener to
|
|
* the last set of parentheses, which has an owner which is in the list of valid owners,
|
|
* will be returned. This may be a set of parentheses higher up.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
|
|
* @param int $stackPtr The position of the token we are checking.
|
|
* @param int|string|array $validOwners Array of token constants for the owners
|
|
* which should be considered valid.
|
|
*
|
|
* @return int|false Integer stack pointer to the parentheses opener; or `FALSE` if the token
|
|
* does not have parentheses owned by any of the valid owners or if
|
|
* the token is not nested in parentheses at all.
|
|
*/
|
|
public static function getLastOpener(File $phpcsFile, $stackPtr, $validOwners = [])
|
|
{
|
|
return self::nestedParensWalker($phpcsFile, $stackPtr, $validOwners, true);
|
|
}
|
|
|
|
/**
|
|
* Retrieve the stack pointer to the parentheses closer of the last (inner) set of parentheses
|
|
* an arbitrary token is wrapped in.
|
|
*
|
|
* If the optional `$validOwners` parameter is passed, the stack pointer to the closer to
|
|
* the last set of parentheses, which has an owner which is in the list of valid owners,
|
|
* will be returned. This may be a set of parentheses higher up.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
|
|
* @param int $stackPtr The position of the token we are checking.
|
|
* @param int|string|array $validOwners Array of token constants for the owners
|
|
* which should be considered valid.
|
|
*
|
|
* @return int|false Integer stack pointer to the parentheses closer; or `FALSE` if the token
|
|
* does not have parentheses owned by any of the valid owners or if
|
|
* the token is not nested in parentheses at all.
|
|
*/
|
|
public static function getLastCloser(File $phpcsFile, $stackPtr, $validOwners = [])
|
|
{
|
|
$opener = self::getLastOpener($phpcsFile, $stackPtr, $validOwners);
|
|
$tokens = $phpcsFile->getTokens();
|
|
if ($opener !== false && isset($tokens[$opener]['parenthesis_closer']) === true) {
|
|
return $tokens[$opener]['parenthesis_closer'];
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the stack pointer to the parentheses owner of the last (inner) set of parentheses
|
|
* an arbitrary token is wrapped in.
|
|
*
|
|
* If the optional `$validOwners` parameter is passed, the stack pointer to the owner of
|
|
* the last set of parentheses, which has an owner which is in the list of valid owners,
|
|
* will be returned. This may be a set of parentheses higher up.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
|
|
* @param int $stackPtr The position of the token we are checking.
|
|
* @param int|string|array $validOwners Array of token constants for the owners
|
|
* which should be considered valid.
|
|
*
|
|
* @return int|false Integer stack pointer to the parentheses owner; or `FALSE` if the token
|
|
* does not have parentheses owned by any of the valid owners or if
|
|
* the token is not nested in parentheses at all.
|
|
*/
|
|
public static function getLastOwner(File $phpcsFile, $stackPtr, $validOwners = [])
|
|
{
|
|
$opener = self::getLastOpener($phpcsFile, $stackPtr, $validOwners);
|
|
if ($opener !== false) {
|
|
return self::getOwner($phpcsFile, $opener);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check whether the owner of the outermost wrapping set of parentheses of an arbitrary token
|
|
* is within a limited set of acceptable token types.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
|
|
* @param int $stackPtr The position in the stack of the
|
|
* token to verify.
|
|
* @param int|string|array $validOwners Array of token constants for the owners
|
|
* which should be considered valid.
|
|
*
|
|
* @return int|false Integer stack pointer to the valid parentheses owner; or `FALSE` if
|
|
* the token was not wrapped in parentheses or if the outermost set
|
|
* of parentheses in which the token is wrapped does not have an owner
|
|
* within the set of owners considered valid.
|
|
*/
|
|
public static function firstOwnerIn(File $phpcsFile, $stackPtr, $validOwners)
|
|
{
|
|
$opener = self::getFirstOpener($phpcsFile, $stackPtr);
|
|
|
|
if ($opener !== false && self::isOwnerIn($phpcsFile, $opener, $validOwners) === true) {
|
|
return self::getOwner($phpcsFile, $opener);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check whether the owner of the innermost wrapping set of parentheses of an arbitrary token
|
|
* is within a limited set of acceptable token types.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
|
|
* @param int $stackPtr The position in the stack of the
|
|
* token to verify.
|
|
* @param int|string|array $validOwners Array of token constants for the owners
|
|
* which should be considered valid.
|
|
*
|
|
* @return int|false Integer stack pointer to the valid parentheses owner; or `FALSE` if
|
|
* the token was not wrapped in parentheses or if the innermost set
|
|
* of parentheses in which the token is wrapped does not have an owner
|
|
* within the set of owners considered valid.
|
|
*/
|
|
public static function lastOwnerIn(File $phpcsFile, $stackPtr, $validOwners)
|
|
{
|
|
$opener = self::getLastOpener($phpcsFile, $stackPtr);
|
|
|
|
if ($opener !== false && self::isOwnerIn($phpcsFile, $opener, $validOwners) === true) {
|
|
return self::getOwner($phpcsFile, $opener);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Helper method. Retrieve the position of a parentheses opener for an arbitrary passed token.
|
|
*
|
|
* If no `$validOwners` are specified, the opener to the first set of parentheses surrounding
|
|
* the token - or if `$reverse = true`, the last set of parentheses - will be returned.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
|
|
* @param int $stackPtr The position of the token we are checking.
|
|
* @param int|string|array $validOwners Optional. Array of token constants for the owners
|
|
* which should be considered valid.
|
|
* @param bool $reverse Optional. Whether to search for the first/outermost
|
|
* (`false`) or the last/innermost (`true`) set of
|
|
* parentheses with the specified owner(s).
|
|
*
|
|
* @return int|false Integer stack pointer to the parentheses opener; or `FALSE` if the token
|
|
* does not have parentheses owned by any of the valid owners or if
|
|
* the token is not nested in parentheses at all.
|
|
*/
|
|
private static function nestedParensWalker(File $phpcsFile, $stackPtr, $validOwners = [], $reverse = false)
|
|
{
|
|
$tokens = $phpcsFile->getTokens();
|
|
|
|
// Check for the existence of the token.
|
|
if (isset($tokens[$stackPtr]) === false) {
|
|
return false;
|
|
}
|
|
|
|
// Make sure the token is nested in parenthesis.
|
|
if (empty($tokens[$stackPtr]['nested_parenthesis']) === true) {
|
|
return false;
|
|
}
|
|
|
|
$validOwners = (array) $validOwners;
|
|
$parentheses = $tokens[$stackPtr]['nested_parenthesis'];
|
|
|
|
if (empty($validOwners) === true) {
|
|
// No owners specified, just return the first/last parentheses opener.
|
|
if ($reverse === true) {
|
|
\end($parentheses);
|
|
} else {
|
|
\reset($parentheses);
|
|
}
|
|
|
|
return \key($parentheses);
|
|
}
|
|
|
|
if ($reverse === true) {
|
|
$parentheses = \array_reverse($parentheses, true);
|
|
}
|
|
|
|
foreach ($parentheses as $opener => $closer) {
|
|
if (self::isOwnerIn($phpcsFile, $opener, $validOwners) === true) {
|
|
// We found a token with a valid owner.
|
|
return $opener;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|