Missing dependancies
This commit is contained in:
Vendored
+266
@@ -0,0 +1,266 @@
|
||||
<?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\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\AlternativeSyntaxAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class ControlStructureBracesFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'The body of each control structure MUST be enclosed within braces.',
|
||||
[new CodeSample("<?php\nif (foo()) echo 'Hello!';\n")]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before ControlStructureContinuationPositionFixer, CurlyBracesPositionFixer, NoMultipleStatementsPerLineFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$alternativeSyntaxAnalyzer = new AlternativeSyntaxAnalyzer();
|
||||
$controlTokens = $this->getControlTokens();
|
||||
|
||||
for ($index = $tokens->count() - 1; 0 <= $index; --$index) {
|
||||
$token = $tokens[$index];
|
||||
|
||||
if (!$token->isGivenKind($controlTokens)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
$token->isGivenKind(T_ELSE)
|
||||
&& $tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(T_IF)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parenthesisEndIndex = $this->findParenthesisEnd($tokens, $index);
|
||||
$nextAfterParenthesisEndIndex = $tokens->getNextMeaningfulToken($parenthesisEndIndex);
|
||||
$tokenAfterParenthesis = $tokens[$nextAfterParenthesisEndIndex];
|
||||
|
||||
if ($tokenAfterParenthesis->equalsAny([';', '{', ':'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$statementEndIndex = null;
|
||||
|
||||
if ($tokenAfterParenthesis->isGivenKind([T_IF, T_FOR, T_FOREACH, T_SWITCH, T_WHILE])) {
|
||||
$tokenAfterParenthesisBlockEnd = $tokens->findBlockEnd( // go to ')'
|
||||
Tokens::BLOCK_TYPE_PARENTHESIS_BRACE,
|
||||
$tokens->getNextMeaningfulToken($nextAfterParenthesisEndIndex)
|
||||
);
|
||||
|
||||
if ($tokens[$tokens->getNextMeaningfulToken($tokenAfterParenthesisBlockEnd)]->equals(':')) {
|
||||
$statementEndIndex = $alternativeSyntaxAnalyzer->findAlternativeSyntaxBlockEnd($tokens, $nextAfterParenthesisEndIndex);
|
||||
|
||||
$tokenAfterStatementEndIndex = $tokens->getNextMeaningfulToken($statementEndIndex);
|
||||
if ($tokens[$tokenAfterStatementEndIndex]->equals(';')) {
|
||||
$statementEndIndex = $tokenAfterStatementEndIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (null === $statementEndIndex) {
|
||||
$statementEndIndex = $this->findStatementEnd($tokens, $parenthesisEndIndex);
|
||||
}
|
||||
|
||||
$tokensToInsertAfterStatement = [
|
||||
new Token([T_WHITESPACE, ' ']),
|
||||
new Token('}'),
|
||||
];
|
||||
|
||||
if (!$tokens[$statementEndIndex]->equalsAny([';', '}'])) {
|
||||
array_unshift($tokensToInsertAfterStatement, new Token(';'));
|
||||
}
|
||||
|
||||
$tokens->insertSlices([$statementEndIndex + 1 => $tokensToInsertAfterStatement]);
|
||||
|
||||
// insert opening brace
|
||||
$tokens->insertSlices([$parenthesisEndIndex + 1 => [
|
||||
new Token([T_WHITESPACE, ' ']),
|
||||
new Token('{'),
|
||||
]]);
|
||||
}
|
||||
}
|
||||
|
||||
private function findParenthesisEnd(Tokens $tokens, int $structureTokenIndex): int
|
||||
{
|
||||
$nextIndex = $tokens->getNextMeaningfulToken($structureTokenIndex);
|
||||
$nextToken = $tokens[$nextIndex];
|
||||
|
||||
if (!$nextToken->equals('(')) {
|
||||
return $structureTokenIndex;
|
||||
}
|
||||
|
||||
return $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $nextIndex);
|
||||
}
|
||||
|
||||
private function findStatementEnd(Tokens $tokens, int $parenthesisEndIndex): int
|
||||
{
|
||||
$nextIndex = $tokens->getNextMeaningfulToken($parenthesisEndIndex);
|
||||
$nextToken = $tokens[$nextIndex];
|
||||
|
||||
if (!$nextToken) {
|
||||
return $parenthesisEndIndex;
|
||||
}
|
||||
|
||||
if ($nextToken->equals('{')) {
|
||||
return $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $nextIndex);
|
||||
}
|
||||
|
||||
if ($nextToken->isGivenKind($this->getControlTokens())) {
|
||||
$parenthesisEndIndex = $this->findParenthesisEnd($tokens, $nextIndex);
|
||||
|
||||
$endIndex = $this->findStatementEnd($tokens, $parenthesisEndIndex);
|
||||
|
||||
if ($nextToken->isGivenKind([T_IF, T_TRY, T_DO])) {
|
||||
$openingTokenKind = $nextToken->getId();
|
||||
|
||||
while (true) {
|
||||
$nextIndex = $tokens->getNextMeaningfulToken($endIndex);
|
||||
$nextToken = isset($nextIndex) ? $tokens[$nextIndex] : null;
|
||||
if ($nextToken && $nextToken->isGivenKind($this->getControlContinuationTokensForOpeningToken($openingTokenKind))) {
|
||||
$parenthesisEndIndex = $this->findParenthesisEnd($tokens, $nextIndex);
|
||||
|
||||
$endIndex = $this->findStatementEnd($tokens, $parenthesisEndIndex);
|
||||
|
||||
if ($nextToken->isGivenKind($this->getFinalControlContinuationTokensForOpeningToken($openingTokenKind))) {
|
||||
return $endIndex;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $endIndex;
|
||||
}
|
||||
|
||||
$index = $parenthesisEndIndex;
|
||||
|
||||
while (true) {
|
||||
$token = $tokens[++$index];
|
||||
|
||||
// if there is some block in statement (eg lambda function) we need to skip it
|
||||
if ($token->equals('{')) {
|
||||
$index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->equals(';')) {
|
||||
return $index;
|
||||
}
|
||||
|
||||
if ($token->isGivenKind(T_CLOSE_TAG)) {
|
||||
return $tokens->getPrevNonWhitespace($index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<int>
|
||||
*/
|
||||
private function getControlTokens(): array
|
||||
{
|
||||
static $tokens = [
|
||||
T_DECLARE,
|
||||
T_DO,
|
||||
T_ELSE,
|
||||
T_ELSEIF,
|
||||
T_FINALLY,
|
||||
T_FOR,
|
||||
T_FOREACH,
|
||||
T_IF,
|
||||
T_WHILE,
|
||||
T_TRY,
|
||||
T_CATCH,
|
||||
T_SWITCH,
|
||||
];
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<int>
|
||||
*/
|
||||
private function getControlContinuationTokensForOpeningToken(int $openingTokenKind): array
|
||||
{
|
||||
if (T_IF === $openingTokenKind) {
|
||||
return [
|
||||
T_ELSE,
|
||||
T_ELSEIF,
|
||||
];
|
||||
}
|
||||
|
||||
if (T_DO === $openingTokenKind) {
|
||||
return [T_WHILE];
|
||||
}
|
||||
|
||||
if (T_TRY === $openingTokenKind) {
|
||||
return [
|
||||
T_CATCH,
|
||||
T_FINALLY,
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<int>
|
||||
*/
|
||||
private function getFinalControlContinuationTokensForOpeningToken(int $openingTokenKind): array
|
||||
{
|
||||
if (T_IF === $openingTokenKind) {
|
||||
return [T_ELSE];
|
||||
}
|
||||
|
||||
if (T_TRY === $openingTokenKind) {
|
||||
return [T_FINALLY];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
<?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\WhitespacesAwareFixerInterface;
|
||||
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\Analyzer\WhitespacesAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class ControlStructureContinuationPositionFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public const NEXT_LINE = 'next_line';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public const SAME_LINE = 'same_line';
|
||||
|
||||
private const CONTROL_CONTINUATION_TOKENS = [
|
||||
T_CATCH,
|
||||
T_ELSE,
|
||||
T_ELSEIF,
|
||||
T_FINALLY,
|
||||
T_WHILE,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Control structure continuation keyword must be on the configured line.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
if ($baz == true) {
|
||||
echo "foo";
|
||||
}
|
||||
else {
|
||||
echo "bar";
|
||||
}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
if ($baz == true) {
|
||||
echo "foo";
|
||||
} else {
|
||||
echo "bar";
|
||||
}
|
||||
',
|
||||
['position' => self::NEXT_LINE]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound(self::CONTROL_CONTINUATION_TOKENS);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after ControlStructureBracesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return parent::getPriority();
|
||||
}
|
||||
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('position', 'the position of the keyword that continues the control structure.'))
|
||||
->setAllowedValues([self::NEXT_LINE, self::SAME_LINE])
|
||||
->setDefault(self::SAME_LINE)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$this->fixControlContinuationBraces($tokens);
|
||||
}
|
||||
|
||||
private function fixControlContinuationBraces(Tokens $tokens): void
|
||||
{
|
||||
for ($index = \count($tokens) - 1; 0 < $index; --$index) {
|
||||
$token = $tokens[$index];
|
||||
|
||||
if (!$token->isGivenKind(self::CONTROL_CONTINUATION_TOKENS)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$prevIndex = $tokens->getPrevNonWhitespace($index);
|
||||
$prevToken = $tokens[$prevIndex];
|
||||
|
||||
if (!$prevToken->equals('}')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->isGivenKind(T_WHILE)) {
|
||||
$prevIndex = $tokens->getPrevMeaningfulToken(
|
||||
$tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $prevIndex)
|
||||
);
|
||||
|
||||
if (!$tokens[$prevIndex]->isGivenKind(T_DO)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$tokens->ensureWhitespaceAtIndex(
|
||||
$index - 1,
|
||||
1,
|
||||
self::NEXT_LINE === $this->configuration['position'] ?
|
||||
$this->whitespacesConfig->getLineEnding().WhitespacesAnalyzer::detectIndent($tokens, $index)
|
||||
: ' '
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
<?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\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* Fixer for rules defined in PSR2 ¶5.1.
|
||||
*
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class ElseifFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'The keyword `elseif` should be used instead of `else if` so that all control keywords look like single words.',
|
||||
[new CodeSample("<?php\nif (\$a) {\n} else if (\$b) {\n}\n")]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after NoAlternativeSyntaxFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 40;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAllTokenKindsFound([T_IF, T_ELSE]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace all `else if` (T_ELSE T_IF) with `elseif` (T_ELSEIF).
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->isGivenKind(T_ELSE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$ifTokenIndex = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
// if next meaningful token is not T_IF - continue searching, this is not the case for fixing
|
||||
if (!$tokens[$ifTokenIndex]->isGivenKind(T_IF)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if next meaningful token is T_IF, but uses an alternative syntax - this is not the case for fixing neither
|
||||
$conditionEndBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $tokens->getNextMeaningfulToken($ifTokenIndex));
|
||||
$afterConditionIndex = $tokens->getNextMeaningfulToken($conditionEndBraceIndex);
|
||||
if ($tokens[$afterConditionIndex]->equals(':')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// now we have T_ELSE following by T_IF with no alternative syntax so we could fix this
|
||||
// 1. clear whitespaces between T_ELSE and T_IF
|
||||
$tokens->clearAt($index + 1);
|
||||
|
||||
// 2. change token from T_ELSE into T_ELSEIF
|
||||
$tokens[$index] = new Token([T_ELSEIF, 'elseif']);
|
||||
|
||||
// 3. clear succeeding T_IF
|
||||
$tokens->clearAt($ifTokenIndex);
|
||||
|
||||
$beforeIfTokenIndex = $tokens->getPrevNonWhitespace($ifTokenIndex);
|
||||
|
||||
// 4. clear extra whitespace after T_IF in T_COMMENT,T_WHITESPACE?,T_IF,T_WHITESPACE sequence
|
||||
if ($tokens[$beforeIfTokenIndex]->isComment() && $tokens[$ifTokenIndex + 1]->isWhitespace()) {
|
||||
$tokens->clearAt($ifTokenIndex + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
<?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\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\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use PhpCsFixer\Tokenizer\TokensAnalyzer;
|
||||
|
||||
final class EmptyLoopBodyFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
private const STYLE_BRACES = 'braces';
|
||||
|
||||
private const STYLE_SEMICOLON = 'semicolon';
|
||||
|
||||
private const TOKEN_LOOP_KINDS = [T_FOR, T_FOREACH, T_WHILE];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Empty loop-body must be in configured style.',
|
||||
[
|
||||
new CodeSample("<?php while(foo()){}\n"),
|
||||
new CodeSample(
|
||||
"<?php while(foo());\n",
|
||||
[
|
||||
'style' => 'braces',
|
||||
]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before BracesFixer, NoExtraBlankLinesFixer, NoTrailingWhitespaceFixer.
|
||||
* Must run after NoEmptyStatementFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 39;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound(self::TOKEN_LOOP_KINDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
if (self::STYLE_BRACES === $this->configuration['style']) {
|
||||
$analyzer = new TokensAnalyzer($tokens);
|
||||
$fixLoop = static function (int $index, int $endIndex) use ($tokens, $analyzer): void {
|
||||
if ($tokens[$index]->isGivenKind(T_WHILE) && $analyzer->isWhilePartOfDoWhile($index)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$semiColonIndex = $tokens->getNextMeaningfulToken($endIndex);
|
||||
|
||||
if (!$tokens[$semiColonIndex]->equals(';')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tokens[$semiColonIndex] = new Token('{');
|
||||
$tokens->insertAt($semiColonIndex + 1, new Token('}'));
|
||||
};
|
||||
} else {
|
||||
$fixLoop = static function (int $index, int $endIndex) use ($tokens): void {
|
||||
$braceOpenIndex = $tokens->getNextMeaningfulToken($endIndex);
|
||||
|
||||
if (!$tokens[$braceOpenIndex]->equals('{')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$braceCloseIndex = $tokens->getNextMeaningfulToken($braceOpenIndex);
|
||||
|
||||
if (!$tokens[$braceCloseIndex]->equals('}')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tokens[$braceOpenIndex] = new Token(';');
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($braceCloseIndex);
|
||||
};
|
||||
}
|
||||
|
||||
for ($index = $tokens->count() - 1; $index > 0; --$index) {
|
||||
if ($tokens[$index]->isGivenKind(self::TOKEN_LOOP_KINDS)) {
|
||||
$endIndex = $tokens->getNextTokenOfKind($index, ['(']); // proceed to open '('
|
||||
$endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $endIndex); // proceed to close ')'
|
||||
$fixLoop($index, $endIndex); // fix loop if needs fixing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('style', 'Style of empty loop-bodies.'))
|
||||
->setAllowedTypes(['string'])
|
||||
->setAllowedValues([self::STYLE_BRACES, self::STYLE_SEMICOLON])
|
||||
->setDefault(self::STYLE_SEMICOLON)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
Vendored
+200
@@ -0,0 +1,200 @@
|
||||
<?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\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\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class EmptyLoopConditionFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
private const STYLE_FOR = 'for';
|
||||
|
||||
private const STYLE_WHILE = 'while';
|
||||
|
||||
private const TOKEN_LOOP_KINDS = [T_FOR, T_WHILE];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Empty loop-condition must be in configured style.',
|
||||
[
|
||||
new CodeSample("<?php\nfor(;;) {\n foo();\n}\n\ndo {\n foo();\n} while(true); // do while\n"),
|
||||
new CodeSample("<?php\nwhile(true) {\n foo();\n}\n", ['style' => 'for']),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before NoExtraBlankLinesFixer, NoTrailingWhitespaceFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound(self::TOKEN_LOOP_KINDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
if (self::STYLE_WHILE === $this->configuration['style']) {
|
||||
$candidateLoopKinds = [T_FOR, T_WHILE];
|
||||
$replacement = [new Token([T_WHILE, 'while']), new Token([T_WHITESPACE, ' ']), new Token('('), new Token([T_STRING, 'true']), new Token(')')];
|
||||
|
||||
$fixLoop = static function (int $index, int $openIndex, int $endIndex) use ($tokens, $replacement): void {
|
||||
if (self::isForLoopWithEmptyCondition($tokens, $index, $openIndex, $endIndex)) {
|
||||
self::clearNotCommentsInRange($tokens, $index, $endIndex);
|
||||
self::cloneAndInsert($tokens, $index, $replacement);
|
||||
} elseif (self::isWhileLoopWithEmptyCondition($tokens, $index, $openIndex, $endIndex)) {
|
||||
$doIndex = self::getDoIndex($tokens, $index);
|
||||
|
||||
if (null !== $doIndex) {
|
||||
self::clearNotCommentsInRange($tokens, $index, $tokens->getNextMeaningfulToken($endIndex)); // clear including `;`
|
||||
$tokens->clearAt($doIndex);
|
||||
self::cloneAndInsert($tokens, $doIndex, $replacement);
|
||||
}
|
||||
}
|
||||
};
|
||||
} else { // self::STYLE_FOR
|
||||
$candidateLoopKinds = [T_WHILE];
|
||||
$replacement = [new Token([T_FOR, 'for']), new Token('('), new Token(';'), new Token(';'), new Token(')')];
|
||||
|
||||
$fixLoop = static function (int $index, int $openIndex, int $endIndex) use ($tokens, $replacement): void {
|
||||
if (!self::isWhileLoopWithEmptyCondition($tokens, $index, $openIndex, $endIndex)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$doIndex = self::getDoIndex($tokens, $index);
|
||||
|
||||
if (null === $doIndex) {
|
||||
self::clearNotCommentsInRange($tokens, $index, $endIndex);
|
||||
self::cloneAndInsert($tokens, $index, $replacement);
|
||||
} else {
|
||||
self::clearNotCommentsInRange($tokens, $index, $tokens->getNextMeaningfulToken($endIndex)); // clear including `;`
|
||||
$tokens->clearAt($doIndex);
|
||||
self::cloneAndInsert($tokens, $doIndex, $replacement);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
for ($index = $tokens->count() - 1; $index > 0; --$index) {
|
||||
if ($tokens[$index]->isGivenKind($candidateLoopKinds)) {
|
||||
$openIndex = $tokens->getNextTokenOfKind($index, ['(']); // proceed to open '('
|
||||
$endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex); // proceed to close ')'
|
||||
$fixLoop($index, $openIndex, $endIndex); // fix loop if needed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('style', 'Style of empty loop-condition.'))
|
||||
->setAllowedTypes(['string'])
|
||||
->setAllowedValues([self::STYLE_WHILE, self::STYLE_FOR])
|
||||
->setDefault(self::STYLE_WHILE)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
private static function clearNotCommentsInRange(Tokens $tokens, int $indexStart, int $indexEnd): void
|
||||
{
|
||||
for ($i = $indexStart; $i <= $indexEnd; ++$i) {
|
||||
if (!$tokens[$i]->isComment()) {
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Token[] $replacement
|
||||
*/
|
||||
private static function cloneAndInsert(Tokens $tokens, int $index, array $replacement): void
|
||||
{
|
||||
$replacementClones = [];
|
||||
|
||||
foreach ($replacement as $token) {
|
||||
$replacementClones[] = clone $token;
|
||||
}
|
||||
|
||||
$tokens->insertAt($index, $replacementClones);
|
||||
}
|
||||
|
||||
private static function getDoIndex(Tokens $tokens, int $index): ?int
|
||||
{
|
||||
$endIndex = $tokens->getPrevMeaningfulToken($index);
|
||||
|
||||
if (!$tokens[$endIndex]->equals('}')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$startIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $endIndex);
|
||||
$index = $tokens->getPrevMeaningfulToken($startIndex);
|
||||
|
||||
return null === $index || !$tokens[$index]->isGivenKind(T_DO) ? null : $index;
|
||||
}
|
||||
|
||||
private static function isForLoopWithEmptyCondition(Tokens $tokens, int $index, int $openIndex, int $endIndex): bool
|
||||
{
|
||||
if (!$tokens[$index]->isGivenKind(T_FOR)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$index = $tokens->getNextMeaningfulToken($openIndex);
|
||||
|
||||
if (null === $index || !$tokens[$index]->equals(';')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$index = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
return null !== $index && $tokens[$index]->equals(';') && $endIndex === $tokens->getNextMeaningfulToken($index);
|
||||
}
|
||||
|
||||
private static function isWhileLoopWithEmptyCondition(Tokens $tokens, int $index, int $openIndex, int $endIndex): bool
|
||||
{
|
||||
if (!$tokens[$index]->isGivenKind(T_WHILE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$index = $tokens->getNextMeaningfulToken($openIndex);
|
||||
|
||||
return null !== $index && $tokens[$index]->equals([T_STRING, 'true']) && $endIndex === $tokens->getNextMeaningfulToken($index);
|
||||
}
|
||||
}
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
<?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\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\BlocksAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Sebastiaan Stok <s.stok@rollerscapes.net>
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
* @author Kuba Werłos <werlos@gmail.com>
|
||||
*/
|
||||
final class IncludeFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Include/Require and file path should be divided with a single space. File path should not be placed under brackets.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
require ("sample1.php");
|
||||
require_once "sample2.php";
|
||||
include "sample3.php";
|
||||
include_once("sample4.php");
|
||||
'
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound([T_REQUIRE, T_REQUIRE_ONCE, T_INCLUDE, T_INCLUDE_ONCE]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$this->clearIncludies($tokens, $this->findIncludies($tokens));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array{begin: int, braces: ?array{open: int, close: int}, end: int}> $includies
|
||||
*/
|
||||
private function clearIncludies(Tokens $tokens, array $includies): void
|
||||
{
|
||||
$blocksAnalyzer = new BlocksAnalyzer();
|
||||
|
||||
foreach ($includies as $includy) {
|
||||
if ($includy['end'] && !$tokens[$includy['end']]->isGivenKind(T_CLOSE_TAG)) {
|
||||
$afterEndIndex = $tokens->getNextNonWhitespace($includy['end']);
|
||||
|
||||
if (null === $afterEndIndex || !$tokens[$afterEndIndex]->isComment()) {
|
||||
$tokens->removeLeadingWhitespace($includy['end']);
|
||||
}
|
||||
}
|
||||
|
||||
$braces = $includy['braces'];
|
||||
|
||||
if (null !== $braces) {
|
||||
$prevIndex = $tokens->getPrevMeaningfulToken($includy['begin']);
|
||||
$nextIndex = $tokens->getNextMeaningfulToken($braces['close']);
|
||||
|
||||
// Include is also legal as function parameter or condition statement but requires being wrapped then.
|
||||
if (!$tokens[$nextIndex]->equalsAny([';', [T_CLOSE_TAG]]) && !$blocksAnalyzer->isBlock($tokens, $prevIndex, $nextIndex)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->removeWhitespaceAroundIfPossible($tokens, $braces['open']);
|
||||
$this->removeWhitespaceAroundIfPossible($tokens, $braces['close']);
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($braces['open']);
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($braces['close']);
|
||||
}
|
||||
|
||||
$nextIndex = $tokens->getNonEmptySibling($includy['begin'], 1);
|
||||
|
||||
if ($tokens[$nextIndex]->isWhitespace()) {
|
||||
$tokens[$nextIndex] = new Token([T_WHITESPACE, ' ']);
|
||||
} elseif (null !== $braces || $tokens[$nextIndex]->isGivenKind([T_VARIABLE, T_CONSTANT_ENCAPSED_STRING, T_COMMENT])) {
|
||||
$tokens->insertAt($includy['begin'] + 1, new Token([T_WHITESPACE, ' ']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array{begin: int, braces: ?array{open: int, close: int}, end: int}>
|
||||
*/
|
||||
private function findIncludies(Tokens $tokens): array
|
||||
{
|
||||
static $includyTokenKinds = [T_REQUIRE, T_REQUIRE_ONCE, T_INCLUDE, T_INCLUDE_ONCE];
|
||||
|
||||
$includies = [];
|
||||
|
||||
foreach ($tokens->findGivenKind($includyTokenKinds) as $includyTokens) {
|
||||
foreach ($includyTokens as $index => $token) {
|
||||
$includy = [
|
||||
'begin' => $index,
|
||||
'braces' => null,
|
||||
'end' => $tokens->getNextTokenOfKind($index, [';', [T_CLOSE_TAG]]),
|
||||
];
|
||||
|
||||
$braceOpenIndex = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
if ($tokens[$braceOpenIndex]->equals('(')) {
|
||||
$braceCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $braceOpenIndex);
|
||||
|
||||
$includy['braces'] = [
|
||||
'open' => $braceOpenIndex,
|
||||
'close' => $braceCloseIndex,
|
||||
];
|
||||
}
|
||||
|
||||
$includies[$index] = $includy;
|
||||
}
|
||||
}
|
||||
|
||||
krsort($includies);
|
||||
|
||||
return $includies;
|
||||
}
|
||||
|
||||
private function removeWhitespaceAroundIfPossible(Tokens $tokens, int $index): void
|
||||
{
|
||||
$nextIndex = $tokens->getNextNonWhitespace($index);
|
||||
|
||||
if (null === $nextIndex || !$tokens[$nextIndex]->isComment()) {
|
||||
$tokens->removeLeadingWhitespace($index);
|
||||
}
|
||||
|
||||
$prevIndex = $tokens->getPrevNonWhitespace($index);
|
||||
|
||||
if (null === $prevIndex || !$tokens[$prevIndex]->isComment()) {
|
||||
$tokens->removeTrailingWhitespace($index);
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+244
@@ -0,0 +1,244 @@
|
||||
<?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\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\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Eddilbert Macharia <edd.cowan@gmail.com>
|
||||
*/
|
||||
final class NoAlternativeSyntaxFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Replace control structure alternative syntax to use braces.',
|
||||
[
|
||||
new CodeSample(
|
||||
"<?php\nif(true):echo 't';else:echo 'f';endif;\n"
|
||||
),
|
||||
new CodeSample(
|
||||
"<?php if (\$condition): ?>\nLorem ipsum.\n<?php endif; ?>\n",
|
||||
['fix_non_monolithic_code' => true]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->hasAlternativeSyntax() && (true === $this->configuration['fix_non_monolithic_code'] || $tokens->isMonolithicPhp());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before BracesFixer, ElseifFixer, NoSuperfluousElseifFixer, NoUnneededControlParenthesesFixer, NoUselessElseFixer, SwitchContinueToBreakFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 42;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('fix_non_monolithic_code', 'Whether to also fix code with inline HTML.'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(true) // @TODO change to "false" on next major 4.0
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
for ($index = \count($tokens) - 1; 0 <= $index; --$index) {
|
||||
$token = $tokens[$index];
|
||||
$this->fixElseif($index, $token, $tokens);
|
||||
$this->fixElse($index, $token, $tokens);
|
||||
$this->fixOpenCloseControls($index, $token, $tokens);
|
||||
}
|
||||
}
|
||||
|
||||
private function findParenthesisEnd(Tokens $tokens, int $structureTokenIndex): int
|
||||
{
|
||||
$nextIndex = $tokens->getNextMeaningfulToken($structureTokenIndex);
|
||||
$nextToken = $tokens[$nextIndex];
|
||||
|
||||
return $nextToken->equals('(')
|
||||
? $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $nextIndex)
|
||||
: $structureTokenIndex // return if next token is not opening parenthesis
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle both extremes of the control structures.
|
||||
* e.g. if(): or endif;.
|
||||
*
|
||||
* @param int $index the index of the token being processed
|
||||
* @param Token $token the token being processed
|
||||
* @param Tokens $tokens the collection of tokens
|
||||
*/
|
||||
private function fixOpenCloseControls(int $index, Token $token, Tokens $tokens): void
|
||||
{
|
||||
if ($token->isGivenKind([T_IF, T_FOREACH, T_WHILE, T_FOR, T_SWITCH, T_DECLARE])) {
|
||||
$openIndex = $tokens->getNextTokenOfKind($index, ['(']);
|
||||
$closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex);
|
||||
$afterParenthesisIndex = $tokens->getNextMeaningfulToken($closeIndex);
|
||||
$afterParenthesis = $tokens[$afterParenthesisIndex];
|
||||
|
||||
if (!$afterParenthesis->equals(':')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$items = [];
|
||||
|
||||
if (!$tokens[$afterParenthesisIndex - 1]->isWhitespace()) {
|
||||
$items[] = new Token([T_WHITESPACE, ' ']);
|
||||
}
|
||||
|
||||
$items[] = new Token('{');
|
||||
|
||||
if (!$tokens[$afterParenthesisIndex + 1]->isWhitespace()) {
|
||||
$items[] = new Token([T_WHITESPACE, ' ']);
|
||||
}
|
||||
|
||||
$tokens->clearAt($afterParenthesisIndex);
|
||||
$tokens->insertAt($afterParenthesisIndex, $items);
|
||||
}
|
||||
|
||||
if (!$token->isGivenKind([T_ENDIF, T_ENDFOREACH, T_ENDWHILE, T_ENDFOR, T_ENDSWITCH, T_ENDDECLARE])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$nextTokenIndex = $tokens->getNextMeaningfulToken($index);
|
||||
$nextToken = $tokens[$nextTokenIndex];
|
||||
$tokens[$index] = new Token('}');
|
||||
|
||||
if ($nextToken->equals(';')) {
|
||||
$tokens->clearAt($nextTokenIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the else: cases.
|
||||
*
|
||||
* @param int $index the index of the token being processed
|
||||
* @param Token $token the token being processed
|
||||
* @param Tokens $tokens the collection of tokens
|
||||
*/
|
||||
private function fixElse(int $index, Token $token, Tokens $tokens): void
|
||||
{
|
||||
if (!$token->isGivenKind(T_ELSE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tokenAfterElseIndex = $tokens->getNextMeaningfulToken($index);
|
||||
$tokenAfterElse = $tokens[$tokenAfterElseIndex];
|
||||
|
||||
if (!$tokenAfterElse->equals(':')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->addBraces($tokens, new Token([T_ELSE, 'else']), $index, $tokenAfterElseIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the elsif(): cases.
|
||||
*
|
||||
* @param int $index the index of the token being processed
|
||||
* @param Token $token the token being processed
|
||||
* @param Tokens $tokens the collection of tokens
|
||||
*/
|
||||
private function fixElseif(int $index, Token $token, Tokens $tokens): void
|
||||
{
|
||||
if (!$token->isGivenKind(T_ELSEIF)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$parenthesisEndIndex = $this->findParenthesisEnd($tokens, $index);
|
||||
$tokenAfterParenthesisIndex = $tokens->getNextMeaningfulToken($parenthesisEndIndex);
|
||||
$tokenAfterParenthesis = $tokens[$tokenAfterParenthesisIndex];
|
||||
|
||||
if (!$tokenAfterParenthesis->equals(':')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->addBraces($tokens, new Token([T_ELSEIF, 'elseif']), $index, $tokenAfterParenthesisIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add opening and closing braces to the else: and elseif: cases.
|
||||
*
|
||||
* @param Tokens $tokens the tokens collection
|
||||
* @param Token $token the current token
|
||||
* @param int $index the current token index
|
||||
* @param int $colonIndex the index of the colon
|
||||
*/
|
||||
private function addBraces(Tokens $tokens, Token $token, int $index, int $colonIndex): void
|
||||
{
|
||||
$items = [
|
||||
new Token('}'),
|
||||
new Token([T_WHITESPACE, ' ']),
|
||||
$token,
|
||||
];
|
||||
|
||||
if (!$tokens[$index + 1]->isWhitespace()) {
|
||||
$items[] = new Token([T_WHITESPACE, ' ']);
|
||||
}
|
||||
|
||||
$tokens->clearAt($index);
|
||||
$tokens->insertAt(
|
||||
$index,
|
||||
$items
|
||||
);
|
||||
|
||||
// increment the position of the colon by number of items inserted
|
||||
$colonIndex += \count($items);
|
||||
|
||||
$items = [new Token('{')];
|
||||
|
||||
if (!$tokens[$colonIndex + 1]->isWhitespace()) {
|
||||
$items[] = new Token([T_WHITESPACE, ' ']);
|
||||
}
|
||||
|
||||
$tokens->clearAt($colonIndex);
|
||||
$tokens->insertAt(
|
||||
$colonIndex,
|
||||
$items
|
||||
);
|
||||
}
|
||||
}
|
||||
Vendored
+350
@@ -0,0 +1,350 @@
|
||||
<?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\WhitespacesAwareFixerInterface;
|
||||
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\Preg;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
|
||||
/**
|
||||
* Fixer for rule defined in PSR2 ¶5.2.
|
||||
*/
|
||||
final class NoBreakCommentFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'There must be a comment when fall-through is intentional in a non-empty case body.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
switch ($foo) {
|
||||
case 1:
|
||||
foo();
|
||||
case 2:
|
||||
bar();
|
||||
// no break
|
||||
break;
|
||||
case 3:
|
||||
baz();
|
||||
}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
switch ($foo) {
|
||||
case 1:
|
||||
foo();
|
||||
case 2:
|
||||
foo();
|
||||
}
|
||||
',
|
||||
['comment_text' => 'some comment']
|
||||
),
|
||||
],
|
||||
'Adds a "no break" comment before fall-through cases, and removes it if there is no fall-through.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_SWITCH);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after NoUselessElseFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('comment_text', 'The text to use in the added comment and to detect it.'))
|
||||
->setAllowedTypes(['string'])
|
||||
->setAllowedValues([
|
||||
static function (string $value): bool {
|
||||
if (Preg::match('/\R/', $value)) {
|
||||
throw new InvalidOptionsException('The comment text must not contain new lines.');
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
])
|
||||
->setNormalizer(static function (Options $options, string $value): string {
|
||||
return rtrim($value);
|
||||
})
|
||||
->setDefault('no break')
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
for ($index = \count($tokens) - 1; $index >= 0; --$index) {
|
||||
if ($tokens[$index]->isGivenKind(T_DEFAULT)) {
|
||||
if ($tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(T_DOUBLE_ARROW)) {
|
||||
continue; // this is "default" from "match"
|
||||
}
|
||||
} elseif (!$tokens[$index]->isGivenKind(T_CASE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->fixCase($tokens, $tokens->getNextTokenOfKind($index, [':', ';']));
|
||||
}
|
||||
}
|
||||
|
||||
private function fixCase(Tokens $tokens, int $casePosition): void
|
||||
{
|
||||
$empty = true;
|
||||
$fallThrough = true;
|
||||
$commentPosition = null;
|
||||
|
||||
for ($i = $casePosition + 1, $max = \count($tokens); $i < $max; ++$i) {
|
||||
if ($tokens[$i]->isGivenKind([T_SWITCH, T_IF, T_ELSE, T_ELSEIF, T_FOR, T_FOREACH, T_WHILE, T_DO, T_FUNCTION, T_CLASS])) {
|
||||
$empty = false;
|
||||
$i = $this->getStructureEnd($tokens, $i);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$i]->isGivenKind([T_BREAK, T_CONTINUE, T_RETURN, T_EXIT, T_GOTO])) {
|
||||
$fallThrough = false;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$i]->isGivenKind(T_THROW)) {
|
||||
$previousIndex = $tokens->getPrevMeaningfulToken($i);
|
||||
|
||||
if ($previousIndex === $casePosition || $tokens[$previousIndex]->equalsAny(['{', ';', '}', [T_OPEN_TAG]])) {
|
||||
$fallThrough = false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$i]->equals('}') || $tokens[$i]->isGivenKind(T_ENDSWITCH)) {
|
||||
if (null !== $commentPosition) {
|
||||
$this->removeComment($tokens, $commentPosition);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if ($this->isNoBreakComment($tokens[$i])) {
|
||||
$commentPosition = $i;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$i]->isGivenKind([T_CASE, T_DEFAULT])) {
|
||||
if (!$empty && $fallThrough) {
|
||||
if (null !== $commentPosition && $tokens->getPrevNonWhitespace($i) !== $commentPosition) {
|
||||
$this->removeComment($tokens, $commentPosition);
|
||||
$commentPosition = null;
|
||||
}
|
||||
|
||||
if (null === $commentPosition) {
|
||||
$this->insertCommentAt($tokens, $i);
|
||||
} else {
|
||||
$text = $this->configuration['comment_text'];
|
||||
$tokens[$commentPosition] = new Token([
|
||||
$tokens[$commentPosition]->getId(),
|
||||
str_ireplace($text, $text, $tokens[$commentPosition]->getContent()),
|
||||
]);
|
||||
|
||||
$this->ensureNewLineAt($tokens, $commentPosition);
|
||||
}
|
||||
} elseif (null !== $commentPosition) {
|
||||
$this->removeComment($tokens, $commentPosition);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$tokens[$i]->isGivenKind([T_COMMENT, T_WHITESPACE])) {
|
||||
$empty = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function isNoBreakComment(Token $token): bool
|
||||
{
|
||||
if (!$token->isComment()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$text = preg_quote($this->configuration['comment_text'], '~');
|
||||
|
||||
return 1 === Preg::match("~^((//|#)\\s*{$text}\\s*)|(/\\*\\*?\\s*{$text}(\\s+.*)*\\*/)$~i", $token->getContent());
|
||||
}
|
||||
|
||||
private function insertCommentAt(Tokens $tokens, int $casePosition): void
|
||||
{
|
||||
$lineEnding = $this->whitespacesConfig->getLineEnding();
|
||||
$newlinePosition = $this->ensureNewLineAt($tokens, $casePosition);
|
||||
$newlineToken = $tokens[$newlinePosition];
|
||||
$nbNewlines = substr_count($newlineToken->getContent(), $lineEnding);
|
||||
|
||||
if ($newlineToken->isGivenKind(T_OPEN_TAG) && Preg::match('/\R/', $newlineToken->getContent())) {
|
||||
++$nbNewlines;
|
||||
} elseif ($tokens[$newlinePosition - 1]->isGivenKind(T_OPEN_TAG) && Preg::match('/\R/', $tokens[$newlinePosition - 1]->getContent())) {
|
||||
++$nbNewlines;
|
||||
|
||||
if (!Preg::match('/\R/', $newlineToken->getContent())) {
|
||||
$tokens[$newlinePosition] = new Token([$newlineToken->getId(), $lineEnding.$newlineToken->getContent()]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($nbNewlines > 1) {
|
||||
Preg::match('/^(.*?)(\R\h*)$/s', $newlineToken->getContent(), $matches);
|
||||
|
||||
$indent = WhitespacesAnalyzer::detectIndent($tokens, $newlinePosition - 1);
|
||||
$tokens[$newlinePosition] = new Token([$newlineToken->getId(), $matches[1].$lineEnding.$indent]);
|
||||
$tokens->insertAt(++$newlinePosition, new Token([T_WHITESPACE, $matches[2]]));
|
||||
}
|
||||
|
||||
$tokens->insertAt($newlinePosition, new Token([T_COMMENT, '// '.$this->configuration['comment_text']]));
|
||||
$this->ensureNewLineAt($tokens, $newlinePosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int The newline token position
|
||||
*/
|
||||
private function ensureNewLineAt(Tokens $tokens, int $position): int
|
||||
{
|
||||
$lineEnding = $this->whitespacesConfig->getLineEnding();
|
||||
$content = $lineEnding.WhitespacesAnalyzer::detectIndent($tokens, $position);
|
||||
$whitespaceToken = $tokens[$position - 1];
|
||||
|
||||
if (!$whitespaceToken->isGivenKind(T_WHITESPACE)) {
|
||||
if ($whitespaceToken->isGivenKind(T_OPEN_TAG)) {
|
||||
$content = Preg::replace('/\R/', '', $content);
|
||||
|
||||
if (!Preg::match('/\R/', $whitespaceToken->getContent())) {
|
||||
$tokens[$position - 1] = new Token([T_OPEN_TAG, Preg::replace('/\s+$/', $lineEnding, $whitespaceToken->getContent())]);
|
||||
}
|
||||
}
|
||||
|
||||
if ('' !== $content) {
|
||||
$tokens->insertAt($position, new Token([T_WHITESPACE, $content]));
|
||||
|
||||
return $position;
|
||||
}
|
||||
|
||||
return $position - 1;
|
||||
}
|
||||
|
||||
if ($tokens[$position - 2]->isGivenKind(T_OPEN_TAG) && Preg::match('/\R/', $tokens[$position - 2]->getContent())) {
|
||||
$content = Preg::replace('/^\R/', '', $content);
|
||||
}
|
||||
|
||||
if (!Preg::match('/\R/', $whitespaceToken->getContent())) {
|
||||
$tokens[$position - 1] = new Token([T_WHITESPACE, $content]);
|
||||
}
|
||||
|
||||
return $position - 1;
|
||||
}
|
||||
|
||||
private function removeComment(Tokens $tokens, int $commentPosition): void
|
||||
{
|
||||
if ($tokens[$tokens->getPrevNonWhitespace($commentPosition)]->isGivenKind(T_OPEN_TAG)) {
|
||||
$whitespacePosition = $commentPosition + 1;
|
||||
$regex = '/^\R\h*/';
|
||||
} else {
|
||||
$whitespacePosition = $commentPosition - 1;
|
||||
$regex = '/\R\h*$/';
|
||||
}
|
||||
|
||||
$whitespaceToken = $tokens[$whitespacePosition];
|
||||
|
||||
if ($whitespaceToken->isGivenKind(T_WHITESPACE)) {
|
||||
$content = Preg::replace($regex, '', $whitespaceToken->getContent());
|
||||
|
||||
$tokens->ensureWhitespaceAtIndex($whitespacePosition, 0, $content);
|
||||
}
|
||||
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($commentPosition);
|
||||
}
|
||||
|
||||
private function getStructureEnd(Tokens $tokens, int $position): int
|
||||
{
|
||||
$initialToken = $tokens[$position];
|
||||
|
||||
if ($initialToken->isGivenKind([T_FOR, T_FOREACH, T_WHILE, T_IF, T_ELSEIF, T_SWITCH, T_FUNCTION])) {
|
||||
$position = $tokens->findBlockEnd(
|
||||
Tokens::BLOCK_TYPE_PARENTHESIS_BRACE,
|
||||
$tokens->getNextTokenOfKind($position, ['('])
|
||||
);
|
||||
} elseif ($initialToken->isGivenKind(T_CLASS)) {
|
||||
$openParenthesisPosition = $tokens->getNextMeaningfulToken($position);
|
||||
|
||||
if ('(' === $tokens[$openParenthesisPosition]->getContent()) {
|
||||
$position = $tokens->findBlockEnd(
|
||||
Tokens::BLOCK_TYPE_PARENTHESIS_BRACE,
|
||||
$openParenthesisPosition
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$position = $tokens->getNextMeaningfulToken($position);
|
||||
|
||||
if ('{' !== $tokens[$position]->getContent()) {
|
||||
return $tokens->getNextTokenOfKind($position, [';']);
|
||||
}
|
||||
|
||||
$position = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $position);
|
||||
|
||||
if ($initialToken->isGivenKind(T_DO)) {
|
||||
$position = $tokens->findBlockEnd(
|
||||
Tokens::BLOCK_TYPE_PARENTHESIS_BRACE,
|
||||
$tokens->getNextTokenOfKind($position, ['('])
|
||||
);
|
||||
|
||||
return $tokens->getNextTokenOfKind($position, [';']);
|
||||
}
|
||||
|
||||
return $position;
|
||||
}
|
||||
}
|
||||
Vendored
+110
@@ -0,0 +1,110 @@
|
||||
<?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\AbstractNoUselessElseFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Preg;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class NoSuperfluousElseifFixer extends AbstractNoUselessElseFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound([T_ELSE, T_ELSEIF]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Replaces superfluous `elseif` with `if`.',
|
||||
[
|
||||
new CodeSample("<?php\nif (\$a) {\n return 1;\n} elseif (\$b) {\n return 2;\n}\n"),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before SimplifiedIfReturnFixer.
|
||||
* Must run after NoAlternativeSyntaxFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return parent::getPriority();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($tokens as $index => $token) {
|
||||
if ($this->isElseif($tokens, $index) && $this->isSuperfluousElse($tokens, $index)) {
|
||||
$this->convertElseifToIf($tokens, $index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function isElseif(Tokens $tokens, int $index): bool
|
||||
{
|
||||
return
|
||||
$tokens[$index]->isGivenKind(T_ELSEIF)
|
||||
|| ($tokens[$index]->isGivenKind(T_ELSE) && $tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(T_IF))
|
||||
;
|
||||
}
|
||||
|
||||
private function convertElseifToIf(Tokens $tokens, int $index): void
|
||||
{
|
||||
if ($tokens[$index]->isGivenKind(T_ELSE)) {
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($index);
|
||||
} else {
|
||||
$tokens[$index] = new Token([T_IF, 'if']);
|
||||
}
|
||||
|
||||
$whitespace = '';
|
||||
|
||||
for ($previous = $index - 1; $previous > 0; --$previous) {
|
||||
$token = $tokens[$previous];
|
||||
if ($token->isWhitespace() && Preg::match('/(\R\N*)$/', $token->getContent(), $matches)) {
|
||||
$whitespace = $matches[1];
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ('' === $whitespace) {
|
||||
return;
|
||||
}
|
||||
|
||||
$previousToken = $tokens[$index - 1];
|
||||
|
||||
if (!$previousToken->isWhitespace()) {
|
||||
$tokens->insertAt($index, new Token([T_WHITESPACE, $whitespace]));
|
||||
} elseif (!Preg::match('/\R/', $previousToken->getContent())) {
|
||||
$tokens[$index - 1] = new Token([T_WHITESPACE, $whitespace]);
|
||||
}
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
<?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\AbstractProxyFixer;
|
||||
use PhpCsFixer\Fixer\Basic\NoTrailingCommaInSinglelineFixer;
|
||||
use PhpCsFixer\Fixer\DeprecatedFixerInterface;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class NoTrailingCommaInListCallFixer extends AbstractProxyFixer implements DeprecatedFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Remove trailing commas in list function calls.',
|
||||
[new CodeSample("<?php\nlist(\$a, \$b,) = foo();\n")]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSuccessorsNames(): array
|
||||
{
|
||||
return array_keys($this->proxyFixers);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createProxyFixers(): array
|
||||
{
|
||||
$fixer = new NoTrailingCommaInSinglelineFixer();
|
||||
$fixer->configure(['elements' => ['array_destructuring']]);
|
||||
|
||||
return [$fixer];
|
||||
}
|
||||
}
|
||||
+754
@@ -0,0 +1,754 @@
|
||||
<?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\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\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use PhpCsFixer\Tokenizer\TokensAnalyzer;
|
||||
|
||||
/**
|
||||
* @author Sullivan Senechal <soullivaneuh@gmail.com>
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
* @author Gregor Harlan <gharlan@web.de>
|
||||
*/
|
||||
final class NoUnneededControlParenthesesFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* @var 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 NOOP_TYPES = [
|
||||
'$',
|
||||
[T_CONSTANT_ENCAPSED_STRING],
|
||||
[T_DNUMBER],
|
||||
[T_DOUBLE_COLON],
|
||||
[T_LNUMBER],
|
||||
[T_NS_SEPARATOR],
|
||||
[T_OBJECT_OPERATOR],
|
||||
[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],
|
||||
];
|
||||
|
||||
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 TokensAnalyzer $tokensAnalyzer;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Removes unneeded parentheses around control statements.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?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);
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
while ($x) { while ($y) { break (2); } }
|
||||
|
||||
clone($a);
|
||||
|
||||
while ($y) { continue (2); }
|
||||
',
|
||||
['statements' => ['break', 'continue']]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before ConcatSpaceFixer, NoTrailingWhitespaceFixer.
|
||||
* Must run after NoAlternativeSyntaxFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 30;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound(['(', CT::T_BRACE_CLASS_INSTANTIATION_OPEN]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
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 ($this->isExitStatement($tokens, $beforeOpenIndex)) {
|
||||
$this->removeUselessParenthesisPair($tokens, $beforeOpenIndex, $afterCloseIndex, $openIndex, $closeIndex, 'others');
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// do a cheap check for negative case: `foo(1,2)`
|
||||
|
||||
if ($this->isKnownNegativePre($tokens[$beforeOpenIndex])) {
|
||||
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 ($this->isCloneStatement($tokens, $beforeOpenIndex)) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
$defaults = array_filter(
|
||||
self::CONFIG_OPTIONS,
|
||||
static function (string $option): bool {
|
||||
return 'negative_instanceof' !== $option && 'others' !== $option && 'yield_from' !== $option;
|
||||
}
|
||||
);
|
||||
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('statements', 'List of control statements to fix.'))
|
||||
->setAllowedTypes(['array'])
|
||||
->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 isExitStatement(Tokens $tokens, int $beforeOpenIndex): bool
|
||||
{
|
||||
return $tokens[$beforeOpenIndex]->isGivenKind(T_EXIT);
|
||||
}
|
||||
|
||||
private function isCloneStatement(Tokens $tokens, int $beforeOpenIndex): bool
|
||||
{
|
||||
return $tokens[$beforeOpenIndex]->isGivenKind(T_CLONE);
|
||||
}
|
||||
|
||||
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 ($this->isKnownNegativePre($tokens[$beforeOpenIndex])) {
|
||||
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;
|
||||
}
|
||||
|
||||
// cheap check on a tokens type before `(` of which we know the `(` will never be superfluous
|
||||
private function isKnownNegativePre(Token $token): bool
|
||||
{
|
||||
static $knownNegativeTypes;
|
||||
|
||||
if (null === $knownNegativeTypes) {
|
||||
$knownNegativeTypes = [
|
||||
[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],
|
||||
];
|
||||
|
||||
if (\defined('T_MATCH')) { // @TODO: drop condition and add directly in `$knownNegativeTypes` above when PHP 8.0+ is required
|
||||
$knownNegativeTypes[] = T_MATCH;
|
||||
}
|
||||
}
|
||||
|
||||
return $token->equalsAny($knownNegativeTypes);
|
||||
}
|
||||
|
||||
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(self::NOOP_TYPES)) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
Vendored
+172
@@ -0,0 +1,172 @@
|
||||
<?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\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\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class NoUnneededCurlyBracesFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Removes unneeded curly braces that are superfluous and aren\'t part of a control structure\'s body.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php {
|
||||
echo 1;
|
||||
}
|
||||
|
||||
switch ($b) {
|
||||
case 1: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
namespace Foo {
|
||||
function Bar(){}
|
||||
}
|
||||
',
|
||||
['namespaces' => true]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before NoUselessElseFixer, NoUselessReturnFixer, ReturnAssignmentFixer, SimplifiedIfReturnFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 40;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound('}');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($this->findCurlyBraceOpen($tokens) as $index) {
|
||||
if ($this->isOverComplete($tokens, $index)) {
|
||||
$this->clearOverCompleteBraces($tokens, $index, $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index));
|
||||
}
|
||||
}
|
||||
|
||||
if (true === $this->configuration['namespaces']) {
|
||||
$this->clearIfIsOverCompleteNamespaceBlock($tokens);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('namespaces', 'Remove unneeded curly braces from bracketed namespaces.'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(false)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $openIndex index of `{` token
|
||||
* @param int $closeIndex index of `}` token
|
||||
*/
|
||||
private function clearOverCompleteBraces(Tokens $tokens, int $openIndex, int $closeIndex): void
|
||||
{
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($closeIndex);
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($openIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<int>
|
||||
*/
|
||||
private function findCurlyBraceOpen(Tokens $tokens): iterable
|
||||
{
|
||||
for ($i = \count($tokens) - 1; $i > 0; --$i) {
|
||||
if ($tokens[$i]->equals('{')) {
|
||||
yield $i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $index index of `{` token
|
||||
*/
|
||||
private function isOverComplete(Tokens $tokens, int $index): bool
|
||||
{
|
||||
static $include = ['{', '}', [T_OPEN_TAG], ':', ';'];
|
||||
|
||||
return $tokens[$tokens->getPrevMeaningfulToken($index)]->equalsAny($include);
|
||||
}
|
||||
|
||||
private function clearIfIsOverCompleteNamespaceBlock(Tokens $tokens): void
|
||||
{
|
||||
if (1 !== $tokens->countTokenKind(T_NAMESPACE)) {
|
||||
return; // fast check, we never fix if multiple namespaces are defined
|
||||
}
|
||||
|
||||
$index = $tokens->getNextTokenOfKind(0, [[T_NAMESPACE]]);
|
||||
|
||||
do {
|
||||
$index = $tokens->getNextMeaningfulToken($index);
|
||||
} while ($tokens[$index]->isGivenKind([T_STRING, T_NS_SEPARATOR]));
|
||||
|
||||
if (!$tokens[$index]->equals('{')) {
|
||||
return; // `;`
|
||||
}
|
||||
|
||||
$closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index);
|
||||
$afterCloseIndex = $tokens->getNextMeaningfulToken($closeIndex);
|
||||
|
||||
if (null !== $afterCloseIndex && (!$tokens[$afterCloseIndex]->isGivenKind(T_CLOSE_TAG) || null !== $tokens->getNextMeaningfulToken($afterCloseIndex))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// clear up
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($closeIndex);
|
||||
$tokens[$index] = new Token(';');
|
||||
|
||||
if ($tokens[$index - 1]->isWhitespace(" \t") && !$tokens[$index - 2]->isComment()) {
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($index - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
<?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\AbstractNoUselessElseFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class NoUselessElseFixer extends AbstractNoUselessElseFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_ELSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'There should not be useless `else` cases.',
|
||||
[
|
||||
new CodeSample("<?php\nif (\$a) {\n return 1;\n} else {\n return 2;\n}\n"),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before BracesFixer, CombineConsecutiveUnsetsFixer, NoBreakCommentFixer, NoExtraBlankLinesFixer, NoTrailingWhitespaceFixer, NoUselessReturnFixer, NoWhitespaceInBlankLineFixer, SimplifiedIfReturnFixer.
|
||||
* Must run after NoAlternativeSyntaxFixer, NoEmptyStatementFixer, NoUnneededCurlyBracesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return parent::getPriority();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->isGivenKind(T_ELSE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// `else if` vs. `else` and alternative syntax `else:` checks
|
||||
if ($tokens[$tokens->getNextMeaningfulToken($index)]->equalsAny([':', [T_IF]])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// clean up `else` if it is an empty statement
|
||||
$this->fixEmptyElse($tokens, $index);
|
||||
if ($tokens->isEmptyAt($index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// clean up `else` if possible
|
||||
if ($this->isSuperfluousElse($tokens, $index)) {
|
||||
$this->clearElse($tokens, $index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove tokens part of an `else` statement if not empty (i.e. no meaningful tokens inside).
|
||||
*
|
||||
* @param int $index T_ELSE index
|
||||
*/
|
||||
private function fixEmptyElse(Tokens $tokens, int $index): void
|
||||
{
|
||||
$next = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
if ($tokens[$next]->equals('{')) {
|
||||
$close = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $next);
|
||||
if (1 === $close - $next) { // '{}'
|
||||
$this->clearElse($tokens, $index);
|
||||
} elseif ($tokens->getNextMeaningfulToken($next) === $close) { // '{/**/}'
|
||||
$this->clearElse($tokens, $index);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// short `else`
|
||||
$end = $tokens->getNextTokenOfKind($index, [';', [T_CLOSE_TAG]]);
|
||||
if ($next === $end) {
|
||||
$this->clearElse($tokens, $index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $index index of T_ELSE
|
||||
*/
|
||||
private function clearElse(Tokens $tokens, int $index): void
|
||||
{
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($index);
|
||||
|
||||
// clear T_ELSE and the '{' '}' if there are any
|
||||
$next = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
if (!$tokens[$next]->equals('{')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $next));
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($next);
|
||||
}
|
||||
}
|
||||
Vendored
+147
@@ -0,0 +1,147 @@
|
||||
<?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\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Filippo Tessarotto <zoeslam@gmail.com>
|
||||
*/
|
||||
final class SimplifiedIfReturnFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* @var list<array{isNegative: bool, sequence: array<int, list<int|string>|string>}>
|
||||
*/
|
||||
private array $sequences = [
|
||||
[
|
||||
'isNegative' => false,
|
||||
'sequence' => [
|
||||
'{', [T_RETURN], [T_STRING, 'true'], ';', '}',
|
||||
[T_RETURN], [T_STRING, 'false'], ';',
|
||||
],
|
||||
],
|
||||
[
|
||||
'isNegative' => true,
|
||||
'sequence' => [
|
||||
'{', [T_RETURN], [T_STRING, 'false'], ';', '}',
|
||||
[T_RETURN], [T_STRING, 'true'], ';',
|
||||
],
|
||||
],
|
||||
[
|
||||
'isNegative' => false,
|
||||
'sequence' => [
|
||||
[T_RETURN], [T_STRING, 'true'], ';',
|
||||
[T_RETURN], [T_STRING, 'false'], ';',
|
||||
],
|
||||
],
|
||||
[
|
||||
'isNegative' => true,
|
||||
'sequence' => [
|
||||
[T_RETURN], [T_STRING, 'false'], ';',
|
||||
[T_RETURN], [T_STRING, 'true'], ';',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Simplify `if` control structures that return the boolean result of their condition.',
|
||||
[new CodeSample("<?php\nif (\$foo) { return true; } return false;\n")]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before MultilineWhitespaceBeforeSemicolonsFixer, NoSinglelineWhitespaceBeforeSemicolonsFixer.
|
||||
* Must run after NoSuperfluousElseifFixer, NoUnneededCurlyBracesFixer, NoUselessElseFixer, SemicolonAfterInstructionFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAllTokenKindsFound([T_IF, T_RETURN, T_STRING]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
for ($ifIndex = $tokens->count() - 1; 0 <= $ifIndex; --$ifIndex) {
|
||||
if (!$tokens[$ifIndex]->isGivenKind([T_IF, T_ELSEIF])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$tokens->getPrevMeaningfulToken($ifIndex)]->equals(')')) {
|
||||
continue; // in a loop without braces
|
||||
}
|
||||
|
||||
$startParenthesisIndex = $tokens->getNextTokenOfKind($ifIndex, ['(']);
|
||||
$endParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startParenthesisIndex);
|
||||
$firstCandidateIndex = $tokens->getNextMeaningfulToken($endParenthesisIndex);
|
||||
|
||||
foreach ($this->sequences as $sequenceSpec) {
|
||||
$sequenceFound = $tokens->findSequence($sequenceSpec['sequence'], $firstCandidateIndex);
|
||||
|
||||
if (null === $sequenceFound) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$firstSequenceIndex = key($sequenceFound);
|
||||
|
||||
if ($firstSequenceIndex !== $firstCandidateIndex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$indicesToClear = array_keys($sequenceFound);
|
||||
array_pop($indicesToClear); // Preserve last semicolon
|
||||
rsort($indicesToClear);
|
||||
|
||||
foreach ($indicesToClear as $index) {
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($index);
|
||||
}
|
||||
|
||||
$newTokens = [
|
||||
new Token([T_RETURN, 'return']),
|
||||
new Token([T_WHITESPACE, ' ']),
|
||||
];
|
||||
|
||||
if ($sequenceSpec['isNegative']) {
|
||||
$newTokens[] = new Token('!');
|
||||
} else {
|
||||
$newTokens[] = new Token([T_BOOL_CAST, '(bool)']);
|
||||
}
|
||||
|
||||
$tokens->overrideRange($ifIndex, $ifIndex, $newTokens);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
<?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\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\Analysis\SwitchAnalysis;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\ControlCaseStructuresAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* Fixer for rules defined in PSR2 ¶5.2.
|
||||
*/
|
||||
final class SwitchCaseSemicolonToColonFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'A case should be followed by a colon and not a semicolon.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
switch ($a) {
|
||||
case 1;
|
||||
break;
|
||||
default;
|
||||
break;
|
||||
}
|
||||
'
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after NoEmptyStatementFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_SWITCH);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
/** @var SwitchAnalysis $analysis */
|
||||
foreach (ControlCaseStructuresAnalyzer::findControlStructures($tokens, [T_SWITCH]) as $analysis) {
|
||||
$default = $analysis->getDefaultAnalysis();
|
||||
|
||||
if (null !== $default) {
|
||||
$this->fixTokenIfNeeded($tokens, $default->getColonIndex());
|
||||
}
|
||||
|
||||
foreach ($analysis->getCases() as $caseAnalysis) {
|
||||
$this->fixTokenIfNeeded($tokens, $caseAnalysis->getColonIndex());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function fixTokenIfNeeded(Tokens $tokens, int $index): void
|
||||
{
|
||||
if ($tokens[$index]->equals(';')) {
|
||||
$tokens[$index] = new Token(':');
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+94
@@ -0,0 +1,94 @@
|
||||
<?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\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\Analysis\SwitchAnalysis;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\ControlCaseStructuresAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* Fixer for rules defined in PSR2 ¶5.2.
|
||||
*
|
||||
* @author Sullivan Senechal <soullivaneuh@gmail.com>
|
||||
*/
|
||||
final class SwitchCaseSpaceFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Removes extra spaces between colon and case value.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
switch($a) {
|
||||
case 1 :
|
||||
break;
|
||||
default :
|
||||
return 2;
|
||||
}
|
||||
'
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_SWITCH);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
/** @var SwitchAnalysis $analysis */
|
||||
foreach (ControlCaseStructuresAnalyzer::findControlStructures($tokens, [T_SWITCH]) as $analysis) {
|
||||
$default = $analysis->getDefaultAnalysis();
|
||||
|
||||
if (null !== $default) {
|
||||
$index = $default->getIndex();
|
||||
|
||||
if (!$tokens[$index + 1]->isWhitespace() || !$tokens[$index + 2]->equalsAny([':', ';'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens->clearAt($index + 1);
|
||||
}
|
||||
|
||||
foreach ($analysis->getCases() as $caseAnalysis) {
|
||||
$colonIndex = $caseAnalysis->getColonIndex();
|
||||
$valueIndex = $tokens->getPrevNonWhitespace($colonIndex);
|
||||
|
||||
// skip if there is no space between the colon and previous token or is space after comment
|
||||
if ($valueIndex === $colonIndex - 1 || $tokens[$valueIndex]->isComment()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens->clearAt($valueIndex + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+249
@@ -0,0 +1,249 @@
|
||||
<?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\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Preg;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class SwitchContinueToBreakFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* @var int[]
|
||||
*/
|
||||
private array $switchLevels = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Switch case must not be ended with `continue` but with `break`.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
switch ($foo) {
|
||||
case 1:
|
||||
continue;
|
||||
}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
switch ($foo) {
|
||||
case 1:
|
||||
while($bar) {
|
||||
do {
|
||||
continue 3;
|
||||
} while(false);
|
||||
|
||||
if ($foo + 1 > 3) {
|
||||
continue;
|
||||
}
|
||||
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
'
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after NoAlternativeSyntaxFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAllTokenKindsFound([T_SWITCH, T_CONTINUE]) && !$tokens->hasAlternativeSyntax();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$count = \count($tokens);
|
||||
|
||||
for ($index = 1; $index < $count - 1; ++$index) {
|
||||
$index = $this->doFix($tokens, $index, 0, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $depth >= 0
|
||||
*/
|
||||
private function doFix(Tokens $tokens, int $index, int $depth, bool $isInSwitch): int
|
||||
{
|
||||
$token = $tokens[$index];
|
||||
|
||||
if ($token->isGivenKind([T_FOREACH, T_FOR, T_WHILE])) {
|
||||
// go to first `(`, go to its close ')', go to first of '{', ';', '? >'
|
||||
$index = $tokens->getNextTokenOfKind($index, ['(']);
|
||||
$index = $tokens->getNextTokenOfKind($index, [')']);
|
||||
$index = $tokens->getNextTokenOfKind($index, ['{', ';', [T_CLOSE_TAG]]);
|
||||
|
||||
if (!$tokens[$index]->equals('{')) {
|
||||
return $index;
|
||||
}
|
||||
|
||||
return $this->fixInLoop($tokens, $index, $depth + 1);
|
||||
}
|
||||
|
||||
if ($token->isGivenKind(T_DO)) {
|
||||
return $this->fixInLoop($tokens, $tokens->getNextTokenOfKind($index, ['{']), $depth + 1);
|
||||
}
|
||||
|
||||
if ($token->isGivenKind(T_SWITCH)) {
|
||||
return $this->fixInSwitch($tokens, $index, $depth + 1);
|
||||
}
|
||||
|
||||
if ($token->isGivenKind(T_CONTINUE)) {
|
||||
return $this->fixContinueWhenActsAsBreak($tokens, $index, $isInSwitch, $depth);
|
||||
}
|
||||
|
||||
return $index;
|
||||
}
|
||||
|
||||
private function fixInSwitch(Tokens $tokens, int $switchIndex, int $depth): int
|
||||
{
|
||||
$this->switchLevels[] = $depth;
|
||||
|
||||
// figure out where the switch starts
|
||||
$openIndex = $tokens->getNextTokenOfKind($switchIndex, ['{']);
|
||||
|
||||
// figure out where the switch ends
|
||||
$closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $openIndex);
|
||||
|
||||
for ($index = $openIndex + 1; $index < $closeIndex; ++$index) {
|
||||
$index = $this->doFix($tokens, $index, $depth, true);
|
||||
}
|
||||
|
||||
array_pop($this->switchLevels);
|
||||
|
||||
return $closeIndex;
|
||||
}
|
||||
|
||||
private function fixInLoop(Tokens $tokens, int $openIndex, int $depth): int
|
||||
{
|
||||
$openCount = 1;
|
||||
|
||||
while (true) {
|
||||
++$openIndex;
|
||||
$token = $tokens[$openIndex];
|
||||
|
||||
if ($token->equals('{')) {
|
||||
++$openCount;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->equals('}')) {
|
||||
--$openCount;
|
||||
|
||||
if (0 === $openCount) {
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$openIndex = $this->doFix($tokens, $openIndex, $depth, false);
|
||||
}
|
||||
|
||||
return $openIndex;
|
||||
}
|
||||
|
||||
private function fixContinueWhenActsAsBreak(Tokens $tokens, int $continueIndex, bool $isInSwitch, int $depth): int
|
||||
{
|
||||
$followingContinueIndex = $tokens->getNextMeaningfulToken($continueIndex);
|
||||
$followingContinueToken = $tokens[$followingContinueIndex];
|
||||
|
||||
if ($isInSwitch && $followingContinueToken->equals(';')) {
|
||||
$this->replaceContinueWithBreakToken($tokens, $continueIndex); // short continue 1 notation
|
||||
|
||||
return $followingContinueIndex;
|
||||
}
|
||||
|
||||
if (!$followingContinueToken->isGivenKind(T_LNUMBER)) {
|
||||
return $followingContinueIndex;
|
||||
}
|
||||
|
||||
$afterFollowingContinueIndex = $tokens->getNextMeaningfulToken($followingContinueIndex);
|
||||
|
||||
if (!$tokens[$afterFollowingContinueIndex]->equals(';')) {
|
||||
return $afterFollowingContinueIndex; // if next not is `;` return without fixing, for example `continue 1 ? ><?php + $a;`
|
||||
}
|
||||
|
||||
// check if continue {jump} targets a switch statement and if so fix it
|
||||
|
||||
$jump = $followingContinueToken->getContent();
|
||||
$jump = str_replace('_', '', $jump); // support for numeric_literal_separator
|
||||
|
||||
if (\strlen($jump) > 2 && 'x' === $jump[1]) {
|
||||
$jump = hexdec($jump); // hexadecimal - 0x1
|
||||
} elseif (\strlen($jump) > 2 && 'b' === $jump[1]) {
|
||||
$jump = bindec($jump); // binary - 0b1
|
||||
} elseif (\strlen($jump) > 1 && '0' === $jump[0]) {
|
||||
$jump = octdec($jump); // octal 01
|
||||
} elseif (1 === Preg::match('#^\d+$#', $jump)) { // positive int
|
||||
$jump = (float) $jump; // cast to float, might be a number bigger than PHP max. int value
|
||||
} else {
|
||||
return $afterFollowingContinueIndex; // cannot process value, ignore
|
||||
}
|
||||
|
||||
if ($jump > PHP_INT_MAX) {
|
||||
return $afterFollowingContinueIndex; // cannot process value, ignore
|
||||
}
|
||||
|
||||
$jump = (int) $jump;
|
||||
|
||||
if ($isInSwitch && (1 === $jump || 0 === $jump)) {
|
||||
$this->replaceContinueWithBreakToken($tokens, $continueIndex); // long continue 0/1 notation
|
||||
|
||||
return $afterFollowingContinueIndex;
|
||||
}
|
||||
|
||||
$jumpDestination = $depth - $jump + 1;
|
||||
|
||||
if (\in_array($jumpDestination, $this->switchLevels, true)) {
|
||||
$this->replaceContinueWithBreakToken($tokens, $continueIndex);
|
||||
|
||||
return $afterFollowingContinueIndex;
|
||||
}
|
||||
|
||||
return $afterFollowingContinueIndex;
|
||||
}
|
||||
|
||||
private function replaceContinueWithBreakToken(Tokens $tokens, int $index): void
|
||||
{
|
||||
$tokens[$index] = new Token([T_BREAK, 'break']);
|
||||
}
|
||||
}
|
||||
+250
@@ -0,0 +1,250 @@
|
||||
<?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\FixerConfiguration\AllowedValueSubset;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
|
||||
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
|
||||
use PhpCsFixer\FixerConfiguration\InvalidOptionsForEnvException;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\FixerDefinition\VersionSpecification;
|
||||
use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use PhpCsFixer\Tokenizer\TokensAnalyzer;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
|
||||
/**
|
||||
* @author Sebastiaan Stok <s.stok@rollerscapes.net>
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
* @author Kuba Werłos <werlos@gmail.com>
|
||||
*/
|
||||
final class TrailingCommaInMultilineFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public const ELEMENTS_ARRAYS = 'arrays';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public const ELEMENTS_ARGUMENTS = 'arguments';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public const ELEMENTS_PARAMETERS = 'parameters';
|
||||
|
||||
private const MATCH_EXPRESSIONS = 'match';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Multi-line arrays, arguments list, parameters list and `match` expressions must have a trailing comma.',
|
||||
[
|
||||
new CodeSample("<?php\narray(\n 1,\n 2\n);\n"),
|
||||
new VersionSpecificCodeSample(
|
||||
<<<'SAMPLE'
|
||||
<?php
|
||||
$x = [
|
||||
'foo',
|
||||
<<<EOD
|
||||
bar
|
||||
EOD
|
||||
];
|
||||
|
||||
SAMPLE
|
||||
,
|
||||
new VersionSpecification(70300),
|
||||
['after_heredoc' => true]
|
||||
),
|
||||
new VersionSpecificCodeSample("<?php\nfoo(\n 1,\n 2\n);\n", new VersionSpecification(70300), ['elements' => [self::ELEMENTS_ARGUMENTS]]),
|
||||
new VersionSpecificCodeSample("<?php\nfunction foo(\n \$x,\n \$y\n)\n{\n}\n", new VersionSpecification(80000), ['elements' => [self::ELEMENTS_PARAMETERS]]),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after NoMultilineWhitespaceAroundDoubleArrowFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN, '(']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('after_heredoc', 'Whether a trailing comma should also be placed after heredoc end.'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(false)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('elements', sprintf('Where to fix multiline trailing comma (PHP >= 8.0 for `%s` and `%s`).', self::ELEMENTS_PARAMETERS, self::MATCH_EXPRESSIONS))) // @TODO: remove text when PHP 8.0+ is required
|
||||
->setAllowedTypes(['array'])
|
||||
->setAllowedValues([new AllowedValueSubset([self::ELEMENTS_ARRAYS, self::ELEMENTS_ARGUMENTS, self::ELEMENTS_PARAMETERS, self::MATCH_EXPRESSIONS])])
|
||||
->setDefault([self::ELEMENTS_ARRAYS])
|
||||
->setNormalizer(static function (Options $options, $value) {
|
||||
if (\PHP_VERSION_ID < 80000) { // @TODO: drop condition when PHP 8.0+ is required
|
||||
foreach ([self::ELEMENTS_PARAMETERS, self::MATCH_EXPRESSIONS] as $option) {
|
||||
if (\in_array($option, $value, true)) {
|
||||
throw new InvalidOptionsForEnvException(sprintf('"%s" option can only be enabled with PHP 8.0+.', $option));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
})
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$fixArrays = \in_array(self::ELEMENTS_ARRAYS, $this->configuration['elements'], true);
|
||||
$fixArguments = \in_array(self::ELEMENTS_ARGUMENTS, $this->configuration['elements'], true);
|
||||
$fixParameters = \PHP_VERSION_ID >= 80000 && \in_array(self::ELEMENTS_PARAMETERS, $this->configuration['elements'], true); // @TODO: drop condition when PHP 8.0+ is required
|
||||
$fixMatch = \PHP_VERSION_ID >= 80000 && \in_array(self::MATCH_EXPRESSIONS, $this->configuration['elements'], true); // @TODO: drop condition when PHP 8.0+ is required
|
||||
|
||||
for ($index = $tokens->count() - 1; $index >= 0; --$index) {
|
||||
$prevIndex = $tokens->getPrevMeaningfulToken($index);
|
||||
|
||||
if (
|
||||
$fixArrays
|
||||
&& (
|
||||
$tokens[$index]->equals('(') && $tokens[$prevIndex]->isGivenKind(T_ARRAY) // long syntax
|
||||
|| $tokens[$index]->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN) // short syntax
|
||||
)
|
||||
) {
|
||||
$this->fixBlock($tokens, $index);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$tokens[$index]->equals('(')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$prevPrevIndex = $tokens->getPrevMeaningfulToken($prevIndex);
|
||||
|
||||
if ($fixArguments
|
||||
&& $tokens[$prevIndex]->equalsAny([']', [T_CLASS], [T_STRING], [T_VARIABLE], [T_STATIC]])
|
||||
&& !$tokens[$prevPrevIndex]->isGivenKind(T_FUNCTION)
|
||||
) {
|
||||
$this->fixBlock($tokens, $index);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
$fixParameters
|
||||
&& (
|
||||
$tokens[$prevIndex]->isGivenKind(T_STRING) && $tokens[$prevPrevIndex]->isGivenKind(T_FUNCTION)
|
||||
|| $tokens[$prevIndex]->isGivenKind([T_FN, T_FUNCTION])
|
||||
)
|
||||
) {
|
||||
$this->fixBlock($tokens, $index);
|
||||
}
|
||||
|
||||
if ($fixMatch && $tokens[$prevIndex]->isGivenKind(T_MATCH)) {
|
||||
$this->fixMatch($tokens, $index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function fixBlock(Tokens $tokens, int $startIndex): void
|
||||
{
|
||||
$tokensAnalyzer = new TokensAnalyzer($tokens);
|
||||
|
||||
if (!$tokensAnalyzer->isBlockMultiline($tokens, $startIndex)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$blockType = Tokens::detectBlockType($tokens[$startIndex]);
|
||||
$endIndex = $tokens->findBlockEnd($blockType['type'], $startIndex);
|
||||
|
||||
$beforeEndIndex = $tokens->getPrevMeaningfulToken($endIndex);
|
||||
$beforeEndToken = $tokens[$beforeEndIndex];
|
||||
|
||||
// if there is some item between braces then add `,` after it
|
||||
if (
|
||||
$startIndex !== $beforeEndIndex && !$beforeEndToken->equals(',')
|
||||
&& (true === $this->configuration['after_heredoc'] || !$beforeEndToken->isGivenKind(T_END_HEREDOC))
|
||||
) {
|
||||
$tokens->insertAt($beforeEndIndex + 1, new Token(','));
|
||||
|
||||
$endToken = $tokens[$endIndex];
|
||||
|
||||
if (!$endToken->isComment() && !$endToken->isWhitespace()) {
|
||||
$tokens->ensureWhitespaceAtIndex($endIndex, 1, ' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function fixMatch(Tokens $tokens, int $index): void
|
||||
{
|
||||
$index = $tokens->getNextTokenOfKind($index, ['{']);
|
||||
$closeIndex = $index;
|
||||
$isMultiline = false;
|
||||
$depth = 1;
|
||||
|
||||
do {
|
||||
++$closeIndex;
|
||||
|
||||
if ($tokens[$closeIndex]->equals('{')) {
|
||||
++$depth;
|
||||
} elseif ($tokens[$closeIndex]->equals('}')) {
|
||||
--$depth;
|
||||
} elseif (!$isMultiline && str_contains($tokens[$closeIndex]->getContent(), "\n")) {
|
||||
$isMultiline = true;
|
||||
}
|
||||
} while ($depth > 0);
|
||||
|
||||
if (!$isMultiline) {
|
||||
return;
|
||||
}
|
||||
|
||||
$previousIndex = $tokens->getPrevMeaningfulToken($closeIndex);
|
||||
|
||||
if (!$tokens[$previousIndex]->equals(',')) {
|
||||
$tokens->insertAt($previousIndex + 1, new Token(','));
|
||||
}
|
||||
}
|
||||
}
|
||||
+748
@@ -0,0 +1,748 @@
|
||||
<?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\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\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use PhpCsFixer\Tokenizer\TokensAnalyzer;
|
||||
|
||||
/**
|
||||
* @author Bram Gotink <bram@gotink.me>
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class YodaStyleFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* @var array<int|string, Token>
|
||||
*/
|
||||
private $candidatesMap;
|
||||
|
||||
/**
|
||||
* @var array<int|string, null|bool>
|
||||
*/
|
||||
private $candidateTypesConfiguration;
|
||||
|
||||
/**
|
||||
* @var array<int|string>
|
||||
*/
|
||||
private $candidateTypes;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configure(array $configuration): void
|
||||
{
|
||||
parent::configure($configuration);
|
||||
|
||||
$this->resolveConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Write conditions in Yoda style (`true`), non-Yoda style (`[\'equal\' => false, \'identical\' => false, \'less_and_greater\' => false]`) or ignore those conditions (`null`) based on configuration.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
if ($a === null) {
|
||||
echo "null";
|
||||
}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
$b = $c != 1; // equal
|
||||
$a = 1 === $b; // identical
|
||||
$c = $c > 3; // less than
|
||||
',
|
||||
[
|
||||
'equal' => true,
|
||||
'identical' => false,
|
||||
'less_and_greater' => null,
|
||||
]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
return $foo === count($bar);
|
||||
',
|
||||
[
|
||||
'always_move_variable' => true,
|
||||
]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
// Enforce non-Yoda style.
|
||||
if (null === $a) {
|
||||
echo "null";
|
||||
}
|
||||
',
|
||||
[
|
||||
'equal' => false,
|
||||
'identical' => false,
|
||||
'less_and_greater' => false,
|
||||
]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after IsNullFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound($this->candidateTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$this->fixTokens($tokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('equal', 'Style for equal (`==`, `!=`) statements.'))
|
||||
->setAllowedTypes(['bool', 'null'])
|
||||
->setDefault(true)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('identical', 'Style for identical (`===`, `!==`) statements.'))
|
||||
->setAllowedTypes(['bool', 'null'])
|
||||
->setDefault(true)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('less_and_greater', 'Style for less and greater than (`<`, `<=`, `>`, `>=`) statements.'))
|
||||
->setAllowedTypes(['bool', 'null'])
|
||||
->setDefault(null)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('always_move_variable', 'Whether variables should always be on non assignable side when applying Yoda style.'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(false)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the end of the right-hand side of the comparison at the given
|
||||
* index.
|
||||
*
|
||||
* The right-hand side ends when an operator with a lower precedence is
|
||||
* encountered or when the block level for `()`, `{}` or `[]` goes below
|
||||
* zero.
|
||||
*
|
||||
* @param Tokens $tokens The token list
|
||||
* @param int $index The index of the comparison
|
||||
*
|
||||
* @return int The last index of the right-hand side of the comparison
|
||||
*/
|
||||
private function findComparisonEnd(Tokens $tokens, int $index): int
|
||||
{
|
||||
++$index;
|
||||
$count = \count($tokens);
|
||||
|
||||
while ($index < $count) {
|
||||
$token = $tokens[$index];
|
||||
|
||||
if ($token->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) {
|
||||
++$index;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->isOfLowerPrecedence($token)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$block = Tokens::detectBlockType($token);
|
||||
|
||||
if (null === $block) {
|
||||
++$index;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$block['isStart']) {
|
||||
break;
|
||||
}
|
||||
|
||||
$index = $tokens->findBlockEnd($block['type'], $index) + 1;
|
||||
}
|
||||
|
||||
$prev = $tokens->getPrevMeaningfulToken($index);
|
||||
|
||||
return $tokens[$prev]->isGivenKind(T_CLOSE_TAG) ? $tokens->getPrevMeaningfulToken($prev) : $prev;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the start of the left-hand side of the comparison at the given
|
||||
* index.
|
||||
*
|
||||
* The left-hand side ends when an operator with a lower precedence is
|
||||
* encountered or when the block level for `()`, `{}` or `[]` goes below
|
||||
* zero.
|
||||
*
|
||||
* @param Tokens $tokens The token list
|
||||
* @param int $index The index of the comparison
|
||||
*
|
||||
* @return int The first index of the left-hand side of the comparison
|
||||
*/
|
||||
private function findComparisonStart(Tokens $tokens, int $index): int
|
||||
{
|
||||
--$index;
|
||||
$nonBlockFound = false;
|
||||
|
||||
while (0 <= $index) {
|
||||
$token = $tokens[$index];
|
||||
|
||||
if ($token->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) {
|
||||
--$index;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->isGivenKind([CT::T_NAMED_ARGUMENT_COLON])) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ($this->isOfLowerPrecedence($token)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$block = Tokens::detectBlockType($token);
|
||||
|
||||
if (null === $block) {
|
||||
--$index;
|
||||
$nonBlockFound = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
$block['isStart']
|
||||
|| ($nonBlockFound && Tokens::BLOCK_TYPE_CURLY_BRACE === $block['type']) // closing of structure not related to the comparison
|
||||
) {
|
||||
break;
|
||||
}
|
||||
|
||||
$index = $tokens->findBlockStart($block['type'], $index) - 1;
|
||||
}
|
||||
|
||||
return $tokens->getNextMeaningfulToken($index);
|
||||
}
|
||||
|
||||
private function fixTokens(Tokens $tokens): Tokens
|
||||
{
|
||||
for ($i = \count($tokens) - 1; $i > 1; --$i) {
|
||||
if ($tokens[$i]->isGivenKind($this->candidateTypes)) {
|
||||
$yoda = $this->candidateTypesConfiguration[$tokens[$i]->getId()];
|
||||
} elseif (
|
||||
($tokens[$i]->equals('<') && \in_array('<', $this->candidateTypes, true))
|
||||
|| ($tokens[$i]->equals('>') && \in_array('>', $this->candidateTypes, true))
|
||||
) {
|
||||
$yoda = $this->candidateTypesConfiguration[$tokens[$i]->getContent()];
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fixableCompareInfo = $this->getCompareFixableInfo($tokens, $i, $yoda);
|
||||
|
||||
if (null === $fixableCompareInfo) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$i = $this->fixTokensCompare(
|
||||
$tokens,
|
||||
$fixableCompareInfo['left']['start'],
|
||||
$fixableCompareInfo['left']['end'],
|
||||
$i,
|
||||
$fixableCompareInfo['right']['start'],
|
||||
$fixableCompareInfo['right']['end']
|
||||
);
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixes the comparison at the given index.
|
||||
*
|
||||
* A comparison is considered fixed when
|
||||
* - both sides are a variable (e.g. $a === $b)
|
||||
* - neither side is a variable (e.g. self::CONST === 3)
|
||||
* - only the right-hand side is a variable (e.g. 3 === self::$var)
|
||||
*
|
||||
* If the left-hand side and right-hand side of the given comparison are
|
||||
* swapped, this function runs recursively on the previous left-hand-side.
|
||||
*
|
||||
* @return int an upper bound for all non-fixed comparisons
|
||||
*/
|
||||
private function fixTokensCompare(
|
||||
Tokens $tokens,
|
||||
int $startLeft,
|
||||
int $endLeft,
|
||||
int $compareOperatorIndex,
|
||||
int $startRight,
|
||||
int $endRight
|
||||
): int {
|
||||
$type = $tokens[$compareOperatorIndex]->getId();
|
||||
$content = $tokens[$compareOperatorIndex]->getContent();
|
||||
|
||||
if (\array_key_exists($type, $this->candidatesMap)) {
|
||||
$tokens[$compareOperatorIndex] = clone $this->candidatesMap[$type];
|
||||
} elseif (\array_key_exists($content, $this->candidatesMap)) {
|
||||
$tokens[$compareOperatorIndex] = clone $this->candidatesMap[$content];
|
||||
}
|
||||
|
||||
$right = $this->fixTokensComparePart($tokens, $startRight, $endRight);
|
||||
$left = $this->fixTokensComparePart($tokens, $startLeft, $endLeft);
|
||||
|
||||
for ($i = $startRight; $i <= $endRight; ++$i) {
|
||||
$tokens->clearAt($i);
|
||||
}
|
||||
|
||||
for ($i = $startLeft; $i <= $endLeft; ++$i) {
|
||||
$tokens->clearAt($i);
|
||||
}
|
||||
|
||||
$tokens->insertAt($startRight, $left);
|
||||
$tokens->insertAt($startLeft, $right);
|
||||
|
||||
return $startLeft;
|
||||
}
|
||||
|
||||
private function fixTokensComparePart(Tokens $tokens, int $start, int $end): Tokens
|
||||
{
|
||||
$newTokens = $tokens->generatePartialCode($start, $end);
|
||||
$newTokens = $this->fixTokens(Tokens::fromCode(sprintf('<?php %s;', $newTokens)));
|
||||
$newTokens->clearAt(\count($newTokens) - 1);
|
||||
$newTokens->clearAt(0);
|
||||
$newTokens->clearEmptyTokens();
|
||||
|
||||
return $newTokens;
|
||||
}
|
||||
|
||||
private function getCompareFixableInfo(Tokens $tokens, int $index, bool $yoda): ?array
|
||||
{
|
||||
$left = $this->getLeftSideCompareFixableInfo($tokens, $index);
|
||||
$right = $this->getRightSideCompareFixableInfo($tokens, $index);
|
||||
|
||||
if (!$yoda && $this->isOfLowerPrecedenceAssignment($tokens[$tokens->getNextMeaningfulToken($right['end'])])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->isListStatement($tokens, $left['start'], $left['end']) || $this->isListStatement($tokens, $right['start'], $right['end'])) {
|
||||
return null; // do not fix lists assignment inside statements
|
||||
}
|
||||
|
||||
/** @var bool $strict */
|
||||
$strict = $this->configuration['always_move_variable'];
|
||||
$leftSideIsVariable = $this->isVariable($tokens, $left['start'], $left['end'], $strict);
|
||||
$rightSideIsVariable = $this->isVariable($tokens, $right['start'], $right['end'], $strict);
|
||||
|
||||
if (!($leftSideIsVariable ^ $rightSideIsVariable)) {
|
||||
return null; // both are (not) variables, do not touch
|
||||
}
|
||||
|
||||
if (!$strict) { // special handling for braces with not "always_move_variable"
|
||||
$leftSideIsVariable = $leftSideIsVariable && !$tokens[$left['start']]->equals('(');
|
||||
$rightSideIsVariable = $rightSideIsVariable && !$tokens[$right['start']]->equals('(');
|
||||
}
|
||||
|
||||
return ($yoda && !$leftSideIsVariable) || (!$yoda && !$rightSideIsVariable)
|
||||
? null
|
||||
: ['left' => $left, 'right' => $right]
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{start: int, end: int}
|
||||
*/
|
||||
private function getLeftSideCompareFixableInfo(Tokens $tokens, int $index): array
|
||||
{
|
||||
return [
|
||||
'start' => $this->findComparisonStart($tokens, $index),
|
||||
'end' => $tokens->getPrevMeaningfulToken($index),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{start: int, end: int}
|
||||
*/
|
||||
private function getRightSideCompareFixableInfo(Tokens $tokens, int $index): array
|
||||
{
|
||||
return [
|
||||
'start' => $tokens->getNextMeaningfulToken($index),
|
||||
'end' => $this->findComparisonEnd($tokens, $index),
|
||||
];
|
||||
}
|
||||
|
||||
private function isListStatement(Tokens $tokens, int $index, int $end): bool
|
||||
{
|
||||
for ($i = $index; $i <= $end; ++$i) {
|
||||
if ($tokens[$i]->isGivenKind([T_LIST, CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given token has a lower precedence than `T_IS_EQUAL`
|
||||
* or `T_IS_IDENTICAL`.
|
||||
*
|
||||
* @param Token $token The token to check
|
||||
*
|
||||
* @return bool Whether the token has a lower precedence
|
||||
*/
|
||||
private function isOfLowerPrecedence(Token $token): bool
|
||||
{
|
||||
static $tokens;
|
||||
|
||||
if (null === $tokens) {
|
||||
$tokens = [
|
||||
T_BOOLEAN_AND, // &&
|
||||
T_BOOLEAN_OR, // ||
|
||||
T_CASE, // case
|
||||
T_DOUBLE_ARROW, // =>
|
||||
T_ECHO, // echo
|
||||
T_GOTO, // goto
|
||||
T_LOGICAL_AND, // and
|
||||
T_LOGICAL_OR, // or
|
||||
T_LOGICAL_XOR, // xor
|
||||
T_OPEN_TAG, // <?php
|
||||
T_OPEN_TAG_WITH_ECHO,
|
||||
T_PRINT, // print
|
||||
T_RETURN, // return
|
||||
T_THROW, // throw
|
||||
T_COALESCE,
|
||||
T_YIELD, // yield
|
||||
];
|
||||
}
|
||||
|
||||
static $otherTokens = [
|
||||
// bitwise and, or, xor
|
||||
'&', '|', '^',
|
||||
// ternary operators
|
||||
'?', ':',
|
||||
// end of PHP statement
|
||||
',', ';',
|
||||
];
|
||||
|
||||
return $this->isOfLowerPrecedenceAssignment($token) || $token->isGivenKind($tokens) || $token->equalsAny($otherTokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given assignment token has a lower precedence than `T_IS_EQUAL`
|
||||
* or `T_IS_IDENTICAL`.
|
||||
*/
|
||||
private function isOfLowerPrecedenceAssignment(Token $token): bool
|
||||
{
|
||||
static $tokens;
|
||||
|
||||
if (null === $tokens) {
|
||||
$tokens = [
|
||||
T_AND_EQUAL, // &=
|
||||
T_CONCAT_EQUAL, // .=
|
||||
T_DIV_EQUAL, // /=
|
||||
T_MINUS_EQUAL, // -=
|
||||
T_MOD_EQUAL, // %=
|
||||
T_MUL_EQUAL, // *=
|
||||
T_OR_EQUAL, // |=
|
||||
T_PLUS_EQUAL, // +=
|
||||
T_POW_EQUAL, // **=
|
||||
T_SL_EQUAL, // <<=
|
||||
T_SR_EQUAL, // >>=
|
||||
T_XOR_EQUAL, // ^=
|
||||
T_COALESCE_EQUAL, // ??=
|
||||
];
|
||||
}
|
||||
|
||||
return $token->equals('=') || $token->isGivenKind($tokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the tokens between the given start and end describe a
|
||||
* variable.
|
||||
*
|
||||
* @param Tokens $tokens The token list
|
||||
* @param int $start The first index of the possible variable
|
||||
* @param int $end The last index of the possible variable
|
||||
* @param bool $strict Enable strict variable detection
|
||||
*
|
||||
* @return bool Whether the tokens describe a variable
|
||||
*/
|
||||
private function isVariable(Tokens $tokens, int $start, int $end, bool $strict): bool
|
||||
{
|
||||
$tokenAnalyzer = new TokensAnalyzer($tokens);
|
||||
|
||||
if ($start === $end) {
|
||||
return $tokens[$start]->isGivenKind(T_VARIABLE);
|
||||
}
|
||||
|
||||
if ($tokens[$start]->equals('(')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($strict) {
|
||||
for ($index = $start; $index <= $end; ++$index) {
|
||||
if (
|
||||
$tokens[$index]->isCast()
|
||||
|| $tokens[$index]->isGivenKind(T_INSTANCEOF)
|
||||
|| $tokens[$index]->equals('!')
|
||||
|| $tokenAnalyzer->isBinaryOperator($index)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$index = $start;
|
||||
|
||||
// handle multiple braces around statement ((($a === 1)))
|
||||
while (
|
||||
$tokens[$index]->equals('(')
|
||||
&& $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index) === $end
|
||||
) {
|
||||
$index = $tokens->getNextMeaningfulToken($index);
|
||||
$end = $tokens->getPrevMeaningfulToken($end);
|
||||
}
|
||||
|
||||
$expectString = false;
|
||||
|
||||
while ($index <= $end) {
|
||||
$current = $tokens[$index];
|
||||
if ($current->isComment() || $current->isWhitespace() || $tokens->isEmptyAt($index)) {
|
||||
++$index;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if this is the last token
|
||||
if ($index === $end) {
|
||||
return $current->isGivenKind($expectString ? T_STRING : T_VARIABLE);
|
||||
}
|
||||
|
||||
if ($current->isGivenKind([T_LIST, CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$nextIndex = $tokens->getNextMeaningfulToken($index);
|
||||
$next = $tokens[$nextIndex];
|
||||
|
||||
// self:: or ClassName::
|
||||
if ($current->isGivenKind(T_STRING) && $next->isGivenKind(T_DOUBLE_COLON)) {
|
||||
$index = $tokens->getNextMeaningfulToken($nextIndex);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// \ClassName
|
||||
if ($current->isGivenKind(T_NS_SEPARATOR) && $next->isGivenKind(T_STRING)) {
|
||||
$index = $nextIndex;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// ClassName\
|
||||
if ($current->isGivenKind(T_STRING) && $next->isGivenKind(T_NS_SEPARATOR)) {
|
||||
$index = $nextIndex;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// $a-> or a-> (as in $b->a->c)
|
||||
if ($current->isGivenKind([T_STRING, T_VARIABLE]) && $next->isObjectOperator()) {
|
||||
$index = $tokens->getNextMeaningfulToken($nextIndex);
|
||||
$expectString = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// $a[...], a[...] (as in $c->a[$b]), $a{...} or a{...} (as in $c->a{$b})
|
||||
if (
|
||||
$current->isGivenKind($expectString ? T_STRING : T_VARIABLE)
|
||||
&& $next->equalsAny(['[', [CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN, '{']])
|
||||
) {
|
||||
$index = $tokens->findBlockEnd(
|
||||
$next->equals('[') ? Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE : Tokens::BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE,
|
||||
$nextIndex
|
||||
);
|
||||
|
||||
if ($index === $end) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$index = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
if (!$tokens[$index]->equalsAny(['[', [CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN, '{']]) && !$tokens[$index]->isObjectOperator()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$index = $tokens->getNextMeaningfulToken($index);
|
||||
$expectString = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// $a(...) or $a->b(...)
|
||||
if ($strict && $current->isGivenKind([T_STRING, T_VARIABLE]) && $next->equals('(')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// {...} (as in $a->{$b})
|
||||
if ($expectString && $current->isGivenKind(CT::T_DYNAMIC_PROP_BRACE_OPEN)) {
|
||||
$index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_DYNAMIC_PROP_BRACE, $index);
|
||||
if ($index === $end) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$index = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
if (!$tokens[$index]->isObjectOperator()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$index = $tokens->getNextMeaningfulToken($index);
|
||||
$expectString = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return !$this->isConstant($tokens, $start, $end);
|
||||
}
|
||||
|
||||
private function isConstant(Tokens $tokens, int $index, int $end): bool
|
||||
{
|
||||
$expectArrayOnly = false;
|
||||
$expectNumberOnly = false;
|
||||
$expectNothing = false;
|
||||
|
||||
for (; $index <= $end; ++$index) {
|
||||
$token = $tokens[$index];
|
||||
|
||||
if ($token->isComment() || $token->isWhitespace()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($expectNothing) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($expectArrayOnly) {
|
||||
if ($token->equalsAny(['(', ')', [CT::T_ARRAY_SQUARE_BRACE_CLOSE]])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($token->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) {
|
||||
$expectArrayOnly = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($expectNumberOnly && !$token->isGivenKind([T_LNUMBER, T_DNUMBER])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($token->equals('-')) {
|
||||
$expectNumberOnly = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
$token->isGivenKind([T_LNUMBER, T_DNUMBER, T_CONSTANT_ENCAPSED_STRING])
|
||||
|| $token->equalsAny([[T_STRING, 'true'], [T_STRING, 'false'], [T_STRING, 'null']])
|
||||
) {
|
||||
$expectNothing = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function resolveConfiguration(): void
|
||||
{
|
||||
$candidateTypes = [];
|
||||
$this->candidatesMap = [];
|
||||
|
||||
if (null !== $this->configuration['equal']) {
|
||||
// `==`, `!=` and `<>`
|
||||
$candidateTypes[T_IS_EQUAL] = $this->configuration['equal'];
|
||||
$candidateTypes[T_IS_NOT_EQUAL] = $this->configuration['equal'];
|
||||
}
|
||||
|
||||
if (null !== $this->configuration['identical']) {
|
||||
// `===` and `!==`
|
||||
$candidateTypes[T_IS_IDENTICAL] = $this->configuration['identical'];
|
||||
$candidateTypes[T_IS_NOT_IDENTICAL] = $this->configuration['identical'];
|
||||
}
|
||||
|
||||
if (null !== $this->configuration['less_and_greater']) {
|
||||
// `<`, `<=`, `>` and `>=`
|
||||
$candidateTypes[T_IS_SMALLER_OR_EQUAL] = $this->configuration['less_and_greater'];
|
||||
$this->candidatesMap[T_IS_SMALLER_OR_EQUAL] = new Token([T_IS_GREATER_OR_EQUAL, '>=']);
|
||||
|
||||
$candidateTypes[T_IS_GREATER_OR_EQUAL] = $this->configuration['less_and_greater'];
|
||||
$this->candidatesMap[T_IS_GREATER_OR_EQUAL] = new Token([T_IS_SMALLER_OR_EQUAL, '<=']);
|
||||
|
||||
$candidateTypes['<'] = $this->configuration['less_and_greater'];
|
||||
$this->candidatesMap['<'] = new Token('>');
|
||||
|
||||
$candidateTypes['>'] = $this->configuration['less_and_greater'];
|
||||
$this->candidatesMap['>'] = new Token('<');
|
||||
}
|
||||
|
||||
$this->candidateTypesConfiguration = $candidateTypes;
|
||||
$this->candidateTypes = array_keys($candidateTypes);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user