wishthis/vendor/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Operators.php
2023-09-20 13:52:46 +02:00

252 lines
9.1 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;
use PHPCSUtils\Tokens\Collections;
use PHPCSUtils\Utils\FunctionDeclarations;
use PHPCSUtils\Utils\Parentheses;
/**
* Utility functions for use when working with operators.
*
* @link https://www.php.net/language.operators PHP manual on operators.
*
* @since 1.0.0 The `isReference()` method is based on and inspired by
* the method of the same name in the PHPCS native `File` class.
* Also see {@see \PHPCSUtils\BackCompat\BCFile}.
* The `isUnaryPlusMinus()` method is, in part, inspired by the
* `Squiz.WhiteSpace.OperatorSpacing` sniff.
*/
final class Operators
{
/**
* Tokens which indicate that a plus/minus is unary when they preceed it.
*
* @since 1.0.0
*
* @var array <int|string> => <irrelevant>
*/
private static $extraUnaryIndicators = [
\T_STRING_CONCAT => true,
\T_RETURN => true,
\T_EXIT => true,
\T_CONTINUE => true,
\T_BREAK => true,
\T_ECHO => true,
\T_PRINT => true,
\T_YIELD => true,
\T_COMMA => true,
\T_OPEN_PARENTHESIS => true,
\T_OPEN_SQUARE_BRACKET => true,
\T_OPEN_SHORT_ARRAY => true,
\T_OPEN_CURLY_BRACKET => true,
\T_COLON => true,
\T_INLINE_THEN => true,
\T_INLINE_ELSE => true,
\T_CASE => true,
\T_FN_ARROW => true,
\T_MATCH_ARROW => true,
];
/**
* Determine if the passed token is a reference operator.
*
* Main differences with the PHPCS version:
* - Defensive coding against incorrect calls to this method.
*
* @see \PHP_CodeSniffer\Files\File::isReference() Original source.
* @see \PHPCSUtils\BackCompat\BCFile::isReference() Cross-version compatible version of the original.
*
* @since 1.0.0
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the `T_BITWISE_AND` token.
*
* @return bool `TRUE` if the specified token position represents a reference.
* `FALSE` if the token represents a bitwise operator.
*/
public static function isReference(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
if (isset($tokens[$stackPtr]) === false || $tokens[$stackPtr]['code'] !== \T_BITWISE_AND) {
return false;
}
$tokenBefore = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
if (isset(Collections::functionDeclarationTokens()[$tokens[$tokenBefore]['code']]) === true) {
// Function returns a reference.
return true;
}
if ($tokens[$tokenBefore]['code'] === \T_DOUBLE_ARROW) {
// Inside a foreach loop or array assignment, this is a reference.
return true;
}
if ($tokens[$tokenBefore]['code'] === \T_AS) {
// Inside a foreach loop, this is a reference.
return true;
}
if (isset(Tokens::$assignmentTokens[$tokens[$tokenBefore]['code']]) === true) {
// This is directly after an assignment. It's a reference. Even if
// it is part of an operation, the other tests will handle it.
return true;
}
$tokenAfter = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
if ($tokens[$tokenAfter]['code'] === \T_NEW) {
return true;
}
$lastOpener = Parentheses::getLastOpener($phpcsFile, $stackPtr);
if ($lastOpener !== false) {
$lastOwner = Parentheses::getOwner($phpcsFile, $lastOpener);
if (isset(Collections::functionDeclarationTokens()[$tokens[$lastOwner]['code']]) === true
// As of PHPCS 4.x, `T_USE` is a parenthesis owner.
|| $tokens[$lastOwner]['code'] === \T_USE
) {
$params = FunctionDeclarations::getParameters($phpcsFile, $lastOwner);
foreach ($params as $param) {
if ($param['reference_token'] === $stackPtr) {
// Function parameter declared to be passed by reference.
return true;
}
}
}
}
/*
* Pass by reference in function calls, assign by reference in arrays and
* closure use by reference in PHPCS 3.x.
*/
if ($tokens[$tokenBefore]['code'] === \T_OPEN_PARENTHESIS
|| $tokens[$tokenBefore]['code'] === \T_COMMA
|| $tokens[$tokenBefore]['code'] === \T_OPEN_SHORT_ARRAY
) {
if ($tokens[$tokenAfter]['code'] === \T_VARIABLE) {
return true;
} else {
$skip = Tokens::$emptyTokens;
$skip += Collections::namespacedNameTokens();
$skip += Collections::ooHierarchyKeywords();
$skip[] = \T_DOUBLE_COLON;
$nextSignificantAfter = $phpcsFile->findNext(
$skip,
($stackPtr + 1),
null,
true
);
if ($tokens[$nextSignificantAfter]['code'] === \T_VARIABLE) {
return true;
}
}
}
return false;
}
/**
* Determine whether a T_MINUS/T_PLUS token is a unary operator.
*
* @since 1.0.0
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the plus/minus token.
*
* @return bool `TRUE` if the token passed is a unary operator.
* `FALSE` otherwise, i.e. if the token is an arithmetic operator,
* or if the token is not a `T_PLUS`/`T_MINUS` token.
*/
public static function isUnaryPlusMinus(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
if (isset($tokens[$stackPtr]) === false
|| ($tokens[$stackPtr]['code'] !== \T_PLUS
&& $tokens[$stackPtr]['code'] !== \T_MINUS)
) {
return false;
}
$next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
if ($next === false) {
// Live coding or parse error.
return false;
}
if (isset(Tokens::$operators[$tokens[$next]['code']]) === true) {
// Next token is an operator, so this is not a unary.
return false;
}
$prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
/*
* Check the preceeding token for an indication that this is not an arithmetic operation.
*/
if (isset(Tokens::$operators[$tokens[$prev]['code']]) === true
|| isset(Tokens::$comparisonTokens[$tokens[$prev]['code']]) === true
|| isset(Tokens::$booleanOperators[$tokens[$prev]['code']]) === true
|| isset(Tokens::$assignmentTokens[$tokens[$prev]['code']]) === true
|| isset(Tokens::$castTokens[$tokens[$prev]['code']]) === true
|| isset(self::$extraUnaryIndicators[$tokens[$prev]['code']]) === true
) {
return true;
}
return false;
}
/**
* Determine whether a ternary is a short ternary/elvis operator, i.e. without "middle".
*
* @since 1.0.0
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the ternary then/else
* operator in the stack.
*
* @return bool `TRUE` if short ternary; or `FALSE` otherwise.
*/
public static function isShortTernary(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
if (isset($tokens[$stackPtr]) === false) {
return false;
}
if ($tokens[$stackPtr]['code'] === \T_INLINE_THEN) {
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
if ($nextNonEmpty !== false && $tokens[$nextNonEmpty]['code'] === \T_INLINE_ELSE) {
return true;
}
}
if ($tokens[$stackPtr]['code'] === \T_INLINE_ELSE) {
$prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
if ($prevNonEmpty !== false && $tokens[$prevNonEmpty]['code'] === \T_INLINE_THEN) {
return true;
}
}
// Not a ternary operator token.
return false;
}
}