Preview: UselessParenthesesSniff.php
Size: 18.70 KB
/opt/cpanel/ea-wappspector/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/PHP/UselessParenthesesSniff.php
<?php declare(strict_types = 1);
namespace SlevomatCodingStandard\Sniffs\PHP;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Util\Tokens;
use SlevomatCodingStandard\Helpers\FixerHelper;
use SlevomatCodingStandard\Helpers\IdentificatorHelper;
use SlevomatCodingStandard\Helpers\TokenHelper;
use function array_key_exists;
use function array_map;
use function array_merge;
use function count;
use function in_array;
use const T_ANON_CLASS;
use const T_ARRAY_CAST;
use const T_BITWISE_AND;
use const T_BITWISE_NOT;
use const T_BITWISE_OR;
use const T_BITWISE_XOR;
use const T_BOOL_CAST;
use const T_BOOLEAN_NOT;
use const T_CASE;
use const T_CLONE;
use const T_CLOSE_PARENTHESIS;
use const T_CLOSE_SHORT_ARRAY;
use const T_CLOSURE;
use const T_COALESCE;
use const T_COLON;
use const T_COMMA;
use const T_CONSTANT_ENCAPSED_STRING;
use const T_DIVIDE;
use const T_DNUMBER;
use const T_DOLLAR;
use const T_DOUBLE_CAST;
use const T_EMPTY;
use const T_EQUAL;
use const T_EVAL;
use const T_EXIT;
use const T_FN;
use const T_INCLUDE;
use const T_INCLUDE_ONCE;
use const T_INLINE_THEN;
use const T_INT_CAST;
use const T_ISSET;
use const T_LIST;
use const T_LNUMBER;
use const T_LOGICAL_AND;
use const T_LOGICAL_OR;
use const T_LOGICAL_XOR;
use const T_MATCH;
use const T_MINUS;
use const T_MODULUS;
use const T_MULTIPLY;
use const T_NEW;
use const T_OBJECT_CAST;
use const T_OPEN_PARENTHESIS;
use const T_PARENT;
use const T_PLUS;
use const T_POW;
use const T_REQUIRE;
use const T_REQUIRE_ONCE;
use const T_SELF;
use const T_SEMICOLON;
use const T_SL;
use const T_SR;
use const T_STATIC;
use const T_STRING_CAST;
use const T_STRING_CONCAT;
use const T_UNSET;
use const T_UNSET_CAST;
use const T_USE;
use const T_VARIABLE;
use const T_WHITESPACE;
use const T_YIELD;
use const T_YIELD_FROM;
class UselessParenthesesSniff implements Sniff
{
public const CODE_USELESS_PARENTHESES = 'UselessParentheses';
private const OPERATORS = [
T_POW,
T_MULTIPLY,
T_DIVIDE,
T_MODULUS,
T_PLUS,
T_MINUS,
T_STRING_CONCAT,
];
private const OPERATOR_GROUPS = [
T_POW => 1,
T_MULTIPLY => 2,
T_DIVIDE => 2,
T_MODULUS => 3,
T_PLUS => 4,
T_MINUS => 4,
T_STRING_CONCAT => 5,
];
public bool $ignoreComplexTernaryConditions = false;
/**
* @return array<int, (int|string)>
*/
public function register(): array
{
return [
T_OPEN_PARENTHESIS,
];
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param int $parenthesisOpenerPointer
*/
public function process(File $phpcsFile, $parenthesisOpenerPointer): void
{
$tokens = $phpcsFile->getTokens();
if (array_key_exists('parenthesis_owner', $tokens[$parenthesisOpenerPointer])) {
return;
}
if (!array_key_exists('parenthesis_closer', $tokens[$parenthesisOpenerPointer])) {
return;
}
/** @var int $pointerBeforeParenthesisOpener */
$pointerBeforeParenthesisOpener = TokenHelper::findPreviousEffective($phpcsFile, $parenthesisOpenerPointer - 1);
if (in_array($tokens[$pointerBeforeParenthesisOpener]['code'], [
...TokenHelper::NAME_TOKEN_CODES,
T_VARIABLE,
T_ISSET,
T_UNSET,
T_EMPTY,
T_CLOSURE,
T_FN,
T_USE,
T_ANON_CLASS,
T_NEW,
T_SELF,
T_STATIC,
T_PARENT,
T_EXIT,
T_CLOSE_PARENTHESIS,
T_EVAL,
T_LIST,
T_INCLUDE,
T_INCLUDE_ONCE,
T_REQUIRE,
T_REQUIRE_ONCE,
T_INT_CAST,
T_DOUBLE_CAST,
T_STRING_CAST,
T_ARRAY_CAST,
T_OBJECT_CAST,
T_BOOL_CAST,
T_UNSET_CAST,
T_MATCH,
T_BITWISE_NOT,
], true,)) {
return;
}
/** @var int $pointerAfterParenthesisOpener */
$pointerAfterParenthesisOpener = TokenHelper::findNextEffective($phpcsFile, $parenthesisOpenerPointer + 1);
if (in_array(
$tokens[$pointerAfterParenthesisOpener]['code'],
[T_CLONE, T_YIELD, T_YIELD_FROM, T_REQUIRE, T_REQUIRE_ONCE, T_INCLUDE, T_INCLUDE_ONCE, T_ARRAY_CAST],
true,
)) {
return;
}
if (TokenHelper::findNext(
$phpcsFile,
T_EQUAL,
$parenthesisOpenerPointer + 1,
$tokens[$parenthesisOpenerPointer]['parenthesis_closer'],
) !== null) {
return;
}
$pointerAfterParenthesisCloser = TokenHelper::findNextEffective(
$phpcsFile,
$tokens[$parenthesisOpenerPointer]['parenthesis_closer'] + 1,
);
if (
$pointerAfterParenthesisCloser !== null
&& $tokens[$pointerAfterParenthesisCloser]['code'] === T_OPEN_PARENTHESIS
) {
return;
}
if (IdentificatorHelper::findStartPointer($phpcsFile, $pointerBeforeParenthesisOpener) !== null) {
return;
}
$this->checkParenthesesAroundConditionInTernaryOperator($phpcsFile, $parenthesisOpenerPointer);
$this->checkParenthesesAroundCaseInSwitch($phpcsFile, $parenthesisOpenerPointer);
$this->checkParenthesesAroundVariableOrFunctionCall($phpcsFile, $parenthesisOpenerPointer);
$this->checkParenthesesAroundString($phpcsFile, $parenthesisOpenerPointer);
$this->checkParenthesesAroundOperators($phpcsFile, $parenthesisOpenerPointer);
$this->checkParenthesesAroundNew($phpcsFile, $parenthesisOpenerPointer);
}
private function checkParenthesesAroundConditionInTernaryOperator(File $phpcsFile, int $parenthesisOpenerPointer): void
{
$tokens = $phpcsFile->getTokens();
$parenthesisCloserPointer = $tokens[$parenthesisOpenerPointer]['parenthesis_closer'];
$ternaryOperatorPointer = TokenHelper::findNextEffective($phpcsFile, $parenthesisCloserPointer + 1);
if ($tokens[$ternaryOperatorPointer]['code'] !== T_INLINE_THEN) {
return;
}
if (TokenHelper::findNext(
$phpcsFile,
[T_LOGICAL_AND, T_LOGICAL_OR, T_LOGICAL_XOR],
$parenthesisOpenerPointer + 1,
$parenthesisCloserPointer,
) !== null) {
return;
}
$pointerBeforeParenthesisOpener = TokenHelper::findPreviousEffective($phpcsFile, $parenthesisOpenerPointer - 1);
if ($tokens[$pointerBeforeParenthesisOpener]['code'] === T_BOOLEAN_NOT) {
return;
}
if (in_array($tokens[$pointerBeforeParenthesisOpener]['code'], Tokens::$comparisonTokens, true)) {
return;
}
if (in_array($tokens[$pointerBeforeParenthesisOpener]['code'], Tokens::$booleanOperators, true)) {
return;
}
if ($this->ignoreComplexTernaryConditions) {
if (TokenHelper::findNext(
$phpcsFile,
Tokens::$booleanOperators,
$parenthesisOpenerPointer + 1,
$parenthesisCloserPointer,
) !== null) {
return;
}
if (TokenHelper::findNextContent(
$phpcsFile,
T_WHITESPACE,
$phpcsFile->eolChar,
$parenthesisOpenerPointer + 1,
$parenthesisCloserPointer,
) !== null) {
return;
}
}
$contentStartPointer = TokenHelper::findNextEffective($phpcsFile, $parenthesisOpenerPointer + 1);
$contentEndPointer = TokenHelper::findPreviousEffective($phpcsFile, $parenthesisCloserPointer - 1);
for ($i = $contentStartPointer; $i <= $contentEndPointer; $i++) {
if ($tokens[$i]['code'] === T_INLINE_THEN) {
return;
}
}
$fix = $phpcsFile->addFixableError('Useless parentheses.', $parenthesisOpenerPointer, self::CODE_USELESS_PARENTHESES);
if (!$fix) {
return;
}
$phpcsFile->fixer->beginChangeset();
FixerHelper::removeBetweenIncluding($phpcsFile, $parenthesisOpenerPointer, $contentStartPointer - 1);
FixerHelper::removeBetweenIncluding($phpcsFile, $contentEndPointer + 1, $parenthesisCloserPointer);
$phpcsFile->fixer->endChangeset();
}
private function checkParenthesesAroundCaseInSwitch(File $phpcsFile, int $parenthesisOpenerPointer): void
{
$tokens = $phpcsFile->getTokens();
$pointerBeforeParenthesisOpener = TokenHelper::findPreviousEffective($phpcsFile, $parenthesisOpenerPointer - 1);
if ($tokens[$pointerBeforeParenthesisOpener]['code'] !== T_CASE) {
return;
}
$pointerAfterParenthesisCloser = TokenHelper::findNextEffective(
$phpcsFile,
$tokens[$parenthesisOpenerPointer]['parenthesis_closer'] + 1,
);
if ($tokens[$pointerAfterParenthesisCloser]['code'] !== T_COLON) {
return;
}
$fix = $phpcsFile->addFixableError('Useless parentheses.', $parenthesisOpenerPointer, self::CODE_USELESS_PARENTHESES);
if (!$fix) {
return;
}
$contentStartPointer = TokenHelper::findNextEffective($phpcsFile, $parenthesisOpenerPointer + 1);
$contentEndPointer = TokenHelper::findPreviousEffective($phpcsFile, $tokens[$parenthesisOpenerPointer]['parenthesis_closer'] - 1);
$phpcsFile->fixer->beginChangeset();
FixerHelper::removeBetweenIncluding($phpcsFile, $parenthesisOpenerPointer, $contentStartPointer - 1);
FixerHelper::removeBetweenIncluding($phpcsFile, $contentEndPointer + 1, $tokens[$parenthesisOpenerPointer]['parenthesis_closer']);
$phpcsFile->fixer->endChangeset();
}
private function checkParenthesesAroundVariableOrFunctionCall(File $phpcsFile, int $parenthesisOpenerPointer): void
{
$tokens = $phpcsFile->getTokens();
$pointerAfterParenthesis = TokenHelper::findNextEffective($phpcsFile, $parenthesisOpenerPointer + 1);
if ($tokens[$pointerAfterParenthesis]['code'] === T_NEW) {
// Check in other method
return;
}
if ($tokens[$pointerAfterParenthesis]['code'] === T_OPEN_PARENTHESIS) {
return;
}
$operatorsPointers = TokenHelper::findNextAll(
$phpcsFile,
self::OPERATORS,
$parenthesisOpenerPointer + 1,
$tokens[$parenthesisOpenerPointer]['parenthesis_closer'],
);
if ($operatorsPointers !== []) {
return;
}
$casePointer = TokenHelper::findPreviousEffective($phpcsFile, $parenthesisOpenerPointer - 1);
if ($tokens[$casePointer]['code'] === T_CASE) {
return;
}
$pointerBeforeParenthesisOpener = TokenHelper::findPreviousEffective($phpcsFile, $parenthesisOpenerPointer - 1);
if (in_array($tokens[$pointerBeforeParenthesisOpener]['code'], Tokens::$booleanOperators, true)) {
return;
}
$pointerAfterParenthesisCloser = TokenHelper::findNextEffective(
$phpcsFile,
$tokens[$parenthesisOpenerPointer]['parenthesis_closer'] + 1,
);
if (in_array($tokens[$pointerAfterParenthesisCloser]['code'], [T_INLINE_THEN, T_OPEN_PARENTHESIS, T_SR], true)) {
return;
}
/** @var int $contentStartPointer */
$contentStartPointer = TokenHelper::findNextEffective($phpcsFile, $parenthesisOpenerPointer + 1);
if ($tokens[$contentStartPointer]['code'] === T_CONSTANT_ENCAPSED_STRING) {
return;
}
$notBooleanNotOperatorPointer = $contentStartPointer;
if ($tokens[$contentStartPointer]['code'] === T_BOOLEAN_NOT) {
/** @var int $notBooleanNotOperatorPointer */
$notBooleanNotOperatorPointer = TokenHelper::findNextEffective($phpcsFile, $contentStartPointer + 1);
}
if (in_array(
$tokens[$notBooleanNotOperatorPointer]['code'],
[T_SELF, T_STATIC, T_PARENT, T_VARIABLE, T_DOLLAR, ...TokenHelper::NAME_TOKEN_CODES],
true,
)) {
$contentEndPointer = IdentificatorHelper::findEndPointer($phpcsFile, $notBooleanNotOperatorPointer);
if (
$contentEndPointer === null
&& in_array($tokens[$notBooleanNotOperatorPointer]['code'], TokenHelper::NAME_TOKEN_CODES, true)
) {
$nextPointer = TokenHelper::findNextEffective($phpcsFile, $contentStartPointer + 1);
if ($tokens[$nextPointer]['code'] === T_OPEN_PARENTHESIS) {
$contentEndPointer = $contentStartPointer;
}
}
do {
$nextPointer = TokenHelper::findNextEffective($phpcsFile, $contentEndPointer + 1);
if ($tokens[$nextPointer]['code'] !== T_OPEN_PARENTHESIS) {
break;
}
$contentEndPointer = $tokens[$nextPointer]['parenthesis_closer'];
} while (true);
} else {
$nextPointer = TokenHelper::findNext($phpcsFile, T_OPEN_PARENTHESIS, $notBooleanNotOperatorPointer + 1);
if ($nextPointer === null || !isset($tokens[$nextPointer]['parenthesis_closer'])) {
return;
}
$contentEndPointer = $tokens[$nextPointer]['parenthesis_closer'];
}
$pointerAfterContent = TokenHelper::findNextEffective($phpcsFile, $contentEndPointer + 1);
if ($pointerAfterContent !== $tokens[$parenthesisOpenerPointer]['parenthesis_closer']) {
return;
}
$fix = $phpcsFile->addFixableError('Useless parentheses.', $parenthesisOpenerPointer, self::CODE_USELESS_PARENTHESES);
if (!$fix) {
return;
}
$phpcsFile->fixer->beginChangeset();
FixerHelper::removeBetweenIncluding($phpcsFile, $parenthesisOpenerPointer, $contentStartPointer - 1);
FixerHelper::removeBetweenIncluding($phpcsFile, $contentEndPointer + 1, $tokens[$parenthesisOpenerPointer]['parenthesis_closer']);
$phpcsFile->fixer->endChangeset();
}
private function checkParenthesesAroundString(File $phpcsFile, int $parenthesisOpenerPointer): void
{
$tokens = $phpcsFile->getTokens();
/** @var int $stringPointer */
$stringPointer = TokenHelper::findNextEffective($phpcsFile, $parenthesisOpenerPointer + 1);
if ($tokens[$stringPointer]['code'] !== T_CONSTANT_ENCAPSED_STRING) {
return;
}
$pointerAfterString = TokenHelper::findNextEffective($phpcsFile, $stringPointer + 1);
if ($pointerAfterString !== $tokens[$parenthesisOpenerPointer]['parenthesis_closer']) {
return;
}
$fix = $phpcsFile->addFixableError('Useless parentheses.', $parenthesisOpenerPointer, self::CODE_USELESS_PARENTHESES);
if (!$fix) {
return;
}
$phpcsFile->fixer->beginChangeset();
FixerHelper::removeBetweenIncluding($phpcsFile, $parenthesisOpenerPointer, $stringPointer - 1);
FixerHelper::removeBetweenIncluding($phpcsFile, $stringPointer + 1, $tokens[$parenthesisOpenerPointer]['parenthesis_closer']);
$phpcsFile->fixer->endChangeset();
}
private function checkParenthesesAroundOperators(File $phpcsFile, int $parenthesisOpenerPointer): void
{
$tokens = $phpcsFile->getTokens();
$newPointer = TokenHelper::findNextEffective($phpcsFile, $parenthesisOpenerPointer + 1);
if ($tokens[$newPointer]['code'] === T_NEW) {
// Check in other method
return;
}
$pointerBeforeParenthesisOpener = TokenHelper::findPreviousEffective($phpcsFile, $parenthesisOpenerPointer - 1);
$pointerAfterParenthesisCloser = TokenHelper::findNextEffective(
$phpcsFile,
$tokens[$parenthesisOpenerPointer]['parenthesis_closer'] + 1,
);
if ($tokens[$pointerBeforeParenthesisOpener]['code'] === T_MINUS) {
$pointerBeforeMinus = TokenHelper::findPreviousEffective($phpcsFile, $pointerBeforeParenthesisOpener - 1);
if (!in_array($tokens[$pointerBeforeMinus]['code'], [T_DNUMBER, T_LNUMBER], true)) {
return;
}
}
if (
in_array($tokens[$pointerBeforeParenthesisOpener]['code'], Tokens::$booleanOperators, true)
|| in_array($tokens[$pointerAfterParenthesisCloser]['code'], Tokens::$booleanOperators, true)
|| $tokens[$pointerBeforeParenthesisOpener]['code'] === T_BOOLEAN_NOT
) {
return;
}
$complicatedOperators = [T_INLINE_THEN, T_COALESCE, T_BITWISE_AND, T_BITWISE_OR, T_BITWISE_XOR, T_SL, T_SR];
$operatorsPointers = [];
$actualStartPointer = $parenthesisOpenerPointer + 1;
while (true) {
$pointer = TokenHelper::findNext(
$phpcsFile,
array_merge(
[
...self::OPERATORS,
T_OPEN_PARENTHESIS,
...$complicatedOperators,
],
Tokens::$comparisonTokens,
),
$actualStartPointer,
$tokens[$parenthesisOpenerPointer]['parenthesis_closer'],
);
if ($pointer === null) {
break;
}
if (in_array($tokens[$pointer]['code'], $complicatedOperators, true)) {
return;
}
if (in_array($tokens[$pointer]['code'], Tokens::$comparisonTokens, true)) {
return;
}
if ($tokens[$pointer]['code'] === T_OPEN_PARENTHESIS) {
$actualStartPointer = $tokens[$pointer]['parenthesis_closer'] + 1;
continue;
}
$operatorsPointers[] = $pointer;
$actualStartPointer = $pointer + 1;
}
if (count($operatorsPointers) === 0) {
return;
}
if (
$tokens[$pointerBeforeParenthesisOpener]['code'] !== T_EQUAL
|| $tokens[$pointerAfterParenthesisCloser]['code'] !== T_SEMICOLON
) {
$operatorsGroups = array_map(
static fn (int $operatorPointer): int => self::OPERATOR_GROUPS[$tokens[$operatorPointer]['code']],
$operatorsPointers,
);
if (count($operatorsGroups) > 1) {
return;
}
}
$firstOperatorPointer = $operatorsPointers[0];
if (in_array($tokens[$pointerBeforeParenthesisOpener]['code'], self::OPERATORS, true)) {
if (self::OPERATOR_GROUPS[$tokens[$firstOperatorPointer]['code']] !== self::OPERATOR_GROUPS[$tokens[$pointerBeforeParenthesisOpener]['code']]) {
return;
}
if (
$tokens[$pointerBeforeParenthesisOpener]['code'] === T_MINUS
&& in_array($tokens[$firstOperatorPointer]['code'], [T_PLUS, T_MINUS], true)
) {
return;
}
if (
$tokens[$pointerBeforeParenthesisOpener]['code'] === T_DIVIDE
&& in_array($tokens[$firstOperatorPointer]['code'], [T_DIVIDE, T_MULTIPLY], true)
) {
return;
}
if (
$tokens[$pointerBeforeParenthesisOpener]['code'] === T_MODULUS
&& $tokens[$firstOperatorPointer]['code'] === T_MODULUS
) {
return;
}
}
$lastOperatorPointer = $operatorsPointers[count($operatorsPointers) - 1];
if (
in_array($tokens[$pointerAfterParenthesisCloser]['code'], self::OPERATORS, true)
&& self::OPERATOR_GROUPS[$tokens[$lastOperatorPointer]['code']] !== self::OPERATOR_GROUPS[$tokens[$pointerAfterParenthesisCloser]['code']]
) {
return;
}
$fix = $phpcsFile->addFixableError('Useless parentheses.', $parenthesisOpenerPointer, self::CODE_USELESS_PARENTHESES);
if (!$fix) {
return;
}
$contentStartPointer = TokenHelper::findNextEffective($phpcsFile, $parenthesisOpenerPointer + 1);
$contentEndPointer = TokenHelper::findPreviousEffective($phpcsFile, $tokens[$parenthesisOpenerPointer]['parenthesis_closer'] - 1);
$phpcsFile->fixer->beginChangeset();
FixerHelper::removeBetweenIncluding($phpcsFile, $parenthesisOpenerPointer, $contentStartPointer - 1);
FixerHelper::removeBetweenIncluding($phpcsFile, $contentEndPointer + 1, $tokens[$parenthesisOpenerPointer]['parenthesis_closer']);
$phpcsFile->fixer->endChangeset();
}
private function checkParenthesesAroundNew(File $phpcsFile, int $parenthesisOpenerPointer): void
{
$tokens = $phpcsFile->getTokens();
$newPointer = TokenHelper::findNextEffective($phpcsFile, $parenthesisOpenerPointer + 1);
if ($tokens[$newPointer]['code'] !== T_NEW) {
return;
}
$pointerAfterParenthesisCloser = TokenHelper::findNextEffective(
$phpcsFile,
$tokens[$parenthesisOpenerPointer]['parenthesis_closer'] + 1,
);
if (!in_array($tokens[$pointerAfterParenthesisCloser]['code'], [T_COMMA, T_SEMICOLON, T_CLOSE_SHORT_ARRAY], true)) {
return;
}
$fix = $phpcsFile->addFixableError('Useless parentheses.', $parenthesisOpenerPointer, self::CODE_USELESS_PARENTHESES);
if (!$fix) {
return;
}
$contentStartPointer = TokenHelper::findNextEffective($phpcsFile, $parenthesisOpenerPointer + 1);
$contentEndPointer = TokenHelper::findPreviousEffective($phpcsFile, $tokens[$parenthesisOpenerPointer]['parenthesis_closer'] - 1);
$phpcsFile->fixer->beginChangeset();
FixerHelper::removeBetweenIncluding($phpcsFile, $parenthesisOpenerPointer, $contentStartPointer - 1);
FixerHelper::removeBetweenIncluding($phpcsFile, $contentEndPointer + 1, $tokens[$parenthesisOpenerPointer]['parenthesis_closer']);
$phpcsFile->fixer->endChangeset();
}
}
Directory Contents
Dirs: 0 × Files: 11