File "NoUnneededControlParenthesesFixer.php"
Full Path: /var/www/html/back/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUnneededControlParenthesesFixer.php
File size: 26.15 KB
MIME-type: text/x-php
Charset: utf-8
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Fixer\ControlStructure;
use PhpCsFixer\AbstractFixer;
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
use PhpCsFixer\Fixer\ConfigurableFixerTrait;
use PhpCsFixer\FixerConfiguration\AllowedValueSubset;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\FCT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
use PhpCsFixer\Tokenizer\TokensAnalyzer;
/**
* @phpstan-type _AutogeneratedInputConfiguration array{
* statements?: list<'break'|'clone'|'continue'|'echo_print'|'negative_instanceof'|'others'|'return'|'switch_case'|'yield'|'yield_from'>,
* }
* @phpstan-type _AutogeneratedComputedConfiguration array{
* statements: list<'break'|'clone'|'continue'|'echo_print'|'negative_instanceof'|'others'|'return'|'switch_case'|'yield'|'yield_from'>,
* }
*
* @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
*
* @phpstan-import-type _PhpTokenPrototypePartial from Token
*
* @author Sullivan Senechal <soullivaneuh@gmail.com>
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
* @author Gregor Harlan <gharlan@web.de>
*
* @no-named-arguments Parameter names are not covered by the backward compatibility promise.
*/
final class NoUnneededControlParenthesesFixer extends AbstractFixer implements ConfigurableFixerInterface
{
/** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */
use ConfigurableFixerTrait;
/**
* @var non-empty-list<int>
*/
private const BLOCK_TYPES = [
Tokens::BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE,
Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE,
Tokens::BLOCK_TYPE_CURLY_BRACE,
Tokens::BLOCK_TYPE_DESTRUCTURING_SQUARE_BRACE,
Tokens::BLOCK_TYPE_DYNAMIC_PROP_BRACE,
Tokens::BLOCK_TYPE_DYNAMIC_VAR_BRACE,
Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE,
Tokens::BLOCK_TYPE_PARENTHESIS_BRACE,
];
private const BEFORE_TYPES = [
';',
'{',
[\T_OPEN_TAG],
[\T_OPEN_TAG_WITH_ECHO],
[\T_ECHO],
[\T_PRINT],
[\T_RETURN],
[\T_THROW],
[\T_YIELD],
[\T_YIELD_FROM],
[\T_BREAK],
[\T_CONTINUE],
// won't be fixed, but true in concept, helpful for fast check
[\T_REQUIRE],
[\T_REQUIRE_ONCE],
[\T_INCLUDE],
[\T_INCLUDE_ONCE],
];
private const CONFIG_OPTIONS = [
'break',
'clone',
'continue',
'echo_print',
'negative_instanceof',
'others',
'return',
'switch_case',
'yield',
'yield_from',
];
private const TOKEN_TYPE_CONFIG_MAP = [
\T_BREAK => 'break',
\T_CASE => 'switch_case',
\T_CONTINUE => 'continue',
\T_ECHO => 'echo_print',
\T_PRINT => 'echo_print',
\T_RETURN => 'return',
\T_YIELD => 'yield',
\T_YIELD_FROM => 'yield_from',
];
// handled by the `include` rule
private const TOKEN_TYPE_NO_CONFIG = [
\T_REQUIRE,
\T_REQUIRE_ONCE,
\T_INCLUDE,
\T_INCLUDE_ONCE,
];
private const KNOWN_NEGATIVE_PRE_TYPES = [
[CT::T_CLASS_CONSTANT],
[CT::T_DYNAMIC_VAR_BRACE_CLOSE],
[CT::T_RETURN_REF],
[CT::T_USE_LAMBDA],
[\T_ARRAY],
[\T_CATCH],
[\T_CLASS],
[\T_DECLARE],
[\T_ELSEIF],
[\T_EMPTY],
[\T_EXIT],
[\T_EVAL],
[\T_FN],
[\T_FOREACH],
[\T_FOR],
[\T_FUNCTION],
[\T_HALT_COMPILER],
[\T_IF],
[\T_ISSET],
[\T_LIST],
[\T_STRING],
[\T_SWITCH],
[\T_STATIC],
[\T_UNSET],
[\T_VARIABLE],
[\T_WHILE],
// handled by the `include` rule
[\T_REQUIRE],
[\T_REQUIRE_ONCE],
[\T_INCLUDE],
[\T_INCLUDE_ONCE],
[FCT::T_MATCH],
];
/**
* @var list<_PhpTokenPrototypePartial>
*/
private array $noopTypes;
private TokensAnalyzer $tokensAnalyzer;
public function __construct()
{
parent::__construct();
$this->noopTypes = [
'$',
[\T_CONSTANT_ENCAPSED_STRING],
[\T_DNUMBER],
[\T_DOUBLE_COLON],
[\T_LNUMBER],
[\T_NS_SEPARATOR],
[\T_STRING],
[\T_VARIABLE],
[\T_STATIC],
// magic constants
[\T_CLASS_C],
[\T_DIR],
[\T_FILE],
[\T_FUNC_C],
[\T_LINE],
[\T_METHOD_C],
[\T_NS_C],
[\T_TRAIT_C],
];
foreach (Token::getObjectOperatorKinds() as $kind) {
$this->noopTypes[] = [$kind];
}
}
public function getDefinition(): FixerDefinitionInterface
{
return new FixerDefinition(
'Removes unneeded parentheses around control statements.',
[
new CodeSample(
<<<'PHP'
<?php
while ($x) { while ($y) { break (2); } }
clone($a);
while ($y) { continue (2); }
echo("foo");
print("foo");
return (1 + 2);
switch ($a) { case($x); }
yield(2);
PHP,
),
new CodeSample(
<<<'PHP'
<?php
while ($x) { while ($y) { break (2); } }
clone($a);
while ($y) { continue (2); }
PHP,
['statements' => ['break', 'continue']],
),
],
);
}
/**
* {@inheritdoc}
*
* Must run before ConcatSpaceFixer, NewExpressionParenthesesFixer, NoTrailingWhitespaceFixer.
* Must run after ModernizeTypesCastingFixer, NoAlternativeSyntaxFixer.
*/
public function getPriority(): int
{
return 30;
}
public function isCandidate(Tokens $tokens): bool
{
return $tokens->isAnyTokenKindsFound(['(', CT::T_BRACE_CLASS_INSTANTIATION_OPEN]);
}
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
{
$this->tokensAnalyzer = new TokensAnalyzer($tokens);
foreach ($tokens as $openIndex => $token) {
if ($token->equals('(')) {
$closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex);
} elseif ($token->isGivenKind(CT::T_BRACE_CLASS_INSTANTIATION_OPEN)) {
$closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_BRACE_CLASS_INSTANTIATION, $openIndex);
} else {
continue;
}
$beforeOpenIndex = $tokens->getPrevMeaningfulToken($openIndex);
$afterCloseIndex = $tokens->getNextMeaningfulToken($closeIndex);
// do a cheap check for negative case: `X()`
if ($tokens->getNextMeaningfulToken($openIndex) === $closeIndex) {
if ($tokens[$beforeOpenIndex]->isGivenKind(\T_EXIT)) {
$this->removeUselessParenthesisPair($tokens, $beforeOpenIndex, $afterCloseIndex, $openIndex, $closeIndex, 'others');
}
continue;
}
// do a cheap check for negative case: `foo(1,2)`
if ($tokens[$beforeOpenIndex]->equalsAny(self::KNOWN_NEGATIVE_PRE_TYPES)) {
continue;
}
// check for the simple useless wrapped cases
if ($this->isUselessWrapped($tokens, $beforeOpenIndex, $afterCloseIndex)) {
$this->removeUselessParenthesisPair($tokens, $beforeOpenIndex, $afterCloseIndex, $openIndex, $closeIndex, $this->getConfigType($tokens, $beforeOpenIndex));
continue;
}
// handle `clone` statements
if ($tokens[$beforeOpenIndex]->isGivenKind(\T_CLONE)) {
if ($this->isWrappedCloneArgument($tokens, $beforeOpenIndex, $openIndex, $closeIndex, $afterCloseIndex)) {
$this->removeUselessParenthesisPair($tokens, $beforeOpenIndex, $afterCloseIndex, $openIndex, $closeIndex, 'clone');
}
continue;
}
// handle `instance of` statements
$instanceOfIndex = $this->getIndexOfInstanceOfStatement($tokens, $openIndex, $closeIndex);
if (null !== $instanceOfIndex) {
if ($this->isWrappedInstanceOf($tokens, $instanceOfIndex, $beforeOpenIndex, $openIndex, $closeIndex, $afterCloseIndex)) {
$this->removeUselessParenthesisPair(
$tokens,
$beforeOpenIndex,
$afterCloseIndex,
$openIndex,
$closeIndex,
$tokens[$beforeOpenIndex]->equals('!') ? 'negative_instanceof' : 'others',
);
}
continue;
}
// last checks deal with operators, do not swap around
if ($this->isWrappedPartOfOperation($tokens, $beforeOpenIndex, $openIndex, $closeIndex, $afterCloseIndex)) {
$this->removeUselessParenthesisPair($tokens, $beforeOpenIndex, $afterCloseIndex, $openIndex, $closeIndex, $this->getConfigType($tokens, $beforeOpenIndex));
}
}
}
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
{
$defaults = array_filter(
self::CONFIG_OPTIONS,
static fn (string $option): bool => 'negative_instanceof' !== $option && 'others' !== $option && 'yield_from' !== $option,
);
return new FixerConfigurationResolver([
(new FixerOptionBuilder('statements', 'List of control statements to fix.'))
->setAllowedTypes(['string[]'])
->setAllowedValues([new AllowedValueSubset(self::CONFIG_OPTIONS)])
->setDefault(array_values($defaults))
->getOption(),
]);
}
private function isUselessWrapped(Tokens $tokens, int $beforeOpenIndex, int $afterCloseIndex): bool
{
return
$this->isSingleStatement($tokens, $beforeOpenIndex, $afterCloseIndex)
|| $this->isWrappedFnBody($tokens, $beforeOpenIndex, $afterCloseIndex)
|| $this->isWrappedForElement($tokens, $beforeOpenIndex, $afterCloseIndex)
|| $this->isWrappedLanguageConstructArgument($tokens, $beforeOpenIndex, $afterCloseIndex)
|| $this->isWrappedSequenceElement($tokens, $beforeOpenIndex, $afterCloseIndex);
}
private function isWrappedCloneArgument(Tokens $tokens, int $beforeOpenIndex, int $openIndex, int $closeIndex, int $afterCloseIndex): bool
{
$beforeOpenIndex = $tokens->getPrevMeaningfulToken($beforeOpenIndex);
if (
!(
$tokens[$beforeOpenIndex]->equals('?') // For BC reasons
|| $this->isSimpleAssignment($tokens, $beforeOpenIndex, $afterCloseIndex)
|| $this->isSingleStatement($tokens, $beforeOpenIndex, $afterCloseIndex)
|| $this->isWrappedFnBody($tokens, $beforeOpenIndex, $afterCloseIndex)
|| $this->isWrappedForElement($tokens, $beforeOpenIndex, $afterCloseIndex)
|| $this->isWrappedSequenceElement($tokens, $beforeOpenIndex, $afterCloseIndex)
)
) {
return false;
}
$newCandidateIndex = $tokens->getNextMeaningfulToken($openIndex);
if ($tokens[$newCandidateIndex]->isGivenKind(\T_NEW)) {
$openIndex = $newCandidateIndex; // `clone (new X)`, `clone (new X())`, clone (new X(Y))`
}
return !$this->containsOperation($tokens, $openIndex, $closeIndex);
}
private function getIndexOfInstanceOfStatement(Tokens $tokens, int $openIndex, int $closeIndex): ?int
{
$instanceOfIndex = $tokens->findGivenKind(\T_INSTANCEOF, $openIndex, $closeIndex);
return 1 === \count($instanceOfIndex) ? array_key_first($instanceOfIndex) : null;
}
private function isWrappedInstanceOf(Tokens $tokens, int $instanceOfIndex, int $beforeOpenIndex, int $openIndex, int $closeIndex, int $afterCloseIndex): bool
{
if (
$this->containsOperation($tokens, $openIndex, $instanceOfIndex)
|| $this->containsOperation($tokens, $instanceOfIndex, $closeIndex)
) {
return false;
}
if ($tokens[$beforeOpenIndex]->equals('!')) {
$beforeOpenIndex = $tokens->getPrevMeaningfulToken($beforeOpenIndex);
}
return
$this->isSimpleAssignment($tokens, $beforeOpenIndex, $afterCloseIndex)
|| $this->isSingleStatement($tokens, $beforeOpenIndex, $afterCloseIndex)
|| $this->isWrappedFnBody($tokens, $beforeOpenIndex, $afterCloseIndex)
|| $this->isWrappedForElement($tokens, $beforeOpenIndex, $afterCloseIndex)
|| $this->isWrappedSequenceElement($tokens, $beforeOpenIndex, $afterCloseIndex);
}
private function isWrappedPartOfOperation(Tokens $tokens, int $beforeOpenIndex, int $openIndex, int $closeIndex, int $afterCloseIndex): bool
{
if ($this->containsOperation($tokens, $openIndex, $closeIndex)) {
return false;
}
$boundariesMoved = false;
if ($this->isPreUnaryOperation($tokens, $beforeOpenIndex)) {
$beforeOpenIndex = $this->getBeforePreUnaryOperation($tokens, $beforeOpenIndex);
$boundariesMoved = true;
}
if ($this->isAccess($tokens, $afterCloseIndex)) {
$afterCloseIndex = $this->getAfterAccess($tokens, $afterCloseIndex);
$boundariesMoved = true;
if ($this->tokensAnalyzer->isUnarySuccessorOperator($afterCloseIndex)) { // post unary operation are only valid here
$afterCloseIndex = $tokens->getNextMeaningfulToken($afterCloseIndex);
}
}
if ($boundariesMoved) {
if ($tokens[$beforeOpenIndex]->equalsAny(self::KNOWN_NEGATIVE_PRE_TYPES)) {
return false;
}
if ($this->isUselessWrapped($tokens, $beforeOpenIndex, $afterCloseIndex)) {
return true;
}
}
// check if part of some operation sequence
$beforeIsBinaryOperation = $this->tokensAnalyzer->isBinaryOperator($beforeOpenIndex);
$afterIsBinaryOperation = $this->tokensAnalyzer->isBinaryOperator($afterCloseIndex);
if ($beforeIsBinaryOperation && $afterIsBinaryOperation) {
return true; // `+ (x) +`
}
$beforeToken = $tokens[$beforeOpenIndex];
$afterToken = $tokens[$afterCloseIndex];
$beforeIsBlockOpenOrComma = $beforeToken->equals(',') || null !== $this->getBlock($tokens, $beforeOpenIndex, true);
$afterIsBlockEndOrComma = $afterToken->equals(',') || null !== $this->getBlock($tokens, $afterCloseIndex, false);
if (($beforeIsBlockOpenOrComma && $afterIsBinaryOperation) || ($beforeIsBinaryOperation && $afterIsBlockEndOrComma)) {
// $beforeIsBlockOpenOrComma && $afterIsBlockEndOrComma is covered by `isWrappedSequenceElement`
// `[ (x) +` or `+ (X) ]` or `, (X) +` or `+ (X) ,`
return true;
}
if ($tokens[$beforeOpenIndex]->equals('}')) {
$beforeIsStatementOpen = !$this->closeCurlyBelongsToDynamicElement($tokens, $beforeOpenIndex);
} else {
$beforeIsStatementOpen = $beforeToken->equalsAny(self::BEFORE_TYPES) || $beforeToken->isGivenKind(\T_CASE);
}
$afterIsStatementEnd = $afterToken->equalsAny([';', [\T_CLOSE_TAG]]);
return
($beforeIsStatementOpen && $afterIsBinaryOperation) // `<?php (X) +`
|| ($beforeIsBinaryOperation && $afterIsStatementEnd); // `+ (X);`
}
// bounded `print|yield|yield from|require|require_once|include|include_once (X)`
private function isWrappedLanguageConstructArgument(Tokens $tokens, int $beforeOpenIndex, int $afterCloseIndex): bool
{
if (!$tokens[$beforeOpenIndex]->isGivenKind([\T_PRINT, \T_YIELD, \T_YIELD_FROM, \T_REQUIRE, \T_REQUIRE_ONCE, \T_INCLUDE, \T_INCLUDE_ONCE])) {
return false;
}
$beforeOpenIndex = $tokens->getPrevMeaningfulToken($beforeOpenIndex);
return $this->isWrappedSequenceElement($tokens, $beforeOpenIndex, $afterCloseIndex);
}
// any of `<?php|<?|<?=|;|throw|return|... (X) ;|T_CLOSE`
private function isSingleStatement(Tokens $tokens, int $beforeOpenIndex, int $afterCloseIndex): bool
{
if ($tokens[$beforeOpenIndex]->isGivenKind(\T_CASE)) {
return $tokens[$afterCloseIndex]->equalsAny([':', ';']); // `switch case`
}
if (!$tokens[$afterCloseIndex]->equalsAny([';', [\T_CLOSE_TAG]])) {
return false;
}
if ($tokens[$beforeOpenIndex]->equals('}')) {
return !$this->closeCurlyBelongsToDynamicElement($tokens, $beforeOpenIndex);
}
return $tokens[$beforeOpenIndex]->equalsAny(self::BEFORE_TYPES);
}
private function isSimpleAssignment(Tokens $tokens, int $beforeOpenIndex, int $afterCloseIndex): bool
{
return $tokens[$beforeOpenIndex]->equals('=') && $tokens[$afterCloseIndex]->equalsAny([';', [\T_CLOSE_TAG]]); // `= (X) ;`
}
private function isWrappedSequenceElement(Tokens $tokens, int $startIndex, int $endIndex): bool
{
$startIsComma = $tokens[$startIndex]->equals(',');
$endIsComma = $tokens[$endIndex]->equals(',');
if ($startIsComma && $endIsComma) {
return true; // `,(X),`
}
$blockTypeStart = $this->getBlock($tokens, $startIndex, true);
$blockTypeEnd = $this->getBlock($tokens, $endIndex, false);
return
($startIsComma && null !== $blockTypeEnd) // `,(X)]`
|| ($endIsComma && null !== $blockTypeStart) // `[(X),`
|| (null !== $blockTypeEnd && null !== $blockTypeStart); // any type of `{(X)}`, `[(X)]` and `((X))`
}
// any of `for( (X); ;(X)) ;` note that the middle element is covered as 'single statement' as it is `; (X) ;`
private function isWrappedForElement(Tokens $tokens, int $beforeOpenIndex, int $afterCloseIndex): bool
{
$forCandidateIndex = null;
if ($tokens[$beforeOpenIndex]->equals('(') && $tokens[$afterCloseIndex]->equals(';')) {
$forCandidateIndex = $tokens->getPrevMeaningfulToken($beforeOpenIndex);
} elseif ($tokens[$afterCloseIndex]->equals(')') && $tokens[$beforeOpenIndex]->equals(';')) {
$forCandidateIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $afterCloseIndex);
$forCandidateIndex = $tokens->getPrevMeaningfulToken($forCandidateIndex);
}
return null !== $forCandidateIndex && $tokens[$forCandidateIndex]->isGivenKind(\T_FOR);
}
// `fn() => (X);`
private function isWrappedFnBody(Tokens $tokens, int $beforeOpenIndex, int $afterCloseIndex): bool
{
if (!$tokens[$beforeOpenIndex]->isGivenKind(\T_DOUBLE_ARROW)) {
return false;
}
$beforeOpenIndex = $tokens->getPrevMeaningfulToken($beforeOpenIndex);
if ($tokens[$beforeOpenIndex]->isGivenKind(\T_STRING)) {
while (true) {
$beforeOpenIndex = $tokens->getPrevMeaningfulToken($beforeOpenIndex);
if (!$tokens[$beforeOpenIndex]->isGivenKind([\T_STRING, CT::T_TYPE_INTERSECTION, CT::T_TYPE_ALTERNATION])) {
break;
}
}
if (!$tokens[$beforeOpenIndex]->isGivenKind(CT::T_TYPE_COLON)) {
return false;
}
$beforeOpenIndex = $tokens->getPrevMeaningfulToken($beforeOpenIndex);
}
if (!$tokens[$beforeOpenIndex]->equals(')')) {
return false;
}
$beforeOpenIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $beforeOpenIndex);
$beforeOpenIndex = $tokens->getPrevMeaningfulToken($beforeOpenIndex);
if ($tokens[$beforeOpenIndex]->isGivenKind(CT::T_RETURN_REF)) {
$beforeOpenIndex = $tokens->getPrevMeaningfulToken($beforeOpenIndex);
}
if (!$tokens[$beforeOpenIndex]->isGivenKind(\T_FN)) {
return false;
}
return $tokens[$afterCloseIndex]->equalsAny([';', ',', [\T_CLOSE_TAG]]);
}
private function isPreUnaryOperation(Tokens $tokens, int $index): bool
{
return $this->tokensAnalyzer->isUnaryPredecessorOperator($index) || $tokens[$index]->isCast();
}
private function getBeforePreUnaryOperation(Tokens $tokens, int $index): int
{
do {
$index = $tokens->getPrevMeaningfulToken($index);
} while ($this->isPreUnaryOperation($tokens, $index));
return $index;
}
// array access `(X)[` or `(X){` or object access `(X)->` or `(X)?->`
private function isAccess(Tokens $tokens, int $index): bool
{
$token = $tokens[$index];
return $token->isObjectOperator() || $token->equals('[') || $token->isGivenKind(CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN);
}
private function getAfterAccess(Tokens $tokens, int $index): int
{
while (true) {
$block = $this->getBlock($tokens, $index, true);
if (null !== $block) {
$index = $tokens->findBlockEnd($block['type'], $index);
$index = $tokens->getNextMeaningfulToken($index);
continue;
}
if (
$tokens[$index]->isObjectOperator()
|| $tokens[$index]->equalsAny(['$', [\T_PAAMAYIM_NEKUDOTAYIM], [\T_STRING], [\T_VARIABLE]])
) {
$index = $tokens->getNextMeaningfulToken($index);
continue;
}
break;
}
return $index;
}
/**
* @return null|array{type: Tokens::BLOCK_TYPE_*, isStart: bool}
*/
private function getBlock(Tokens $tokens, int $index, bool $isStart): ?array
{
$block = Tokens::detectBlockType($tokens[$index]);
return null !== $block && $isStart === $block['isStart'] && \in_array($block['type'], self::BLOCK_TYPES, true) ? $block : null;
}
private function containsOperation(Tokens $tokens, int $startIndex, int $endIndex): bool
{
while (true) {
$startIndex = $tokens->getNextMeaningfulToken($startIndex);
if ($startIndex === $endIndex) {
break;
}
$block = Tokens::detectBlockType($tokens[$startIndex]);
if (null !== $block && $block['isStart']) {
$startIndex = $tokens->findBlockEnd($block['type'], $startIndex);
continue;
}
if (!$tokens[$startIndex]->equalsAny($this->noopTypes)) {
return true;
}
}
return false;
}
private function getConfigType(Tokens $tokens, int $beforeOpenIndex): ?string
{
if ($tokens[$beforeOpenIndex]->isGivenKind(self::TOKEN_TYPE_NO_CONFIG)) {
return null;
}
foreach (self::TOKEN_TYPE_CONFIG_MAP as $type => $configItem) {
if ($tokens[$beforeOpenIndex]->isGivenKind($type)) {
return $configItem;
}
}
return 'others';
}
private function removeUselessParenthesisPair(
Tokens $tokens,
int $beforeOpenIndex,
int $afterCloseIndex,
int $openIndex,
int $closeIndex,
?string $configType
): void {
$statements = $this->configuration['statements'];
if (null === $configType || !\in_array($configType, $statements, true)) {
return;
}
$needsSpaceAfter = !$this->isAccess($tokens, $afterCloseIndex)
&& !$tokens[$afterCloseIndex]->equalsAny([';', ',', [\T_CLOSE_TAG]])
&& null === $this->getBlock($tokens, $afterCloseIndex, false)
&& !($tokens[$afterCloseIndex]->equalsAny([':', ';']) && $tokens[$beforeOpenIndex]->isGivenKind(\T_CASE));
$needsSpaceBefore = !$this->isPreUnaryOperation($tokens, $beforeOpenIndex)
&& !$tokens[$beforeOpenIndex]->equalsAny(['}', [\T_EXIT], [\T_OPEN_TAG]])
&& null === $this->getBlock($tokens, $beforeOpenIndex, true);
$this->removeBrace($tokens, $closeIndex, $needsSpaceAfter);
$this->removeBrace($tokens, $openIndex, $needsSpaceBefore);
}
private function removeBrace(Tokens $tokens, int $index, bool $needsSpace): void
{
if ($needsSpace) {
foreach ([-1, 1] as $direction) {
$siblingIndex = $tokens->getNonEmptySibling($index, $direction);
if ($tokens[$siblingIndex]->isWhitespace() || $tokens[$siblingIndex]->isComment()) {
$needsSpace = false;
break;
}
}
}
if ($needsSpace) {
$tokens[$index] = new Token([\T_WHITESPACE, ' ']);
} else {
$tokens->clearTokenAndMergeSurroundingWhitespace($index);
}
}
private function closeCurlyBelongsToDynamicElement(Tokens $tokens, int $beforeOpenIndex): bool
{
$index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $beforeOpenIndex);
$index = $tokens->getPrevMeaningfulToken($index);
if ($tokens[$index]->isGivenKind(\T_DOUBLE_COLON)) {
return true;
}
if ($tokens[$index]->equals(':')) {
$index = $tokens->getPrevTokenOfKind($index, [[\T_CASE], '?']);
return !$tokens[$index]->isGivenKind(\T_CASE);
}
return false;
}
}