123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204 |
- <?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;
- use PhpCsFixer\Tokenizer\Tokens;
- abstract class AbstractNoUselessElseFixer extends AbstractFixer
- {
- public function getPriority(): int
- {
- // should be run before NoWhitespaceInBlankLineFixer, NoExtraBlankLinesFixer, BracesFixer and after NoEmptyStatementFixer.
- return 39;
- }
- protected function isSuperfluousElse(Tokens $tokens, int $index): bool
- {
- $previousBlockStart = $index;
- do {
- // Check if all 'if', 'else if ' and 'elseif' blocks above this 'else' always end,
- // if so this 'else' is overcomplete.
- [$previousBlockStart, $previousBlockEnd] = $this->getPreviousBlock($tokens, $previousBlockStart);
- // short 'if' detection
- $previous = $previousBlockEnd;
- if ($tokens[$previous]->equals('}')) {
- $previous = $tokens->getPrevMeaningfulToken($previous);
- }
- if (
- !$tokens[$previous]->equals(';') // 'if' block doesn't end with semicolon, keep 'else'
- || $tokens[$tokens->getPrevMeaningfulToken($previous)]->equals('{') // empty 'if' block, keep 'else'
- ) {
- return false;
- }
- $candidateIndex = $tokens->getPrevTokenOfKind(
- $previous,
- [
- ';',
- [T_BREAK],
- [T_CLOSE_TAG],
- [T_CONTINUE],
- [T_EXIT],
- [T_GOTO],
- [T_IF],
- [T_RETURN],
- [T_THROW],
- ]
- );
- if (null === $candidateIndex || $tokens[$candidateIndex]->equalsAny([';', [T_CLOSE_TAG], [T_IF]])) {
- return false;
- }
- if ($tokens[$candidateIndex]->isGivenKind(T_THROW)) {
- $previousIndex = $tokens->getPrevMeaningfulToken($candidateIndex);
- if (!$tokens[$previousIndex]->equalsAny([';', '{'])) {
- return false;
- }
- }
- if ($this->isInConditional($tokens, $candidateIndex, $previousBlockStart)
- || $this->isInConditionWithoutBraces($tokens, $candidateIndex, $previousBlockStart)
- ) {
- return false;
- }
- // implicit continue, i.e. delete candidate
- } while (!$tokens[$previousBlockStart]->isGivenKind(T_IF));
- return true;
- }
- /**
- * Return the first and last token index of the previous block.
- *
- * [0] First is either T_IF, T_ELSE or T_ELSEIF
- * [1] Last is either '}' or ';' / T_CLOSE_TAG for short notation blocks
- *
- * @param int $index T_IF, T_ELSE, T_ELSEIF
- *
- * @return array{int, int}
- */
- private function getPreviousBlock(Tokens $tokens, int $index): array
- {
- $close = $previous = $tokens->getPrevMeaningfulToken($index);
- // short 'if' detection
- if ($tokens[$close]->equals('}')) {
- $previous = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $close);
- }
- $open = $tokens->getPrevTokenOfKind($previous, [[T_IF], [T_ELSE], [T_ELSEIF]]);
- if ($tokens[$open]->isGivenKind(T_IF)) {
- $elseCandidate = $tokens->getPrevMeaningfulToken($open);
- if ($tokens[$elseCandidate]->isGivenKind(T_ELSE)) {
- $open = $elseCandidate;
- }
- }
- return [$open, $close];
- }
- /**
- * @param int $index Index of the token to check
- * @param int $lowerLimitIndex Lower limit index. Since the token to check will always be in a conditional we must stop checking at this index
- */
- private function isInConditional(Tokens $tokens, int $index, int $lowerLimitIndex): bool
- {
- $candidateIndex = $tokens->getPrevTokenOfKind($index, [')', ';', ':']);
- if ($tokens[$candidateIndex]->equals(':')) {
- return true;
- }
- if (!$tokens[$candidateIndex]->equals(')')) {
- return false; // token is ';' or close tag
- }
- // token is always ')' here.
- // If it is part of the condition the token is always in, return false.
- // If it is not it is a nested condition so return true
- $open = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $candidateIndex);
- return $tokens->getPrevMeaningfulToken($open) > $lowerLimitIndex;
- }
- /**
- * For internal use only, as it is not perfect.
- *
- * Returns if the token at given index is part of an if/elseif/else statement
- * without {}. Assumes not passing the last `;`/close tag of the statement, not
- * out of range index, etc.
- *
- * @param int $index Index of the token to check
- */
- private function isInConditionWithoutBraces(Tokens $tokens, int $index, int $lowerLimitIndex): bool
- {
- do {
- if ($tokens[$index]->isComment() || $tokens[$index]->isWhitespace()) {
- $index = $tokens->getPrevMeaningfulToken($index);
- }
- $token = $tokens[$index];
- if ($token->isGivenKind([T_IF, T_ELSEIF, T_ELSE])) {
- return true;
- }
- if ($token->equals(';')) {
- return false;
- }
- if ($token->equals('{')) {
- $index = $tokens->getPrevMeaningfulToken($index);
- // OK if belongs to: for, do, while, foreach
- // Not OK if belongs to: if, else, elseif
- if ($tokens[$index]->isGivenKind(T_DO)) {
- --$index;
- continue;
- }
- if (!$tokens[$index]->equals(')')) {
- return false; // like `else {`
- }
- $index = $tokens->findBlockStart(
- Tokens::BLOCK_TYPE_PARENTHESIS_BRACE,
- $index
- );
- $index = $tokens->getPrevMeaningfulToken($index);
- if ($tokens[$index]->isGivenKind([T_IF, T_ELSEIF])) {
- return false;
- }
- } elseif ($token->equals(')')) {
- $type = Tokens::detectBlockType($token);
- $index = $tokens->findBlockStart(
- $type['type'],
- $index
- );
- $index = $tokens->getPrevMeaningfulToken($index);
- } else {
- --$index;
- }
- } while ($index > $lowerLimitIndex);
- return false;
- }
- }
|