Missing dependancies
This commit is contained in:
+58
@@ -0,0 +1,58 @@
|
||||
<?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;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
abstract class AbstractIncrementOperatorFixer extends AbstractFixer
|
||||
{
|
||||
final protected function findStart(Tokens $tokens, int $index): int
|
||||
{
|
||||
do {
|
||||
$index = $tokens->getPrevMeaningfulToken($index);
|
||||
$token = $tokens[$index];
|
||||
|
||||
$blockType = Tokens::detectBlockType($token);
|
||||
if (null !== $blockType && !$blockType['isStart']) {
|
||||
$index = $tokens->findBlockStart($blockType['type'], $index);
|
||||
$token = $tokens[$index];
|
||||
}
|
||||
} while (!$token->equalsAny(['$', [T_VARIABLE]]));
|
||||
|
||||
$prevIndex = $tokens->getPrevMeaningfulToken($index);
|
||||
$prevToken = $tokens[$prevIndex];
|
||||
|
||||
if ($prevToken->equals('$')) {
|
||||
return $this->findStart($tokens, $index);
|
||||
}
|
||||
|
||||
if ($prevToken->isObjectOperator()) {
|
||||
return $this->findStart($tokens, $prevIndex);
|
||||
}
|
||||
|
||||
if ($prevToken->isGivenKind(T_PAAMAYIM_NEKUDOTAYIM)) {
|
||||
$prevPrevIndex = $tokens->getPrevMeaningfulToken($prevIndex);
|
||||
if (!$tokens[$prevPrevIndex]->isGivenKind([T_STATIC, T_STRING])) {
|
||||
return $this->findStart($tokens, $prevIndex);
|
||||
}
|
||||
|
||||
$index = $tokens->getTokenNotOfKindsSibling($prevIndex, -1, [T_NS_SEPARATOR, T_STATIC, T_STRING]);
|
||||
$index = $tokens->getNextMeaningfulToken($index);
|
||||
}
|
||||
|
||||
return $index;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?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;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\Indicator\PhpUnitTestCaseIndicator;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class AbstractPhpUnitFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
final public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAllTokenKindsFound([T_CLASS, T_STRING]);
|
||||
}
|
||||
|
||||
final protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator();
|
||||
|
||||
foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indices) {
|
||||
$this->applyPhpUnitClassFix($tokens, $indices[0], $indices[1]);
|
||||
}
|
||||
}
|
||||
|
||||
abstract protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void;
|
||||
|
||||
final protected function getDocBlockIndex(Tokens $tokens, int $index): int
|
||||
{
|
||||
do {
|
||||
$index = $tokens->getPrevNonWhitespace($index);
|
||||
} while ($tokens[$index]->isGivenKind([T_PUBLIC, T_PROTECTED, T_PRIVATE, T_FINAL, T_ABSTRACT, T_COMMENT]));
|
||||
|
||||
return $index;
|
||||
}
|
||||
|
||||
final protected function isPHPDoc(Tokens $tokens, int $index): bool
|
||||
{
|
||||
return $tokens[$index]->isGivenKind(T_DOC_COMMENT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
<?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\Alias;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class ArrayPushFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Converts simple usages of `array_push($x, $y);` to `$x[] = $y;`.',
|
||||
[new CodeSample("<?php\narray_push(\$x, \$y);\n")],
|
||||
null,
|
||||
'Risky when the function `array_push` is overridden.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_STRING) && $tokens->count() > 7;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$functionsAnalyzer = new FunctionsAnalyzer();
|
||||
|
||||
for ($index = $tokens->count() - 7; $index > 0; --$index) {
|
||||
if (!$tokens[$index]->equals([T_STRING, 'array_push'], false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) {
|
||||
continue; // redeclare/override
|
||||
}
|
||||
|
||||
// meaningful before must be `<?php`, `{`, `}` or `;`
|
||||
|
||||
$callIndex = $index;
|
||||
$index = $tokens->getPrevMeaningfulToken($index);
|
||||
$namespaceSeparatorIndex = null;
|
||||
|
||||
if ($tokens[$index]->isGivenKind(T_NS_SEPARATOR)) {
|
||||
$namespaceSeparatorIndex = $index;
|
||||
$index = $tokens->getPrevMeaningfulToken($index);
|
||||
}
|
||||
|
||||
if (!$tokens[$index]->equalsAny([';', '{', '}', ')', [T_OPEN_TAG]])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// figure out where the arguments list opens
|
||||
|
||||
$openBraceIndex = $tokens->getNextMeaningfulToken($callIndex);
|
||||
$blockType = Tokens::detectBlockType($tokens[$openBraceIndex]);
|
||||
|
||||
if (null === $blockType || Tokens::BLOCK_TYPE_PARENTHESIS_BRACE !== $blockType['type']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// figure out where the arguments list closes
|
||||
|
||||
$closeBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openBraceIndex);
|
||||
|
||||
// meaningful after `)` must be `;`, `? >` or nothing
|
||||
|
||||
$afterCloseBraceIndex = $tokens->getNextMeaningfulToken($closeBraceIndex);
|
||||
|
||||
if (null !== $afterCloseBraceIndex && !$tokens[$afterCloseBraceIndex]->equalsAny([';', [T_CLOSE_TAG]])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// must have 2 arguments
|
||||
|
||||
// first argument must be a variable (with possibly array indexing etc.),
|
||||
// after that nothing meaningful should be there till the next `,` or `)`
|
||||
// if `)` than we cannot fix it (it is a single argument call)
|
||||
|
||||
$firstArgumentStop = $this->getFirstArgumentEnd($tokens, $openBraceIndex);
|
||||
$firstArgumentStop = $tokens->getNextMeaningfulToken($firstArgumentStop);
|
||||
|
||||
if (!$tokens[$firstArgumentStop]->equals(',')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// second argument can be about anything but ellipsis, we must make sure there is not
|
||||
// a third argument (or more) passed to `array_push`
|
||||
|
||||
$secondArgumentStart = $tokens->getNextMeaningfulToken($firstArgumentStop);
|
||||
$secondArgumentStop = $this->getSecondArgumentEnd($tokens, $secondArgumentStart, $closeBraceIndex);
|
||||
|
||||
if (null === $secondArgumentStop) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// candidate is valid, replace tokens
|
||||
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($closeBraceIndex);
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($firstArgumentStop);
|
||||
$tokens->insertAt(
|
||||
$firstArgumentStop,
|
||||
[
|
||||
new Token('['),
|
||||
new Token(']'),
|
||||
new Token([T_WHITESPACE, ' ']),
|
||||
new Token('='),
|
||||
]
|
||||
);
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($openBraceIndex);
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($callIndex);
|
||||
|
||||
if (null !== $namespaceSeparatorIndex) {
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($namespaceSeparatorIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getFirstArgumentEnd(Tokens $tokens, int $index): int
|
||||
{
|
||||
$nextIndex = $tokens->getNextMeaningfulToken($index);
|
||||
$nextToken = $tokens[$nextIndex];
|
||||
|
||||
while ($nextToken->equalsAny([
|
||||
'$',
|
||||
'[',
|
||||
'(',
|
||||
[CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN],
|
||||
[CT::T_DYNAMIC_PROP_BRACE_OPEN],
|
||||
[CT::T_DYNAMIC_VAR_BRACE_OPEN],
|
||||
[CT::T_NAMESPACE_OPERATOR],
|
||||
[T_NS_SEPARATOR],
|
||||
[T_STATIC],
|
||||
[T_STRING],
|
||||
[T_VARIABLE],
|
||||
])) {
|
||||
$blockType = Tokens::detectBlockType($nextToken);
|
||||
|
||||
if (null !== $blockType) {
|
||||
$nextIndex = $tokens->findBlockEnd($blockType['type'], $nextIndex);
|
||||
}
|
||||
|
||||
$index = $nextIndex;
|
||||
$nextIndex = $tokens->getNextMeaningfulToken($nextIndex);
|
||||
$nextToken = $tokens[$nextIndex];
|
||||
}
|
||||
|
||||
if ($nextToken->isGivenKind(T_OBJECT_OPERATOR)) {
|
||||
return $this->getFirstArgumentEnd($tokens, $nextIndex);
|
||||
}
|
||||
|
||||
if ($nextToken->isGivenKind(T_PAAMAYIM_NEKUDOTAYIM)) {
|
||||
return $this->getFirstArgumentEnd($tokens, $tokens->getNextMeaningfulToken($nextIndex));
|
||||
}
|
||||
|
||||
return $index;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $endIndex boundary, i.e. tokens index of `)`
|
||||
*/
|
||||
private function getSecondArgumentEnd(Tokens $tokens, int $index, int $endIndex): ?int
|
||||
{
|
||||
if ($tokens[$index]->isGivenKind(T_ELLIPSIS)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (; $index <= $endIndex; ++$index) {
|
||||
$blockType = Tokens::detectBlockType($tokens[$index]);
|
||||
|
||||
while (null !== $blockType && $blockType['isStart']) {
|
||||
$index = $tokens->findBlockEnd($blockType['type'], $index);
|
||||
$index = $tokens->getNextMeaningfulToken($index);
|
||||
$blockType = Tokens::detectBlockType($tokens[$index]);
|
||||
}
|
||||
|
||||
if ($tokens[$index]->equals(',') || $tokens[$index]->isGivenKind([T_YIELD, T_YIELD_FROM, T_LOGICAL_AND, T_LOGICAL_OR, T_LOGICAL_XOR])) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return $endIndex;
|
||||
}
|
||||
}
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
<?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\Alias;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author Filippo Tessarotto <zoeslam@gmail.com>
|
||||
*/
|
||||
final class BacktickToShellExecFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound('`');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Converts backtick operators to `shell_exec` calls.',
|
||||
[
|
||||
new CodeSample(
|
||||
<<<'EOT'
|
||||
<?php
|
||||
$plain = `ls -lah`;
|
||||
$withVar = `ls -lah $var1 ${var2} {$var3} {$var4[0]} {$var5->call()}`;
|
||||
|
||||
EOT
|
||||
),
|
||||
],
|
||||
'Conversion is done only when it is non risky, so when special chars like single-quotes, double-quotes and backticks are not used inside the command.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before EscapeImplicitBackslashesFixer, ExplicitStringVariableFixer, NativeFunctionInvocationFixer, SingleQuoteFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 17;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$backtickStarted = false;
|
||||
$backtickTokens = [];
|
||||
for ($index = $tokens->count() - 1; $index > 0; --$index) {
|
||||
$token = $tokens[$index];
|
||||
|
||||
if (!$token->equals('`')) {
|
||||
if ($backtickStarted) {
|
||||
$backtickTokens[$index] = $token;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$backtickTokens[$index] = $token;
|
||||
|
||||
if ($backtickStarted) {
|
||||
$this->fixBackticks($tokens, $backtickTokens);
|
||||
$backtickTokens = [];
|
||||
}
|
||||
|
||||
$backtickStarted = !$backtickStarted;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override backtick code with corresponding double-quoted string.
|
||||
*
|
||||
* @param array<int, Token> $backtickTokens
|
||||
*/
|
||||
private function fixBackticks(Tokens $tokens, array $backtickTokens): void
|
||||
{
|
||||
// Track indices for final override
|
||||
ksort($backtickTokens);
|
||||
$openingBacktickIndex = key($backtickTokens);
|
||||
end($backtickTokens);
|
||||
$closingBacktickIndex = key($backtickTokens);
|
||||
|
||||
// Strip enclosing backticks
|
||||
array_shift($backtickTokens);
|
||||
array_pop($backtickTokens);
|
||||
|
||||
// Double-quoted strings are parsed differently if they contain
|
||||
// variables or not, so we need to build the new token array accordingly
|
||||
$count = \count($backtickTokens);
|
||||
|
||||
$newTokens = [
|
||||
new Token([T_STRING, 'shell_exec']),
|
||||
new Token('('),
|
||||
];
|
||||
|
||||
if (1 !== $count) {
|
||||
$newTokens[] = new Token('"');
|
||||
}
|
||||
|
||||
foreach ($backtickTokens as $token) {
|
||||
if (!$token->isGivenKind(T_ENCAPSED_AND_WHITESPACE)) {
|
||||
$newTokens[] = $token;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$content = $token->getContent();
|
||||
// Escaping special chars depends on the context: too tricky
|
||||
if (Preg::match('/[`"\']/u', $content)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$kind = T_ENCAPSED_AND_WHITESPACE;
|
||||
|
||||
if (1 === $count) {
|
||||
$content = '"'.$content.'"';
|
||||
$kind = T_CONSTANT_ENCAPSED_STRING;
|
||||
}
|
||||
|
||||
$newTokens[] = new Token([$kind, $content]);
|
||||
}
|
||||
|
||||
if (1 !== $count) {
|
||||
$newTokens[] = new Token('"');
|
||||
}
|
||||
|
||||
$newTokens[] = new Token(')');
|
||||
|
||||
$tokens->overrideRange($openingBacktickIndex, $closingBacktickIndex, $newTokens);
|
||||
}
|
||||
}
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
<?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\Alias;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Preg;
|
||||
use PhpCsFixer\PregException;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Matteo Beccati <matteo@beccati.com>
|
||||
*/
|
||||
final class EregToPregFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* @var list<array<int, string>> the list of the ext/ereg function names, their preg equivalent and the preg modifier(s), if any
|
||||
* all condensed in an array of arrays
|
||||
*/
|
||||
private static array $functions = [
|
||||
['ereg', 'preg_match', ''],
|
||||
['eregi', 'preg_match', 'i'],
|
||||
['ereg_replace', 'preg_replace', ''],
|
||||
['eregi_replace', 'preg_replace', 'i'],
|
||||
['split', 'preg_split', ''],
|
||||
['spliti', 'preg_split', 'i'],
|
||||
];
|
||||
|
||||
/**
|
||||
* @var list<string> the list of preg delimiters, in order of preference
|
||||
*/
|
||||
private static array $delimiters = ['/', '#', '!'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Replace deprecated `ereg` regular expression functions with `preg`.',
|
||||
[new CodeSample("<?php \$x = ereg('[A-Z]');\n")],
|
||||
null,
|
||||
'Risky if the `ereg` function is overridden.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after NoUselessConcatOperatorFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_STRING);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$end = $tokens->count() - 1;
|
||||
$functionsAnalyzer = new FunctionsAnalyzer();
|
||||
|
||||
foreach (self::$functions as $map) {
|
||||
// the sequence is the function name, followed by "(" and a quoted string
|
||||
$seq = [[T_STRING, $map[0]], '(', [T_CONSTANT_ENCAPSED_STRING]];
|
||||
$currIndex = 0;
|
||||
|
||||
while (true) {
|
||||
$match = $tokens->findSequence($seq, $currIndex, $end, false);
|
||||
|
||||
// did we find a match?
|
||||
if (null === $match) {
|
||||
break;
|
||||
}
|
||||
|
||||
// findSequence also returns the tokens, but we're only interested in the indices, i.e.:
|
||||
// 0 => function name,
|
||||
// 1 => bracket "("
|
||||
// 2 => quoted string passed as 1st parameter
|
||||
$match = array_keys($match);
|
||||
|
||||
// advance tokenizer cursor
|
||||
$currIndex = $match[2];
|
||||
|
||||
if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $match[0])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// ensure the first parameter is just a string (e.g. has nothing appended)
|
||||
$next = $tokens->getNextMeaningfulToken($match[2]);
|
||||
|
||||
if (null === $next || !$tokens[$next]->equalsAny([',', ')'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// convert to PCRE
|
||||
$regexTokenContent = $tokens[$match[2]]->getContent();
|
||||
|
||||
if ('b' === $regexTokenContent[0] || 'B' === $regexTokenContent[0]) {
|
||||
$quote = $regexTokenContent[1];
|
||||
$prefix = $regexTokenContent[0];
|
||||
$string = substr($regexTokenContent, 2, -1);
|
||||
} else {
|
||||
$quote = $regexTokenContent[0];
|
||||
$prefix = '';
|
||||
$string = substr($regexTokenContent, 1, -1);
|
||||
}
|
||||
|
||||
$delim = $this->getBestDelimiter($string);
|
||||
$preg = $delim.addcslashes($string, $delim).$delim.'D'.$map[2];
|
||||
|
||||
// check if the preg is valid
|
||||
if (!$this->checkPreg($preg)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// modify function and argument
|
||||
$tokens[$match[0]] = new Token([T_STRING, $map[1]]);
|
||||
$tokens[$match[2]] = new Token([T_CONSTANT_ENCAPSED_STRING, $prefix.$quote.$preg.$quote]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the validity of a PCRE.
|
||||
*
|
||||
* @param string $pattern the regular expression
|
||||
*/
|
||||
private function checkPreg(string $pattern): bool
|
||||
{
|
||||
try {
|
||||
Preg::match($pattern, '');
|
||||
|
||||
return true;
|
||||
} catch (PregException $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the delimiter that would require the least escaping in a regular expression.
|
||||
*
|
||||
* @param string $pattern the regular expression
|
||||
*
|
||||
* @return string the preg delimiter
|
||||
*/
|
||||
private function getBestDelimiter(string $pattern): string
|
||||
{
|
||||
// try to find something that's not used
|
||||
$delimiters = [];
|
||||
|
||||
foreach (self::$delimiters as $k => $d) {
|
||||
if (!str_contains($pattern, $d)) {
|
||||
return $d;
|
||||
}
|
||||
|
||||
$delimiters[$d] = [substr_count($pattern, $d), $k];
|
||||
}
|
||||
|
||||
// return the least used delimiter, using the position in the list as a tiebreaker
|
||||
uasort($delimiters, static function (array $a, array $b): int {
|
||||
if ($a[0] === $b[0]) {
|
||||
return $a[1] <=> $b[1];
|
||||
}
|
||||
|
||||
return $a[0] <=> $b[0];
|
||||
});
|
||||
|
||||
return key($delimiters);
|
||||
}
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
<?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\Alias;
|
||||
|
||||
use PhpCsFixer\AbstractFunctionReferenceFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Filippo Tessarotto <zoeslam@gmail.com>
|
||||
*/
|
||||
final class MbStrFunctionsFixer extends AbstractFunctionReferenceFixer
|
||||
{
|
||||
/**
|
||||
* list of the string-related function names and their mb_ equivalent.
|
||||
*
|
||||
* @var array<
|
||||
* string,
|
||||
* array{
|
||||
* alternativeName: string,
|
||||
* argumentCount: list<int>,
|
||||
* },
|
||||
* >
|
||||
*/
|
||||
private static array $functionsMap = [
|
||||
'str_split' => ['alternativeName' => 'mb_str_split', 'argumentCount' => [1, 2, 3]],
|
||||
'stripos' => ['alternativeName' => 'mb_stripos', 'argumentCount' => [2, 3]],
|
||||
'stristr' => ['alternativeName' => 'mb_stristr', 'argumentCount' => [2, 3]],
|
||||
'strlen' => ['alternativeName' => 'mb_strlen', 'argumentCount' => [1]],
|
||||
'strpos' => ['alternativeName' => 'mb_strpos', 'argumentCount' => [2, 3]],
|
||||
'strrchr' => ['alternativeName' => 'mb_strrchr', 'argumentCount' => [2]],
|
||||
'strripos' => ['alternativeName' => 'mb_strripos', 'argumentCount' => [2, 3]],
|
||||
'strrpos' => ['alternativeName' => 'mb_strrpos', 'argumentCount' => [2, 3]],
|
||||
'strstr' => ['alternativeName' => 'mb_strstr', 'argumentCount' => [2, 3]],
|
||||
'strtolower' => ['alternativeName' => 'mb_strtolower', 'argumentCount' => [1]],
|
||||
'strtoupper' => ['alternativeName' => 'mb_strtoupper', 'argumentCount' => [1]],
|
||||
'substr' => ['alternativeName' => 'mb_substr', 'argumentCount' => [2, 3]],
|
||||
'substr_count' => ['alternativeName' => 'mb_substr_count', 'argumentCount' => [2, 3, 4]],
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<
|
||||
* string,
|
||||
* array{
|
||||
* alternativeName: string,
|
||||
* argumentCount: list<int>,
|
||||
* },
|
||||
* >
|
||||
*/
|
||||
private array $functions;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->functions = array_filter(
|
||||
self::$functionsMap,
|
||||
static function (array $mapping): bool {
|
||||
return (new \ReflectionFunction($mapping['alternativeName']))->isInternal();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Replace non multibyte-safe functions with corresponding mb function.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
$a = strlen($a);
|
||||
$a = strpos($a, $b);
|
||||
$a = strrpos($a, $b);
|
||||
$a = substr($a, $b);
|
||||
$a = strtolower($a);
|
||||
$a = strtoupper($a);
|
||||
$a = stripos($a, $b);
|
||||
$a = strripos($a, $b);
|
||||
$a = strstr($a, $b);
|
||||
$a = stristr($a, $b);
|
||||
$a = strrchr($a, $b);
|
||||
$a = substr_count($a, $b);
|
||||
'
|
||||
),
|
||||
],
|
||||
null,
|
||||
'Risky when any of the functions are overridden, or when relying on the string byte size rather than its length in characters.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$argumentsAnalyzer = new ArgumentsAnalyzer();
|
||||
foreach ($this->functions as $functionIdentity => $functionReplacement) {
|
||||
$currIndex = 0;
|
||||
do {
|
||||
// try getting function reference and translate boundaries for humans
|
||||
$boundaries = $this->find($functionIdentity, $tokens, $currIndex, $tokens->count() - 1);
|
||||
if (null === $boundaries) {
|
||||
// next function search, as current one not found
|
||||
continue 2;
|
||||
}
|
||||
|
||||
[$functionName, $openParenthesis, $closeParenthesis] = $boundaries;
|
||||
$count = $argumentsAnalyzer->countArguments($tokens, $openParenthesis, $closeParenthesis);
|
||||
if (!\in_array($count, $functionReplacement['argumentCount'], true)) {
|
||||
continue 2;
|
||||
}
|
||||
|
||||
// analysing cursor shift, so nested calls could be processed
|
||||
$currIndex = $openParenthesis;
|
||||
|
||||
$tokens[$functionName] = new Token([T_STRING, $functionReplacement['alternativeName']]);
|
||||
} while (null !== $currIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
+233
@@ -0,0 +1,233 @@
|
||||
<?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\Alias;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Alexander M. Turek <me@derrabus.de>
|
||||
*/
|
||||
final class ModernizeStrposFixer extends AbstractFixer
|
||||
{
|
||||
private const REPLACEMENTS = [
|
||||
[
|
||||
'operator' => [T_IS_IDENTICAL, '==='],
|
||||
'operand' => [T_LNUMBER, '0'],
|
||||
'replacement' => [T_STRING, 'str_starts_with'],
|
||||
'negate' => false,
|
||||
],
|
||||
[
|
||||
'operator' => [T_IS_NOT_IDENTICAL, '!=='],
|
||||
'operand' => [T_LNUMBER, '0'],
|
||||
'replacement' => [T_STRING, 'str_starts_with'],
|
||||
'negate' => true,
|
||||
],
|
||||
[
|
||||
'operator' => [T_IS_NOT_IDENTICAL, '!=='],
|
||||
'operand' => [T_STRING, 'false'],
|
||||
'replacement' => [T_STRING, 'str_contains'],
|
||||
'negate' => false,
|
||||
],
|
||||
[
|
||||
'operator' => [T_IS_IDENTICAL, '==='],
|
||||
'operand' => [T_STRING, 'false'],
|
||||
'replacement' => [T_STRING, 'str_contains'],
|
||||
'negate' => true,
|
||||
],
|
||||
];
|
||||
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Replace `strpos()` calls with `str_starts_with()` or `str_contains()` if possible.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
if (strpos($haystack, $needle) === 0) {}
|
||||
if (strpos($haystack, $needle) !== 0) {}
|
||||
if (strpos($haystack, $needle) !== false) {}
|
||||
if (strpos($haystack, $needle) === false) {}
|
||||
'
|
||||
),
|
||||
],
|
||||
null,
|
||||
'Risky if `strpos`, `str_starts_with` or `str_contains` functions are overridden.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before BinaryOperatorSpacesFixer, NoExtraBlankLinesFixer, NoSpacesInsideParenthesisFixer, NoTrailingWhitespaceFixer, NotOperatorWithSpaceFixer, NotOperatorWithSuccessorSpaceFixer, PhpUnitDedicateAssertFixer, SingleSpaceAfterConstructFixer.
|
||||
* Must run after StrictComparisonFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 37;
|
||||
}
|
||||
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_STRING) && $tokens->isAnyTokenKindsFound([T_IS_IDENTICAL, T_IS_NOT_IDENTICAL]);
|
||||
}
|
||||
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$functionsAnalyzer = new FunctionsAnalyzer();
|
||||
$argumentsAnalyzer = new ArgumentsAnalyzer();
|
||||
|
||||
for ($index = \count($tokens) - 1; $index > 0; --$index) {
|
||||
// find candidate function call
|
||||
if (!$tokens[$index]->equals([T_STRING, 'strpos'], false) || !$functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// assert called with 2 arguments
|
||||
$openIndex = $tokens->getNextMeaningfulToken($index);
|
||||
$closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex);
|
||||
$arguments = $argumentsAnalyzer->getArguments($tokens, $openIndex, $closeIndex);
|
||||
|
||||
if (2 !== \count($arguments)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if part condition and fix if needed
|
||||
$compareTokens = $this->getCompareTokens($tokens, $index, -1); // look behind
|
||||
|
||||
if (null === $compareTokens) {
|
||||
$compareTokens = $this->getCompareTokens($tokens, $closeIndex, 1); // look ahead
|
||||
}
|
||||
|
||||
if (null !== $compareTokens) {
|
||||
$this->fixCall($tokens, $index, $compareTokens);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{operator_index: int, operand_index: int} $operatorIndices
|
||||
*/
|
||||
private function fixCall(Tokens $tokens, int $functionIndex, array $operatorIndices): void
|
||||
{
|
||||
foreach (self::REPLACEMENTS as $replacement) {
|
||||
if (!$tokens[$operatorIndices['operator_index']]->equals($replacement['operator'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$tokens[$operatorIndices['operand_index']]->equals($replacement['operand'], false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($operatorIndices['operator_index']);
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($operatorIndices['operand_index']);
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($functionIndex);
|
||||
|
||||
if ($replacement['negate']) {
|
||||
$negateInsertIndex = $functionIndex;
|
||||
|
||||
$prevFunctionIndex = $tokens->getPrevMeaningfulToken($functionIndex);
|
||||
if ($tokens[$prevFunctionIndex]->isGivenKind(T_NS_SEPARATOR)) {
|
||||
$negateInsertIndex = $prevFunctionIndex;
|
||||
}
|
||||
|
||||
$tokens->insertAt($negateInsertIndex, new Token('!'));
|
||||
++$functionIndex;
|
||||
}
|
||||
|
||||
$tokens->insertAt($functionIndex, new Token($replacement['replacement']));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param -1|1 $direction
|
||||
*
|
||||
* @return null|array{operator_index: int, operand_index: int}
|
||||
*/
|
||||
private function getCompareTokens(Tokens $tokens, int $offsetIndex, int $direction): ?array
|
||||
{
|
||||
$operatorIndex = $tokens->getMeaningfulTokenSibling($offsetIndex, $direction);
|
||||
|
||||
if (null !== $operatorIndex && $tokens[$operatorIndex]->isGivenKind(T_NS_SEPARATOR)) {
|
||||
$operatorIndex = $tokens->getMeaningfulTokenSibling($operatorIndex, $direction);
|
||||
}
|
||||
|
||||
if (null === $operatorIndex || !$tokens[$operatorIndex]->isGivenKind([T_IS_IDENTICAL, T_IS_NOT_IDENTICAL])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$operandIndex = $tokens->getMeaningfulTokenSibling($operatorIndex, $direction);
|
||||
|
||||
if (null === $operandIndex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$operand = $tokens[$operandIndex];
|
||||
|
||||
if (!$operand->equals([T_LNUMBER, '0']) && !$operand->equals([T_STRING, 'false'], false)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$precedenceTokenIndex = $tokens->getMeaningfulTokenSibling($operandIndex, $direction);
|
||||
|
||||
if (null !== $precedenceTokenIndex && $this->isOfHigherPrecedence($tokens[$precedenceTokenIndex])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ['operator_index' => $operatorIndex, 'operand_index' => $operandIndex];
|
||||
}
|
||||
|
||||
private function isOfHigherPrecedence(Token $token): bool
|
||||
{
|
||||
static $operatorsKinds = [
|
||||
T_DEC, // --
|
||||
T_INC, // ++
|
||||
T_INSTANCEOF, // instanceof
|
||||
T_IS_GREATER_OR_EQUAL, // >=
|
||||
T_IS_SMALLER_OR_EQUAL, // <=
|
||||
T_POW, // **
|
||||
T_SL, // <<
|
||||
T_SR, // >>
|
||||
];
|
||||
|
||||
static $operatorsPerContent = [
|
||||
'!',
|
||||
'%',
|
||||
'*',
|
||||
'+',
|
||||
'-',
|
||||
'.',
|
||||
'/',
|
||||
'<',
|
||||
'>',
|
||||
'~',
|
||||
];
|
||||
|
||||
return $token->isGivenKind($operatorsKinds) || $token->equalsAny($operatorsPerContent);
|
||||
}
|
||||
}
|
||||
+335
@@ -0,0 +1,335 @@
|
||||
<?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\Alias;
|
||||
|
||||
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\Analyzer\ArgumentsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Vladimir Reznichenko <kalessil@gmail.com>
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class NoAliasFunctionsFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
private const SETS = [
|
||||
'@internal' => [
|
||||
'diskfreespace' => 'disk_free_space',
|
||||
|
||||
'dns_check_record' => 'checkdnsrr',
|
||||
'dns_get_mx' => 'getmxrr',
|
||||
|
||||
'session_commit' => 'session_write_close',
|
||||
|
||||
'stream_register_wrapper' => 'stream_wrapper_register',
|
||||
'set_file_buffer' => 'stream_set_write_buffer',
|
||||
'socket_set_blocking' => 'stream_set_blocking',
|
||||
'socket_get_status' => 'stream_get_meta_data',
|
||||
'socket_set_timeout' => 'stream_set_timeout',
|
||||
'socket_getopt' => 'socket_get_option',
|
||||
'socket_setopt' => 'socket_set_option',
|
||||
|
||||
'chop' => 'rtrim',
|
||||
'close' => 'closedir',
|
||||
'doubleval' => 'floatval',
|
||||
'fputs' => 'fwrite',
|
||||
'get_required_files' => 'get_included_files',
|
||||
'ini_alter' => 'ini_set',
|
||||
'is_double' => 'is_float',
|
||||
'is_integer' => 'is_int',
|
||||
'is_long' => 'is_int',
|
||||
'is_real' => 'is_float',
|
||||
'is_writeable' => 'is_writable',
|
||||
'join' => 'implode',
|
||||
'key_exists' => 'array_key_exists',
|
||||
'magic_quotes_runtime' => 'set_magic_quotes_runtime',
|
||||
'pos' => 'current',
|
||||
'show_source' => 'highlight_file',
|
||||
'sizeof' => 'count',
|
||||
'strchr' => 'strstr',
|
||||
'user_error' => 'trigger_error',
|
||||
],
|
||||
|
||||
'@IMAP' => [
|
||||
'imap_create' => 'imap_createmailbox',
|
||||
'imap_fetchtext' => 'imap_body',
|
||||
'imap_header' => 'imap_headerinfo',
|
||||
'imap_listmailbox' => 'imap_list',
|
||||
'imap_listsubscribed' => 'imap_lsub',
|
||||
'imap_rename' => 'imap_renamemailbox',
|
||||
'imap_scan' => 'imap_listscan',
|
||||
'imap_scanmailbox' => 'imap_listscan',
|
||||
],
|
||||
|
||||
'@ldap' => [
|
||||
'ldap_close' => 'ldap_unbind',
|
||||
'ldap_modify' => 'ldap_mod_replace',
|
||||
],
|
||||
|
||||
'@mysqli' => [
|
||||
'mysqli_execute' => 'mysqli_stmt_execute',
|
||||
'mysqli_set_opt' => 'mysqli_options',
|
||||
'mysqli_escape_string' => 'mysqli_real_escape_string',
|
||||
],
|
||||
|
||||
'@pg' => [
|
||||
'pg_exec' => 'pg_query',
|
||||
],
|
||||
|
||||
'@oci' => [
|
||||
'oci_free_cursor' => 'oci_free_statement',
|
||||
],
|
||||
|
||||
'@odbc' => [
|
||||
'odbc_do' => 'odbc_exec',
|
||||
'odbc_field_precision' => 'odbc_field_len',
|
||||
],
|
||||
|
||||
'@mbreg' => [
|
||||
'mbereg' => 'mb_ereg',
|
||||
'mbereg_match' => 'mb_ereg_match',
|
||||
'mbereg_replace' => 'mb_ereg_replace',
|
||||
'mbereg_search' => 'mb_ereg_search',
|
||||
'mbereg_search_getpos' => 'mb_ereg_search_getpos',
|
||||
'mbereg_search_getregs' => 'mb_ereg_search_getregs',
|
||||
'mbereg_search_init' => 'mb_ereg_search_init',
|
||||
'mbereg_search_pos' => 'mb_ereg_search_pos',
|
||||
'mbereg_search_regs' => 'mb_ereg_search_regs',
|
||||
'mbereg_search_setpos' => 'mb_ereg_search_setpos',
|
||||
'mberegi' => 'mb_eregi',
|
||||
'mberegi_replace' => 'mb_eregi_replace',
|
||||
'mbregex_encoding' => 'mb_regex_encoding',
|
||||
'mbsplit' => 'mb_split',
|
||||
],
|
||||
|
||||
'@openssl' => [
|
||||
'openssl_get_publickey' => 'openssl_pkey_get_public',
|
||||
'openssl_get_privatekey' => 'openssl_pkey_get_private',
|
||||
],
|
||||
|
||||
'@sodium' => [
|
||||
'sodium_crypto_scalarmult_base' => 'sodium_crypto_box_publickey_from_secretkey',
|
||||
],
|
||||
|
||||
'@exif' => [
|
||||
'read_exif_data' => 'exif_read_data',
|
||||
],
|
||||
|
||||
'@ftp' => [
|
||||
'ftp_quit' => 'ftp_close',
|
||||
],
|
||||
|
||||
'@posix' => [
|
||||
'posix_errno' => 'posix_get_last_error',
|
||||
],
|
||||
|
||||
'@pcntl' => [
|
||||
'pcntl_errno' => 'pcntl_get_last_error',
|
||||
],
|
||||
|
||||
'@time' => [
|
||||
'mktime' => ['time', 0],
|
||||
'gmmktime' => ['time', 0],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<string, array<int|string>|string> stores alias (key) - master (value) functions mapping
|
||||
*/
|
||||
private array $aliases = [];
|
||||
|
||||
public function configure(array $configuration): void
|
||||
{
|
||||
parent::configure($configuration);
|
||||
|
||||
$this->aliases = [];
|
||||
|
||||
foreach ($this->configuration['sets'] as $set) {
|
||||
if ('@all' === $set) {
|
||||
$this->aliases = array_merge(...array_values(self::SETS));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
$this->aliases = array_merge($this->aliases, self::SETS[$set]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Master functions shall be used instead of aliases.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
$a = chop($b);
|
||||
close($b);
|
||||
$a = doubleval($b);
|
||||
$a = fputs($b, $c);
|
||||
$a = get_required_files();
|
||||
ini_alter($b, $c);
|
||||
$a = is_double($b);
|
||||
$a = is_integer($b);
|
||||
$a = is_long($b);
|
||||
$a = is_real($b);
|
||||
$a = is_writeable($b);
|
||||
$a = join($glue, $pieces);
|
||||
$a = key_exists($key, $array);
|
||||
magic_quotes_runtime($new_setting);
|
||||
$a = pos($array);
|
||||
$a = show_source($filename, true);
|
||||
$a = sizeof($b);
|
||||
$a = strchr($haystack, $needle);
|
||||
$a = imap_header($imap_stream, 1);
|
||||
user_error($message);
|
||||
mbereg_search_getregs();
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
$a = is_double($b);
|
||||
mbereg_search_getregs();
|
||||
',
|
||||
['sets' => ['@mbreg']]
|
||||
),
|
||||
],
|
||||
null,
|
||||
'Risky when any of the alias functions are overridden.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before ImplodeCallFixer, PhpUnitDedicateAssertFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 40;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_STRING);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$functionsAnalyzer = new FunctionsAnalyzer();
|
||||
$argumentsAnalyzer = new ArgumentsAnalyzer();
|
||||
|
||||
/** @var Token $token */
|
||||
foreach ($tokens->findGivenKind(T_STRING) as $index => $token) {
|
||||
// check mapping hit
|
||||
$tokenContent = strtolower($token->getContent());
|
||||
|
||||
if (!isset($this->aliases[$tokenContent])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// skip expressions without parameters list
|
||||
$openParenthesis = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
if (!$tokens[$openParenthesis]->equals('(')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (\is_array($this->aliases[$tokenContent])) {
|
||||
[$alias, $numberOfArguments] = $this->aliases[$tokenContent];
|
||||
|
||||
$count = $argumentsAnalyzer->countArguments($tokens, $openParenthesis, $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesis));
|
||||
|
||||
if ($numberOfArguments !== $count) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
$alias = $this->aliases[$tokenContent];
|
||||
}
|
||||
|
||||
$tokens[$index] = new Token([T_STRING, $alias]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
$sets = [
|
||||
'@all' => 'all listed sets',
|
||||
'@internal' => 'native functions',
|
||||
'@exif' => 'EXIF functions',
|
||||
'@ftp' => 'FTP functions',
|
||||
'@IMAP' => 'IMAP functions',
|
||||
'@ldap' => 'LDAP functions',
|
||||
'@mbreg' => 'from `ext-mbstring`',
|
||||
'@mysqli' => 'mysqli functions',
|
||||
'@oci' => 'oci functions',
|
||||
'@odbc' => 'odbc functions',
|
||||
'@openssl' => 'openssl functions',
|
||||
'@pcntl' => 'PCNTL functions',
|
||||
'@pg' => 'pg functions',
|
||||
'@posix' => 'POSIX functions',
|
||||
'@snmp' => 'SNMP functions', // @TODO Remove on next major 4.0 as this set is now empty
|
||||
'@sodium' => 'libsodium functions',
|
||||
'@time' => 'time functions',
|
||||
];
|
||||
|
||||
$list = "List of sets to fix. Defined sets are:\n\n";
|
||||
|
||||
foreach ($sets as $set => $description) {
|
||||
$list .= sprintf("* `%s` (%s)\n", $set, $description);
|
||||
}
|
||||
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('sets', $list))
|
||||
->setAllowedTypes(['array'])
|
||||
->setAllowedValues([new AllowedValueSubset(array_keys($sets))])
|
||||
->setDefault(['@internal', '@IMAP', '@pg'])
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
Vendored
+68
@@ -0,0 +1,68 @@
|
||||
<?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\Alias;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class NoAliasLanguageConstructCallFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Master language constructs shall be used instead of aliases.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
die;
|
||||
'
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_EXIT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->isGivenKind(T_EXIT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('exit' === strtolower($token->getContent())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens[$index] = new Token([T_EXIT, 'exit']);
|
||||
}
|
||||
}
|
||||
}
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
<?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\Alias;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author Sullivan Senechal <soullivaneuh@gmail.com>
|
||||
*/
|
||||
final class NoMixedEchoPrintFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $callBack;
|
||||
|
||||
/**
|
||||
* @var int T_ECHO or T_PRINT
|
||||
*/
|
||||
private $candidateTokenType;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configure(array $configuration): void
|
||||
{
|
||||
parent::configure($configuration);
|
||||
|
||||
if ('echo' === $this->configuration['use']) {
|
||||
$this->candidateTokenType = T_PRINT;
|
||||
$this->callBack = 'fixPrintToEcho';
|
||||
} else {
|
||||
$this->candidateTokenType = T_ECHO;
|
||||
$this->callBack = 'fixEchoToPrint';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Either language construct `print` or `echo` should be used.',
|
||||
[
|
||||
new CodeSample("<?php print 'example';\n"),
|
||||
new CodeSample("<?php echo('example');\n", ['use' => 'print']),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after EchoTagSyntaxFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return -10;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound($this->candidateTokenType);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$callBack = $this->callBack;
|
||||
foreach ($tokens as $index => $token) {
|
||||
if ($token->isGivenKind($this->candidateTokenType)) {
|
||||
$this->{$callBack}($tokens, $index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('use', 'The desired language construct.'))
|
||||
->setAllowedValues(['print', 'echo'])
|
||||
->setDefault('echo')
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function fixEchoToPrint(Tokens $tokens, int $index): void
|
||||
{
|
||||
$nextTokenIndex = $tokens->getNextMeaningfulToken($index);
|
||||
$endTokenIndex = $tokens->getNextTokenOfKind($index, [';', [T_CLOSE_TAG]]);
|
||||
$canBeConverted = true;
|
||||
|
||||
for ($i = $nextTokenIndex; $i < $endTokenIndex; ++$i) {
|
||||
if ($tokens[$i]->equalsAny(['(', [CT::T_ARRAY_SQUARE_BRACE_OPEN]])) {
|
||||
$blockType = Tokens::detectBlockType($tokens[$i]);
|
||||
$i = $tokens->findBlockEnd($blockType['type'], $i);
|
||||
}
|
||||
|
||||
if ($tokens[$i]->equals(',')) {
|
||||
$canBeConverted = false;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (false === $canBeConverted) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tokens[$index] = new Token([T_PRINT, 'print']);
|
||||
}
|
||||
|
||||
private function fixPrintToEcho(Tokens $tokens, int $index): void
|
||||
{
|
||||
$prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)];
|
||||
|
||||
if (!$prevToken->equalsAny([';', '{', '}', ')', [T_OPEN_TAG], [T_ELSE]])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tokens[$index] = new Token([T_ECHO, 'echo']);
|
||||
}
|
||||
}
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
<?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\Alias;
|
||||
|
||||
use PhpCsFixer\AbstractFunctionReferenceFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class PowToExponentiationFixer extends AbstractFunctionReferenceFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
// minimal candidate to fix is seven tokens: pow(x,y);
|
||||
return $tokens->count() > 7 && $tokens->isTokenKindFound(T_STRING);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Converts `pow` to the `**` operator.',
|
||||
[
|
||||
new CodeSample(
|
||||
"<?php\n pow(\$a, 1);\n"
|
||||
),
|
||||
],
|
||||
null,
|
||||
'Risky when the function `pow` is overridden.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before BinaryOperatorSpacesFixer, MethodArgumentSpaceFixer, NativeFunctionCasingFixer, NoSpacesAfterFunctionNameFixer, NoSpacesInsideParenthesisFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 32;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$candidates = $this->findPowCalls($tokens);
|
||||
$argumentsAnalyzer = new ArgumentsAnalyzer();
|
||||
$numberOfTokensAdded = 0;
|
||||
$previousCloseParenthesisIndex = \count($tokens);
|
||||
|
||||
foreach (array_reverse($candidates) as $candidate) {
|
||||
// if in the previous iteration(s) tokens were added to the collection and this is done within the tokens
|
||||
// indices of the current candidate than the index of the close ')' of the candidate has moved and so
|
||||
// the index needs to be updated
|
||||
if ($previousCloseParenthesisIndex < $candidate[2]) {
|
||||
$previousCloseParenthesisIndex = $candidate[2];
|
||||
$candidate[2] += $numberOfTokensAdded;
|
||||
} else {
|
||||
$previousCloseParenthesisIndex = $candidate[2];
|
||||
$numberOfTokensAdded = 0;
|
||||
}
|
||||
|
||||
$arguments = $argumentsAnalyzer->getArguments($tokens, $candidate[1], $candidate[2]);
|
||||
|
||||
if (2 !== \count($arguments)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for ($i = $candidate[1]; $i < $candidate[2]; ++$i) {
|
||||
if ($tokens[$i]->isGivenKind(T_ELLIPSIS)) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
$numberOfTokensAdded += $this->fixPowToExponentiation(
|
||||
$tokens,
|
||||
$candidate[0], // functionNameIndex,
|
||||
$candidate[1], // openParenthesisIndex,
|
||||
$candidate[2], // closeParenthesisIndex,
|
||||
$arguments
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int[]>
|
||||
*/
|
||||
private function findPowCalls(Tokens $tokens): array
|
||||
{
|
||||
$candidates = [];
|
||||
|
||||
// Minimal candidate to fix is seven tokens: pow(x,y);
|
||||
$end = \count($tokens) - 6;
|
||||
|
||||
// First possible location is after the open token: 1
|
||||
for ($i = 1; $i < $end; ++$i) {
|
||||
$candidate = $this->find('pow', $tokens, $i, $end);
|
||||
|
||||
if (null === $candidate) {
|
||||
break;
|
||||
}
|
||||
|
||||
$i = $candidate[1]; // proceed to openParenthesisIndex
|
||||
$candidates[] = $candidate;
|
||||
}
|
||||
|
||||
return $candidates;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, int> $arguments
|
||||
*
|
||||
* @return int number of tokens added to the collection
|
||||
*/
|
||||
private function fixPowToExponentiation(Tokens $tokens, int $functionNameIndex, int $openParenthesisIndex, int $closeParenthesisIndex, array $arguments): int
|
||||
{
|
||||
// find the argument separator ',' directly after the last token of the first argument;
|
||||
// replace it with T_POW '**'
|
||||
$tokens[$tokens->getNextTokenOfKind(reset($arguments), [','])] = new Token([T_POW, '**']);
|
||||
|
||||
// clean up the function call tokens prt. I
|
||||
$tokens->clearAt($closeParenthesisIndex);
|
||||
$previousIndex = $tokens->getPrevMeaningfulToken($closeParenthesisIndex);
|
||||
|
||||
if ($tokens[$previousIndex]->equals(',')) {
|
||||
$tokens->clearAt($previousIndex); // trailing ',' in function call (PHP 7.3)
|
||||
}
|
||||
|
||||
$added = 0;
|
||||
|
||||
// check if the arguments need to be wrapped in parentheses
|
||||
foreach (array_reverse($arguments, true) as $argumentStartIndex => $argumentEndIndex) {
|
||||
if ($this->isParenthesisNeeded($tokens, $argumentStartIndex, $argumentEndIndex)) {
|
||||
$tokens->insertAt($argumentEndIndex + 1, new Token(')'));
|
||||
$tokens->insertAt($argumentStartIndex, new Token('('));
|
||||
$added += 2;
|
||||
}
|
||||
}
|
||||
|
||||
// clean up the function call tokens prt. II
|
||||
$tokens->clearAt($openParenthesisIndex);
|
||||
$tokens->clearAt($functionNameIndex);
|
||||
|
||||
$prevMeaningfulTokenIndex = $tokens->getPrevMeaningfulToken($functionNameIndex);
|
||||
|
||||
if ($tokens[$prevMeaningfulTokenIndex]->isGivenKind(T_NS_SEPARATOR)) {
|
||||
$tokens->clearAt($prevMeaningfulTokenIndex);
|
||||
}
|
||||
|
||||
return $added;
|
||||
}
|
||||
|
||||
private function isParenthesisNeeded(Tokens $tokens, int $argumentStartIndex, int $argumentEndIndex): bool
|
||||
{
|
||||
static $allowedKinds = null;
|
||||
|
||||
if (null === $allowedKinds) {
|
||||
$allowedKinds = $this->getAllowedKinds();
|
||||
}
|
||||
|
||||
for ($i = $argumentStartIndex; $i <= $argumentEndIndex; ++$i) {
|
||||
if ($tokens[$i]->isGivenKind($allowedKinds) || $tokens->isEmptyAt($i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$blockType = Tokens::detectBlockType($tokens[$i]);
|
||||
|
||||
if (null !== $blockType) {
|
||||
$i = $tokens->findBlockEnd($blockType['type'], $i);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$i]->equals('$')) {
|
||||
$i = $tokens->getNextMeaningfulToken($i);
|
||||
if ($tokens[$i]->isGivenKind(CT::T_DYNAMIC_VAR_BRACE_OPEN)) {
|
||||
$i = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_DYNAMIC_VAR_BRACE, $i);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($tokens[$i]->equals('+') && $tokens->getPrevMeaningfulToken($i) < $argumentStartIndex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
private function getAllowedKinds(): array
|
||||
{
|
||||
return array_merge(
|
||||
[
|
||||
T_DNUMBER, T_LNUMBER, T_VARIABLE, T_STRING, T_CONSTANT_ENCAPSED_STRING, T_DOUBLE_CAST,
|
||||
T_INT_CAST, T_INC, T_DEC, T_NS_SEPARATOR, T_WHITESPACE, T_DOUBLE_COLON, T_LINE, T_COMMENT, T_DOC_COMMENT,
|
||||
CT::T_NAMESPACE_OPERATOR,
|
||||
],
|
||||
Token::getObjectOperatorKinds()
|
||||
);
|
||||
}
|
||||
}
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
<?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\Alias;
|
||||
|
||||
use PhpCsFixer\AbstractFunctionReferenceFixer;
|
||||
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\Analyzer\ArgumentsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
|
||||
|
||||
/**
|
||||
* @author Vladimir Reznichenko <kalessil@gmail.com>
|
||||
*/
|
||||
final class RandomApiMigrationFixer extends AbstractFunctionReferenceFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* @var array<string, array<int, int>>
|
||||
*/
|
||||
private static array $argumentCounts = [
|
||||
'getrandmax' => [0],
|
||||
'mt_rand' => [1, 2],
|
||||
'rand' => [0, 2],
|
||||
'srand' => [0, 1],
|
||||
'random_int' => [0, 2],
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configure(array $configuration): void
|
||||
{
|
||||
parent::configure($configuration);
|
||||
|
||||
foreach ($this->configuration['replacements'] as $functionName => $replacement) {
|
||||
$this->configuration['replacements'][$functionName] = [
|
||||
'alternativeName' => $replacement,
|
||||
'argumentCount' => self::$argumentCounts[$functionName],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Replaces `rand`, `srand`, `getrandmax` functions calls with their `mt_*` analogs or `random_int`.',
|
||||
[
|
||||
new CodeSample("<?php\n\$a = getrandmax();\n\$a = rand(\$b, \$c);\n\$a = srand();\n"),
|
||||
new CodeSample(
|
||||
"<?php\n\$a = getrandmax();\n\$a = rand(\$b, \$c);\n\$a = srand();\n",
|
||||
['replacements' => ['getrandmax' => 'mt_getrandmax']]
|
||||
),
|
||||
new CodeSample(
|
||||
"<?php \$a = rand(\$b, \$c);\n",
|
||||
['replacements' => ['rand' => 'random_int']]
|
||||
),
|
||||
],
|
||||
null,
|
||||
'Risky when the configured functions are overridden. Or when relying on the seed based generating of the numbers.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$argumentsAnalyzer = new ArgumentsAnalyzer();
|
||||
|
||||
foreach ($this->configuration['replacements'] as $functionIdentity => $functionReplacement) {
|
||||
if ($functionIdentity === $functionReplacement['alternativeName']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$currIndex = 0;
|
||||
|
||||
do {
|
||||
// try getting function reference and translate boundaries for humans
|
||||
$boundaries = $this->find($functionIdentity, $tokens, $currIndex, $tokens->count() - 1);
|
||||
|
||||
if (null === $boundaries) {
|
||||
// next function search, as current one not found
|
||||
continue 2;
|
||||
}
|
||||
|
||||
[$functionName, $openParenthesis, $closeParenthesis] = $boundaries;
|
||||
$count = $argumentsAnalyzer->countArguments($tokens, $openParenthesis, $closeParenthesis);
|
||||
|
||||
if (!\in_array($count, $functionReplacement['argumentCount'], true)) {
|
||||
continue 2;
|
||||
}
|
||||
|
||||
// analysing cursor shift, so nested calls could be processed
|
||||
$currIndex = $openParenthesis;
|
||||
$tokens[$functionName] = new Token([T_STRING, $functionReplacement['alternativeName']]);
|
||||
|
||||
if (0 === $count && 'random_int' === $functionReplacement['alternativeName']) {
|
||||
$tokens->insertAt($currIndex + 1, [
|
||||
new Token([T_LNUMBER, '0']),
|
||||
new Token(','),
|
||||
new Token([T_WHITESPACE, ' ']),
|
||||
new Token([T_STRING, 'getrandmax']),
|
||||
new Token('('),
|
||||
new Token(')'),
|
||||
]);
|
||||
|
||||
$currIndex += 6;
|
||||
}
|
||||
} while (null !== $currIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('replacements', 'Mapping between replaced functions with the new ones.'))
|
||||
->setAllowedTypes(['array'])
|
||||
->setAllowedValues([static function (array $value): bool {
|
||||
foreach ($value as $functionName => $replacement) {
|
||||
if (!\array_key_exists($functionName, self::$argumentCounts)) {
|
||||
throw new InvalidOptionsException(sprintf(
|
||||
'Function "%s" is not handled by the fixer.',
|
||||
$functionName
|
||||
));
|
||||
}
|
||||
|
||||
if (!\is_string($replacement)) {
|
||||
throw new InvalidOptionsException(sprintf(
|
||||
'Replacement for function "%s" must be a string, "%s" given.',
|
||||
$functionName,
|
||||
get_debug_type($replacement)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}])
|
||||
->setDefault([
|
||||
'getrandmax' => 'mt_getrandmax',
|
||||
'rand' => 'mt_rand', // @TODO change to `random_int` as default on 4.0
|
||||
'srand' => 'mt_srand',
|
||||
])
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
+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\Alias;
|
||||
|
||||
use PhpCsFixer\AbstractFunctionReferenceFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class SetTypeToCastFixer extends AbstractFunctionReferenceFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Cast shall be used, not `settype`.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
settype($foo, "integer");
|
||||
settype($bar, "string");
|
||||
settype($bar, "null");
|
||||
'
|
||||
),
|
||||
],
|
||||
null,
|
||||
'Risky when the `settype` function is overridden or when used as the 2nd or 3rd expression in a `for` loop .'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after NoBinaryStringFixer, NoUselessConcatOperatorFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAllTokenKindsFound([T_CONSTANT_ENCAPSED_STRING, T_STRING, T_VARIABLE]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$map = [
|
||||
'array' => [T_ARRAY_CAST, '(array)'],
|
||||
'bool' => [T_BOOL_CAST, '(bool)'],
|
||||
'boolean' => [T_BOOL_CAST, '(bool)'],
|
||||
'double' => [T_DOUBLE_CAST, '(float)'],
|
||||
'float' => [T_DOUBLE_CAST, '(float)'],
|
||||
'int' => [T_INT_CAST, '(int)'],
|
||||
'integer' => [T_INT_CAST, '(int)'],
|
||||
'object' => [T_OBJECT_CAST, '(object)'],
|
||||
'string' => [T_STRING_CAST, '(string)'],
|
||||
// note: `'null' is dealt with later on
|
||||
];
|
||||
|
||||
$argumentsAnalyzer = new ArgumentsAnalyzer();
|
||||
|
||||
foreach (array_reverse($this->findSettypeCalls($tokens)) as $candidate) {
|
||||
$functionNameIndex = $candidate[0];
|
||||
|
||||
$arguments = $argumentsAnalyzer->getArguments($tokens, $candidate[1], $candidate[2]);
|
||||
if (2 !== \count($arguments)) {
|
||||
continue; // function must be overridden or used incorrectly
|
||||
}
|
||||
|
||||
$prev = $tokens->getPrevMeaningfulToken($functionNameIndex);
|
||||
|
||||
if (!$tokens[$prev]->equalsAny([';', '{', '}', [T_OPEN_TAG]])) {
|
||||
continue; // return value of the function is used
|
||||
}
|
||||
|
||||
reset($arguments);
|
||||
|
||||
// --- Test first argument --------------------
|
||||
|
||||
$firstArgumentStart = key($arguments);
|
||||
if ($tokens[$firstArgumentStart]->isComment() || $tokens[$firstArgumentStart]->isWhitespace()) {
|
||||
$firstArgumentStart = $tokens->getNextMeaningfulToken($firstArgumentStart);
|
||||
}
|
||||
|
||||
if (!$tokens[$firstArgumentStart]->isGivenKind(T_VARIABLE)) {
|
||||
continue; // settype only works with variables pass by reference, function must be overridden
|
||||
}
|
||||
|
||||
$commaIndex = $tokens->getNextMeaningfulToken($firstArgumentStart);
|
||||
|
||||
if (null === $commaIndex || !$tokens[$commaIndex]->equals(',')) {
|
||||
continue; // first argument is complex statement; function must be overridden
|
||||
}
|
||||
|
||||
// --- Test second argument -------------------
|
||||
|
||||
next($arguments);
|
||||
$secondArgumentStart = key($arguments);
|
||||
$secondArgumentEnd = $arguments[$secondArgumentStart];
|
||||
|
||||
if ($tokens[$secondArgumentStart]->isComment() || $tokens[$secondArgumentStart]->isWhitespace()) {
|
||||
$secondArgumentStart = $tokens->getNextMeaningfulToken($secondArgumentStart);
|
||||
}
|
||||
|
||||
if (
|
||||
!$tokens[$secondArgumentStart]->isGivenKind(T_CONSTANT_ENCAPSED_STRING)
|
||||
|| $tokens->getNextMeaningfulToken($secondArgumentStart) < $secondArgumentEnd
|
||||
) {
|
||||
continue; // second argument is of the wrong type or is a (complex) statement of some sort (function is overridden)
|
||||
}
|
||||
|
||||
// --- Test type ------------------------------
|
||||
|
||||
$type = strtolower(trim($tokens[$secondArgumentStart]->getContent(), '"\'"'));
|
||||
|
||||
if ('null' !== $type && !isset($map[$type])) {
|
||||
continue; // we don't know how to map
|
||||
}
|
||||
|
||||
// --- Fixing ---------------------------------
|
||||
|
||||
$argumentToken = $tokens[$firstArgumentStart];
|
||||
|
||||
$this->removeSettypeCall(
|
||||
$tokens,
|
||||
$functionNameIndex,
|
||||
$candidate[1],
|
||||
$firstArgumentStart,
|
||||
$commaIndex,
|
||||
$secondArgumentStart,
|
||||
$candidate[2]
|
||||
);
|
||||
|
||||
if ('null' === $type) {
|
||||
$this->fixSettypeNullCall($tokens, $functionNameIndex, $argumentToken);
|
||||
} else {
|
||||
$this->fixSettypeCall($tokens, $functionNameIndex, $argumentToken, new Token($map[$type]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<list<int>>
|
||||
*/
|
||||
private function findSettypeCalls(Tokens $tokens): array
|
||||
{
|
||||
$candidates = [];
|
||||
|
||||
$end = \count($tokens);
|
||||
for ($i = 1; $i < $end; ++$i) {
|
||||
$candidate = $this->find('settype', $tokens, $i, $end);
|
||||
if (null === $candidate) {
|
||||
break;
|
||||
}
|
||||
|
||||
$i = $candidate[1]; // proceed to openParenthesisIndex
|
||||
$candidates[] = $candidate;
|
||||
}
|
||||
|
||||
return $candidates;
|
||||
}
|
||||
|
||||
private function removeSettypeCall(
|
||||
Tokens $tokens,
|
||||
int $functionNameIndex,
|
||||
int $openParenthesisIndex,
|
||||
int $firstArgumentStart,
|
||||
int $commaIndex,
|
||||
int $secondArgumentStart,
|
||||
int $closeParenthesisIndex
|
||||
): void {
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($closeParenthesisIndex);
|
||||
$prevIndex = $tokens->getPrevMeaningfulToken($closeParenthesisIndex);
|
||||
if ($tokens[$prevIndex]->equals(',')) {
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($prevIndex);
|
||||
}
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($secondArgumentStart);
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($commaIndex);
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($firstArgumentStart);
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($openParenthesisIndex);
|
||||
$tokens->clearAt($functionNameIndex); // we'll be inserting here so no need to merge the space tokens
|
||||
$tokens->clearEmptyTokens();
|
||||
}
|
||||
|
||||
private function fixSettypeCall(
|
||||
Tokens $tokens,
|
||||
int $functionNameIndex,
|
||||
Token $argumentToken,
|
||||
Token $castToken
|
||||
): void {
|
||||
$tokens->insertAt(
|
||||
$functionNameIndex,
|
||||
[
|
||||
clone $argumentToken,
|
||||
new Token([T_WHITESPACE, ' ']),
|
||||
new Token('='),
|
||||
new Token([T_WHITESPACE, ' ']),
|
||||
$castToken,
|
||||
new Token([T_WHITESPACE, ' ']),
|
||||
clone $argumentToken,
|
||||
]
|
||||
);
|
||||
|
||||
$tokens->removeTrailingWhitespace($functionNameIndex + 6); // 6 = number of inserted tokens -1 for offset correction
|
||||
}
|
||||
|
||||
private function fixSettypeNullCall(
|
||||
Tokens $tokens,
|
||||
int $functionNameIndex,
|
||||
Token $argumentToken
|
||||
): void {
|
||||
$tokens->insertAt(
|
||||
$functionNameIndex,
|
||||
[
|
||||
clone $argumentToken,
|
||||
new Token([T_WHITESPACE, ' ']),
|
||||
new Token('='),
|
||||
new Token([T_WHITESPACE, ' ']),
|
||||
new Token([T_STRING, 'null']),
|
||||
]
|
||||
);
|
||||
|
||||
$tokens->removeTrailingWhitespace($functionNameIndex + 4); // 4 = number of inserted tokens -1 for offset correction
|
||||
}
|
||||
}
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
<?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\ArrayNotation;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author Gregor Harlan <gharlan@web.de>
|
||||
* @author Sebastiaan Stok <s.stok@rollerscapes.net>
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class ArraySyntaxFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* @var null|int
|
||||
*/
|
||||
private $candidateTokenKind;
|
||||
|
||||
/**
|
||||
* @var null|string
|
||||
*/
|
||||
private $fixCallback;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configure(array $configuration): void
|
||||
{
|
||||
parent::configure($configuration);
|
||||
|
||||
$this->resolveCandidateTokenKind();
|
||||
$this->resolveFixCallback();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'PHP arrays should be declared using the configured syntax.',
|
||||
[
|
||||
new CodeSample(
|
||||
"<?php\narray(1,2);\n"
|
||||
),
|
||||
new CodeSample(
|
||||
"<?php\n[1,2];\n",
|
||||
['syntax' => 'long']
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before BinaryOperatorSpacesFixer, TernaryOperatorSpacesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound($this->candidateTokenKind);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$callback = $this->fixCallback;
|
||||
|
||||
for ($index = $tokens->count() - 1; 0 <= $index; --$index) {
|
||||
if ($tokens[$index]->isGivenKind($this->candidateTokenKind)) {
|
||||
$this->{$callback}($tokens, $index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('syntax', 'Whether to use the `long` or `short` array syntax.'))
|
||||
->setAllowedValues(['long', 'short'])
|
||||
->setDefault('short')
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function fixToLongArraySyntax(Tokens $tokens, int $index): void
|
||||
{
|
||||
$closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $index);
|
||||
|
||||
$tokens[$index] = new Token('(');
|
||||
$tokens[$closeIndex] = new Token(')');
|
||||
|
||||
$tokens->insertAt($index, new Token([T_ARRAY, 'array']));
|
||||
}
|
||||
|
||||
private function fixToShortArraySyntax(Tokens $tokens, int $index): void
|
||||
{
|
||||
$openIndex = $tokens->getNextTokenOfKind($index, ['(']);
|
||||
$closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex);
|
||||
|
||||
$tokens[$openIndex] = new Token([CT::T_ARRAY_SQUARE_BRACE_OPEN, '[']);
|
||||
$tokens[$closeIndex] = new Token([CT::T_ARRAY_SQUARE_BRACE_CLOSE, ']']);
|
||||
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($index);
|
||||
}
|
||||
|
||||
private function resolveFixCallback(): void
|
||||
{
|
||||
$this->fixCallback = sprintf('fixTo%sArraySyntax', ucfirst($this->configuration['syntax']));
|
||||
}
|
||||
|
||||
private function resolveCandidateTokenKind(): void
|
||||
{
|
||||
$this->candidateTokenKind = 'long' === $this->configuration['syntax'] ? CT::T_ARRAY_SQUARE_BRACE_OPEN : T_ARRAY;
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
<?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\ArrayNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Carlos Cirello <carlos.cirello.nl@gmail.com>
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
* @author Graham Campbell <hello@gjcampbell.co.uk>
|
||||
*/
|
||||
final class NoMultilineWhitespaceAroundDoubleArrowFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Operator `=>` should not be surrounded by multi-line whitespaces.',
|
||||
[new CodeSample("<?php\n\$a = array(1\n\n=> 2);\n")]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before BinaryOperatorSpacesFixer, MethodArgumentSpaceFixer, TrailingCommaInMultilineFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 31;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_DOUBLE_ARROW);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->isGivenKind(T_DOUBLE_ARROW)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$tokens[$index - 2]->isComment() || str_starts_with($tokens[$index - 2]->getContent(), '/*')) {
|
||||
$this->fixWhitespace($tokens, $index - 1);
|
||||
}
|
||||
|
||||
// do not move anything about if there is a comment following the whitespace
|
||||
if (!$tokens[$index + 2]->isComment()) {
|
||||
$this->fixWhitespace($tokens, $index + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function fixWhitespace(Tokens $tokens, int $index): void
|
||||
{
|
||||
$token = $tokens[$index];
|
||||
|
||||
if ($token->isWhitespace() && !$token->isWhitespace(" \t")) {
|
||||
$tokens[$index] = new Token([T_WHITESPACE, rtrim($token->getContent()).' ']);
|
||||
}
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
<?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\ArrayNotation;
|
||||
|
||||
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>
|
||||
* @author Sebastiaan Stok <s.stok@rollerscapes.net>
|
||||
*/
|
||||
final class NoTrailingCommaInSinglelineArrayFixer extends AbstractProxyFixer implements DeprecatedFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'PHP single-line arrays should not have trailing comma.',
|
||||
[new CodeSample("<?php\n\$a = array('sample', );\n")]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSuccessorsNames(): array
|
||||
{
|
||||
return array_keys($this->proxyFixers);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createProxyFixers(): array
|
||||
{
|
||||
$fixer = new NoTrailingCommaInSinglelineFixer();
|
||||
$fixer->configure(['elements' => ['array']]);
|
||||
|
||||
return [$fixer];
|
||||
}
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
<?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\ArrayNotation;
|
||||
|
||||
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\FixerDefinition\VersionSpecification;
|
||||
use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Adam Marczuk <adam@marczuk.info>
|
||||
*/
|
||||
final class NoWhitespaceBeforeCommaInArrayFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'In array declaration, there MUST NOT be a whitespace before each comma.',
|
||||
[
|
||||
new CodeSample("<?php \$x = array(1 , \"2\");\n"),
|
||||
new VersionSpecificCodeSample(
|
||||
<<<'PHP'
|
||||
<?php
|
||||
$x = [<<<EOD
|
||||
foo
|
||||
EOD
|
||||
, 'bar'
|
||||
];
|
||||
|
||||
PHP,
|
||||
new VersionSpecification(70300),
|
||||
['after_heredoc' => true]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
for ($index = $tokens->count() - 1; $index > 0; --$index) {
|
||||
if ($tokens[$index]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) {
|
||||
$this->fixSpacing($index, $tokens);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('after_heredoc', 'Whether the whitespace between heredoc end and comma should be removed.'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(false)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to fix spacing in array declaration.
|
||||
*/
|
||||
private function fixSpacing(int $index, Tokens $tokens): void
|
||||
{
|
||||
if ($tokens[$index]->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) {
|
||||
$startIndex = $index;
|
||||
$endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $startIndex);
|
||||
} else {
|
||||
$startIndex = $tokens->getNextTokenOfKind($index, ['(']);
|
||||
$endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startIndex);
|
||||
}
|
||||
|
||||
for ($i = $endIndex - 1; $i > $startIndex; --$i) {
|
||||
$i = $this->skipNonArrayElements($i, $tokens);
|
||||
$currentToken = $tokens[$i];
|
||||
$prevIndex = $tokens->getPrevNonWhitespace($i - 1);
|
||||
|
||||
if (
|
||||
$currentToken->equals(',') && !$tokens[$prevIndex]->isComment()
|
||||
&& (true === $this->configuration['after_heredoc'] || !$tokens[$prevIndex]->isGivenKind(T_END_HEREDOC))
|
||||
) {
|
||||
$tokens->removeLeadingWhitespace($i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to move index over the non-array elements like function calls or function declarations.
|
||||
*/
|
||||
private function skipNonArrayElements(int $index, Tokens $tokens): int
|
||||
{
|
||||
if ($tokens[$index]->equals('}')) {
|
||||
return $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $index);
|
||||
}
|
||||
|
||||
if ($tokens[$index]->equals(')')) {
|
||||
$startIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index);
|
||||
$startIndex = $tokens->getPrevMeaningfulToken($startIndex);
|
||||
if (!$tokens[$startIndex]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) {
|
||||
return $startIndex;
|
||||
}
|
||||
}
|
||||
|
||||
if ($tokens[$index]->equals(',') && $this->commaIsPartOfImplementsList($index, $tokens)) {
|
||||
--$index;
|
||||
}
|
||||
|
||||
return $index;
|
||||
}
|
||||
|
||||
private function commaIsPartOfImplementsList(int $index, Tokens $tokens): bool
|
||||
{
|
||||
do {
|
||||
$index = $tokens->getPrevMeaningfulToken($index);
|
||||
|
||||
$current = $tokens[$index];
|
||||
} while ($current->isGivenKind(T_STRING) || $current->equals(','));
|
||||
|
||||
return $current->isGivenKind(T_IMPLEMENTS);
|
||||
}
|
||||
}
|
||||
Vendored
+62
@@ -0,0 +1,62 @@
|
||||
<?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\ArrayNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class NormalizeIndexBraceFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Array index should always be written by using square braces.',
|
||||
[new CodeSample("<?php\necho \$sample{\$index};\n")]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($tokens as $index => $token) {
|
||||
if ($token->isGivenKind(CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN)) {
|
||||
$tokens[$index] = new Token('[');
|
||||
} elseif ($token->isGivenKind(CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE)) {
|
||||
$tokens[$index] = new Token(']');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+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\ArrayNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Jared Henderson <jared@netrivet.com>
|
||||
*/
|
||||
final class TrimArraySpacesFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Arrays should be formatted like function/method arguments, without leading or trailing single line space.',
|
||||
[new CodeSample("<?php\n\$sample = array( );\n\$sample = array( 'a', 'b' );\n")]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
for ($index = 0, $c = $tokens->count(); $index < $c; ++$index) {
|
||||
if ($tokens[$index]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) {
|
||||
self::fixArray($tokens, $index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to trim leading/trailing whitespace within single line arrays.
|
||||
*/
|
||||
private static function fixArray(Tokens $tokens, int $index): void
|
||||
{
|
||||
$startIndex = $index;
|
||||
|
||||
if ($tokens[$startIndex]->isGivenKind(T_ARRAY)) {
|
||||
$startIndex = $tokens->getNextMeaningfulToken($startIndex);
|
||||
$endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startIndex);
|
||||
} else {
|
||||
$endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $startIndex);
|
||||
}
|
||||
|
||||
$nextIndex = $startIndex + 1;
|
||||
$nextToken = $tokens[$nextIndex];
|
||||
$nextNonWhitespaceIndex = $tokens->getNextNonWhitespace($startIndex);
|
||||
$nextNonWhitespaceToken = $tokens[$nextNonWhitespaceIndex];
|
||||
$tokenAfterNextNonWhitespaceToken = $tokens[$nextNonWhitespaceIndex + 1];
|
||||
|
||||
$prevIndex = $endIndex - 1;
|
||||
$prevToken = $tokens[$prevIndex];
|
||||
$prevNonWhitespaceIndex = $tokens->getPrevNonWhitespace($endIndex);
|
||||
$prevNonWhitespaceToken = $tokens[$prevNonWhitespaceIndex];
|
||||
|
||||
if (
|
||||
$nextToken->isWhitespace(" \t")
|
||||
&& (
|
||||
!$nextNonWhitespaceToken->isComment()
|
||||
|| $nextNonWhitespaceIndex === $prevNonWhitespaceIndex
|
||||
|| $tokenAfterNextNonWhitespaceToken->isWhitespace(" \t")
|
||||
|| str_starts_with($nextNonWhitespaceToken->getContent(), '/*')
|
||||
)
|
||||
) {
|
||||
$tokens->clearAt($nextIndex);
|
||||
}
|
||||
|
||||
if (
|
||||
$prevToken->isWhitespace(" \t")
|
||||
&& !$prevNonWhitespaceToken->equals(',')
|
||||
) {
|
||||
$tokens->clearAt($prevIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
<?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\ArrayNotation;
|
||||
|
||||
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\Preg;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Adam Marczuk <adam@marczuk.info>
|
||||
*/
|
||||
final class WhitespaceAfterCommaInArrayFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'In array declaration, there MUST be a whitespace after each comma.',
|
||||
[
|
||||
new CodeSample("<?php\n\$sample = array(1,'a',\$b,);\n"),
|
||||
new CodeSample("<?php\n\$sample = [1,2, 3, 4, 5];\n", ['ensure_single_space' => true]),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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('ensure_single_space', 'If there are only horizontal whitespaces after the comma then ensure it is a single space.'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(false)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$tokensToInsert = [];
|
||||
|
||||
for ($index = $tokens->count() - 1; $index >= 0; --$index) {
|
||||
if (!$tokens[$index]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$index]->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) {
|
||||
$startIndex = $index;
|
||||
$endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $startIndex);
|
||||
} else {
|
||||
$startIndex = $tokens->getNextTokenOfKind($index, ['(']);
|
||||
$endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startIndex);
|
||||
}
|
||||
|
||||
for ($i = $endIndex - 1; $i > $startIndex; --$i) {
|
||||
$i = $this->skipNonArrayElements($i, $tokens);
|
||||
if (!$tokens[$i]->equals(',')) {
|
||||
continue;
|
||||
}
|
||||
if (!$tokens[$i + 1]->isWhitespace()) {
|
||||
$tokensToInsert[$i + 1] = new Token([T_WHITESPACE, ' ']);
|
||||
} elseif (
|
||||
$this->configuration['ensure_single_space']
|
||||
&& ' ' !== $tokens[$i + 1]->getContent()
|
||||
&& 1 === Preg::match('/^\h+$/', $tokens[$i + 1]->getContent())
|
||||
&& (!$tokens[$i + 2]->isComment() || 1 === Preg::match('/^\h+$/', $tokens[$i + 3]->getContent()))
|
||||
) {
|
||||
$tokens[$i + 1] = new Token([T_WHITESPACE, ' ']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ([] !== $tokensToInsert) {
|
||||
$tokens->insertSlices($tokensToInsert);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to move index over the non-array elements like function calls or function declarations.
|
||||
*
|
||||
* @return int New index
|
||||
*/
|
||||
private function skipNonArrayElements(int $index, Tokens $tokens): int
|
||||
{
|
||||
if ($tokens[$index]->equals('}')) {
|
||||
return $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $index);
|
||||
}
|
||||
|
||||
if ($tokens[$index]->equals(')')) {
|
||||
$startIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index);
|
||||
$startIndex = $tokens->getPrevMeaningfulToken($startIndex);
|
||||
if (!$tokens[$startIndex]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) {
|
||||
return $startIndex;
|
||||
}
|
||||
}
|
||||
|
||||
if ($tokens[$index]->equals(',') && $this->commaIsPartOfImplementsList($index, $tokens)) {
|
||||
--$index;
|
||||
}
|
||||
|
||||
return $index;
|
||||
}
|
||||
|
||||
private function commaIsPartOfImplementsList(int $index, Tokens $tokens): bool
|
||||
{
|
||||
do {
|
||||
$index = $tokens->getPrevMeaningfulToken($index);
|
||||
|
||||
$current = $tokens[$index];
|
||||
} while ($current->isGivenKind(T_STRING) || $current->equals(','));
|
||||
|
||||
return $current->isGivenKind(T_IMPLEMENTS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
<?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\Basic;
|
||||
|
||||
use PhpCsFixer\AbstractProxyFixer;
|
||||
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
||||
use PhpCsFixer\Fixer\ControlStructure\ControlStructureBracesFixer;
|
||||
use PhpCsFixer\Fixer\ControlStructure\ControlStructureContinuationPositionFixer;
|
||||
use PhpCsFixer\Fixer\LanguageConstruct\DeclareParenthesesFixer;
|
||||
use PhpCsFixer\Fixer\LanguageConstruct\SingleSpaceAfterConstructFixer;
|
||||
use PhpCsFixer\Fixer\Whitespace\NoExtraBlankLinesFixer;
|
||||
use PhpCsFixer\Fixer\Whitespace\StatementIndentationFixer;
|
||||
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\CT;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* Fixer for rules defined in PSR2 ¶4.1, ¶4.4, ¶5.
|
||||
*
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class BracesFixer extends AbstractProxyFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public const LINE_NEXT = 'next';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public const LINE_SAME = 'same';
|
||||
|
||||
/**
|
||||
* @var null|CurlyBracesPositionFixer
|
||||
*/
|
||||
private $curlyBracesPositionFixer;
|
||||
|
||||
/**
|
||||
* @var null|ControlStructureContinuationPositionFixer
|
||||
*/
|
||||
private $controlStructureContinuationPositionFixer;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'The body of each structure MUST be enclosed by braces. Braces should be properly placed. Body of braces should be properly indented.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
|
||||
class Foo {
|
||||
public function bar($baz) {
|
||||
if ($baz = 900) echo "Hello!";
|
||||
|
||||
if ($baz = 9000)
|
||||
echo "Wait!";
|
||||
|
||||
if ($baz == true)
|
||||
{
|
||||
echo "Why?";
|
||||
}
|
||||
else
|
||||
{
|
||||
echo "Ha?";
|
||||
}
|
||||
|
||||
if (is_array($baz))
|
||||
foreach ($baz as $b)
|
||||
{
|
||||
echo $b;
|
||||
}
|
||||
}
|
||||
}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
$positive = function ($item) { return $item >= 0; };
|
||||
$negative = function ($item) {
|
||||
return $item < 0; };
|
||||
',
|
||||
['allow_single_line_closure' => true]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
|
||||
class Foo
|
||||
{
|
||||
public function bar($baz)
|
||||
{
|
||||
if ($baz = 900) echo "Hello!";
|
||||
|
||||
if ($baz = 9000)
|
||||
echo "Wait!";
|
||||
|
||||
if ($baz == true)
|
||||
{
|
||||
echo "Why?";
|
||||
}
|
||||
else
|
||||
{
|
||||
echo "Ha?";
|
||||
}
|
||||
|
||||
if (is_array($baz))
|
||||
foreach ($baz as $b)
|
||||
{
|
||||
echo $b;
|
||||
}
|
||||
}
|
||||
}
|
||||
',
|
||||
['position_after_functions_and_oop_constructs' => self::LINE_SAME]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before HeredocIndentationFixer.
|
||||
* Must run after ClassAttributesSeparationFixer, ClassDefinitionFixer, EmptyLoopBodyFixer, NoAlternativeSyntaxFixer, NoEmptyStatementFixer, NoUselessElseFixer, SingleLineThrowFixer, SingleSpaceAfterConstructFixer, SingleTraitInsertPerStatementFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 35;
|
||||
}
|
||||
|
||||
public function configure(array $configuration = null): void
|
||||
{
|
||||
parent::configure($configuration);
|
||||
|
||||
$this->getCurlyBracesPositionFixer()->configure([
|
||||
'control_structures_opening_brace' => $this->translatePositionOption($this->configuration['position_after_control_structures']),
|
||||
'functions_opening_brace' => $this->translatePositionOption($this->configuration['position_after_functions_and_oop_constructs']),
|
||||
'anonymous_functions_opening_brace' => $this->translatePositionOption($this->configuration['position_after_anonymous_constructs']),
|
||||
'classes_opening_brace' => $this->translatePositionOption($this->configuration['position_after_functions_and_oop_constructs']),
|
||||
'anonymous_classes_opening_brace' => $this->translatePositionOption($this->configuration['position_after_anonymous_constructs']),
|
||||
'allow_single_line_empty_anonymous_classes' => $this->configuration['allow_single_line_anonymous_class_with_empty_body'],
|
||||
'allow_single_line_anonymous_functions' => $this->configuration['allow_single_line_closure'],
|
||||
]);
|
||||
|
||||
$this->getControlStructureContinuationPositionFixer()->configure([
|
||||
'position' => self::LINE_NEXT === $this->configuration['position_after_control_structures']
|
||||
? ControlStructureContinuationPositionFixer::NEXT_LINE
|
||||
: ControlStructureContinuationPositionFixer::SAME_LINE,
|
||||
]);
|
||||
|
||||
$this->configuration = $configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
for ($index = \count($tokens) - 1; $index > 0; --$index) {
|
||||
if ($tokens[$index]->isGivenKind(CT::T_USE_LAMBDA)) {
|
||||
$tokens->ensureWhitespaceAtIndex($index - 1, 1, ' ');
|
||||
}
|
||||
}
|
||||
|
||||
parent::applyFix($file, $tokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('allow_single_line_anonymous_class_with_empty_body', 'Whether single line anonymous class with empty body notation should be allowed.'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(false)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('allow_single_line_closure', 'Whether single line lambda notation should be allowed.'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(false)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('position_after_functions_and_oop_constructs', 'whether the opening brace should be placed on "next" or "same" line after classy constructs (non-anonymous classes, interfaces, traits, methods and non-lambda functions).'))
|
||||
->setAllowedValues([self::LINE_NEXT, self::LINE_SAME])
|
||||
->setDefault(self::LINE_NEXT)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('position_after_control_structures', 'whether the opening brace should be placed on "next" or "same" line after control structures.'))
|
||||
->setAllowedValues([self::LINE_NEXT, self::LINE_SAME])
|
||||
->setDefault(self::LINE_SAME)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('position_after_anonymous_constructs', 'whether the opening brace should be placed on "next" or "same" line after anonymous constructs (anonymous classes and lambda functions).'))
|
||||
->setAllowedValues([self::LINE_NEXT, self::LINE_SAME])
|
||||
->setDefault(self::LINE_SAME)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function createProxyFixers(): array
|
||||
{
|
||||
$singleSpaceAfterConstructFixer = new SingleSpaceAfterConstructFixer();
|
||||
$singleSpaceAfterConstructFixer->configure([
|
||||
'constructs' => ['elseif', 'for', 'foreach', 'if', 'match', 'while', 'use_lambda'],
|
||||
]);
|
||||
|
||||
$noExtraBlankLinesFixer = new NoExtraBlankLinesFixer();
|
||||
$noExtraBlankLinesFixer->configure([
|
||||
'tokens' => ['curly_brace_block'],
|
||||
]);
|
||||
|
||||
return [
|
||||
$singleSpaceAfterConstructFixer,
|
||||
new ControlStructureBracesFixer(),
|
||||
$noExtraBlankLinesFixer,
|
||||
$this->getCurlyBracesPositionFixer(),
|
||||
$this->getControlStructureContinuationPositionFixer(),
|
||||
new DeclareParenthesesFixer(),
|
||||
new NoMultipleStatementsPerLineFixer(),
|
||||
new StatementIndentationFixer(true),
|
||||
];
|
||||
}
|
||||
|
||||
private function getCurlyBracesPositionFixer(): CurlyBracesPositionFixer
|
||||
{
|
||||
if (null === $this->curlyBracesPositionFixer) {
|
||||
$this->curlyBracesPositionFixer = new CurlyBracesPositionFixer();
|
||||
}
|
||||
|
||||
return $this->curlyBracesPositionFixer;
|
||||
}
|
||||
|
||||
private function getControlStructureContinuationPositionFixer(): ControlStructureContinuationPositionFixer
|
||||
{
|
||||
if (null === $this->controlStructureContinuationPositionFixer) {
|
||||
$this->controlStructureContinuationPositionFixer = new ControlStructureContinuationPositionFixer();
|
||||
}
|
||||
|
||||
return $this->controlStructureContinuationPositionFixer;
|
||||
}
|
||||
|
||||
private function translatePositionOption(string $option): string
|
||||
{
|
||||
return self::LINE_NEXT === $option
|
||||
? CurlyBracesPositionFixer::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END
|
||||
: CurlyBracesPositionFixer::SAME_LINE
|
||||
;
|
||||
}
|
||||
}
|
||||
+429
@@ -0,0 +1,429 @@
|
||||
<?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\Basic;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
||||
use PhpCsFixer\Fixer\Indentation;
|
||||
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\FixerDefinition\VersionSpecification;
|
||||
use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
|
||||
use PhpCsFixer\Preg;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use PhpCsFixer\Tokenizer\TokensAnalyzer;
|
||||
|
||||
final class CurlyBracesPositionFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
|
||||
{
|
||||
use Indentation;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public const NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END = 'next_line_unless_newline_at_signature_end';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public const SAME_LINE = 'same_line';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Curly braces must be placed as configured.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
class Foo {
|
||||
}
|
||||
|
||||
function foo() {
|
||||
}
|
||||
|
||||
$foo = function()
|
||||
{
|
||||
};
|
||||
|
||||
if (foo())
|
||||
{
|
||||
bar();
|
||||
}
|
||||
'
|
||||
),
|
||||
new VersionSpecificCodeSample(
|
||||
'<?php
|
||||
$foo = new class
|
||||
{
|
||||
};
|
||||
',
|
||||
new VersionSpecification(70000)
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
if (foo()) {
|
||||
bar();
|
||||
}
|
||||
',
|
||||
['control_structures_opening_brace' => self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
function foo()
|
||||
{
|
||||
}
|
||||
',
|
||||
['functions_opening_brace' => self::SAME_LINE]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
$foo = function () {
|
||||
};
|
||||
',
|
||||
['anonymous_functions_opening_brace' => self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
class Foo
|
||||
{
|
||||
}
|
||||
',
|
||||
['classes_opening_brace' => self::SAME_LINE]
|
||||
),
|
||||
new VersionSpecificCodeSample(
|
||||
'<?php
|
||||
$foo = new class {
|
||||
};
|
||||
',
|
||||
new VersionSpecification(70000),
|
||||
['anonymous_classes_opening_brace' => self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END]
|
||||
),
|
||||
new VersionSpecificCodeSample(
|
||||
'<?php
|
||||
$foo = new class { };
|
||||
$bar = new class { private $baz; };
|
||||
',
|
||||
new VersionSpecification(70000),
|
||||
['allow_single_line_empty_anonymous_classes' => true]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
$foo = function () { return true; };
|
||||
$bar = function () { $result = true;
|
||||
return $result; };
|
||||
',
|
||||
['allow_single_line_anonymous_functions' => true]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound('{');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after ControlStructureBracesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return parent::getPriority();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$classyTokens = Token::getClassyTokenKinds();
|
||||
$controlStructureTokens = [T_DECLARE, T_DO, T_ELSE, T_ELSEIF, T_FINALLY, T_FOR, T_FOREACH, T_IF, T_WHILE, T_TRY, T_CATCH, T_SWITCH];
|
||||
// @TODO: drop condition when PHP 8.0+ is required
|
||||
if (\defined('T_MATCH')) {
|
||||
$controlStructureTokens[] = T_MATCH;
|
||||
}
|
||||
|
||||
$tokensAnalyzer = new TokensAnalyzer($tokens);
|
||||
|
||||
$allowSingleLineUntil = null;
|
||||
|
||||
foreach ($tokens as $index => $token) {
|
||||
$allowSingleLine = false;
|
||||
$allowSingleLineIfEmpty = false;
|
||||
|
||||
if ($token->isGivenKind($classyTokens)) {
|
||||
$openBraceIndex = $tokens->getNextTokenOfKind($index, ['{']);
|
||||
|
||||
if ($tokensAnalyzer->isAnonymousClass($index)) {
|
||||
$allowSingleLineIfEmpty = $this->configuration['allow_single_line_empty_anonymous_classes'];
|
||||
$positionOption = 'anonymous_classes_opening_brace';
|
||||
} else {
|
||||
$positionOption = 'classes_opening_brace';
|
||||
}
|
||||
} elseif ($token->isGivenKind(T_FUNCTION)) {
|
||||
$openBraceIndex = $tokens->getNextTokenOfKind($index, ['{', ';']);
|
||||
|
||||
if ($tokens[$openBraceIndex]->equals(';')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokensAnalyzer->isLambda($index)) {
|
||||
$allowSingleLine = $this->configuration['allow_single_line_anonymous_functions'];
|
||||
$positionOption = 'anonymous_functions_opening_brace';
|
||||
} else {
|
||||
$positionOption = 'functions_opening_brace';
|
||||
}
|
||||
} elseif ($token->isGivenKind($controlStructureTokens)) {
|
||||
$parenthesisEndIndex = $this->findParenthesisEnd($tokens, $index);
|
||||
$openBraceIndex = $tokens->getNextMeaningfulToken($parenthesisEndIndex);
|
||||
|
||||
if (!$tokens[$openBraceIndex]->equals('{')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$positionOption = 'control_structures_opening_brace';
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
$closeBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $openBraceIndex);
|
||||
|
||||
$addNewlinesInsideBraces = true;
|
||||
if ($allowSingleLine || $allowSingleLineIfEmpty || $index < $allowSingleLineUntil) {
|
||||
$addNewlinesInsideBraces = false;
|
||||
|
||||
for ($indexInsideBraces = $openBraceIndex + 1; $indexInsideBraces < $closeBraceIndex; ++$indexInsideBraces) {
|
||||
$tokenInsideBraces = $tokens[$indexInsideBraces];
|
||||
|
||||
if (
|
||||
($allowSingleLineIfEmpty && !$tokenInsideBraces->isWhitespace() && !$tokenInsideBraces->isComment())
|
||||
|| ($tokenInsideBraces->isWhitespace() && 1 === Preg::match('/\R/', $tokenInsideBraces->getContent()))
|
||||
) {
|
||||
$addNewlinesInsideBraces = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$addNewlinesInsideBraces && null === $allowSingleLineUntil) {
|
||||
$allowSingleLineUntil = $closeBraceIndex;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
$addNewlinesInsideBraces
|
||||
&& !$this->isFollowedByNewLine($tokens, $openBraceIndex)
|
||||
&& !$this->hasCommentOnSameLine($tokens, $openBraceIndex)
|
||||
&& !$tokens[$tokens->getNextMeaningfulToken($openBraceIndex)]->isGivenKind(T_CLOSE_TAG)
|
||||
) {
|
||||
$whitespace = $this->whitespacesConfig->getLineEnding().$this->getLineIndentation($tokens, $openBraceIndex);
|
||||
if ($tokens->ensureWhitespaceAtIndex($openBraceIndex + 1, 0, $whitespace)) {
|
||||
++$closeBraceIndex;
|
||||
}
|
||||
}
|
||||
|
||||
$whitespace = ' ';
|
||||
if (self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END === $this->configuration[$positionOption]) {
|
||||
$whitespace = $this->whitespacesConfig->getLineEnding().$this->getLineIndentation($tokens, $index);
|
||||
|
||||
$previousTokenIndex = $openBraceIndex;
|
||||
do {
|
||||
$previousTokenIndex = $tokens->getPrevMeaningfulToken($previousTokenIndex);
|
||||
} while ($tokens[$previousTokenIndex]->isGivenKind([CT::T_TYPE_COLON, CT::T_NULLABLE_TYPE, T_STRING, T_NS_SEPARATOR, CT::T_ARRAY_TYPEHINT, T_STATIC, CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION]));
|
||||
|
||||
if ($tokens[$previousTokenIndex]->equals(')')) {
|
||||
if ($tokens[--$previousTokenIndex]->isComment()) {
|
||||
--$previousTokenIndex;
|
||||
}
|
||||
if (
|
||||
$tokens[$previousTokenIndex]->isWhitespace()
|
||||
&& 1 === Preg::match('/\R/', $tokens[$previousTokenIndex]->getContent())
|
||||
) {
|
||||
$whitespace = ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$moveBraceToIndex = null;
|
||||
|
||||
if (' ' === $whitespace) {
|
||||
$previousMeaningfulIndex = $tokens->getPrevMeaningfulToken($openBraceIndex);
|
||||
for ($indexBeforeOpenBrace = $openBraceIndex - 1; $indexBeforeOpenBrace > $previousMeaningfulIndex; --$indexBeforeOpenBrace) {
|
||||
if (!$tokens[$indexBeforeOpenBrace]->isComment()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokenBeforeOpenBrace = $tokens[--$indexBeforeOpenBrace];
|
||||
if ($tokenBeforeOpenBrace->isWhitespace()) {
|
||||
$moveBraceToIndex = $indexBeforeOpenBrace;
|
||||
} elseif ($indexBeforeOpenBrace === $previousMeaningfulIndex) {
|
||||
$moveBraceToIndex = $previousMeaningfulIndex + 1;
|
||||
}
|
||||
}
|
||||
} elseif (!$tokens[$openBraceIndex - 1]->isWhitespace() || !Preg::match('/\R/', $tokens[$openBraceIndex - 1]->getContent())) {
|
||||
for ($indexAfterOpenBrace = $openBraceIndex + 1; $indexAfterOpenBrace < $closeBraceIndex; ++$indexAfterOpenBrace) {
|
||||
if ($tokens[$indexAfterOpenBrace]->isWhitespace() && Preg::match('/\R/', $tokens[$indexAfterOpenBrace]->getContent())) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ($tokens[$indexAfterOpenBrace]->isComment() && !str_starts_with($tokens[$indexAfterOpenBrace]->getContent(), '/*')) {
|
||||
$moveBraceToIndex = $indexAfterOpenBrace + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $moveBraceToIndex) {
|
||||
/** @var Token $movedToken */
|
||||
$movedToken = clone $tokens[$openBraceIndex];
|
||||
|
||||
$delta = $openBraceIndex < $moveBraceToIndex ? 1 : -1;
|
||||
|
||||
if ($tokens[$openBraceIndex + $delta]->isWhitespace()) {
|
||||
if (-1 === $delta && Preg::match('/\R/', $tokens[$openBraceIndex - 1]->getContent())) {
|
||||
$content = Preg::replace('/^(\h*?\R)?\h*/', '', $tokens[$openBraceIndex + 1]->getContent());
|
||||
if ('' !== $content) {
|
||||
$tokens[$openBraceIndex + 1] = new Token([T_WHITESPACE, $content]);
|
||||
} else {
|
||||
$tokens->clearAt($openBraceIndex + 1);
|
||||
}
|
||||
} else {
|
||||
$tokens->clearAt($openBraceIndex - 1);
|
||||
}
|
||||
}
|
||||
|
||||
for (; $openBraceIndex !== $moveBraceToIndex; $openBraceIndex += $delta) {
|
||||
/** @var Token $siblingToken */
|
||||
$siblingToken = $tokens[$openBraceIndex + $delta];
|
||||
$tokens[$openBraceIndex] = $siblingToken;
|
||||
}
|
||||
|
||||
$tokens[$openBraceIndex] = $movedToken;
|
||||
|
||||
$openBraceIndex = $moveBraceToIndex;
|
||||
}
|
||||
|
||||
if ($tokens->ensureWhitespaceAtIndex($openBraceIndex - 1, 1, $whitespace)) {
|
||||
++$closeBraceIndex;
|
||||
if (null !== $allowSingleLineUntil) {
|
||||
++$allowSingleLineUntil;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!$addNewlinesInsideBraces
|
||||
|| $tokens[$tokens->getPrevMeaningfulToken($closeBraceIndex)]->isGivenKind(T_OPEN_TAG)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for ($prevIndex = $closeBraceIndex - 1; $tokens->isEmptyAt($prevIndex); --$prevIndex);
|
||||
|
||||
$prevToken = $tokens[$prevIndex];
|
||||
if ($prevToken->isWhitespace() && 1 === Preg::match('/\R/', $prevToken->getContent())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$whitespace = $this->whitespacesConfig->getLineEnding().$this->getLineIndentation($tokens, $openBraceIndex);
|
||||
$tokens->ensureWhitespaceAtIndex($prevIndex, 1, $whitespace);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('control_structures_opening_brace', 'the position of the opening brace of control structures body.'))
|
||||
->setAllowedValues([self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END, self::SAME_LINE])
|
||||
->setDefault(self::SAME_LINE)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('functions_opening_brace', 'the position of the opening brace of functions body.'))
|
||||
->setAllowedValues([self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END, self::SAME_LINE])
|
||||
->setDefault(self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('anonymous_functions_opening_brace', 'the position of the opening brace of anonymous functions body.'))
|
||||
->setAllowedValues([self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END, self::SAME_LINE])
|
||||
->setDefault(self::SAME_LINE)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('classes_opening_brace', 'the position of the opening brace of classes body.'))
|
||||
->setAllowedValues([self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END, self::SAME_LINE])
|
||||
->setDefault(self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('anonymous_classes_opening_brace', 'the position of the opening brace of anonymous classes body.'))
|
||||
->setAllowedValues([self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END, self::SAME_LINE])
|
||||
->setDefault(self::SAME_LINE)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('allow_single_line_empty_anonymous_classes', 'allow anonymous classes to have opening and closing braces on the same line.'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(true)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('allow_single_line_anonymous_functions', 'allow anonymous functions to have opening and closing braces on the same line.'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(true)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function findParenthesisEnd(Tokens $tokens, int $structureTokenIndex): int
|
||||
{
|
||||
$nextIndex = $tokens->getNextMeaningfulToken($structureTokenIndex);
|
||||
$nextToken = $tokens[$nextIndex];
|
||||
|
||||
// return if next token is not opening parenthesis
|
||||
if (!$nextToken->equals('(')) {
|
||||
return $structureTokenIndex;
|
||||
}
|
||||
|
||||
return $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $nextIndex);
|
||||
}
|
||||
|
||||
private function isFollowedByNewLine(Tokens $tokens, int $index): bool
|
||||
{
|
||||
for (++$index, $max = \count($tokens) - 1; $index < $max; ++$index) {
|
||||
$token = $tokens[$index];
|
||||
if (!$token->isComment()) {
|
||||
return $token->isWhitespace() && 1 === Preg::match('/\R/', $token->getContent());
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function hasCommentOnSameLine(Tokens $tokens, int $index): bool
|
||||
{
|
||||
$token = $tokens[$index + 1];
|
||||
|
||||
if ($token->isWhitespace() && 1 !== Preg::match('/\R/', $token->getContent())) {
|
||||
$token = $tokens[$index + 2];
|
||||
}
|
||||
|
||||
return $token->isComment();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?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\Basic;
|
||||
|
||||
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 PSR1 ¶2.2.
|
||||
*
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class EncodingFixer extends AbstractFixer
|
||||
{
|
||||
private string $BOM;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->BOM = pack('CCC', 0xEF, 0xBB, 0xBF);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'PHP code MUST use only UTF-8 without BOM (remove BOM).',
|
||||
[
|
||||
new CodeSample(
|
||||
$this->BOM.'<?php
|
||||
|
||||
echo "Hello!";
|
||||
'
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
// must run first (at least before Fixers that using Tokens) - for speed reason of whole fixing process
|
||||
return 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$content = $tokens[0]->getContent();
|
||||
|
||||
if (0 === strncmp($content, $this->BOM, 3)) {
|
||||
$newContent = substr($content, 3);
|
||||
|
||||
if ('' === $newContent) {
|
||||
$tokens->clearAt(0);
|
||||
} else {
|
||||
$tokens[0] = new Token([$tokens[0]->getId(), $newContent]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+109
@@ -0,0 +1,109 @@
|
||||
<?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\Basic;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\Fixer\Indentation;
|
||||
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Preg;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* Fixer for rules defined in PSR2 ¶2.3 Lines: There must not be more than one statement per line.
|
||||
*/
|
||||
final class NoMultipleStatementsPerLineFixer extends AbstractFixer implements WhitespacesAwareFixerInterface
|
||||
{
|
||||
use Indentation;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'There must not be more than one statement per line.',
|
||||
[new CodeSample("<?php\nfoo(); bar();\n")]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after ControlStructureBracesFixer, NoEmptyStatementFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(';');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
for ($index = 1, $max = \count($tokens) - 1; $index < $max; ++$index) {
|
||||
if ($tokens[$index]->isGivenKind(T_FOR)) {
|
||||
$index = $tokens->findBlockEnd(
|
||||
Tokens::BLOCK_TYPE_PARENTHESIS_BRACE,
|
||||
$tokens->getNextTokenOfKind($index, ['('])
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$tokens[$index]->equals(';')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for ($nextIndex = $index + 1; $nextIndex < $max; ++$nextIndex) {
|
||||
$token = $tokens[$nextIndex];
|
||||
|
||||
if ($token->isWhitespace() || $token->isComment()) {
|
||||
if (1 === Preg::match('/\R/', $token->getContent())) {
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$token->equalsAny(['}', [T_CLOSE_TAG], [T_ENDIF], [T_ENDFOR], [T_ENDSWITCH], [T_ENDWHILE], [T_ENDFOREACH]])) {
|
||||
$whitespaceIndex = $index;
|
||||
do {
|
||||
$token = $tokens[++$whitespaceIndex];
|
||||
} while ($token->isComment());
|
||||
|
||||
$newline = $this->whitespacesConfig->getLineEnding().$this->getLineIndentation($tokens, $index);
|
||||
|
||||
if ($tokens->ensureWhitespaceAtIndex($whitespaceIndex, 0, $newline)) {
|
||||
++$max;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+163
@@ -0,0 +1,163 @@
|
||||
<?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\Basic;
|
||||
|
||||
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\Analyzer\AttributeAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class NoTrailingCommaInSinglelineFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'If a list of values separated by a comma is contained on a single line, then the last item MUST NOT have a trailing comma.',
|
||||
[
|
||||
new CodeSample("<?php\nfoo(\$a,);\n\$foo = array(1,);\n[\$foo, \$bar,] = \$array;\nuse a\\{ClassA, ClassB,};\n"),
|
||||
new CodeSample("<?php\nfoo(\$a,);\n[\$foo, \$bar,] = \$array;\n", ['elements' => ['array_destructuring']]),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return
|
||||
$tokens->isTokenKindFound(',')
|
||||
&& $tokens->isAnyTokenKindsFound([')', CT::T_ARRAY_SQUARE_BRACE_CLOSE, CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, CT::T_GROUP_IMPORT_BRACE_CLOSE])
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
$elements = ['arguments', 'array_destructuring', 'array', 'group_import'];
|
||||
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('elements', 'Which elements to fix.'))
|
||||
->setAllowedTypes(['array'])
|
||||
->setAllowedValues([new AllowedValueSubset($elements)])
|
||||
->setDefault($elements)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
for ($index = $tokens->count() - 1; $index >= 0; --$index) {
|
||||
if (!$tokens[$index]->equals(')') && !$tokens[$index]->isGivenKind([CT::T_ARRAY_SQUARE_BRACE_CLOSE, CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, CT::T_GROUP_IMPORT_BRACE_CLOSE])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$commaIndex = $tokens->getPrevMeaningfulToken($index);
|
||||
|
||||
if (!$tokens[$commaIndex]->equals(',')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$block = Tokens::detectBlockType($tokens[$index]);
|
||||
$blockOpenIndex = $tokens->findBlockStart($block['type'], $index);
|
||||
|
||||
if ($tokens->isPartialCodeMultiline($blockOpenIndex, $index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->shouldBeCleared($tokens, $blockOpenIndex)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
do {
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($commaIndex);
|
||||
$commaIndex = $tokens->getPrevMeaningfulToken($commaIndex);
|
||||
} while ($tokens[$commaIndex]->equals(','));
|
||||
|
||||
$tokens->removeTrailingWhitespace($commaIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private function shouldBeCleared(Tokens $tokens, int $openIndex): bool
|
||||
{
|
||||
/** @var string[] $elements */
|
||||
$elements = $this->configuration['elements'];
|
||||
|
||||
if ($tokens[$openIndex]->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) {
|
||||
return \in_array('array', $elements, true);
|
||||
}
|
||||
|
||||
if ($tokens[$openIndex]->isGivenKind(CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN)) {
|
||||
return \in_array('array_destructuring', $elements, true);
|
||||
}
|
||||
|
||||
if ($tokens[$openIndex]->isGivenKind(CT::T_GROUP_IMPORT_BRACE_OPEN)) {
|
||||
return \in_array('group_import', $elements, true);
|
||||
}
|
||||
|
||||
if (!$tokens[$openIndex]->equals('(')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$beforeOpen = $tokens->getPrevMeaningfulToken($openIndex);
|
||||
|
||||
if ($tokens[$beforeOpen]->isGivenKind(T_ARRAY)) {
|
||||
return \in_array('array', $elements, true);
|
||||
}
|
||||
|
||||
if ($tokens[$beforeOpen]->isGivenKind(T_LIST)) {
|
||||
return \in_array('array_destructuring', $elements, true);
|
||||
}
|
||||
|
||||
if ($tokens[$beforeOpen]->isGivenKind([T_UNSET, T_ISSET, T_VARIABLE, T_CLASS])) {
|
||||
return \in_array('arguments', $elements, true);
|
||||
}
|
||||
|
||||
if ($tokens[$beforeOpen]->isGivenKind(T_STRING)) {
|
||||
return !AttributeAnalyzer::isAttribute($tokens, $beforeOpen) && \in_array('arguments', $elements, true);
|
||||
}
|
||||
|
||||
if ($tokens[$beforeOpen]->equalsAny([')', ']', [CT::T_DYNAMIC_VAR_BRACE_CLOSE], [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE]])) {
|
||||
$block = Tokens::detectBlockType($tokens[$beforeOpen]);
|
||||
|
||||
return
|
||||
(
|
||||
Tokens::BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE === $block['type']
|
||||
|| Tokens::BLOCK_TYPE_DYNAMIC_VAR_BRACE === $block['type']
|
||||
|| Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE === $block['type']
|
||||
|| Tokens::BLOCK_TYPE_PARENTHESIS_BRACE === $block['type']
|
||||
) && \in_array('arguments', $elements, true)
|
||||
;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
<?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\Basic;
|
||||
|
||||
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\Preg;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* Removes Zero-width space (ZWSP), Non-breaking space (NBSP) and other invisible unicode symbols.
|
||||
*
|
||||
* @author Ivan Boprzenkov <ivan.borzenkov@gmail.com>
|
||||
*/
|
||||
final class NonPrintableCharacterFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* @var array<string, string[]>
|
||||
*/
|
||||
private array $symbolsReplace;
|
||||
|
||||
/**
|
||||
* @var int[]
|
||||
*/
|
||||
private static array $tokens = [
|
||||
T_STRING_VARNAME,
|
||||
T_INLINE_HTML,
|
||||
T_VARIABLE,
|
||||
T_COMMENT,
|
||||
T_ENCAPSED_AND_WHITESPACE,
|
||||
T_CONSTANT_ENCAPSED_STRING,
|
||||
T_DOC_COMMENT,
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->symbolsReplace = [
|
||||
pack('H*', 'e2808b') => ['', '200b'], // ZWSP U+200B
|
||||
pack('H*', 'e28087') => [' ', '2007'], // FIGURE SPACE U+2007
|
||||
pack('H*', 'e280af') => [' ', '202f'], // NBSP U+202F
|
||||
pack('H*', 'e281a0') => ['', '2060'], // WORD JOINER U+2060
|
||||
pack('H*', 'c2a0') => [' ', 'a0'], // NO-BREAK SPACE U+A0
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Remove Zero-width space (ZWSP), Non-breaking space (NBSP) and other invisible unicode symbols.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php echo "'.pack('H*', 'e2808b').'Hello'.pack('H*', 'e28087').'World'.pack('H*', 'c2a0')."!\";\n"
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php echo "'.pack('H*', 'e2808b').'Hello'.pack('H*', 'e28087').'World'.pack('H*', 'c2a0')."!\";\n",
|
||||
['use_escape_sequences_in_strings' => false]
|
||||
),
|
||||
],
|
||||
null,
|
||||
'Risky when strings contain intended invisible characters.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound(self::$tokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('use_escape_sequences_in_strings', 'Whether characters should be replaced with escape sequences in strings.'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(true)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$replacements = [];
|
||||
$escapeSequences = [];
|
||||
|
||||
foreach ($this->symbolsReplace as $character => [$replacement, $codepoint]) {
|
||||
$replacements[$character] = $replacement;
|
||||
$escapeSequences[$character] = '\u{'.$codepoint.'}';
|
||||
}
|
||||
|
||||
foreach ($tokens as $index => $token) {
|
||||
$content = $token->getContent();
|
||||
|
||||
if (
|
||||
$this->configuration['use_escape_sequences_in_strings']
|
||||
&& $token->isGivenKind([T_CONSTANT_ENCAPSED_STRING, T_ENCAPSED_AND_WHITESPACE])
|
||||
) {
|
||||
if (!Preg::match('/'.implode('|', array_keys($escapeSequences)).'/', $content)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$previousToken = $tokens[$index - 1];
|
||||
$stringTypeChanged = false;
|
||||
$swapQuotes = false;
|
||||
|
||||
if ($previousToken->isGivenKind(T_START_HEREDOC)) {
|
||||
$previousTokenContent = $previousToken->getContent();
|
||||
|
||||
if (str_contains($previousTokenContent, '\'')) {
|
||||
$tokens[$index - 1] = new Token([T_START_HEREDOC, str_replace('\'', '', $previousTokenContent)]);
|
||||
$stringTypeChanged = true;
|
||||
}
|
||||
} elseif (str_starts_with($content, "'")) {
|
||||
$stringTypeChanged = true;
|
||||
$swapQuotes = true;
|
||||
}
|
||||
|
||||
if ($swapQuotes) {
|
||||
$content = str_replace("\\'", "'", $content);
|
||||
}
|
||||
|
||||
if ($stringTypeChanged) {
|
||||
$content = Preg::replace('/(\\\\{1,2})/', '\\\\\\\\', $content);
|
||||
$content = str_replace('$', '\$', $content);
|
||||
}
|
||||
|
||||
if ($swapQuotes) {
|
||||
$content = str_replace('"', '\"', $content);
|
||||
$content = Preg::replace('/^\'(.*)\'$/s', '"$1"', $content);
|
||||
}
|
||||
|
||||
$tokens[$index] = new Token([$token->getId(), strtr($content, $escapeSequences)]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->isGivenKind(self::$tokens)) {
|
||||
$newContent = strtr($content, $replacements);
|
||||
|
||||
// variable name cannot contain space
|
||||
if ($token->isGivenKind([T_STRING_VARNAME, T_VARIABLE]) && str_contains($newContent, ' ')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// multiline comment must have "*/" only at the end
|
||||
if ($token->isGivenKind([T_COMMENT, T_DOC_COMMENT]) && str_starts_with($newContent, '/*') && strpos($newContent, '*/') !== \strlen($newContent) - 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens[$index] = new Token([$token->getId(), $newContent]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
<?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\Basic;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\FixerDefinition\VersionSpecification;
|
||||
use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
|
||||
use PhpCsFixer\Preg;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class OctalNotationFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Literal octal must be in `0o` notation.',
|
||||
[
|
||||
new VersionSpecificCodeSample(
|
||||
"<?php \$foo = 0123;\n",
|
||||
new VersionSpecification(80100)
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return \PHP_VERSION_ID >= 80100 && $tokens->isTokenKindFound(T_LNUMBER);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->isGivenKind(T_LNUMBER)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$content = $token->getContent();
|
||||
|
||||
if (1 !== Preg::match('#^0\d+$#', $content)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens[$index] = 1 === Preg::match('#^0+$#', $content)
|
||||
? new Token([T_LNUMBER, '0'])
|
||||
: new Token([T_LNUMBER, '0o'.substr($content, 1)])
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
+298
@@ -0,0 +1,298 @@
|
||||
<?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\Basic;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
|
||||
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
|
||||
use PhpCsFixer\FixerDefinition\FileSpecificCodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Preg;
|
||||
use PhpCsFixer\StdinFileInfo;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use PhpCsFixer\Tokenizer\TokensAnalyzer;
|
||||
|
||||
/**
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
* @author Bram Gotink <bram@gotink.me>
|
||||
* @author Graham Campbell <hello@gjcampbell.co.uk>
|
||||
* @author Kuba Werłos <werlos@gmail.com>
|
||||
*/
|
||||
final class PsrAutoloadingFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Classes must be in a path that matches their namespace, be at least one namespace deep and the class name should match the file name.',
|
||||
[
|
||||
new FileSpecificCodeSample(
|
||||
'<?php
|
||||
namespace PhpCsFixer\FIXER\Basic;
|
||||
class InvalidName {}
|
||||
',
|
||||
new \SplFileInfo(__FILE__)
|
||||
),
|
||||
new FileSpecificCodeSample(
|
||||
'<?php
|
||||
namespace PhpCsFixer\FIXER\Basic;
|
||||
class InvalidName {}
|
||||
',
|
||||
new \SplFileInfo(__FILE__),
|
||||
['dir' => './src']
|
||||
),
|
||||
],
|
||||
null,
|
||||
'This fixer may change your class name, which will break the code that depends on the old name.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configure(array $configuration): void
|
||||
{
|
||||
parent::configure($configuration);
|
||||
|
||||
if (null !== $this->configuration['dir']) {
|
||||
$realpath = realpath($this->configuration['dir']);
|
||||
|
||||
if (false === $realpath) {
|
||||
throw new \InvalidArgumentException(sprintf('Failed to resolve configured directory "%s".', $this->configuration['dir']));
|
||||
}
|
||||
|
||||
$this->configuration['dir'] = $realpath;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return -10;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function supports(\SplFileInfo $file): bool
|
||||
{
|
||||
if ($file instanceof StdinFileInfo) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
// ignore file with extension other than php
|
||||
('php' !== $file->getExtension())
|
||||
// ignore file with name that cannot be a class name
|
||||
|| 0 === Preg::match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $file->getBasename('.php'))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$tokens = Tokens::fromCode(sprintf('<?php class %s {}', $file->getBasename('.php')));
|
||||
|
||||
if ($tokens[3]->isKeyword() || $tokens[3]->isMagicConstant()) {
|
||||
// name cannot be a class name - detected by PHP 5.x
|
||||
return false;
|
||||
}
|
||||
} catch (\ParseError $e) {
|
||||
// name cannot be a class name - detected by PHP 7.x
|
||||
return false;
|
||||
}
|
||||
|
||||
// ignore stubs/fixtures, since they typically contain invalid files for various reasons
|
||||
return !Preg::match('{[/\\\\](stub|fixture)s?[/\\\\]}i', $file->getRealPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('dir', 'If provided, the directory where the project code is placed.'))
|
||||
->setAllowedTypes(['null', 'string'])
|
||||
->setDefault(null)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$tokenAnalyzer = new TokensAnalyzer($tokens);
|
||||
|
||||
if (null !== $this->configuration['dir'] && !str_starts_with($file->getRealPath(), $this->configuration['dir'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$namespace = null;
|
||||
$namespaceStartIndex = null;
|
||||
$namespaceEndIndex = null;
|
||||
|
||||
$classyName = null;
|
||||
$classyIndex = null;
|
||||
|
||||
foreach ($tokens as $index => $token) {
|
||||
if ($token->isGivenKind(T_NAMESPACE)) {
|
||||
if (null !== $namespace) {
|
||||
return;
|
||||
}
|
||||
|
||||
$namespaceStartIndex = $tokens->getNextMeaningfulToken($index);
|
||||
$namespaceEndIndex = $tokens->getNextTokenOfKind($namespaceStartIndex, [';']);
|
||||
$namespace = trim($tokens->generatePartialCode($namespaceStartIndex, $namespaceEndIndex - 1));
|
||||
} elseif ($token->isClassy()) {
|
||||
if ($tokenAnalyzer->isAnonymousClass($index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (null !== $classyName) {
|
||||
return;
|
||||
}
|
||||
|
||||
$classyIndex = $tokens->getNextMeaningfulToken($index);
|
||||
$classyName = $tokens[$classyIndex]->getContent();
|
||||
}
|
||||
}
|
||||
|
||||
if (null === $classyName) {
|
||||
return;
|
||||
}
|
||||
|
||||
$expectedClassyName = $this->calculateClassyName($file, $namespace, $classyName);
|
||||
|
||||
if ($classyName !== $expectedClassyName) {
|
||||
$tokens[$classyIndex] = new Token([T_STRING, $expectedClassyName]);
|
||||
}
|
||||
|
||||
if (null === $this->configuration['dir'] || null === $namespace) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_dir($this->configuration['dir'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$configuredDir = realpath($this->configuration['dir']);
|
||||
$fileDir = \dirname($file->getRealPath());
|
||||
|
||||
if (\strlen($configuredDir) >= \strlen($fileDir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$newNamespace = substr(str_replace('/', '\\', $fileDir), \strlen($configuredDir) + 1);
|
||||
$originalNamespace = substr($namespace, -\strlen($newNamespace));
|
||||
|
||||
if ($originalNamespace !== $newNamespace && strtolower($originalNamespace) === strtolower($newNamespace)) {
|
||||
$tokens->clearRange($namespaceStartIndex, $namespaceEndIndex);
|
||||
$namespace = substr($namespace, 0, -\strlen($newNamespace)).$newNamespace;
|
||||
|
||||
$newNamespace = Tokens::fromCode('<?php namespace '.$namespace.';');
|
||||
$newNamespace->clearRange(0, 2);
|
||||
$newNamespace->clearEmptyTokens();
|
||||
|
||||
$tokens->insertAt($namespaceStartIndex, $newNamespace);
|
||||
}
|
||||
}
|
||||
|
||||
private function calculateClassyName(\SplFileInfo $file, ?string $namespace, string $currentName): string
|
||||
{
|
||||
$name = $file->getBasename('.php');
|
||||
$maxNamespace = $this->calculateMaxNamespace($file, $namespace);
|
||||
|
||||
if (null !== $this->configuration['dir']) {
|
||||
return ('' !== $maxNamespace ? (str_replace('\\', '_', $maxNamespace).'_') : '').$name;
|
||||
}
|
||||
|
||||
$namespaceParts = array_reverse(explode('\\', $maxNamespace));
|
||||
|
||||
foreach ($namespaceParts as $namespacePart) {
|
||||
$nameCandidate = sprintf('%s_%s', $namespacePart, $name);
|
||||
|
||||
if (strtolower($nameCandidate) !== strtolower(substr($currentName, -\strlen($nameCandidate)))) {
|
||||
break;
|
||||
}
|
||||
|
||||
$name = $nameCandidate;
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
private function calculateMaxNamespace(\SplFileInfo $file, ?string $namespace): string
|
||||
{
|
||||
if (null === $this->configuration['dir']) {
|
||||
$root = \dirname($file->getRealPath());
|
||||
|
||||
while ($root !== \dirname($root)) {
|
||||
$root = \dirname($root);
|
||||
}
|
||||
} else {
|
||||
$root = realpath($this->configuration['dir']);
|
||||
}
|
||||
|
||||
$namespaceAccordingToFileLocation = trim(str_replace(\DIRECTORY_SEPARATOR, '\\', substr(\dirname($file->getRealPath()), \strlen($root))), '\\');
|
||||
|
||||
if (null === $namespace) {
|
||||
return $namespaceAccordingToFileLocation;
|
||||
}
|
||||
|
||||
$namespaceAccordingToFileLocationPartsReversed = array_reverse(explode('\\', $namespaceAccordingToFileLocation));
|
||||
$namespacePartsReversed = array_reverse(explode('\\', $namespace));
|
||||
|
||||
foreach ($namespacePartsReversed as $key => $namespaceParte) {
|
||||
if (!isset($namespaceAccordingToFileLocationPartsReversed[$key])) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (strtolower($namespaceParte) !== strtolower($namespaceAccordingToFileLocationPartsReversed[$key])) {
|
||||
break;
|
||||
}
|
||||
|
||||
unset($namespaceAccordingToFileLocationPartsReversed[$key]);
|
||||
}
|
||||
|
||||
return implode('\\', array_reverse($namespaceAccordingToFileLocationPartsReversed));
|
||||
}
|
||||
}
|
||||
Vendored
+175
@@ -0,0 +1,175 @@
|
||||
<?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\Casing;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class ClassReferenceNameCasingFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'When referencing an internal class it must be written using the correct casing.',
|
||||
[
|
||||
new CodeSample("<?php\nthrow new \\exception();\n"),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_STRING);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$namespacesAnalyzer = new NamespacesAnalyzer();
|
||||
$namespaceUsesAnalyzer = new NamespaceUsesAnalyzer();
|
||||
$classNames = $this->getClassNames();
|
||||
|
||||
foreach ($namespacesAnalyzer->getDeclarations($tokens) as $namespace) {
|
||||
$uses = [];
|
||||
|
||||
foreach ($namespaceUsesAnalyzer->getDeclarationsInNamespace($tokens, $namespace) as $use) {
|
||||
$uses[strtolower($use->getShortName())] = true;
|
||||
}
|
||||
|
||||
foreach ($this->getClassReference($tokens, $namespace) as $reference) {
|
||||
$currentContent = $tokens[$reference]->getContent();
|
||||
$lowerCurrentContent = strtolower($currentContent);
|
||||
|
||||
if (isset($classNames[$lowerCurrentContent]) && $currentContent !== $classNames[$lowerCurrentContent] && !isset($uses[$lowerCurrentContent])) {
|
||||
$tokens[$reference] = new Token([T_STRING, $classNames[$lowerCurrentContent]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getClassReference(Tokens $tokens, NamespaceAnalysis $namespace): \Generator
|
||||
{
|
||||
static $notBeforeKinds;
|
||||
static $blockKinds;
|
||||
|
||||
if (null === $notBeforeKinds) {
|
||||
$notBeforeKinds = [
|
||||
CT::T_USE_TRAIT,
|
||||
T_AS,
|
||||
T_CASE, // PHP 8.1 trait enum-case
|
||||
T_CLASS,
|
||||
T_CONST,
|
||||
T_DOUBLE_ARROW,
|
||||
T_DOUBLE_COLON,
|
||||
T_FUNCTION,
|
||||
T_INTERFACE,
|
||||
T_OBJECT_OPERATOR,
|
||||
T_TRAIT,
|
||||
];
|
||||
|
||||
if (\defined('T_ENUM')) { // @TODO: drop condition when PHP 8.1+ is required
|
||||
$notBeforeKinds[] = T_ENUM;
|
||||
}
|
||||
}
|
||||
|
||||
if (null === $blockKinds) {
|
||||
$blockKinds = ['before' => [','], 'after' => [',']];
|
||||
|
||||
foreach (Tokens::getBlockEdgeDefinitions() as $definition) {
|
||||
$blockKinds['before'][] = $definition['start'];
|
||||
$blockKinds['after'][] = $definition['end'];
|
||||
}
|
||||
}
|
||||
|
||||
$namespaceIsGlobal = $namespace->isGlobalNamespace();
|
||||
|
||||
for ($index = $namespace->getScopeStartIndex(); $index < $namespace->getScopeEndIndex(); ++$index) {
|
||||
if (!$tokens[$index]->isGivenKind(T_STRING)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$nextIndex = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
if ($tokens[$nextIndex]->isGivenKind(T_NS_SEPARATOR)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$prevIndex = $tokens->getPrevMeaningfulToken($index);
|
||||
$nextIndex = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
$isNamespaceSeparator = $tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR);
|
||||
|
||||
if (!$isNamespaceSeparator && !$namespaceIsGlobal) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($isNamespaceSeparator) {
|
||||
$prevIndex = $tokens->getPrevMeaningfulToken($prevIndex);
|
||||
|
||||
if ($tokens[$prevIndex]->isGivenKind(T_STRING)) {
|
||||
continue;
|
||||
}
|
||||
} elseif ($tokens[$prevIndex]->isGivenKind($notBeforeKinds)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$prevIndex]->equalsAny($blockKinds['before']) && $tokens[$nextIndex]->equalsAny($blockKinds['after'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$tokens[$prevIndex]->isGivenKind(T_NEW) && $tokens[$nextIndex]->equalsAny(['(', ';', [T_CLOSE_TAG]])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
yield $index;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function getClassNames(): array
|
||||
{
|
||||
static $classes = null;
|
||||
|
||||
if (null === $classes) {
|
||||
$classes = [];
|
||||
|
||||
foreach (get_declared_classes() as $class) {
|
||||
if ((new \ReflectionClass($class))->isInternal()) {
|
||||
$classes[strtolower($class)] = $class;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $classes;
|
||||
}
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
<?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\Casing;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Fixer for constants case.
|
||||
*
|
||||
* @author Pol Dellaiera <pol.dellaiera@protonmail.com>
|
||||
*/
|
||||
final class ConstantCaseFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* Hold the function that will be used to convert the constants.
|
||||
*
|
||||
* @var callable
|
||||
*/
|
||||
private $fixFunction;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configure(array $configuration): void
|
||||
{
|
||||
parent::configure($configuration);
|
||||
|
||||
if ('lower' === $this->configuration['case']) {
|
||||
$this->fixFunction = static function (string $content): string {
|
||||
return strtolower($content);
|
||||
};
|
||||
}
|
||||
|
||||
if ('upper' === $this->configuration['case']) {
|
||||
$this->fixFunction = static function (string $content): string {
|
||||
return strtoupper($content);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'The PHP constants `true`, `false`, and `null` MUST be written using the correct casing.',
|
||||
[
|
||||
new CodeSample("<?php\n\$a = FALSE;\n\$b = True;\n\$c = nuLL;\n"),
|
||||
new CodeSample("<?php\n\$a = FALSE;\n\$b = True;\n\$c = nuLL;\n", ['case' => 'upper']),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_STRING);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('case', 'Whether to use the `upper` or `lower` case syntax.'))
|
||||
->setAllowedValues(['upper', 'lower'])
|
||||
->setDefault('lower')
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$fixFunction = $this->fixFunction;
|
||||
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->isNativeConstant()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
$this->isNeighbourAccepted($tokens, $tokens->getPrevMeaningfulToken($index))
|
||||
&& $this->isNeighbourAccepted($tokens, $tokens->getNextMeaningfulToken($index))
|
||||
&& !$this->isEnumCaseName($tokens, $index)
|
||||
) {
|
||||
$tokens[$index] = new Token([$token->getId(), $fixFunction($token->getContent())]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function isNeighbourAccepted(Tokens $tokens, int $index): bool
|
||||
{
|
||||
static $forbiddenTokens = null;
|
||||
|
||||
if (null === $forbiddenTokens) {
|
||||
$forbiddenTokens = array_merge(
|
||||
[
|
||||
T_AS,
|
||||
T_CLASS,
|
||||
T_CONST,
|
||||
T_EXTENDS,
|
||||
T_IMPLEMENTS,
|
||||
T_INSTANCEOF,
|
||||
T_INSTEADOF,
|
||||
T_INTERFACE,
|
||||
T_NEW,
|
||||
T_NS_SEPARATOR,
|
||||
T_PAAMAYIM_NEKUDOTAYIM,
|
||||
T_TRAIT,
|
||||
T_USE,
|
||||
CT::T_USE_TRAIT,
|
||||
CT::T_USE_LAMBDA,
|
||||
],
|
||||
Token::getObjectOperatorKinds()
|
||||
);
|
||||
}
|
||||
|
||||
$token = $tokens[$index];
|
||||
|
||||
if ($token->equalsAny(['{', '}'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !$token->isGivenKind($forbiddenTokens);
|
||||
}
|
||||
|
||||
private function isEnumCaseName(Tokens $tokens, int $index): bool
|
||||
{
|
||||
if (!\defined('T_ENUM') || !$tokens->isTokenKindFound(T_ENUM)) { // @TODO: drop condition when PHP 8.1+ is required
|
||||
return false;
|
||||
}
|
||||
|
||||
$prevIndex = $tokens->getPrevMeaningfulToken($index);
|
||||
|
||||
if (null === $prevIndex || !$tokens[$prevIndex]->isGivenKind(T_CASE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$tokens->isTokenKindFound(T_SWITCH)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$prevIndex = $tokens->getPrevTokenOfKind($prevIndex, [[T_ENUM], [T_SWITCH]]);
|
||||
|
||||
return null !== $prevIndex && $tokens[$prevIndex]->isGivenKind(T_ENUM);
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
<?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\Casing;
|
||||
|
||||
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 IntegerLiteralCaseFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Integer literals must be in correct case.',
|
||||
[
|
||||
new CodeSample(
|
||||
"<?php\n\$foo = 0Xff;\n\$bar = 0B11111111;\n"
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_LNUMBER);
|
||||
}
|
||||
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->isGivenKind(T_LNUMBER)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$content = $token->getContent();
|
||||
|
||||
if (1 !== Preg::match('#^0[bxoBXO][0-9a-fA-F]+$#', $content)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$newContent = '0'.strtolower($content[1]).strtoupper(substr($content, 2));
|
||||
|
||||
if ($content === $newContent) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens[$index] = new Token([T_LNUMBER, $newContent]);
|
||||
}
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
<?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\Casing;
|
||||
|
||||
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 ¶2.5.
|
||||
*
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class LowercaseKeywordsFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* @var int[]
|
||||
*/
|
||||
private static array $excludedTokens = [T_HALT_COMPILER];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'PHP keywords MUST be in lower case.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
FOREACH($a AS $B) {
|
||||
TRY {
|
||||
NEW $C($a, ISSET($B));
|
||||
WHILE($B) {
|
||||
INCLUDE "test.php";
|
||||
}
|
||||
} CATCH(\Exception $e) {
|
||||
EXIT(1);
|
||||
}
|
||||
}
|
||||
'
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound(Token::getKeywords());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($tokens as $index => $token) {
|
||||
if ($token->isKeyword() && !$token->isGivenKind(self::$excludedTokens)) {
|
||||
$tokens[$index] = new Token([$token->getId(), strtolower($token->getContent())]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+106
@@ -0,0 +1,106 @@
|
||||
<?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\Casing;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Kuba Werłos <werlos@gmail.com>
|
||||
*/
|
||||
final class LowercaseStaticReferenceFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Class static references `self`, `static` and `parent` MUST be in lower case.',
|
||||
[
|
||||
new CodeSample('<?php
|
||||
class Foo extends Bar
|
||||
{
|
||||
public function baz1()
|
||||
{
|
||||
return STATIC::baz2();
|
||||
}
|
||||
|
||||
public function baz2($x)
|
||||
{
|
||||
return $x instanceof Self;
|
||||
}
|
||||
|
||||
public function baz3(PaRent $x)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
'),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
class Foo extends Bar
|
||||
{
|
||||
public function baz(?self $x) : SELF
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
'
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound([T_STATIC, T_STRING]);
|
||||
}
|
||||
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->equalsAny([[T_STRING, 'self'], [T_STATIC, 'static'], [T_STRING, 'parent']], false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$newContent = strtolower($token->getContent());
|
||||
if ($token->getContent() === $newContent) {
|
||||
continue; // case is already correct
|
||||
}
|
||||
|
||||
$prevIndex = $tokens->getPrevMeaningfulToken($index);
|
||||
if ($tokens[$prevIndex]->isGivenKind([T_CONST, T_DOUBLE_COLON, T_FUNCTION, T_NAMESPACE, T_NS_SEPARATOR]) || $tokens[$prevIndex]->isObjectOperator()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$nextIndex = $tokens->getNextMeaningfulToken($index);
|
||||
if ($tokens[$nextIndex]->isGivenKind([T_FUNCTION, T_NS_SEPARATOR, T_PRIVATE, T_PROTECTED, T_PUBLIC, T_STRING, CT::T_NULLABLE_TYPE])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('static' === $newContent && $tokens[$nextIndex]->isGivenKind(T_VARIABLE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens[$index] = new Token([$token->getId(), $newContent]);
|
||||
}
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
<?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\Casing;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author ntzm
|
||||
*/
|
||||
final class MagicConstantCasingFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Magic constants should be referred to using the correct casing.',
|
||||
[new CodeSample("<?php\necho __dir__;\n")]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound($this->getMagicConstantTokens());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$magicConstants = $this->getMagicConstants();
|
||||
$magicConstantTokens = $this->getMagicConstantTokens();
|
||||
|
||||
foreach ($tokens as $index => $token) {
|
||||
if ($token->isGivenKind($magicConstantTokens)) {
|
||||
$tokens[$index] = new Token([$token->getId(), $magicConstants[$token->getId()]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
private function getMagicConstants(): array
|
||||
{
|
||||
static $magicConstants = null;
|
||||
|
||||
if (null === $magicConstants) {
|
||||
$magicConstants = [
|
||||
T_LINE => '__LINE__',
|
||||
T_FILE => '__FILE__',
|
||||
T_DIR => '__DIR__',
|
||||
T_FUNC_C => '__FUNCTION__',
|
||||
T_CLASS_C => '__CLASS__',
|
||||
T_METHOD_C => '__METHOD__',
|
||||
T_NS_C => '__NAMESPACE__',
|
||||
CT::T_CLASS_CONSTANT => 'class',
|
||||
T_TRAIT_C => '__TRAIT__',
|
||||
];
|
||||
}
|
||||
|
||||
return $magicConstants;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int>
|
||||
*/
|
||||
private function getMagicConstantTokens(): array
|
||||
{
|
||||
static $magicConstantTokens = null;
|
||||
|
||||
if (null === $magicConstantTokens) {
|
||||
$magicConstantTokens = array_keys($this->getMagicConstants());
|
||||
}
|
||||
|
||||
return $magicConstantTokens;
|
||||
}
|
||||
}
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
<?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\Casing;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class MagicMethodCasingFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* @var array<string,string>
|
||||
*/
|
||||
private static array $magicNames = [
|
||||
'__call' => '__call',
|
||||
'__callstatic' => '__callStatic',
|
||||
'__clone' => '__clone',
|
||||
'__construct' => '__construct',
|
||||
'__debuginfo' => '__debugInfo',
|
||||
'__destruct' => '__destruct',
|
||||
'__get' => '__get',
|
||||
'__invoke' => '__invoke',
|
||||
'__isset' => '__isset',
|
||||
'__serialize' => '__serialize',
|
||||
'__set' => '__set',
|
||||
'__set_state' => '__set_state',
|
||||
'__sleep' => '__sleep',
|
||||
'__tostring' => '__toString',
|
||||
'__unserialize' => '__unserialize',
|
||||
'__unset' => '__unset',
|
||||
'__wakeup' => '__wakeup',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Magic method definitions and calls must be using the correct casing.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
class Foo
|
||||
{
|
||||
public function __Sleep()
|
||||
{
|
||||
}
|
||||
}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
$foo->__INVOKE(1);
|
||||
'
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_STRING) && $tokens->isAnyTokenKindsFound(array_merge([T_FUNCTION, T_DOUBLE_COLON], Token::getObjectOperatorKinds()));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$inClass = 0;
|
||||
$tokenCount = \count($tokens);
|
||||
|
||||
for ($index = 1; $index < $tokenCount - 2; ++$index) {
|
||||
if (0 === $inClass && $tokens[$index]->isClassy()) {
|
||||
$inClass = 1;
|
||||
$index = $tokens->getNextTokenOfKind($index, ['{']);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (0 !== $inClass) {
|
||||
if ($tokens[$index]->equals('{')) {
|
||||
++$inClass;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$index]->equals('}')) {
|
||||
--$inClass;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$tokens[$index]->isGivenKind(T_STRING)) {
|
||||
continue; // wrong type
|
||||
}
|
||||
|
||||
$content = $tokens[$index]->getContent();
|
||||
|
||||
if (!str_starts_with($content, '__')) {
|
||||
continue; // cheap look ahead
|
||||
}
|
||||
|
||||
$name = strtolower($content);
|
||||
|
||||
if (!$this->isMagicMethodName($name)) {
|
||||
continue; // method name is not one of the magic ones we can fix
|
||||
}
|
||||
|
||||
$nameInCorrectCasing = $this->getMagicMethodNameInCorrectCasing($name);
|
||||
if ($nameInCorrectCasing === $content) {
|
||||
continue; // method name is already in the correct casing, no fix needed
|
||||
}
|
||||
|
||||
if ($this->isFunctionSignature($tokens, $index)) {
|
||||
if (0 !== $inClass) {
|
||||
// this is a method definition we want to fix
|
||||
$this->setTokenToCorrectCasing($tokens, $index, $nameInCorrectCasing);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->isMethodCall($tokens, $index)) {
|
||||
$this->setTokenToCorrectCasing($tokens, $index, $nameInCorrectCasing);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
('__callstatic' === $name || '__set_state' === $name)
|
||||
&& $this->isStaticMethodCall($tokens, $index)
|
||||
) {
|
||||
$this->setTokenToCorrectCasing($tokens, $index, $nameInCorrectCasing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function isFunctionSignature(Tokens $tokens, int $index): bool
|
||||
{
|
||||
$prevIndex = $tokens->getPrevMeaningfulToken($index);
|
||||
if (!$tokens[$prevIndex]->isGivenKind(T_FUNCTION)) {
|
||||
return false; // not a method signature
|
||||
}
|
||||
|
||||
return $tokens[$tokens->getNextMeaningfulToken($index)]->equals('(');
|
||||
}
|
||||
|
||||
private function isMethodCall(Tokens $tokens, int $index): bool
|
||||
{
|
||||
$prevIndex = $tokens->getPrevMeaningfulToken($index);
|
||||
if (!$tokens[$prevIndex]->isObjectOperator()) {
|
||||
return false; // not a "simple" method call
|
||||
}
|
||||
|
||||
return $tokens[$tokens->getNextMeaningfulToken($index)]->equals('(');
|
||||
}
|
||||
|
||||
private function isStaticMethodCall(Tokens $tokens, int $index): bool
|
||||
{
|
||||
$prevIndex = $tokens->getPrevMeaningfulToken($index);
|
||||
if (!$tokens[$prevIndex]->isGivenKind(T_DOUBLE_COLON)) {
|
||||
return false; // not a "simple" static method call
|
||||
}
|
||||
|
||||
return $tokens[$tokens->getNextMeaningfulToken($index)]->equals('(');
|
||||
}
|
||||
|
||||
private function isMagicMethodName(string $name): bool
|
||||
{
|
||||
return isset(self::$magicNames[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name name of a magic method
|
||||
*/
|
||||
private function getMagicMethodNameInCorrectCasing(string $name): string
|
||||
{
|
||||
return self::$magicNames[$name];
|
||||
}
|
||||
|
||||
private function setTokenToCorrectCasing(Tokens $tokens, int $index, string $nameInCorrectCasing): void
|
||||
{
|
||||
$tokens[$index] = new Token([T_STRING, $nameInCorrectCasing]);
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
<?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\Casing;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class NativeFunctionCasingFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Function defined by PHP should be called using the correct casing.',
|
||||
[new CodeSample("<?php\nSTRLEN(\$str);\n")]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after FunctionToConstantFixer, NoUselessSprintfFixer, PowToExponentiationFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_STRING);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$functionsAnalyzer = new FunctionsAnalyzer();
|
||||
|
||||
static $nativeFunctionNames = null;
|
||||
|
||||
if (null === $nativeFunctionNames) {
|
||||
$nativeFunctionNames = $this->getNativeFunctionNames();
|
||||
}
|
||||
|
||||
for ($index = 0, $count = $tokens->count(); $index < $count; ++$index) {
|
||||
// test if we are at a function all
|
||||
if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// test if the function call is to a native PHP function
|
||||
$lower = strtolower($tokens[$index]->getContent());
|
||||
if (!\array_key_exists($lower, $nativeFunctionNames)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens[$index] = new Token([T_STRING, $nativeFunctionNames[$lower]]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function getNativeFunctionNames(): array
|
||||
{
|
||||
$allFunctions = get_defined_functions();
|
||||
$functions = [];
|
||||
foreach ($allFunctions['internal'] as $function) {
|
||||
$functions[strtolower($function)] = $function;
|
||||
}
|
||||
|
||||
return $functions;
|
||||
}
|
||||
}
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
<?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\Casing;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\FixerDefinition\VersionSpecification;
|
||||
use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class NativeFunctionTypeDeclarationCasingFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* https://secure.php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration.
|
||||
*
|
||||
* self PHP 5.0
|
||||
* array PHP 5.1
|
||||
* callable PHP 5.4
|
||||
* bool PHP 7.0
|
||||
* float PHP 7.0
|
||||
* int PHP 7.0
|
||||
* string PHP 7.0
|
||||
* iterable PHP 7.1
|
||||
* void PHP 7.1
|
||||
* object PHP 7.2
|
||||
* static PHP 8.0 (return type only)
|
||||
* mixed PHP 8.0
|
||||
* false PHP 8.0 (union return type only)
|
||||
* null PHP 8.0 (union return type only)
|
||||
* never PHP 8.1 (return type only)
|
||||
* true PHP 8.2 (standalone type: https://wiki.php.net/rfc/true-type)
|
||||
* false PHP 8.2 (standalone type: https://wiki.php.net/rfc/null-false-standalone-types)
|
||||
* null PHP 8.2 (standalone type: https://wiki.php.net/rfc/null-false-standalone-types)
|
||||
*
|
||||
* @var array<string, true>
|
||||
*/
|
||||
private array $hints;
|
||||
|
||||
private FunctionsAnalyzer $functionsAnalyzer;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->hints = [
|
||||
'array' => true,
|
||||
'bool' => true,
|
||||
'callable' => true,
|
||||
'float' => true,
|
||||
'int' => true,
|
||||
'iterable' => true,
|
||||
'object' => true,
|
||||
'self' => true,
|
||||
'string' => true,
|
||||
'void' => true,
|
||||
];
|
||||
|
||||
if (\PHP_VERSION_ID >= 80000) {
|
||||
$this->hints['false'] = true;
|
||||
$this->hints['mixed'] = true;
|
||||
$this->hints['null'] = true;
|
||||
$this->hints['static'] = true;
|
||||
}
|
||||
|
||||
if (\PHP_VERSION_ID >= 80100) {
|
||||
$this->hints['never'] = true;
|
||||
}
|
||||
|
||||
if (\PHP_VERSION_ID >= 80200) {
|
||||
$this->hints['true'] = true;
|
||||
}
|
||||
|
||||
$this->functionsAnalyzer = new FunctionsAnalyzer();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Native type hints for functions should use the correct case.',
|
||||
[
|
||||
new CodeSample("<?php\nclass Bar {\n public function Foo(CALLABLE \$bar)\n {\n return 1;\n }\n}\n"),
|
||||
new CodeSample(
|
||||
"<?php\nfunction Foo(INT \$a): Bool\n{\n return true;\n}\n"
|
||||
),
|
||||
new CodeSample(
|
||||
"<?php\nfunction Foo(Iterable \$a): VOID\n{\n echo 'Hello world';\n}\n"
|
||||
),
|
||||
new VersionSpecificCodeSample(
|
||||
"<?php\nfunction Foo(Object \$a)\n{\n return 'hi!';\n}\n",
|
||||
new VersionSpecification(70200)
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAllTokenKindsFound([T_FUNCTION, T_STRING]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
for ($index = $tokens->count() - 1; $index >= 0; --$index) {
|
||||
if ($tokens[$index]->isGivenKind(T_FUNCTION)) {
|
||||
$this->fixFunctionReturnType($tokens, $index);
|
||||
$this->fixFunctionArgumentTypes($tokens, $index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function fixFunctionArgumentTypes(Tokens $tokens, int $index): void
|
||||
{
|
||||
foreach ($this->functionsAnalyzer->getFunctionArguments($tokens, $index) as $argument) {
|
||||
$this->fixArgumentType($tokens, $argument->getTypeAnalysis());
|
||||
}
|
||||
}
|
||||
|
||||
private function fixFunctionReturnType(Tokens $tokens, int $index): void
|
||||
{
|
||||
$this->fixArgumentType($tokens, $this->functionsAnalyzer->getFunctionReturnType($tokens, $index));
|
||||
}
|
||||
|
||||
private function fixArgumentType(Tokens $tokens, ?TypeAnalysis $type = null): void
|
||||
{
|
||||
if (null === $type) {
|
||||
return;
|
||||
}
|
||||
|
||||
for ($index = $type->getStartIndex(); $index <= $type->getEndIndex(); ++$index) {
|
||||
if ($tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(T_NS_SEPARATOR)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$lowerCasedName = strtolower($tokens[$index]->getContent());
|
||||
|
||||
if (!isset($this->hints[$lowerCasedName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens[$index] = new Token([$tokens[$index]->getId(), $lowerCasedName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
<?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\CastNotation;
|
||||
|
||||
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 Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class CastSpacesFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
private const INSIDE_CAST_SPACE_REPLACE_MAP = [
|
||||
' ' => '',
|
||||
"\t" => '',
|
||||
"\n" => '',
|
||||
"\r" => '',
|
||||
"\0" => '',
|
||||
"\x0B" => '',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'A single space or none should be between cast and variable.',
|
||||
[
|
||||
new CodeSample(
|
||||
"<?php\n\$bar = ( string ) \$a;\n\$foo = (int)\$b;\n"
|
||||
),
|
||||
new CodeSample(
|
||||
"<?php\n\$bar = ( string ) \$a;\n\$foo = (int)\$b;\n",
|
||||
['space' => 'single']
|
||||
),
|
||||
new CodeSample(
|
||||
"<?php\n\$bar = ( string ) \$a;\n\$foo = (int) \$b;\n",
|
||||
['space' => 'none']
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after NoShortBoolCastFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return -10;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound(Token::getCastTokenKinds());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->isCast()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens[$index] = new Token([
|
||||
$token->getId(),
|
||||
strtr($token->getContent(), self::INSIDE_CAST_SPACE_REPLACE_MAP),
|
||||
]);
|
||||
|
||||
if ('single' === $this->configuration['space']) {
|
||||
// force single whitespace after cast token:
|
||||
if ($tokens[$index + 1]->isWhitespace(" \t")) {
|
||||
// - if next token is whitespaces that contains only spaces and tabs - override next token with single space
|
||||
$tokens[$index + 1] = new Token([T_WHITESPACE, ' ']);
|
||||
} elseif (!$tokens[$index + 1]->isWhitespace()) {
|
||||
// - if next token is not whitespaces that contains spaces, tabs and new lines - append single space to current token
|
||||
$tokens->insertAt($index + 1, new Token([T_WHITESPACE, ' ']));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// force no whitespace after cast token:
|
||||
if ($tokens[$index + 1]->isWhitespace()) {
|
||||
$tokens->clearAt($index + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('space', 'spacing to apply between cast and variable.'))
|
||||
->setAllowedValues(['none', 'single'])
|
||||
->setDefault('single')
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
<?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\CastNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\FixerDefinition\VersionSpecification;
|
||||
use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class LowercaseCastFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Cast should be written in lower case.',
|
||||
[
|
||||
new VersionSpecificCodeSample(
|
||||
'<?php
|
||||
$a = (BOOLEAN) $b;
|
||||
$a = (BOOL) $b;
|
||||
$a = (INTEGER) $b;
|
||||
$a = (INT) $b;
|
||||
$a = (DOUBLE) $b;
|
||||
$a = (FLoaT) $b;
|
||||
$a = (reaL) $b;
|
||||
$a = (flOAT) $b;
|
||||
$a = (sTRING) $b;
|
||||
$a = (ARRAy) $b;
|
||||
$a = (OBJect) $b;
|
||||
$a = (UNset) $b;
|
||||
$a = (Binary) $b;
|
||||
',
|
||||
new VersionSpecification(null, 70399)
|
||||
),
|
||||
new VersionSpecificCodeSample(
|
||||
'<?php
|
||||
$a = (BOOLEAN) $b;
|
||||
$a = (BOOL) $b;
|
||||
$a = (INTEGER) $b;
|
||||
$a = (INT) $b;
|
||||
$a = (DOUBLE) $b;
|
||||
$a = (FLoaT) $b;
|
||||
$a = (flOAT) $b;
|
||||
$a = (sTRING) $b;
|
||||
$a = (ARRAy) $b;
|
||||
$a = (OBJect) $b;
|
||||
$a = (UNset) $b;
|
||||
$a = (Binary) $b;
|
||||
',
|
||||
new VersionSpecification(70400)
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound(Token::getCastTokenKinds());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
for ($index = 0, $count = $tokens->count(); $index < $count; ++$index) {
|
||||
if (!$tokens[$index]->isCast()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens[$index] = new Token([$tokens[$index]->getId(), strtolower($tokens[$index]->getContent())]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+160
@@ -0,0 +1,160 @@
|
||||
<?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\CastNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFunctionReferenceFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Vladimir Reznichenko <kalessil@gmail.com>
|
||||
*/
|
||||
final class ModernizeTypesCastingFixer extends AbstractFunctionReferenceFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Replaces `intval`, `floatval`, `doubleval`, `strval` and `boolval` function calls with according type casting operator.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
$a = intval($b);
|
||||
$a = floatval($b);
|
||||
$a = doubleval($b);
|
||||
$a = strval ($b);
|
||||
$a = boolval($b);
|
||||
'
|
||||
),
|
||||
],
|
||||
null,
|
||||
'Risky if any of the functions `intval`, `floatval`, `doubleval`, `strval` or `boolval` are overridden.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
// replacement patterns
|
||||
static $replacement = [
|
||||
'intval' => [T_INT_CAST, '(int)'],
|
||||
'floatval' => [T_DOUBLE_CAST, '(float)'],
|
||||
'doubleval' => [T_DOUBLE_CAST, '(float)'],
|
||||
'strval' => [T_STRING_CAST, '(string)'],
|
||||
'boolval' => [T_BOOL_CAST, '(bool)'],
|
||||
];
|
||||
|
||||
$argumentsAnalyzer = new ArgumentsAnalyzer();
|
||||
|
||||
foreach ($replacement as $functionIdentity => $newToken) {
|
||||
$currIndex = 0;
|
||||
|
||||
do {
|
||||
// try getting function reference and translate boundaries for humans
|
||||
$boundaries = $this->find($functionIdentity, $tokens, $currIndex, $tokens->count() - 1);
|
||||
|
||||
if (null === $boundaries) {
|
||||
// next function search, as current one not found
|
||||
continue 2;
|
||||
}
|
||||
|
||||
[$functionName, $openParenthesis, $closeParenthesis] = $boundaries;
|
||||
|
||||
// analysing cursor shift
|
||||
$currIndex = $openParenthesis;
|
||||
|
||||
// indicator that the function is overridden
|
||||
if (1 !== $argumentsAnalyzer->countArguments($tokens, $openParenthesis, $closeParenthesis)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$paramContentEnd = $closeParenthesis;
|
||||
$commaCandidate = $tokens->getPrevMeaningfulToken($paramContentEnd);
|
||||
|
||||
if ($tokens[$commaCandidate]->equals(',')) {
|
||||
$tokens->removeTrailingWhitespace($commaCandidate);
|
||||
$tokens->clearAt($commaCandidate);
|
||||
$paramContentEnd = $commaCandidate;
|
||||
}
|
||||
|
||||
// check if something complex passed as an argument and preserve parentheses then
|
||||
$countParamTokens = 0;
|
||||
|
||||
for ($paramContentIndex = $openParenthesis + 1; $paramContentIndex < $paramContentEnd; ++$paramContentIndex) {
|
||||
// not a space, means some sensible token
|
||||
if (!$tokens[$paramContentIndex]->isGivenKind(T_WHITESPACE)) {
|
||||
++$countParamTokens;
|
||||
}
|
||||
}
|
||||
|
||||
$preserveParentheses = $countParamTokens > 1;
|
||||
|
||||
$afterCloseParenthesisIndex = $tokens->getNextMeaningfulToken($closeParenthesis);
|
||||
$afterCloseParenthesisToken = $tokens[$afterCloseParenthesisIndex];
|
||||
$wrapInParentheses = $afterCloseParenthesisToken->equalsAny(['[', '{']) || $afterCloseParenthesisToken->isGivenKind(T_POW);
|
||||
|
||||
// analyse namespace specification (root one or none) and decide what to do
|
||||
$prevTokenIndex = $tokens->getPrevMeaningfulToken($functionName);
|
||||
|
||||
if ($tokens[$prevTokenIndex]->isGivenKind(T_NS_SEPARATOR)) {
|
||||
// get rid of root namespace when it used
|
||||
$tokens->removeTrailingWhitespace($prevTokenIndex);
|
||||
$tokens->clearAt($prevTokenIndex);
|
||||
}
|
||||
|
||||
// perform transformation
|
||||
$replacementSequence = [
|
||||
new Token($newToken),
|
||||
new Token([T_WHITESPACE, ' ']),
|
||||
];
|
||||
|
||||
if ($wrapInParentheses) {
|
||||
array_unshift($replacementSequence, new Token('('));
|
||||
}
|
||||
|
||||
if (!$preserveParentheses) {
|
||||
// closing parenthesis removed with leading spaces
|
||||
$tokens->removeLeadingWhitespace($closeParenthesis);
|
||||
$tokens->clearAt($closeParenthesis);
|
||||
|
||||
// opening parenthesis removed with trailing spaces
|
||||
$tokens->removeLeadingWhitespace($openParenthesis);
|
||||
$tokens->removeTrailingWhitespace($openParenthesis);
|
||||
$tokens->clearAt($openParenthesis);
|
||||
} else {
|
||||
// we'll need to provide a space after a casting operator
|
||||
$tokens->removeTrailingWhitespace($functionName);
|
||||
}
|
||||
|
||||
if ($wrapInParentheses) {
|
||||
$tokens->insertAt($closeParenthesis, new Token(')'));
|
||||
}
|
||||
|
||||
$tokens->overrideRange($functionName, $functionName, $replacementSequence);
|
||||
|
||||
// nested transformations support
|
||||
$currIndex = $functionName;
|
||||
} while (null !== $currIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
<?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\CastNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class NoShortBoolCastFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before CastSpacesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return -9;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Short cast `bool` using double exclamation mark should not be used.',
|
||||
[new CodeSample("<?php\n\$a = !!\$b;\n")]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound('!');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
for ($index = \count($tokens) - 1; $index > 1; --$index) {
|
||||
if ($tokens[$index]->equals('!')) {
|
||||
$index = $this->fixShortCast($tokens, $index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function fixShortCast(Tokens $tokens, int $index): int
|
||||
{
|
||||
for ($i = $index - 1; $i > 1; --$i) {
|
||||
if ($tokens[$i]->equals('!')) {
|
||||
$this->fixShortCastToBoolCast($tokens, $i, $index);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$tokens[$i]->isComment() && !$tokens[$i]->isWhitespace()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $i;
|
||||
}
|
||||
|
||||
private function fixShortCastToBoolCast(Tokens $tokens, int $start, int $end): void
|
||||
{
|
||||
for (; $start <= $end; ++$start) {
|
||||
if (
|
||||
!$tokens[$start]->isComment()
|
||||
&& !($tokens[$start]->isWhitespace() && $tokens[$start - 1]->isComment())
|
||||
) {
|
||||
$tokens->clearAt($start);
|
||||
}
|
||||
}
|
||||
|
||||
$tokens->insertAt($start, new Token([T_BOOL_CAST, '(bool)']));
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
<?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\CastNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class NoUnsetCastFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Variables must be set `null` instead of using `(unset)` casting.',
|
||||
[new CodeSample("<?php\n\$a = (unset) \$b;\n")]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_UNSET_CAST);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before BinaryOperatorSpacesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
for ($index = \count($tokens) - 1; $index > 0; --$index) {
|
||||
if ($tokens[$index]->isGivenKind(T_UNSET_CAST)) {
|
||||
$this->fixUnsetCast($tokens, $index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function fixUnsetCast(Tokens $tokens, int $index): void
|
||||
{
|
||||
$assignmentIndex = $tokens->getPrevMeaningfulToken($index);
|
||||
if (null === $assignmentIndex || !$tokens[$assignmentIndex]->equals('=')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$varIndex = $tokens->getNextMeaningfulToken($index);
|
||||
if (null === $varIndex || !$tokens[$varIndex]->isGivenKind(T_VARIABLE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$afterVar = $tokens->getNextMeaningfulToken($varIndex);
|
||||
if (null === $afterVar || !$tokens[$afterVar]->equalsAny([';', [T_CLOSE_TAG]])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$nextIsWhiteSpace = $tokens[$assignmentIndex + 1]->isWhitespace();
|
||||
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($index);
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($varIndex);
|
||||
|
||||
++$assignmentIndex;
|
||||
if (!$nextIsWhiteSpace) {
|
||||
$tokens->insertAt($assignmentIndex, new Token([T_WHITESPACE, ' ']));
|
||||
}
|
||||
|
||||
++$assignmentIndex;
|
||||
$tokens->insertAt($assignmentIndex, new Token([T_STRING, 'null']));
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
<?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\CastNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\FixerDefinition\VersionSpecification;
|
||||
use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class ShortScalarCastFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Cast `(boolean)` and `(integer)` should be written as `(bool)` and `(int)`, `(double)` and `(real)` as `(float)`, `(binary)` as `(string)`.',
|
||||
[
|
||||
new VersionSpecificCodeSample(
|
||||
"<?php\n\$a = (boolean) \$b;\n\$a = (integer) \$b;\n\$a = (double) \$b;\n\$a = (real) \$b;\n\n\$a = (binary) \$b;\n",
|
||||
new VersionSpecification(null, 70399)
|
||||
),
|
||||
new VersionSpecificCodeSample(
|
||||
"<?php\n\$a = (boolean) \$b;\n\$a = (integer) \$b;\n\$a = (double) \$b;\n\n\$a = (binary) \$b;\n",
|
||||
new VersionSpecification(70400)
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound(Token::getCastTokenKinds());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
static $castMap = [
|
||||
'boolean' => 'bool',
|
||||
'integer' => 'int',
|
||||
'double' => 'float',
|
||||
'real' => 'float',
|
||||
'binary' => 'string',
|
||||
];
|
||||
|
||||
for ($index = 0, $count = $tokens->count(); $index < $count; ++$index) {
|
||||
if (!$tokens[$index]->isCast()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$castFrom = trim(substr($tokens[$index]->getContent(), 1, -1));
|
||||
$castFromLowered = strtolower($castFrom);
|
||||
|
||||
if (!\array_key_exists($castFromLowered, $castMap)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens[$index] = new Token([
|
||||
$tokens[$index]->getId(),
|
||||
str_replace($castFrom, $castMap[$castFromLowered], $tokens[$index]->getContent()),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+578
@@ -0,0 +1,578 @@
|
||||
<?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\ClassNotation;
|
||||
|
||||
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\FixerDefinition\VersionSpecification;
|
||||
use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
|
||||
use PhpCsFixer\Preg;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use PhpCsFixer\Tokenizer\TokensAnalyzer;
|
||||
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
|
||||
|
||||
/**
|
||||
* Make sure there is one blank line above and below class elements.
|
||||
*
|
||||
* The exception is when an element is the first or last item in a 'classy'.
|
||||
*/
|
||||
final class ClassAttributesSeparationFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public const SPACING_NONE = 'none';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public const SPACING_ONE = 'one';
|
||||
|
||||
private const SPACING_ONLY_IF_META = 'only_if_meta';
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private array $classElementTypes = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configure(array $configuration): void
|
||||
{
|
||||
parent::configure($configuration);
|
||||
|
||||
$this->classElementTypes = []; // reset previous configuration
|
||||
|
||||
foreach ($this->configuration['elements'] as $elementType => $spacing) {
|
||||
$this->classElementTypes[$elementType] = $spacing;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Class, trait and interface elements must be separated with one or none blank line.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
final class Sample
|
||||
{
|
||||
protected function foo()
|
||||
{
|
||||
}
|
||||
protected function bar()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
class Sample
|
||||
{private $a; // foo
|
||||
/** second in a hour */
|
||||
private $b;
|
||||
}
|
||||
',
|
||||
['elements' => ['property' => self::SPACING_ONE]]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
class Sample
|
||||
{
|
||||
const A = 1;
|
||||
/** seconds in some hours */
|
||||
const B = 3600;
|
||||
}
|
||||
',
|
||||
['elements' => ['const' => self::SPACING_ONE]]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
class Sample
|
||||
{
|
||||
/** @var int */
|
||||
const SECOND = 1;
|
||||
/** @var int */
|
||||
const MINUTE = 60;
|
||||
|
||||
const HOUR = 3600;
|
||||
|
||||
const DAY = 86400;
|
||||
}
|
||||
',
|
||||
['elements' => ['const' => self::SPACING_ONLY_IF_META]]
|
||||
),
|
||||
new VersionSpecificCodeSample(
|
||||
'<?php
|
||||
class Sample
|
||||
{
|
||||
public $a;
|
||||
#[SetUp]
|
||||
public $b;
|
||||
/** @var string */
|
||||
public $c;
|
||||
/** @internal */
|
||||
#[Assert\String()]
|
||||
public $d;
|
||||
|
||||
public $e;
|
||||
}
|
||||
',
|
||||
new VersionSpecification(80000),
|
||||
['elements' => ['property' => self::SPACING_ONLY_IF_META]]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before BracesFixer, IndentationTypeFixer, NoExtraBlankLinesFixer, StatementIndentationFixer.
|
||||
* Must run after OrderedClassElementsFixer, SingleClassElementPerStatementFixer, VisibilityRequiredFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 55;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($this->getElementsByClass($tokens) as $class) {
|
||||
$elements = $class['elements'];
|
||||
$elementCount = \count($elements);
|
||||
|
||||
if (0 === $elementCount) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($this->classElementTypes[$elements[0]['type']])) {
|
||||
$this->fixSpaceBelowClassElement($tokens, $class);
|
||||
$this->fixSpaceAboveClassElement($tokens, $class, 0);
|
||||
}
|
||||
|
||||
for ($index = 1; $index < $elementCount; ++$index) {
|
||||
if (isset($this->classElementTypes[$elements[$index]['type']])) {
|
||||
$this->fixSpaceAboveClassElement($tokens, $class, $index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('elements', 'Dictionary of `const|method|property|trait_import|case` => `none|one|only_if_meta` values.'))
|
||||
->setAllowedTypes(['array'])
|
||||
->setAllowedValues([static function (array $option): bool {
|
||||
foreach ($option as $type => $spacing) {
|
||||
$supportedTypes = ['const', 'method', 'property', 'trait_import', 'case'];
|
||||
|
||||
if (!\in_array($type, $supportedTypes, true)) {
|
||||
throw new InvalidOptionsException(
|
||||
sprintf(
|
||||
'Unexpected element type, expected any of "%s", got "%s".',
|
||||
implode('", "', $supportedTypes),
|
||||
\gettype($type).'#'.$type
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$supportedSpacings = [self::SPACING_NONE, self::SPACING_ONE, self::SPACING_ONLY_IF_META];
|
||||
|
||||
if (!\in_array($spacing, $supportedSpacings, true)) {
|
||||
throw new InvalidOptionsException(
|
||||
sprintf(
|
||||
'Unexpected spacing for element type "%s", expected any of "%s", got "%s".',
|
||||
$spacing,
|
||||
implode('", "', $supportedSpacings),
|
||||
\is_object($spacing) ? \get_class($spacing) : (null === $spacing ? 'null' : \gettype($spacing).'#'.$spacing)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}])
|
||||
->setDefault([
|
||||
'const' => self::SPACING_ONE,
|
||||
'method' => self::SPACING_ONE,
|
||||
'property' => self::SPACING_ONE,
|
||||
'trait_import' => self::SPACING_NONE,
|
||||
'case' => self::SPACING_NONE,
|
||||
])
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix spacing above an element of a class, interface or trait.
|
||||
*
|
||||
* Deals with comments, PHPDocs and spaces above the element with respect to the position of the
|
||||
* element within the class, interface or trait.
|
||||
*/
|
||||
private function fixSpaceAboveClassElement(Tokens $tokens, array $class, int $elementIndex): void
|
||||
{
|
||||
$element = $class['elements'][$elementIndex];
|
||||
$elementAboveEnd = isset($class['elements'][$elementIndex + 1]) ? $class['elements'][$elementIndex + 1]['end'] : 0;
|
||||
$nonWhiteAbove = $tokens->getPrevNonWhitespace($element['start']);
|
||||
|
||||
// element is directly after class open brace
|
||||
if ($nonWhiteAbove === $class['open']) {
|
||||
$this->correctLineBreaks($tokens, $nonWhiteAbove, $element['start'], 1);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// deal with comments above an element
|
||||
if ($tokens[$nonWhiteAbove]->isGivenKind(T_COMMENT)) {
|
||||
// check if the comment belongs to the previous element
|
||||
if ($elementAboveEnd === $nonWhiteAbove) {
|
||||
$this->correctLineBreaks($tokens, $nonWhiteAbove, $element['start'], $this->determineRequiredLineCount($tokens, $class, $elementIndex));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// more than one line break, always bring it back to 2 line breaks between the element start and what is above it
|
||||
if ($tokens[$nonWhiteAbove + 1]->isWhitespace() && substr_count($tokens[$nonWhiteAbove + 1]->getContent(), "\n") > 1) {
|
||||
$this->correctLineBreaks($tokens, $nonWhiteAbove, $element['start'], 2);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// there are 2 cases:
|
||||
if (
|
||||
1 === $element['start'] - $nonWhiteAbove
|
||||
|| $tokens[$nonWhiteAbove - 1]->isWhitespace() && substr_count($tokens[$nonWhiteAbove - 1]->getContent(), "\n") > 0
|
||||
|| $tokens[$nonWhiteAbove + 1]->isWhitespace() && substr_count($tokens[$nonWhiteAbove + 1]->getContent(), "\n") > 0
|
||||
) {
|
||||
// 1. The comment is meant for the element (although not a PHPDoc),
|
||||
// make sure there is one line break between the element and the comment...
|
||||
$this->correctLineBreaks($tokens, $nonWhiteAbove, $element['start'], 1);
|
||||
// ... and make sure there is blank line above the comment (with the exception when it is directly after a class opening)
|
||||
$nonWhiteAbove = $this->findCommentBlockStart($tokens, $nonWhiteAbove, $elementAboveEnd);
|
||||
$nonWhiteAboveComment = $tokens->getPrevNonWhitespace($nonWhiteAbove);
|
||||
|
||||
$this->correctLineBreaks($tokens, $nonWhiteAboveComment, $nonWhiteAbove, $nonWhiteAboveComment === $class['open'] ? 1 : 2);
|
||||
} else {
|
||||
// 2. The comment belongs to the code above the element,
|
||||
// make sure there is a blank line above the element (i.e. 2 line breaks)
|
||||
$this->correctLineBreaks($tokens, $nonWhiteAbove, $element['start'], 2);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// deal with element with a PHPDoc/attribute above it
|
||||
if ($tokens[$nonWhiteAbove]->isGivenKind([T_DOC_COMMENT, CT::T_ATTRIBUTE_CLOSE])) {
|
||||
// there should be one linebreak between the element and the attribute above it
|
||||
$this->correctLineBreaks($tokens, $nonWhiteAbove, $element['start'], 1);
|
||||
|
||||
// make sure there is blank line above the comment (with the exception when it is directly after a class opening)
|
||||
$nonWhiteAbove = $this->findCommentBlockStart($tokens, $nonWhiteAbove, $elementAboveEnd);
|
||||
$nonWhiteAboveComment = $tokens->getPrevNonWhitespace($nonWhiteAbove);
|
||||
|
||||
$this->correctLineBreaks($tokens, $nonWhiteAboveComment, $nonWhiteAbove, $nonWhiteAboveComment === $class['open'] ? 1 : 2);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->correctLineBreaks($tokens, $nonWhiteAbove, $element['start'], $this->determineRequiredLineCount($tokens, $class, $elementIndex));
|
||||
}
|
||||
|
||||
private function determineRequiredLineCount(Tokens $tokens, array $class, int $elementIndex): int
|
||||
{
|
||||
$type = $class['elements'][$elementIndex]['type'];
|
||||
$spacing = $this->classElementTypes[$type];
|
||||
|
||||
if (self::SPACING_ONE === $spacing) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (self::SPACING_NONE === $spacing) {
|
||||
if (!isset($class['elements'][$elementIndex + 1])) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$aboveElement = $class['elements'][$elementIndex + 1];
|
||||
|
||||
if ($aboveElement['type'] !== $type) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
$aboveElementDocCandidateIndex = $tokens->getPrevNonWhitespace($aboveElement['start']);
|
||||
|
||||
return $tokens[$aboveElementDocCandidateIndex]->isGivenKind([T_DOC_COMMENT, CT::T_ATTRIBUTE_CLOSE]) ? 2 : 1;
|
||||
}
|
||||
|
||||
if (self::SPACING_ONLY_IF_META === $spacing) {
|
||||
$aboveElementDocCandidateIndex = $tokens->getPrevNonWhitespace($class['elements'][$elementIndex]['start']);
|
||||
|
||||
return $tokens[$aboveElementDocCandidateIndex]->isGivenKind([T_DOC_COMMENT, CT::T_ATTRIBUTE_CLOSE]) ? 2 : 1;
|
||||
}
|
||||
|
||||
throw new \RuntimeException(sprintf('Unknown spacing "%s".', $spacing));
|
||||
}
|
||||
|
||||
private function fixSpaceBelowClassElement(Tokens $tokens, array $class): void
|
||||
{
|
||||
$element = $class['elements'][0];
|
||||
|
||||
// if this is last element fix; fix to the class end `}` here if appropriate
|
||||
if ($class['close'] === $tokens->getNextNonWhitespace($element['end'])) {
|
||||
$this->correctLineBreaks($tokens, $element['end'], $class['close'], 1);
|
||||
}
|
||||
}
|
||||
|
||||
private function correctLineBreaks(Tokens $tokens, int $startIndex, int $endIndex, int $reqLineCount): void
|
||||
{
|
||||
$lineEnding = $this->whitespacesConfig->getLineEnding();
|
||||
|
||||
++$startIndex;
|
||||
$numbOfWhiteTokens = $endIndex - $startIndex;
|
||||
|
||||
if (0 === $numbOfWhiteTokens) {
|
||||
$tokens->insertAt($startIndex, new Token([T_WHITESPACE, str_repeat($lineEnding, $reqLineCount)]));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$lineBreakCount = $this->getLineBreakCount($tokens, $startIndex, $endIndex);
|
||||
|
||||
if ($reqLineCount === $lineBreakCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($lineBreakCount < $reqLineCount) {
|
||||
$tokens[$startIndex] = new Token([
|
||||
T_WHITESPACE,
|
||||
str_repeat($lineEnding, $reqLineCount - $lineBreakCount).$tokens[$startIndex]->getContent(),
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// $lineCount = > $reqLineCount : check the one Token case first since this one will be true most of the time
|
||||
if (1 === $numbOfWhiteTokens) {
|
||||
$tokens[$startIndex] = new Token([
|
||||
T_WHITESPACE,
|
||||
Preg::replace('/\r\n|\n/', '', $tokens[$startIndex]->getContent(), $lineBreakCount - $reqLineCount),
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// $numbOfWhiteTokens = > 1
|
||||
$toReplaceCount = $lineBreakCount - $reqLineCount;
|
||||
|
||||
for ($i = $startIndex; $i < $endIndex && $toReplaceCount > 0; ++$i) {
|
||||
$tokenLineCount = substr_count($tokens[$i]->getContent(), "\n");
|
||||
|
||||
if ($tokenLineCount > 0) {
|
||||
$tokens[$i] = new Token([
|
||||
T_WHITESPACE,
|
||||
Preg::replace('/\r\n|\n/', '', $tokens[$i]->getContent(), min($toReplaceCount, $tokenLineCount)),
|
||||
]);
|
||||
$toReplaceCount -= $tokenLineCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getLineBreakCount(Tokens $tokens, int $startIndex, int $endIndex): int
|
||||
{
|
||||
$lineCount = 0;
|
||||
|
||||
for ($i = $startIndex; $i < $endIndex; ++$i) {
|
||||
$lineCount += substr_count($tokens[$i]->getContent(), "\n");
|
||||
}
|
||||
|
||||
return $lineCount;
|
||||
}
|
||||
|
||||
private function findCommentBlockStart(Tokens $tokens, int $start, int $elementAboveEnd): int
|
||||
{
|
||||
for ($i = $start; $i > $elementAboveEnd; --$i) {
|
||||
if ($tokens[$i]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) {
|
||||
$start = $i = $tokens->findBlockStart(Tokens::BLOCK_TYPE_ATTRIBUTE, $i);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$i]->isComment()) {
|
||||
$start = $i;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$tokens[$i]->isWhitespace() || $this->getLineBreakCount($tokens, $i, $i + 1) > 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $start;
|
||||
}
|
||||
|
||||
private function getElementsByClass(Tokens $tokens): \Generator
|
||||
{
|
||||
$tokensAnalyzer = new TokensAnalyzer($tokens);
|
||||
$class = $classIndex = false;
|
||||
$elements = $tokensAnalyzer->getClassyElements();
|
||||
|
||||
for (end($elements);; prev($elements)) {
|
||||
$index = key($elements);
|
||||
|
||||
if (null === $index) {
|
||||
break;
|
||||
}
|
||||
|
||||
$element = current($elements);
|
||||
$element['index'] = $index;
|
||||
|
||||
if ($element['classIndex'] !== $classIndex) {
|
||||
if (false !== $class) {
|
||||
yield $class;
|
||||
}
|
||||
|
||||
$classIndex = $element['classIndex'];
|
||||
$classOpen = $tokens->getNextTokenOfKind($classIndex, ['{']);
|
||||
$classEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classOpen);
|
||||
$class = [
|
||||
'index' => $classIndex,
|
||||
'open' => $classOpen,
|
||||
'close' => $classEnd,
|
||||
'elements' => [],
|
||||
];
|
||||
}
|
||||
|
||||
unset($element['classIndex']);
|
||||
$element['start'] = $this->getFirstTokenIndexOfClassElement($tokens, $class, $element);
|
||||
$element['end'] = $this->getLastTokenIndexOfClassElement($tokens, $class, $element, $tokensAnalyzer);
|
||||
|
||||
$class['elements'][] = $element; // reset the key by design
|
||||
}
|
||||
|
||||
if (false !== $class) {
|
||||
yield $class;
|
||||
}
|
||||
}
|
||||
|
||||
private function getFirstTokenIndexOfClassElement(Tokens $tokens, array $class, array $element): int
|
||||
{
|
||||
$modifierTypes = [T_PRIVATE, T_PROTECTED, T_PUBLIC, T_ABSTRACT, T_FINAL, T_STATIC, T_STRING, T_NS_SEPARATOR, T_VAR, CT::T_NULLABLE_TYPE, CT::T_ARRAY_TYPEHINT, CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION];
|
||||
|
||||
if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required
|
||||
$modifierTypes[] = T_READONLY;
|
||||
}
|
||||
|
||||
$firstElementAttributeIndex = $element['index'];
|
||||
|
||||
do {
|
||||
$nonWhiteAbove = $tokens->getPrevMeaningfulToken($firstElementAttributeIndex);
|
||||
|
||||
if (null !== $nonWhiteAbove && $tokens[$nonWhiteAbove]->isGivenKind($modifierTypes)) {
|
||||
$firstElementAttributeIndex = $nonWhiteAbove;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} while ($firstElementAttributeIndex > $class['open']);
|
||||
|
||||
return $firstElementAttributeIndex;
|
||||
}
|
||||
|
||||
// including trailing single line comments if belonging to the class element
|
||||
private function getLastTokenIndexOfClassElement(Tokens $tokens, array $class, array $element, TokensAnalyzer $tokensAnalyzer): int
|
||||
{
|
||||
// find last token of the element
|
||||
if ('method' === $element['type'] && !$tokens[$class['index']]->isGivenKind(T_INTERFACE)) {
|
||||
$attributes = $tokensAnalyzer->getMethodAttributes($element['index']);
|
||||
|
||||
if (true === $attributes['abstract']) {
|
||||
$elementEndIndex = $tokens->getNextTokenOfKind($element['index'], [';']);
|
||||
} else {
|
||||
$elementEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $tokens->getNextTokenOfKind($element['index'], ['{']));
|
||||
}
|
||||
} elseif ('trait_import' === $element['type']) {
|
||||
$elementEndIndex = $element['index'];
|
||||
|
||||
do {
|
||||
$elementEndIndex = $tokens->getNextMeaningfulToken($elementEndIndex);
|
||||
} while ($tokens[$elementEndIndex]->isGivenKind([T_STRING, T_NS_SEPARATOR]) || $tokens[$elementEndIndex]->equals(','));
|
||||
|
||||
if (!$tokens[$elementEndIndex]->equals(';')) {
|
||||
$elementEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $tokens->getNextTokenOfKind($element['index'], ['{']));
|
||||
}
|
||||
} else { // 'const', 'property', enum-'case', or 'method' of an interface
|
||||
$elementEndIndex = $tokens->getNextTokenOfKind($element['index'], [';']);
|
||||
}
|
||||
|
||||
$singleLineElement = true;
|
||||
|
||||
for ($i = $element['index'] + 1; $i < $elementEndIndex; ++$i) {
|
||||
if (str_contains($tokens[$i]->getContent(), "\n")) {
|
||||
$singleLineElement = false;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($singleLineElement) {
|
||||
while (true) {
|
||||
$nextToken = $tokens[$elementEndIndex + 1];
|
||||
|
||||
if (($nextToken->isComment() || $nextToken->isWhitespace()) && !str_contains($nextToken->getContent(), "\n")) {
|
||||
++$elementEndIndex;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($tokens[$elementEndIndex]->isWhitespace()) {
|
||||
$elementEndIndex = $tokens->getPrevNonWhitespace($elementEndIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return $elementEndIndex;
|
||||
}
|
||||
}
|
||||
+460
@@ -0,0 +1,460 @@
|
||||
<?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\ClassNotation;
|
||||
|
||||
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\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use PhpCsFixer\Tokenizer\TokensAnalyzer;
|
||||
|
||||
/**
|
||||
* Fixer for part of the rules defined in PSR2 ¶4.1 Extends and Implements and PSR12 ¶8. Anonymous Classes.
|
||||
*/
|
||||
final class ClassDefinitionFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Whitespace around the keywords of a class, trait, enum or interfaces definition should be one space.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
|
||||
class Foo extends Bar implements Baz, BarBaz
|
||||
{
|
||||
}
|
||||
|
||||
final class Foo extends Bar implements Baz, BarBaz
|
||||
{
|
||||
}
|
||||
|
||||
trait Foo
|
||||
{
|
||||
}
|
||||
|
||||
$foo = new class extends Bar implements Baz, BarBaz {};
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
|
||||
class Foo
|
||||
extends Bar
|
||||
implements Baz, BarBaz
|
||||
{}
|
||||
',
|
||||
['single_line' => true]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
|
||||
class Foo
|
||||
extends Bar
|
||||
implements Baz
|
||||
{}
|
||||
',
|
||||
['single_item_single_line' => true]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
|
||||
interface Bar extends
|
||||
Bar, BarBaz, FooBarBaz
|
||||
{}
|
||||
',
|
||||
['multi_line_extends_each_single_line' => true]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
$foo = new class(){};
|
||||
',
|
||||
['space_before_parenthesis' => true]
|
||||
),
|
||||
new CodeSample(
|
||||
"<?php\n\$foo = new class(\n \$bar,\n \$baz\n) {};\n",
|
||||
['inline_constructor_arguments' => true]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before BracesFixer.
|
||||
* Must run after NewWithBracesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 36;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
// -4, one for count to index, 3 because min. of tokens for a classy location.
|
||||
for ($index = $tokens->getSize() - 4; $index > 0; --$index) {
|
||||
if ($tokens[$index]->isClassy()) {
|
||||
$this->fixClassyDefinition($tokens, $index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('multi_line_extends_each_single_line', 'Whether definitions should be multiline.'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(false)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('single_item_single_line', 'Whether definitions should be single line when including a single item.'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(false)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('single_line', 'Whether definitions should be single line.'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(false)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('space_before_parenthesis', 'Whether there should be a single space after the parenthesis of anonymous class (PSR12) or not.'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(false)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('inline_constructor_arguments', 'Whether constructor argument list in anonymous classes should be single line.'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(true)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $classyIndex Class definition token start index
|
||||
*/
|
||||
private function fixClassyDefinition(Tokens $tokens, int $classyIndex): void
|
||||
{
|
||||
$classDefInfo = $this->getClassyDefinitionInfo($tokens, $classyIndex);
|
||||
|
||||
// PSR2 4.1 Lists of implements MAY be split across multiple lines, where each subsequent line is indented once.
|
||||
// When doing so, the first item in the list MUST be on the next line, and there MUST be only one interface per line.
|
||||
|
||||
if (false !== $classDefInfo['implements']) {
|
||||
$classDefInfo['implements'] = $this->fixClassyDefinitionImplements(
|
||||
$tokens,
|
||||
$classDefInfo['open'],
|
||||
$classDefInfo['implements']
|
||||
);
|
||||
}
|
||||
|
||||
if (false !== $classDefInfo['extends']) {
|
||||
$classDefInfo['extends'] = $this->fixClassyDefinitionExtends(
|
||||
$tokens,
|
||||
false === $classDefInfo['implements'] ? $classDefInfo['open'] : $classDefInfo['implements']['start'],
|
||||
$classDefInfo['extends']
|
||||
);
|
||||
}
|
||||
|
||||
// PSR2: class definition open curly brace must go on a new line.
|
||||
// PSR12: anonymous class curly brace on same line if not multi line implements.
|
||||
|
||||
$classDefInfo['open'] = $this->fixClassyDefinitionOpenSpacing($tokens, $classDefInfo);
|
||||
|
||||
if ($classDefInfo['implements']) {
|
||||
$end = $classDefInfo['implements']['start'];
|
||||
} elseif ($classDefInfo['extends']) {
|
||||
$end = $classDefInfo['extends']['start'];
|
||||
} else {
|
||||
$end = $tokens->getPrevNonWhitespace($classDefInfo['open']);
|
||||
}
|
||||
|
||||
if ($classDefInfo['anonymousClass'] && !$this->configuration['inline_constructor_arguments']) {
|
||||
if (!$tokens[$end]->equals(')')) { // anonymous class with `extends` and/or `implements`
|
||||
$start = $tokens->getPrevMeaningfulToken($end);
|
||||
$this->makeClassyDefinitionSingleLine($tokens, $start, $end);
|
||||
$end = $start;
|
||||
}
|
||||
|
||||
if ($tokens[$end]->equals(')')) { // skip constructor arguments of anonymous class
|
||||
$end = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $end);
|
||||
}
|
||||
}
|
||||
|
||||
// 4.1 The extends and implements keywords MUST be declared on the same line as the class name.
|
||||
$this->makeClassyDefinitionSingleLine($tokens, $classDefInfo['start'], $end);
|
||||
}
|
||||
|
||||
private function fixClassyDefinitionExtends(Tokens $tokens, int $classOpenIndex, array $classExtendsInfo): array
|
||||
{
|
||||
$endIndex = $tokens->getPrevNonWhitespace($classOpenIndex);
|
||||
|
||||
if (true === $this->configuration['single_line'] || false === $classExtendsInfo['multiLine']) {
|
||||
$this->makeClassyDefinitionSingleLine($tokens, $classExtendsInfo['start'], $endIndex);
|
||||
$classExtendsInfo['multiLine'] = false;
|
||||
} elseif (true === $this->configuration['single_item_single_line'] && 1 === $classExtendsInfo['numberOfExtends']) {
|
||||
$this->makeClassyDefinitionSingleLine($tokens, $classExtendsInfo['start'], $endIndex);
|
||||
$classExtendsInfo['multiLine'] = false;
|
||||
} elseif (true === $this->configuration['multi_line_extends_each_single_line'] && $classExtendsInfo['multiLine']) {
|
||||
$this->makeClassyInheritancePartMultiLine($tokens, $classExtendsInfo['start'], $endIndex);
|
||||
$classExtendsInfo['multiLine'] = true;
|
||||
}
|
||||
|
||||
return $classExtendsInfo;
|
||||
}
|
||||
|
||||
private function fixClassyDefinitionImplements(Tokens $tokens, int $classOpenIndex, array $classImplementsInfo): array
|
||||
{
|
||||
$endIndex = $tokens->getPrevNonWhitespace($classOpenIndex);
|
||||
|
||||
if (true === $this->configuration['single_line'] || false === $classImplementsInfo['multiLine']) {
|
||||
$this->makeClassyDefinitionSingleLine($tokens, $classImplementsInfo['start'], $endIndex);
|
||||
$classImplementsInfo['multiLine'] = false;
|
||||
} elseif (true === $this->configuration['single_item_single_line'] && 1 === $classImplementsInfo['numberOfImplements']) {
|
||||
$this->makeClassyDefinitionSingleLine($tokens, $classImplementsInfo['start'], $endIndex);
|
||||
$classImplementsInfo['multiLine'] = false;
|
||||
} else {
|
||||
$this->makeClassyInheritancePartMultiLine($tokens, $classImplementsInfo['start'], $endIndex);
|
||||
$classImplementsInfo['multiLine'] = true;
|
||||
}
|
||||
|
||||
return $classImplementsInfo;
|
||||
}
|
||||
|
||||
private function fixClassyDefinitionOpenSpacing(Tokens $tokens, array $classDefInfo): int
|
||||
{
|
||||
if ($classDefInfo['anonymousClass']) {
|
||||
if (false !== $classDefInfo['implements']) {
|
||||
$spacing = $classDefInfo['implements']['multiLine'] ? $this->whitespacesConfig->getLineEnding() : ' ';
|
||||
} elseif (false !== $classDefInfo['extends']) {
|
||||
$spacing = $classDefInfo['extends']['multiLine'] ? $this->whitespacesConfig->getLineEnding() : ' ';
|
||||
} else {
|
||||
$spacing = ' ';
|
||||
}
|
||||
} else {
|
||||
$spacing = $this->whitespacesConfig->getLineEnding();
|
||||
}
|
||||
|
||||
$openIndex = $tokens->getNextTokenOfKind($classDefInfo['classy'], ['{']);
|
||||
|
||||
if (' ' !== $spacing && str_contains($tokens[$openIndex - 1]->getContent(), "\n")) {
|
||||
return $openIndex;
|
||||
}
|
||||
|
||||
if ($tokens[$openIndex - 1]->isWhitespace()) {
|
||||
if (' ' !== $spacing || !$tokens[$tokens->getPrevNonWhitespace($openIndex - 1)]->isComment()) {
|
||||
$tokens[$openIndex - 1] = new Token([T_WHITESPACE, $spacing]);
|
||||
}
|
||||
|
||||
return $openIndex;
|
||||
}
|
||||
|
||||
$tokens->insertAt($openIndex, new Token([T_WHITESPACE, $spacing]));
|
||||
|
||||
return $openIndex + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* start: int,
|
||||
* classy: int,
|
||||
* open: int,
|
||||
* extends: false|array{start: int, numberOfExtends: int, multiLine: bool},
|
||||
* implements: false|array{start: int, numberOfImplements: int, multiLine: bool},
|
||||
* anonymousClass: bool,
|
||||
* }
|
||||
*/
|
||||
private function getClassyDefinitionInfo(Tokens $tokens, int $classyIndex): array
|
||||
{
|
||||
$openIndex = $tokens->getNextTokenOfKind($classyIndex, ['{']);
|
||||
$extends = false;
|
||||
$implements = false;
|
||||
$anonymousClass = false;
|
||||
|
||||
if (!$tokens[$classyIndex]->isGivenKind(T_TRAIT)) {
|
||||
$extends = $tokens->findGivenKind(T_EXTENDS, $classyIndex, $openIndex);
|
||||
$extends = \count($extends) ? $this->getClassyInheritanceInfo($tokens, key($extends), 'numberOfExtends') : false;
|
||||
|
||||
if (!$tokens[$classyIndex]->isGivenKind(T_INTERFACE)) {
|
||||
$implements = $tokens->findGivenKind(T_IMPLEMENTS, $classyIndex, $openIndex);
|
||||
$implements = \count($implements) ? $this->getClassyInheritanceInfo($tokens, key($implements), 'numberOfImplements') : false;
|
||||
$tokensAnalyzer = new TokensAnalyzer($tokens);
|
||||
$anonymousClass = $tokensAnalyzer->isAnonymousClass($classyIndex);
|
||||
}
|
||||
}
|
||||
|
||||
if ($anonymousClass) {
|
||||
$startIndex = $tokens->getPrevMeaningfulToken($classyIndex); // go to "new" for anonymous class
|
||||
} else {
|
||||
$prev = $tokens->getPrevMeaningfulToken($classyIndex);
|
||||
$startIndex = $tokens[$prev]->isGivenKind([T_FINAL, T_ABSTRACT]) ? $prev : $classyIndex;
|
||||
}
|
||||
|
||||
return [
|
||||
'start' => $startIndex,
|
||||
'classy' => $classyIndex,
|
||||
'open' => $openIndex,
|
||||
'extends' => $extends,
|
||||
'implements' => $implements,
|
||||
'anonymousClass' => $anonymousClass,
|
||||
];
|
||||
}
|
||||
|
||||
private function getClassyInheritanceInfo(Tokens $tokens, int $startIndex, string $label): array
|
||||
{
|
||||
$implementsInfo = ['start' => $startIndex, $label => 1, 'multiLine' => false];
|
||||
++$startIndex;
|
||||
$endIndex = $tokens->getNextTokenOfKind($startIndex, ['{', [T_IMPLEMENTS], [T_EXTENDS]]);
|
||||
$endIndex = $tokens[$endIndex]->equals('{') ? $tokens->getPrevNonWhitespace($endIndex) : $endIndex;
|
||||
|
||||
for ($i = $startIndex; $i < $endIndex; ++$i) {
|
||||
if ($tokens[$i]->equals(',')) {
|
||||
++$implementsInfo[$label];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$implementsInfo['multiLine'] && str_contains($tokens[$i]->getContent(), "\n")) {
|
||||
$implementsInfo['multiLine'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $implementsInfo;
|
||||
}
|
||||
|
||||
private function makeClassyDefinitionSingleLine(Tokens $tokens, int $startIndex, int $endIndex): void
|
||||
{
|
||||
for ($i = $endIndex; $i >= $startIndex; --$i) {
|
||||
if ($tokens[$i]->isWhitespace()) {
|
||||
if ($tokens[$i - 1]->isComment() || $tokens[$i + 1]->isComment()) {
|
||||
$content = $tokens[$i - 1]->getContent();
|
||||
|
||||
if (!('#' === $content || str_starts_with($content, '//'))) {
|
||||
$content = $tokens[$i + 1]->getContent();
|
||||
|
||||
if (!('#' === $content || str_starts_with($content, '//'))) {
|
||||
$tokens[$i] = new Token([T_WHITESPACE, ' ']);
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$i - 1]->isGivenKind(T_CLASS) && $tokens[$i + 1]->equals('(')) {
|
||||
if (true === $this->configuration['space_before_parenthesis']) {
|
||||
$tokens[$i] = new Token([T_WHITESPACE, ' ']);
|
||||
} else {
|
||||
$tokens->clearAt($i);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$tokens[$i - 1]->equals(',') && $tokens[$i + 1]->equalsAny([',', ')']) || $tokens[$i - 1]->equals('(')) {
|
||||
$tokens->clearAt($i);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens[$i] = new Token([T_WHITESPACE, ' ']);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$i]->equals(',') && !$tokens[$i + 1]->isWhitespace()) {
|
||||
$tokens->insertAt($i + 1, new Token([T_WHITESPACE, ' ']));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (true === $this->configuration['space_before_parenthesis'] && $tokens[$i]->isGivenKind(T_CLASS) && !$tokens[$i + 1]->isWhitespace()) {
|
||||
$tokens->insertAt($i + 1, new Token([T_WHITESPACE, ' ']));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$tokens[$i]->isComment()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$tokens[$i + 1]->isWhitespace() && !$tokens[$i + 1]->isComment() && !str_contains($tokens[$i]->getContent(), "\n")) {
|
||||
$tokens->insertAt($i + 1, new Token([T_WHITESPACE, ' ']));
|
||||
}
|
||||
|
||||
if (!$tokens[$i - 1]->isWhitespace() && !$tokens[$i - 1]->isComment()) {
|
||||
$tokens->insertAt($i, new Token([T_WHITESPACE, ' ']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function makeClassyInheritancePartMultiLine(Tokens $tokens, int $startIndex, int $endIndex): void
|
||||
{
|
||||
for ($i = $endIndex; $i > $startIndex; --$i) {
|
||||
$previousInterfaceImplementingIndex = $tokens->getPrevTokenOfKind($i, [',', [T_IMPLEMENTS], [T_EXTENDS]]);
|
||||
$breakAtIndex = $tokens->getNextMeaningfulToken($previousInterfaceImplementingIndex);
|
||||
|
||||
// make the part of a ',' or 'implements' single line
|
||||
$this->makeClassyDefinitionSingleLine(
|
||||
$tokens,
|
||||
$breakAtIndex,
|
||||
$i
|
||||
);
|
||||
|
||||
// make sure the part is on its own line
|
||||
$isOnOwnLine = false;
|
||||
|
||||
for ($j = $breakAtIndex; $j > $previousInterfaceImplementingIndex; --$j) {
|
||||
if (str_contains($tokens[$j]->getContent(), "\n")) {
|
||||
$isOnOwnLine = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$isOnOwnLine) {
|
||||
if ($tokens[$breakAtIndex - 1]->isWhitespace()) {
|
||||
$tokens[$breakAtIndex - 1] = new Token([
|
||||
T_WHITESPACE,
|
||||
$this->whitespacesConfig->getLineEnding().$this->whitespacesConfig->getIndent(),
|
||||
]);
|
||||
} else {
|
||||
$tokens->insertAt($breakAtIndex, new Token([T_WHITESPACE, $this->whitespacesConfig->getLineEnding().$this->whitespacesConfig->getIndent()]));
|
||||
}
|
||||
}
|
||||
|
||||
$i = $previousInterfaceImplementingIndex + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
<?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\ClassNotation;
|
||||
|
||||
use PhpCsFixer\AbstractProxyFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
|
||||
/**
|
||||
* @author Filippo Tessarotto <zoeslam@gmail.com>
|
||||
*/
|
||||
final class FinalClassFixer extends AbstractProxyFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'All classes must be final, except abstract ones and Doctrine entities.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
class MyApp {}
|
||||
'
|
||||
),
|
||||
],
|
||||
'No exception and no configuration are intentional. Beside Doctrine entities and of course abstract classes, there is no single reason not to declare all classes final. '
|
||||
.'If you want to subclass a class, mark the parent class as abstract and create two child classes, one empty if necessary: you\'ll gain much more fine grained type-hinting. '
|
||||
.'If you need to mock a standalone class, create an interface, or maybe it\'s a value-object that shouldn\'t be mocked at all. '
|
||||
.'If you need to extend a standalone class, create an interface and use the Composite pattern. '
|
||||
.'If you aren\'t ready yet for serious OOP, go with FinalInternalClassFixer, it\'s fine.',
|
||||
'Risky when subclassing non-abstract classes.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createProxyFixers(): array
|
||||
{
|
||||
$fixer = new FinalInternalClassFixer();
|
||||
$fixer->configure([
|
||||
'annotation_include' => [],
|
||||
'consider_absent_docblock_as_internal_class' => true,
|
||||
]);
|
||||
|
||||
return [$fixer];
|
||||
}
|
||||
}
|
||||
Vendored
+223
@@ -0,0 +1,223 @@
|
||||
<?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\ClassNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException;
|
||||
use PhpCsFixer\DocBlock\DocBlock;
|
||||
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\Preg;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use PhpCsFixer\Tokenizer\TokensAnalyzer;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
|
||||
/**
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class FinalInternalClassFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configure(array $configuration): void
|
||||
{
|
||||
parent::configure($configuration);
|
||||
|
||||
$intersect = array_intersect_assoc(
|
||||
$this->configuration['annotation_include'],
|
||||
$this->configuration['annotation_exclude']
|
||||
);
|
||||
|
||||
if (\count($intersect) > 0) {
|
||||
throw new InvalidFixerConfigurationException($this->getName(), sprintf('Annotation cannot be used in both the include and exclude list, got duplicates: "%s".', implode('", "', array_keys($intersect))));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Internal classes should be `final`.',
|
||||
[
|
||||
new CodeSample("<?php\n/**\n * @internal\n */\nclass Sample\n{\n}\n"),
|
||||
new CodeSample(
|
||||
"<?php\n/**\n * @CUSTOM\n */\nclass A{}\n\n/**\n * @CUSTOM\n * @not-fix\n */\nclass B{}\n",
|
||||
[
|
||||
'annotation_include' => ['@Custom'],
|
||||
'annotation_exclude' => ['@not-fix'],
|
||||
]
|
||||
),
|
||||
],
|
||||
null,
|
||||
'Changing classes to `final` might cause code execution to break.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before ProtectedToPrivateFixer, SelfStaticAccessorFixer.
|
||||
* Must run after PhpUnitInternalClassFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 67;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_CLASS);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$tokensAnalyzer = new TokensAnalyzer($tokens);
|
||||
|
||||
for ($index = $tokens->count() - 1; 0 <= $index; --$index) {
|
||||
if (!$tokens[$index]->isGivenKind(T_CLASS) || $tokensAnalyzer->isAnonymousClass($index) || !$this->isClassCandidate($tokens, $index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// make class final
|
||||
$tokens->insertAt(
|
||||
$index,
|
||||
[
|
||||
new Token([T_FINAL, 'final']),
|
||||
new Token([T_WHITESPACE, ' ']),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
$annotationsAsserts = [static function (array $values): bool {
|
||||
foreach ($values as $value) {
|
||||
if (!\is_string($value) || '' === $value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}];
|
||||
|
||||
$annotationsNormalizer = static function (Options $options, array $value): array {
|
||||
$newValue = [];
|
||||
foreach ($value as $key) {
|
||||
if ('@' === $key[0]) {
|
||||
$key = substr($key, 1);
|
||||
}
|
||||
|
||||
$newValue[strtolower($key)] = true;
|
||||
}
|
||||
|
||||
return $newValue;
|
||||
};
|
||||
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('annotation_include', 'Class level annotations tags that must be set in order to fix the class. (case insensitive)'))
|
||||
->setAllowedTypes(['array'])
|
||||
->setAllowedValues($annotationsAsserts)
|
||||
->setDefault(['@internal'])
|
||||
->setNormalizer($annotationsNormalizer)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('annotation_exclude', 'Class level annotations tags that must be omitted to fix the class, even if all of the white list ones are used as well. (case insensitive)'))
|
||||
->setAllowedTypes(['array'])
|
||||
->setAllowedValues($annotationsAsserts)
|
||||
->setDefault([
|
||||
'@final',
|
||||
'@Entity',
|
||||
'@ORM\Entity',
|
||||
'@ORM\Mapping\Entity',
|
||||
'@Mapping\Entity',
|
||||
'@Document',
|
||||
'@ODM\Document',
|
||||
])
|
||||
->setNormalizer($annotationsNormalizer)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('consider_absent_docblock_as_internal_class', 'Should classes without any DocBlock be fixed to final?'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(false)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $index T_CLASS index
|
||||
*/
|
||||
private function isClassCandidate(Tokens $tokens, int $index): bool
|
||||
{
|
||||
if ($tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind([T_ABSTRACT, T_FINAL])) {
|
||||
return false; // ignore class; it is abstract or already final
|
||||
}
|
||||
|
||||
$docToken = $tokens[$tokens->getPrevNonWhitespace($index)];
|
||||
|
||||
if (!$docToken->isGivenKind(T_DOC_COMMENT)) {
|
||||
return $this->configuration['consider_absent_docblock_as_internal_class'];
|
||||
}
|
||||
|
||||
$doc = new DocBlock($docToken->getContent());
|
||||
$tags = [];
|
||||
|
||||
foreach ($doc->getAnnotations() as $annotation) {
|
||||
if (1 !== Preg::match('/@\S+(?=\s|$)/', $annotation->getContent(), $matches)) {
|
||||
continue;
|
||||
}
|
||||
$tag = strtolower(substr(array_shift($matches), 1));
|
||||
foreach ($this->configuration['annotation_exclude'] as $tagStart => $true) {
|
||||
if (str_starts_with($tag, $tagStart)) {
|
||||
return false; // ignore class: class-level PHPDoc contains tag that has been excluded through configuration
|
||||
}
|
||||
}
|
||||
|
||||
$tags[$tag] = true;
|
||||
}
|
||||
|
||||
foreach ($this->configuration['annotation_include'] as $tag => $true) {
|
||||
if (!isset($tags[$tag])) {
|
||||
return false; // ignore class: class-level PHPDoc does not contain all tags that has been included through configuration
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+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\ClassNotation;
|
||||
|
||||
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 FinalPublicMethodForAbstractClassFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* @var array<string, true>
|
||||
*/
|
||||
private array $magicMethods = [
|
||||
'__construct' => true,
|
||||
'__destruct' => true,
|
||||
'__call' => true,
|
||||
'__callstatic' => true,
|
||||
'__get' => true,
|
||||
'__set' => true,
|
||||
'__isset' => true,
|
||||
'__unset' => true,
|
||||
'__sleep' => true,
|
||||
'__wakeup' => true,
|
||||
'__tostring' => true,
|
||||
'__invoke' => true,
|
||||
'__set_state' => true,
|
||||
'__clone' => true,
|
||||
'__debuginfo' => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'All `public` methods of `abstract` classes should be `final`.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
|
||||
abstract class AbstractMachine
|
||||
{
|
||||
public function start()
|
||||
{}
|
||||
}
|
||||
'
|
||||
),
|
||||
],
|
||||
'Enforce API encapsulation in an inheritance architecture. '
|
||||
.'If you want to override a method, use the Template method pattern.',
|
||||
'Risky when overriding `public` methods of `abstract` classes.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAllTokenKindsFound([T_CLASS, T_ABSTRACT, T_PUBLIC, T_FUNCTION]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$classes = array_keys($tokens->findGivenKind(T_CLASS));
|
||||
|
||||
while ($classIndex = array_pop($classes)) {
|
||||
$prevToken = $tokens[$tokens->getPrevMeaningfulToken($classIndex)];
|
||||
if (!$prevToken->isGivenKind(T_ABSTRACT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$classOpen = $tokens->getNextTokenOfKind($classIndex, ['{']);
|
||||
$classClose = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classOpen);
|
||||
|
||||
$this->fixClass($tokens, $classOpen, $classClose);
|
||||
}
|
||||
}
|
||||
|
||||
private function fixClass(Tokens $tokens, int $classOpenIndex, int $classCloseIndex): void
|
||||
{
|
||||
for ($index = $classCloseIndex - 1; $index > $classOpenIndex; --$index) {
|
||||
// skip method contents
|
||||
if ($tokens[$index]->equals('}')) {
|
||||
$index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $index);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// skip non public methods
|
||||
if (!$tokens[$index]->isGivenKind(T_PUBLIC)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$nextIndex = $tokens->getNextMeaningfulToken($index);
|
||||
$nextToken = $tokens[$nextIndex];
|
||||
|
||||
if ($nextToken->isGivenKind(T_STATIC)) {
|
||||
$nextIndex = $tokens->getNextMeaningfulToken($nextIndex);
|
||||
$nextToken = $tokens[$nextIndex];
|
||||
}
|
||||
|
||||
// skip uses, attributes, constants etc
|
||||
if (!$nextToken->isGivenKind(T_FUNCTION)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$nextIndex = $tokens->getNextMeaningfulToken($nextIndex);
|
||||
$nextToken = $tokens[$nextIndex];
|
||||
|
||||
// skip magic methods
|
||||
if (isset($this->magicMethods[strtolower($nextToken->getContent())])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$prevIndex = $tokens->getPrevMeaningfulToken($index);
|
||||
$prevToken = $tokens[$prevIndex];
|
||||
|
||||
if ($prevToken->isGivenKind(T_STATIC)) {
|
||||
$index = $prevIndex;
|
||||
$prevIndex = $tokens->getPrevMeaningfulToken($index);
|
||||
$prevToken = $tokens[$prevIndex];
|
||||
}
|
||||
|
||||
// skip abstract or already final methods
|
||||
if ($prevToken->isGivenKind([T_ABSTRACT, T_FINAL])) {
|
||||
$index = $prevIndex;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens->insertAt(
|
||||
$index,
|
||||
[
|
||||
new Token([T_FINAL, 'final']),
|
||||
new Token([T_WHITESPACE, ' ']),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
<?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\ClassNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Ceeram <ceeram@cakephp.org>
|
||||
*/
|
||||
final class NoBlankLinesAfterClassOpeningFixer extends AbstractFixer implements WhitespacesAwareFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'There should be no empty lines after class opening brace.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
final class Sample
|
||||
{
|
||||
|
||||
protected function foo()
|
||||
{
|
||||
}
|
||||
}
|
||||
'
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after OrderedClassElementsFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->isClassy()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$startBraceIndex = $tokens->getNextTokenOfKind($index, ['{']);
|
||||
if (!$tokens[$startBraceIndex + 1]->isWhitespace()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->fixWhitespace($tokens, $startBraceIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup a whitespace token.
|
||||
*/
|
||||
private function fixWhitespace(Tokens $tokens, int $index): void
|
||||
{
|
||||
$content = $tokens[$index]->getContent();
|
||||
// if there is more than one new line in the whitespace, then we need to fix it
|
||||
if (substr_count($content, "\n") > 1) {
|
||||
// the final bit of the whitespace must be the next statement's indentation
|
||||
$tokens[$index] = new Token([T_WHITESPACE, $this->whitespacesConfig->getLineEnding().substr($content, strrpos($content, "\n") + 1)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
<?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\ClassNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author ntzm
|
||||
*/
|
||||
final class NoNullPropertyInitializationFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Properties MUST not be explicitly initialized with `null` except when they have a type declaration (PHP 7.4).',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
class Foo {
|
||||
public $foo = null;
|
||||
}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
class Foo {
|
||||
public static $foo = null;
|
||||
}
|
||||
'
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound([T_CLASS, T_TRAIT]) && $tokens->isAnyTokenKindsFound([T_PUBLIC, T_PROTECTED, T_PRIVATE, T_VAR, T_STATIC]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$inClass = [];
|
||||
$classLevel = 0;
|
||||
|
||||
for ($index = 0, $count = $tokens->count(); $index < $count; ++$index) {
|
||||
if ($tokens[$index]->isGivenKind([T_CLASS, T_TRAIT])) { // Enums and interfaces do not have properties
|
||||
++$classLevel;
|
||||
$inClass[$classLevel] = 1;
|
||||
|
||||
$index = $tokens->getNextTokenOfKind($index, ['{']);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (0 === $classLevel) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$index]->equals('{')) {
|
||||
++$inClass[$classLevel];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$index]->equals('}')) {
|
||||
--$inClass[$classLevel];
|
||||
|
||||
if (0 === $inClass[$classLevel]) {
|
||||
unset($inClass[$classLevel]);
|
||||
--$classLevel;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ensure we are in a class but not in a method in case there are static variables defined
|
||||
if (1 !== $inClass[$classLevel]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$tokens[$index]->isGivenKind([T_PUBLIC, T_PROTECTED, T_PRIVATE, T_VAR, T_STATIC])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
$varTokenIndex = $index = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
if ($tokens[$index]->isGivenKind(T_STATIC)) {
|
||||
$varTokenIndex = $index = $tokens->getNextMeaningfulToken($index);
|
||||
}
|
||||
|
||||
if (!$tokens[$index]->isGivenKind(T_VARIABLE)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$index = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
if ($tokens[$index]->equals('=')) {
|
||||
$index = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
if ($tokens[$index]->isGivenKind(T_NS_SEPARATOR)) {
|
||||
$index = $tokens->getNextMeaningfulToken($index);
|
||||
}
|
||||
|
||||
if ($tokens[$index]->equals([T_STRING, 'null'], false)) {
|
||||
for ($i = $varTokenIndex + 1; $i <= $index; ++$i) {
|
||||
if (
|
||||
!($tokens[$i]->isWhitespace() && str_contains($tokens[$i]->getContent(), "\n"))
|
||||
&& !$tokens[$i]->isComment()
|
||||
) {
|
||||
$tokens->clearAt($i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
++$index;
|
||||
}
|
||||
|
||||
if (!$tokens[$index]->equals(',')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+419
@@ -0,0 +1,419 @@
|
||||
<?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\ClassNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use PhpCsFixer\Tokenizer\TokensAnalyzer;
|
||||
|
||||
/**
|
||||
* @author Matteo Beccati <matteo@beccati.com>
|
||||
*/
|
||||
final class NoPhp4ConstructorFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Convert PHP4-style constructors to `__construct`.',
|
||||
[
|
||||
new CodeSample('<?php
|
||||
class Foo
|
||||
{
|
||||
public function Foo($bar)
|
||||
{
|
||||
}
|
||||
}
|
||||
'),
|
||||
],
|
||||
null,
|
||||
'Risky when old style constructor being fixed is overridden or overrides parent one.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before OrderedClassElementsFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 75;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_CLASS);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$tokensAnalyzer = new TokensAnalyzer($tokens);
|
||||
$classes = array_keys($tokens->findGivenKind(T_CLASS));
|
||||
$numClasses = \count($classes);
|
||||
|
||||
for ($i = 0; $i < $numClasses; ++$i) {
|
||||
$index = $classes[$i];
|
||||
|
||||
// is it an anonymous class definition?
|
||||
if ($tokensAnalyzer->isAnonymousClass($index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// is it inside a namespace?
|
||||
$nspIndex = $tokens->getPrevTokenOfKind($index, [[T_NAMESPACE, 'namespace']]);
|
||||
|
||||
if (null !== $nspIndex) {
|
||||
$nspIndex = $tokens->getNextMeaningfulToken($nspIndex);
|
||||
|
||||
// make sure it's not the global namespace, as PHP4 constructors are allowed in there
|
||||
if (!$tokens[$nspIndex]->equals('{')) {
|
||||
// unless it's the global namespace, the index currently points to the name
|
||||
$nspIndex = $tokens->getNextTokenOfKind($nspIndex, [';', '{']);
|
||||
|
||||
if ($tokens[$nspIndex]->equals(';')) {
|
||||
// the class is inside a (non-block) namespace, no PHP4-code should be in there
|
||||
break;
|
||||
}
|
||||
|
||||
// the index points to the { of a block-namespace
|
||||
$nspEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $nspIndex);
|
||||
|
||||
if ($index < $nspEnd) {
|
||||
// the class is inside a block namespace, skip other classes that might be in it
|
||||
for ($j = $i + 1; $j < $numClasses; ++$j) {
|
||||
if ($classes[$j] < $nspEnd) {
|
||||
++$i;
|
||||
}
|
||||
}
|
||||
// and continue checking the classes that might follow
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$classNameIndex = $tokens->getNextMeaningfulToken($index);
|
||||
$className = $tokens[$classNameIndex]->getContent();
|
||||
$classStart = $tokens->getNextTokenOfKind($classNameIndex, ['{']);
|
||||
$classEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classStart);
|
||||
|
||||
$this->fixConstructor($tokens, $className, $classStart, $classEnd);
|
||||
$this->fixParent($tokens, $classStart, $classEnd);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix constructor within a class, if possible.
|
||||
*
|
||||
* @param Tokens $tokens the Tokens instance
|
||||
* @param string $className the class name
|
||||
* @param int $classStart the class start index
|
||||
* @param int $classEnd the class end index
|
||||
*/
|
||||
private function fixConstructor(Tokens $tokens, string $className, int $classStart, int $classEnd): void
|
||||
{
|
||||
$php4 = $this->findFunction($tokens, $className, $classStart, $classEnd);
|
||||
|
||||
if (null === $php4) {
|
||||
return; // no PHP4-constructor!
|
||||
}
|
||||
|
||||
if (!empty($php4['modifiers'][T_ABSTRACT]) || !empty($php4['modifiers'][T_STATIC])) {
|
||||
return; // PHP4 constructor can't be abstract or static
|
||||
}
|
||||
|
||||
$php5 = $this->findFunction($tokens, '__construct', $classStart, $classEnd);
|
||||
|
||||
if (null === $php5) {
|
||||
// no PHP5-constructor, we can rename the old one to __construct
|
||||
$tokens[$php4['nameIndex']] = new Token([T_STRING, '__construct']);
|
||||
|
||||
// in some (rare) cases we might have just created an infinite recursion issue
|
||||
$this->fixInfiniteRecursion($tokens, $php4['bodyIndex'], $php4['endIndex']);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// does the PHP4-constructor only call $this->__construct($args, ...)?
|
||||
[$sequences, $case] = $this->getWrapperMethodSequence($tokens, '__construct', $php4['startIndex'], $php4['bodyIndex']);
|
||||
|
||||
foreach ($sequences as $seq) {
|
||||
if (null !== $tokens->findSequence($seq, $php4['bodyIndex'] - 1, $php4['endIndex'], $case)) {
|
||||
// good, delete it!
|
||||
for ($i = $php4['startIndex']; $i <= $php4['endIndex']; ++$i) {
|
||||
$tokens->clearAt($i);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// does __construct only call the PHP4-constructor (with the same args)?
|
||||
[$sequences, $case] = $this->getWrapperMethodSequence($tokens, $className, $php4['startIndex'], $php4['bodyIndex']);
|
||||
|
||||
foreach ($sequences as $seq) {
|
||||
if (null !== $tokens->findSequence($seq, $php5['bodyIndex'] - 1, $php5['endIndex'], $case)) {
|
||||
// that was a weird choice, but we can safely delete it and...
|
||||
for ($i = $php5['startIndex']; $i <= $php5['endIndex']; ++$i) {
|
||||
$tokens->clearAt($i);
|
||||
}
|
||||
|
||||
// rename the PHP4 one to __construct
|
||||
$tokens[$php4['nameIndex']] = new Token([T_STRING, '__construct']);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix calls to the parent constructor within a class.
|
||||
*
|
||||
* @param Tokens $tokens the Tokens instance
|
||||
* @param int $classStart the class start index
|
||||
* @param int $classEnd the class end index
|
||||
*/
|
||||
private function fixParent(Tokens $tokens, int $classStart, int $classEnd): void
|
||||
{
|
||||
// check calls to the parent constructor
|
||||
foreach ($tokens->findGivenKind(T_EXTENDS) as $index => $token) {
|
||||
$parentIndex = $tokens->getNextMeaningfulToken($index);
|
||||
$parentClass = $tokens[$parentIndex]->getContent();
|
||||
|
||||
// using parent::ParentClassName() or ParentClassName::ParentClassName()
|
||||
$parentSeq = $tokens->findSequence([
|
||||
[T_STRING],
|
||||
[T_DOUBLE_COLON],
|
||||
[T_STRING, $parentClass],
|
||||
'(',
|
||||
], $classStart, $classEnd, [2 => false]);
|
||||
|
||||
if (null !== $parentSeq) {
|
||||
// we only need indices
|
||||
$parentSeq = array_keys($parentSeq);
|
||||
|
||||
// match either of the possibilities
|
||||
if ($tokens[$parentSeq[0]]->equalsAny([[T_STRING, 'parent'], [T_STRING, $parentClass]], false)) {
|
||||
// replace with parent::__construct
|
||||
$tokens[$parentSeq[0]] = new Token([T_STRING, 'parent']);
|
||||
$tokens[$parentSeq[2]] = new Token([T_STRING, '__construct']);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Token::getObjectOperatorKinds() as $objectOperatorKind) {
|
||||
// using $this->ParentClassName()
|
||||
$parentSeq = $tokens->findSequence([
|
||||
[T_VARIABLE, '$this'],
|
||||
[$objectOperatorKind],
|
||||
[T_STRING, $parentClass],
|
||||
'(',
|
||||
], $classStart, $classEnd, [2 => false]);
|
||||
|
||||
if (null !== $parentSeq) {
|
||||
// we only need indices
|
||||
$parentSeq = array_keys($parentSeq);
|
||||
|
||||
// replace call with parent::__construct()
|
||||
$tokens[$parentSeq[0]] = new Token([
|
||||
T_STRING,
|
||||
'parent',
|
||||
]);
|
||||
$tokens[$parentSeq[1]] = new Token([
|
||||
T_DOUBLE_COLON,
|
||||
'::',
|
||||
]);
|
||||
$tokens[$parentSeq[2]] = new Token([T_STRING, '__construct']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix a particular infinite recursion issue happening when the parent class has __construct and the child has only
|
||||
* a PHP4 constructor that calls the parent constructor as $this->__construct().
|
||||
*
|
||||
* @param Tokens $tokens the Tokens instance
|
||||
* @param int $start the PHP4 constructor body start
|
||||
* @param int $end the PHP4 constructor body end
|
||||
*/
|
||||
private function fixInfiniteRecursion(Tokens $tokens, int $start, int $end): void
|
||||
{
|
||||
foreach (Token::getObjectOperatorKinds() as $objectOperatorKind) {
|
||||
$seq = [
|
||||
[T_VARIABLE, '$this'],
|
||||
[$objectOperatorKind],
|
||||
[T_STRING, '__construct'],
|
||||
];
|
||||
|
||||
while (true) {
|
||||
$callSeq = $tokens->findSequence($seq, $start, $end, [2 => false]);
|
||||
|
||||
if (null === $callSeq) {
|
||||
return;
|
||||
}
|
||||
|
||||
$callSeq = array_keys($callSeq);
|
||||
|
||||
$tokens[$callSeq[0]] = new Token([T_STRING, 'parent']);
|
||||
$tokens[$callSeq[1]] = new Token([T_DOUBLE_COLON, '::']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the sequence of tokens necessary for the body of a wrapper method that simply
|
||||
* calls $this->{$method}( [args...] ) with the same arguments as its own signature.
|
||||
*
|
||||
* @param Tokens $tokens the Tokens instance
|
||||
* @param string $method the wrapped method name
|
||||
* @param int $startIndex function/method start index
|
||||
* @param int $bodyIndex function/method body index
|
||||
*
|
||||
* @return array an array containing the sequence and case sensitiveness [ 0 => $seq, 1 => $case ]
|
||||
*/
|
||||
private function getWrapperMethodSequence(Tokens $tokens, string $method, int $startIndex, int $bodyIndex): array
|
||||
{
|
||||
$sequences = [];
|
||||
|
||||
foreach (Token::getObjectOperatorKinds() as $objectOperatorKind) {
|
||||
// initialise sequence as { $this->{$method}(
|
||||
$seq = [
|
||||
'{',
|
||||
[T_VARIABLE, '$this'],
|
||||
[$objectOperatorKind],
|
||||
[T_STRING, $method],
|
||||
'(',
|
||||
];
|
||||
|
||||
// parse method parameters, if any
|
||||
$index = $startIndex;
|
||||
|
||||
while (true) {
|
||||
// find the next variable name
|
||||
$index = $tokens->getNextTokenOfKind($index, [[T_VARIABLE]]);
|
||||
|
||||
if (null === $index || $index >= $bodyIndex) {
|
||||
// we've reached the body already
|
||||
break;
|
||||
}
|
||||
|
||||
// append a comma if it's not the first variable
|
||||
if (\count($seq) > 5) {
|
||||
$seq[] = ',';
|
||||
}
|
||||
|
||||
// append variable name to the sequence
|
||||
$seq[] = [T_VARIABLE, $tokens[$index]->getContent()];
|
||||
}
|
||||
|
||||
// almost done, close the sequence with ); }
|
||||
$seq[] = ')';
|
||||
$seq[] = ';';
|
||||
$seq[] = '}';
|
||||
|
||||
$sequences[] = $seq;
|
||||
}
|
||||
|
||||
return [$sequences, [3 => false]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a function or method matching a given name within certain bounds.
|
||||
*
|
||||
* Returns:
|
||||
* - nameIndex (int): The index of the function/method name.
|
||||
* - startIndex (int): The index of the function/method start.
|
||||
* - endIndex (int): The index of the function/method end.
|
||||
* - bodyIndex (int): The index of the function/method body.
|
||||
* - modifiers (array): The modifiers as array keys and their index as the values, e.g. array(T_PUBLIC => 10)
|
||||
*
|
||||
* @param Tokens $tokens the Tokens instance
|
||||
* @param string $name the function/Method name
|
||||
* @param int $startIndex the search start index
|
||||
* @param int $endIndex the search end index
|
||||
*
|
||||
* @return null|array{
|
||||
* nameIndex: int,
|
||||
* startIndex: int,
|
||||
* endIndex: int,
|
||||
* bodyIndex: int,
|
||||
* modifiers: list<int>,
|
||||
* }
|
||||
*/
|
||||
private function findFunction(Tokens $tokens, string $name, int $startIndex, int $endIndex): ?array
|
||||
{
|
||||
$function = $tokens->findSequence([
|
||||
[T_FUNCTION],
|
||||
[T_STRING, $name],
|
||||
'(',
|
||||
], $startIndex, $endIndex, false);
|
||||
|
||||
if (null === $function) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// keep only the indices
|
||||
$function = array_keys($function);
|
||||
|
||||
// find previous block, saving method modifiers for later use
|
||||
$possibleModifiers = [T_PUBLIC, T_PROTECTED, T_PRIVATE, T_STATIC, T_ABSTRACT, T_FINAL];
|
||||
$modifiers = [];
|
||||
|
||||
$prevBlock = $tokens->getPrevMeaningfulToken($function[0]);
|
||||
|
||||
while (null !== $prevBlock && $tokens[$prevBlock]->isGivenKind($possibleModifiers)) {
|
||||
$modifiers[$tokens[$prevBlock]->getId()] = $prevBlock;
|
||||
$prevBlock = $tokens->getPrevMeaningfulToken($prevBlock);
|
||||
}
|
||||
|
||||
if (isset($modifiers[T_ABSTRACT])) {
|
||||
// abstract methods have no body
|
||||
$bodyStart = null;
|
||||
$funcEnd = $tokens->getNextTokenOfKind($function[2], [';']);
|
||||
} else {
|
||||
// find method body start and the end of the function definition
|
||||
$bodyStart = $tokens->getNextTokenOfKind($function[2], ['{']);
|
||||
$funcEnd = null !== $bodyStart ? $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $bodyStart) : null;
|
||||
}
|
||||
|
||||
return [
|
||||
'nameIndex' => $function[1],
|
||||
'startIndex' => $prevBlock + 1,
|
||||
'endIndex' => $funcEnd,
|
||||
'bodyIndex' => $bodyStart,
|
||||
'modifiers' => $modifiers,
|
||||
];
|
||||
}
|
||||
}
|
||||
Vendored
+210
@@ -0,0 +1,210 @@
|
||||
<?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\ClassNotation;
|
||||
|
||||
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\Tokens;
|
||||
use PhpCsFixer\Tokenizer\TokensAnalyzer;
|
||||
|
||||
/**
|
||||
* @author Filippo Tessarotto <zoeslam@gmail.com>
|
||||
*/
|
||||
final class NoUnneededFinalMethodFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Removes `final` from methods where possible.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
final class Foo
|
||||
{
|
||||
final public function foo1() {}
|
||||
final protected function bar() {}
|
||||
final private function baz() {}
|
||||
}
|
||||
|
||||
class Bar
|
||||
{
|
||||
final private function bar1() {}
|
||||
}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
final class Foo
|
||||
{
|
||||
final private function baz() {}
|
||||
}
|
||||
|
||||
class Bar
|
||||
{
|
||||
final private function bar1() {}
|
||||
}
|
||||
',
|
||||
['private_methods' => false]
|
||||
),
|
||||
],
|
||||
null,
|
||||
'Risky when child class overrides a `private` method.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
if (!$tokens->isAllTokenKindsFound([T_FINAL, T_FUNCTION])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (\defined('T_ENUM') && $tokens->isTokenKindFound(T_ENUM)) { // @TODO: drop condition when PHP 8.1+ is required
|
||||
return true;
|
||||
}
|
||||
|
||||
return $tokens->isTokenKindFound(T_CLASS);
|
||||
}
|
||||
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($this->getMethods($tokens) as $element) {
|
||||
$index = $element['method_final_index'];
|
||||
|
||||
if ($element['method_of_enum'] || $element['class_is_final']) {
|
||||
$this->clearFinal($tokens, $index);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$element['method_is_private'] || false === $this->configuration['private_methods'] || $element['method_is_constructor']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->clearFinal($tokens, $index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('private_methods', 'Private methods of non-`final` classes must not be declared `final`.'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(true)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function getMethods(Tokens $tokens): \Generator
|
||||
{
|
||||
$tokensAnalyzer = new TokensAnalyzer($tokens);
|
||||
$modifierKinds = [T_PUBLIC, T_PROTECTED, T_PRIVATE, T_FINAL, T_ABSTRACT, T_STATIC];
|
||||
|
||||
$enums = [];
|
||||
$classesAreFinal = [];
|
||||
$elements = $tokensAnalyzer->getClassyElements();
|
||||
|
||||
for (end($elements);; prev($elements)) {
|
||||
$index = key($elements);
|
||||
|
||||
if (null === $index) {
|
||||
break;
|
||||
}
|
||||
|
||||
$element = current($elements);
|
||||
|
||||
if ('method' !== $element['type']) {
|
||||
continue; // not a method
|
||||
}
|
||||
|
||||
$classIndex = $element['classIndex'];
|
||||
|
||||
if (!\array_key_exists($classIndex, $enums)) {
|
||||
$enums[$classIndex] = \defined('T_ENUM') && $tokens[$classIndex]->isGivenKind(T_ENUM); // @TODO: drop condition when PHP 8.1+ is required
|
||||
}
|
||||
|
||||
$element['method_final_index'] = null;
|
||||
$element['method_is_private'] = false;
|
||||
|
||||
$previous = $index;
|
||||
|
||||
do {
|
||||
$previous = $tokens->getPrevMeaningfulToken($previous);
|
||||
|
||||
if ($tokens[$previous]->isGivenKind(T_PRIVATE)) {
|
||||
$element['method_is_private'] = true;
|
||||
} elseif ($tokens[$previous]->isGivenKind(T_FINAL)) {
|
||||
$element['method_final_index'] = $previous;
|
||||
}
|
||||
} while ($tokens[$previous]->isGivenKind($modifierKinds));
|
||||
|
||||
if ($enums[$classIndex]) {
|
||||
$element['method_of_enum'] = true;
|
||||
|
||||
yield $element;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!\array_key_exists($classIndex, $classesAreFinal)) {
|
||||
$prevToken = $tokens[$tokens->getPrevMeaningfulToken($classIndex)];
|
||||
$classesAreFinal[$classIndex] = $prevToken->isGivenKind(T_FINAL);
|
||||
}
|
||||
|
||||
$element['method_of_enum'] = false;
|
||||
$element['class_is_final'] = $classesAreFinal[$classIndex];
|
||||
$element['method_is_constructor'] = '__construct' === strtolower($tokens[$tokens->getNextMeaningfulToken($index)]->getContent());
|
||||
|
||||
yield $element;
|
||||
}
|
||||
}
|
||||
|
||||
private function clearFinal(Tokens $tokens, ?int $index): void
|
||||
{
|
||||
if (null === $index) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tokens->clearAt($index);
|
||||
|
||||
++$index;
|
||||
|
||||
if ($tokens[$index]->isWhitespace()) {
|
||||
$tokens->clearAt($index);
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+589
@@ -0,0 +1,589 @@
|
||||
<?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\ClassNotation;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author Gregor Harlan <gharlan@web.de>
|
||||
*/
|
||||
final class OrderedClassElementsFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/** @internal */
|
||||
public const SORT_ALPHA = 'alpha';
|
||||
|
||||
/** @internal */
|
||||
public const SORT_NONE = 'none';
|
||||
|
||||
private const SUPPORTED_SORT_ALGORITHMS = [
|
||||
self::SORT_NONE,
|
||||
self::SORT_ALPHA,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<string, null|list<string>> Array containing all class element base types (keys) and their parent types (values)
|
||||
*/
|
||||
private static array $typeHierarchy = [
|
||||
'use_trait' => null,
|
||||
'public' => null,
|
||||
'protected' => null,
|
||||
'private' => null,
|
||||
'case' => ['public'],
|
||||
'constant' => null,
|
||||
'constant_public' => ['constant', 'public'],
|
||||
'constant_protected' => ['constant', 'protected'],
|
||||
'constant_private' => ['constant', 'private'],
|
||||
'property' => null,
|
||||
'property_static' => ['property'],
|
||||
'property_public' => ['property', 'public'],
|
||||
'property_protected' => ['property', 'protected'],
|
||||
'property_private' => ['property', 'private'],
|
||||
'property_public_readonly' => ['property_readonly', 'property_public'],
|
||||
'property_protected_readonly' => ['property_readonly', 'property_protected'],
|
||||
'property_private_readonly' => ['property_readonly', 'property_private'],
|
||||
'property_public_static' => ['property_static', 'property_public'],
|
||||
'property_protected_static' => ['property_static', 'property_protected'],
|
||||
'property_private_static' => ['property_static', 'property_private'],
|
||||
'method' => null,
|
||||
'method_abstract' => ['method'],
|
||||
'method_static' => ['method'],
|
||||
'method_public' => ['method', 'public'],
|
||||
'method_protected' => ['method', 'protected'],
|
||||
'method_private' => ['method', 'private'],
|
||||
'method_public_abstract' => ['method_abstract', 'method_public'],
|
||||
'method_protected_abstract' => ['method_abstract', 'method_protected'],
|
||||
'method_private_abstract' => ['method_abstract', 'method_private'],
|
||||
'method_public_abstract_static' => ['method_abstract', 'method_static', 'method_public'],
|
||||
'method_protected_abstract_static' => ['method_abstract', 'method_static', 'method_protected'],
|
||||
'method_private_abstract_static' => ['method_abstract', 'method_static', 'method_private'],
|
||||
'method_public_static' => ['method_static', 'method_public'],
|
||||
'method_protected_static' => ['method_static', 'method_protected'],
|
||||
'method_private_static' => ['method_static', 'method_private'],
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<string, null> Array containing special method types
|
||||
*/
|
||||
private static array $specialTypes = [
|
||||
'construct' => null,
|
||||
'destruct' => null,
|
||||
'magic' => null,
|
||||
'phpunit' => null,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<string, int> Resolved configuration array (type => position)
|
||||
*/
|
||||
private array $typePosition;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configure(array $configuration): void
|
||||
{
|
||||
parent::configure($configuration);
|
||||
|
||||
$this->typePosition = [];
|
||||
$pos = 0;
|
||||
|
||||
foreach ($this->configuration['order'] as $type) {
|
||||
$this->typePosition[$type] = $pos++;
|
||||
}
|
||||
|
||||
foreach (self::$typeHierarchy as $type => $parents) {
|
||||
if (isset($this->typePosition[$type])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$parents) {
|
||||
$this->typePosition[$type] = null;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($parents as $parent) {
|
||||
if (isset($this->typePosition[$parent])) {
|
||||
$this->typePosition[$type] = $this->typePosition[$parent];
|
||||
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
$this->typePosition[$type] = null;
|
||||
}
|
||||
|
||||
$lastPosition = \count($this->configuration['order']);
|
||||
|
||||
foreach ($this->typePosition as &$pos) {
|
||||
if (null === $pos) {
|
||||
$pos = $lastPosition;
|
||||
}
|
||||
|
||||
$pos *= 10; // last digit is used by phpunit method ordering
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Orders the elements of classes/interfaces/traits/enums.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
final class Example
|
||||
{
|
||||
use BarTrait;
|
||||
use BazTrait;
|
||||
const C1 = 1;
|
||||
const C2 = 2;
|
||||
protected static $protStatProp;
|
||||
public static $pubStatProp1;
|
||||
public $pubProp1;
|
||||
protected $protProp;
|
||||
var $pubProp2;
|
||||
private static $privStatProp;
|
||||
private $privProp;
|
||||
public static $pubStatProp2;
|
||||
public $pubProp3;
|
||||
protected function __construct() {}
|
||||
private static function privStatFunc() {}
|
||||
public function pubFunc1() {}
|
||||
public function __toString() {}
|
||||
protected function protFunc() {}
|
||||
function pubFunc2() {}
|
||||
public static function pubStatFunc1() {}
|
||||
public function pubFunc3() {}
|
||||
static function pubStatFunc2() {}
|
||||
private function privFunc() {}
|
||||
public static function pubStatFunc3() {}
|
||||
protected static function protStatFunc() {}
|
||||
public function __destruct() {}
|
||||
}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
class Example
|
||||
{
|
||||
public function A(){}
|
||||
private function B(){}
|
||||
}
|
||||
',
|
||||
['order' => ['method_private', 'method_public']]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
class Example
|
||||
{
|
||||
public function D(){}
|
||||
public function B(){}
|
||||
public function A(){}
|
||||
public function C(){}
|
||||
}
|
||||
',
|
||||
['order' => ['method_public'], 'sort_algorithm' => self::SORT_ALPHA]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before ClassAttributesSeparationFixer, NoBlankLinesAfterClassOpeningFixer, SpaceAfterSemicolonFixer.
|
||||
* Must run after NoPhp4ConstructorFixer, ProtectedToPrivateFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 65;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
for ($i = 1, $count = $tokens->count(); $i < $count; ++$i) {
|
||||
if (!$tokens[$i]->isClassy()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$i = $tokens->getNextTokenOfKind($i, ['{']);
|
||||
$elements = $this->getElements($tokens, $i);
|
||||
|
||||
if (0 === \count($elements)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$sorted = $this->sortElements($elements);
|
||||
$endIndex = $elements[\count($elements) - 1]['end'];
|
||||
|
||||
if ($sorted !== $elements) {
|
||||
$this->sortTokens($tokens, $i, $endIndex, $sorted);
|
||||
}
|
||||
|
||||
$i = $endIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('order', 'List of strings defining order of elements.'))
|
||||
->setAllowedTypes(['array'])
|
||||
->setAllowedValues([new AllowedValueSubset(array_keys(array_merge(self::$typeHierarchy, self::$specialTypes)))])
|
||||
->setDefault([
|
||||
'use_trait',
|
||||
'case',
|
||||
'constant_public',
|
||||
'constant_protected',
|
||||
'constant_private',
|
||||
'property_public',
|
||||
'property_protected',
|
||||
'property_private',
|
||||
'construct',
|
||||
'destruct',
|
||||
'magic',
|
||||
'phpunit',
|
||||
'method_public',
|
||||
'method_protected',
|
||||
'method_private',
|
||||
])
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('sort_algorithm', 'How multiple occurrences of same type statements should be sorted'))
|
||||
->setAllowedValues(self::SUPPORTED_SORT_ALGORITHMS)
|
||||
->setDefault(self::SORT_NONE)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{
|
||||
* start: int,
|
||||
* visibility: string,
|
||||
* abstract: bool,
|
||||
* static: bool,
|
||||
* readonly: bool,
|
||||
* type: string,
|
||||
* name: string,
|
||||
* end: int,
|
||||
* }>
|
||||
*/
|
||||
private function getElements(Tokens $tokens, int $startIndex): array
|
||||
{
|
||||
static $elementTokenKinds = [CT::T_USE_TRAIT, T_CASE, T_CONST, T_VARIABLE, T_FUNCTION];
|
||||
|
||||
++$startIndex;
|
||||
$elements = [];
|
||||
|
||||
while (true) {
|
||||
$element = [
|
||||
'start' => $startIndex,
|
||||
'visibility' => 'public',
|
||||
'abstract' => false,
|
||||
'static' => false,
|
||||
'readonly' => false,
|
||||
];
|
||||
|
||||
for ($i = $startIndex;; ++$i) {
|
||||
$token = $tokens[$i];
|
||||
|
||||
// class end
|
||||
if ($token->equals('}')) {
|
||||
return $elements;
|
||||
}
|
||||
|
||||
if ($token->isGivenKind(T_ABSTRACT)) {
|
||||
$element['abstract'] = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->isGivenKind(T_STATIC)) {
|
||||
$element['static'] = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (\defined('T_READONLY') && $token->isGivenKind(T_READONLY)) { // @TODO: drop condition when PHP 8.1+ is required
|
||||
$element['readonly'] = true;
|
||||
}
|
||||
|
||||
if ($token->isGivenKind([T_PROTECTED, T_PRIVATE])) {
|
||||
$element['visibility'] = strtolower($token->getContent());
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$token->isGivenKind($elementTokenKinds)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$type = $this->detectElementType($tokens, $i);
|
||||
|
||||
if (\is_array($type)) {
|
||||
$element['type'] = $type[0];
|
||||
$element['name'] = $type[1];
|
||||
} else {
|
||||
$element['type'] = $type;
|
||||
}
|
||||
|
||||
if ('property' === $element['type']) {
|
||||
$element['name'] = $tokens[$i]->getContent();
|
||||
} elseif (\in_array($element['type'], ['use_trait', 'case', 'constant', 'method', 'magic', 'construct', 'destruct'], true)) {
|
||||
$element['name'] = $tokens[$tokens->getNextMeaningfulToken($i)]->getContent();
|
||||
}
|
||||
|
||||
$element['end'] = $this->findElementEnd($tokens, $i);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
$elements[] = $element;
|
||||
$startIndex = $element['end'] + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string>|string type or array of type and name
|
||||
*/
|
||||
private function detectElementType(Tokens $tokens, int $index)
|
||||
{
|
||||
$token = $tokens[$index];
|
||||
|
||||
if ($token->isGivenKind(CT::T_USE_TRAIT)) {
|
||||
return 'use_trait';
|
||||
}
|
||||
|
||||
if ($token->isGivenKind(T_CASE)) {
|
||||
return 'case';
|
||||
}
|
||||
|
||||
if ($token->isGivenKind(T_CONST)) {
|
||||
return 'constant';
|
||||
}
|
||||
|
||||
if ($token->isGivenKind(T_VARIABLE)) {
|
||||
return 'property';
|
||||
}
|
||||
|
||||
$nameToken = $tokens[$tokens->getNextMeaningfulToken($index)];
|
||||
|
||||
if ($nameToken->equals([T_STRING, '__construct'], false)) {
|
||||
return 'construct';
|
||||
}
|
||||
|
||||
if ($nameToken->equals([T_STRING, '__destruct'], false)) {
|
||||
return 'destruct';
|
||||
}
|
||||
|
||||
if (
|
||||
$nameToken->equalsAny([
|
||||
[T_STRING, 'setUpBeforeClass'],
|
||||
[T_STRING, 'doSetUpBeforeClass'],
|
||||
[T_STRING, 'tearDownAfterClass'],
|
||||
[T_STRING, 'doTearDownAfterClass'],
|
||||
[T_STRING, 'setUp'],
|
||||
[T_STRING, 'doSetUp'],
|
||||
[T_STRING, 'assertPreConditions'],
|
||||
[T_STRING, 'assertPostConditions'],
|
||||
[T_STRING, 'tearDown'],
|
||||
[T_STRING, 'doTearDown'],
|
||||
], false)
|
||||
) {
|
||||
return ['phpunit', strtolower($nameToken->getContent())];
|
||||
}
|
||||
|
||||
return str_starts_with($nameToken->getContent(), '__') ? 'magic' : 'method';
|
||||
}
|
||||
|
||||
private function findElementEnd(Tokens $tokens, int $index): int
|
||||
{
|
||||
$index = $tokens->getNextTokenOfKind($index, ['{', ';']);
|
||||
|
||||
if ($tokens[$index]->equals('{')) {
|
||||
$index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index);
|
||||
}
|
||||
|
||||
for (++$index; $tokens[$index]->isWhitespace(" \t") || $tokens[$index]->isComment(); ++$index);
|
||||
|
||||
--$index;
|
||||
|
||||
return $tokens[$index]->isWhitespace() ? $index - 1 : $index;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{
|
||||
* start: int,
|
||||
* visibility: string,
|
||||
* abstract: bool,
|
||||
* static: bool,
|
||||
* readonly: bool,
|
||||
* type: string,
|
||||
* name: string,
|
||||
* end: int,
|
||||
* position: int,
|
||||
* }>
|
||||
*/
|
||||
private function sortElements(array $elements): array
|
||||
{
|
||||
static $phpunitPositions = [
|
||||
'setupbeforeclass' => 1,
|
||||
'dosetupbeforeclass' => 2,
|
||||
'teardownafterclass' => 3,
|
||||
'doteardownafterclass' => 4,
|
||||
'setup' => 5,
|
||||
'dosetup' => 6,
|
||||
'assertpreconditions' => 7,
|
||||
'assertpostconditions' => 8,
|
||||
'teardown' => 9,
|
||||
'doteardown' => 10,
|
||||
];
|
||||
|
||||
foreach ($elements as &$element) {
|
||||
$type = $element['type'];
|
||||
|
||||
if (\array_key_exists($type, self::$specialTypes)) {
|
||||
if (isset($this->typePosition[$type])) {
|
||||
$element['position'] = $this->typePosition[$type];
|
||||
|
||||
if ('phpunit' === $type) {
|
||||
$element['position'] += $phpunitPositions[$element['name']];
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$type = 'method';
|
||||
}
|
||||
|
||||
if (\in_array($type, ['constant', 'property', 'method'], true)) {
|
||||
$type .= '_'.$element['visibility'];
|
||||
|
||||
if ($element['abstract']) {
|
||||
$type .= '_abstract';
|
||||
}
|
||||
|
||||
if ($element['static']) {
|
||||
$type .= '_static';
|
||||
}
|
||||
|
||||
if ($element['readonly']) {
|
||||
$type .= '_readonly';
|
||||
}
|
||||
}
|
||||
|
||||
$element['position'] = $this->typePosition[$type];
|
||||
}
|
||||
|
||||
unset($element);
|
||||
|
||||
usort($elements, function (array $a, array $b): int {
|
||||
if ($a['position'] === $b['position']) {
|
||||
return $this->sortGroupElements($a, $b);
|
||||
}
|
||||
|
||||
return $a['position'] <=> $b['position'];
|
||||
});
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{
|
||||
* start: int,
|
||||
* visibility: string,
|
||||
* abstract: bool,
|
||||
* static: bool,
|
||||
* readonly: bool,
|
||||
* type: string,
|
||||
* name: string,
|
||||
* end: int,
|
||||
* position: int,
|
||||
* } $a
|
||||
* @param array{
|
||||
* start: int,
|
||||
* visibility: string,
|
||||
* abstract: bool,
|
||||
* static: bool,
|
||||
* readonly: bool,
|
||||
* type: string,
|
||||
* name: string,
|
||||
* end: int,
|
||||
* position: int,
|
||||
* } $b
|
||||
*/
|
||||
private function sortGroupElements(array $a, array $b): int
|
||||
{
|
||||
$selectedSortAlgorithm = $this->configuration['sort_algorithm'];
|
||||
|
||||
if (self::SORT_ALPHA === $selectedSortAlgorithm) {
|
||||
return strcasecmp($a['name'], $b['name']);
|
||||
}
|
||||
|
||||
return $a['start'] <=> $b['start'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array{
|
||||
* start: int,
|
||||
* visibility: string,
|
||||
* abstract: bool,
|
||||
* static: bool,
|
||||
* readonly: bool,
|
||||
* type: string,
|
||||
* name: string,
|
||||
* end: int,
|
||||
* position: int,
|
||||
* }> $elements
|
||||
*/
|
||||
private function sortTokens(Tokens $tokens, int $startIndex, int $endIndex, array $elements): void
|
||||
{
|
||||
$replaceTokens = [];
|
||||
|
||||
foreach ($elements as $element) {
|
||||
for ($i = $element['start']; $i <= $element['end']; ++$i) {
|
||||
$replaceTokens[] = clone $tokens[$i];
|
||||
}
|
||||
}
|
||||
|
||||
$tokens->overrideRange($startIndex + 1, $endIndex, $replaceTokens);
|
||||
}
|
||||
}
|
||||
Vendored
+246
@@ -0,0 +1,246 @@
|
||||
<?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\ClassNotation;
|
||||
|
||||
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 Dave van der Brugge <dmvdbrugge@gmail.com>
|
||||
*/
|
||||
final class OrderedInterfacesFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/** @internal */
|
||||
public const OPTION_DIRECTION = 'direction';
|
||||
|
||||
/** @internal */
|
||||
public const OPTION_ORDER = 'order';
|
||||
|
||||
/** @internal */
|
||||
public const DIRECTION_ASCEND = 'ascend';
|
||||
|
||||
/** @internal */
|
||||
public const DIRECTION_DESCEND = 'descend';
|
||||
|
||||
/** @internal */
|
||||
public const ORDER_ALPHA = 'alpha';
|
||||
|
||||
/** @internal */
|
||||
public const ORDER_LENGTH = 'length';
|
||||
|
||||
/**
|
||||
* Array of supported directions in configuration.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private const SUPPORTED_DIRECTION_OPTIONS = [
|
||||
self::DIRECTION_ASCEND,
|
||||
self::DIRECTION_DESCEND,
|
||||
];
|
||||
|
||||
/**
|
||||
* Array of supported orders in configuration.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private const SUPPORTED_ORDER_OPTIONS = [
|
||||
self::ORDER_ALPHA,
|
||||
self::ORDER_LENGTH,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Orders the interfaces in an `implements` or `interface extends` clause.',
|
||||
[
|
||||
new CodeSample(
|
||||
"<?php\n\nfinal class ExampleA implements Gamma, Alpha, Beta {}\n\ninterface ExampleB extends Gamma, Alpha, Beta {}\n"
|
||||
),
|
||||
new CodeSample(
|
||||
"<?php\n\nfinal class ExampleA implements Gamma, Alpha, Beta {}\n\ninterface ExampleB extends Gamma, Alpha, Beta {}\n",
|
||||
[self::OPTION_DIRECTION => self::DIRECTION_DESCEND]
|
||||
),
|
||||
new CodeSample(
|
||||
"<?php\n\nfinal class ExampleA implements MuchLonger, Short, Longer {}\n\ninterface ExampleB extends MuchLonger, Short, Longer {}\n",
|
||||
[self::OPTION_ORDER => self::ORDER_LENGTH]
|
||||
),
|
||||
new CodeSample(
|
||||
"<?php\n\nfinal class ExampleA implements MuchLonger, Short, Longer {}\n\ninterface ExampleB extends MuchLonger, Short, Longer {}\n",
|
||||
[
|
||||
self::OPTION_ORDER => self::ORDER_LENGTH,
|
||||
self::OPTION_DIRECTION => self::DIRECTION_DESCEND,
|
||||
]
|
||||
),
|
||||
],
|
||||
null,
|
||||
"Risky for `implements` when specifying both an interface and its parent interface, because PHP doesn't break on `parent, child` but does on `child, parent`."
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_IMPLEMENTS)
|
||||
|| $tokens->isAllTokenKindsFound([T_INTERFACE, T_EXTENDS]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->isGivenKind(T_IMPLEMENTS)) {
|
||||
if (!$token->isGivenKind(T_EXTENDS)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$nameTokenIndex = $tokens->getPrevMeaningfulToken($index);
|
||||
$interfaceTokenIndex = $tokens->getPrevMeaningfulToken($nameTokenIndex);
|
||||
$interfaceToken = $tokens[$interfaceTokenIndex];
|
||||
|
||||
if (!$interfaceToken->isGivenKind(T_INTERFACE)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$implementsStart = $index + 1;
|
||||
$implementsEnd = $tokens->getPrevNonWhitespace($tokens->getNextTokenOfKind($implementsStart, ['{']));
|
||||
|
||||
$interfaces = $this->getInterfaces($tokens, $implementsStart, $implementsEnd);
|
||||
|
||||
if (1 === \count($interfaces)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($interfaces as $interfaceIndex => $interface) {
|
||||
$interfaceTokens = Tokens::fromArray($interface, false);
|
||||
$normalized = '';
|
||||
$actualInterfaceIndex = $interfaceTokens->getNextMeaningfulToken(-1);
|
||||
|
||||
while ($interfaceTokens->offsetExists($actualInterfaceIndex)) {
|
||||
$token = $interfaceTokens[$actualInterfaceIndex];
|
||||
|
||||
if ($token->isComment() || $token->isWhitespace()) {
|
||||
break;
|
||||
}
|
||||
|
||||
$normalized .= str_replace('\\', ' ', $token->getContent());
|
||||
++$actualInterfaceIndex;
|
||||
}
|
||||
|
||||
$interfaces[$interfaceIndex] = [
|
||||
'tokens' => $interface,
|
||||
'normalized' => $normalized,
|
||||
'originalIndex' => $interfaceIndex,
|
||||
];
|
||||
}
|
||||
|
||||
usort($interfaces, function (array $first, array $second): int {
|
||||
$score = self::ORDER_LENGTH === $this->configuration[self::OPTION_ORDER]
|
||||
? \strlen($first['normalized']) - \strlen($second['normalized'])
|
||||
: strcasecmp($first['normalized'], $second['normalized']);
|
||||
|
||||
if (self::DIRECTION_DESCEND === $this->configuration[self::OPTION_DIRECTION]) {
|
||||
$score *= -1;
|
||||
}
|
||||
|
||||
return $score;
|
||||
});
|
||||
|
||||
$changed = false;
|
||||
|
||||
foreach ($interfaces as $interfaceIndex => $interface) {
|
||||
if ($interface['originalIndex'] !== $interfaceIndex) {
|
||||
$changed = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$changed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$newTokens = array_shift($interfaces)['tokens'];
|
||||
|
||||
foreach ($interfaces as $interface) {
|
||||
array_push($newTokens, new Token(','), ...$interface['tokens']);
|
||||
}
|
||||
|
||||
$tokens->overrideRange($implementsStart, $implementsEnd, $newTokens);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder(self::OPTION_ORDER, 'How the interfaces should be ordered'))
|
||||
->setAllowedValues(self::SUPPORTED_ORDER_OPTIONS)
|
||||
->setDefault(self::ORDER_ALPHA)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder(self::OPTION_DIRECTION, 'Which direction the interfaces should be ordered'))
|
||||
->setAllowedValues(self::SUPPORTED_DIRECTION_OPTIONS)
|
||||
->setDefault(self::DIRECTION_ASCEND)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, list<Token>>
|
||||
*/
|
||||
private function getInterfaces(Tokens $tokens, int $implementsStart, int $implementsEnd): array
|
||||
{
|
||||
$interfaces = [];
|
||||
$interfaceIndex = 0;
|
||||
|
||||
for ($i = $implementsStart; $i <= $implementsEnd; ++$i) {
|
||||
if ($tokens[$i]->equals(',')) {
|
||||
++$interfaceIndex;
|
||||
$interfaces[$interfaceIndex] = [];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$interfaces[$interfaceIndex][] = $tokens[$i];
|
||||
}
|
||||
|
||||
return $interfaces;
|
||||
}
|
||||
}
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
<?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\ClassNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class OrderedTraitsFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Trait `use` statements must be sorted alphabetically.',
|
||||
[
|
||||
new CodeSample("<?php class Foo { \nuse Z; use A; }\n"),
|
||||
],
|
||||
null,
|
||||
'Risky when depending on order of the imports.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(CT::T_USE_TRAIT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($this->findUseStatementsGroups($tokens) as $uses) {
|
||||
$this->sortUseStatements($tokens, $uses);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<array<int, Tokens>>
|
||||
*/
|
||||
private function findUseStatementsGroups(Tokens $tokens): iterable
|
||||
{
|
||||
$uses = [];
|
||||
|
||||
for ($index = 1, $max = \count($tokens); $index < $max; ++$index) {
|
||||
$token = $tokens[$index];
|
||||
|
||||
if ($token->isWhitespace() || $token->isComment()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$token->isGivenKind(CT::T_USE_TRAIT)) {
|
||||
if (\count($uses) > 0) {
|
||||
yield $uses;
|
||||
|
||||
$uses = [];
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$startIndex = $tokens->getNextNonWhitespace($tokens->getPrevMeaningfulToken($index));
|
||||
$endIndex = $tokens->getNextTokenOfKind($index, [';', '{']);
|
||||
|
||||
if ($tokens[$endIndex]->equals('{')) {
|
||||
$endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $endIndex);
|
||||
}
|
||||
|
||||
$use = [];
|
||||
|
||||
for ($i = $startIndex; $i <= $endIndex; ++$i) {
|
||||
$use[] = $tokens[$i];
|
||||
}
|
||||
|
||||
$uses[$startIndex] = Tokens::fromArray($use);
|
||||
|
||||
$index = $endIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, Tokens> $uses
|
||||
*/
|
||||
private function sortUseStatements(Tokens $tokens, array $uses): void
|
||||
{
|
||||
foreach ($uses as $use) {
|
||||
$this->sortMultipleTraitsInStatement($use);
|
||||
}
|
||||
|
||||
$this->sort($tokens, $uses);
|
||||
}
|
||||
|
||||
private function sortMultipleTraitsInStatement(Tokens $use): void
|
||||
{
|
||||
$traits = [];
|
||||
$indexOfName = null;
|
||||
$name = [];
|
||||
|
||||
for ($index = 0, $max = \count($use); $index < $max; ++$index) {
|
||||
$token = $use[$index];
|
||||
|
||||
if ($token->isGivenKind([T_STRING, T_NS_SEPARATOR])) {
|
||||
$name[] = $token;
|
||||
|
||||
if (null === $indexOfName) {
|
||||
$indexOfName = $index;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->equalsAny([',', ';', '{'])) {
|
||||
$traits[$indexOfName] = Tokens::fromArray($name);
|
||||
|
||||
$name = [];
|
||||
$indexOfName = null;
|
||||
}
|
||||
|
||||
if ($token->equals('{')) {
|
||||
$index = $use->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index);
|
||||
}
|
||||
}
|
||||
|
||||
$this->sort($use, $traits);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, Tokens> $elements
|
||||
*/
|
||||
private function sort(Tokens $tokens, array $elements): void
|
||||
{
|
||||
$toTraitName = static function (Tokens $use): string {
|
||||
$string = '';
|
||||
|
||||
foreach ($use as $token) {
|
||||
if ($token->equalsAny([';', '{'])) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ($token->isGivenKind([T_NS_SEPARATOR, T_STRING])) {
|
||||
$string .= $token->getContent();
|
||||
}
|
||||
}
|
||||
|
||||
return ltrim($string, '\\');
|
||||
};
|
||||
|
||||
$sortedElements = $elements;
|
||||
uasort($sortedElements, static function (Tokens $useA, Tokens $useB) use ($toTraitName): int {
|
||||
return strcasecmp($toTraitName($useA), $toTraitName($useB));
|
||||
});
|
||||
|
||||
$sortedElements = array_combine(
|
||||
array_keys($elements),
|
||||
array_values($sortedElements)
|
||||
);
|
||||
|
||||
foreach (array_reverse($sortedElements, true) as $index => $tokensToInsert) {
|
||||
$tokens->overrideRange(
|
||||
$index,
|
||||
$index + \count($elements[$index]) - 1,
|
||||
$tokensToInsert
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+164
@@ -0,0 +1,164 @@
|
||||
<?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\ClassNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
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 Filippo Tessarotto <zoeslam@gmail.com>
|
||||
*/
|
||||
final class ProtectedToPrivateFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Converts `protected` variables and methods to `private` where possible.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
final class Sample
|
||||
{
|
||||
protected $a;
|
||||
|
||||
protected function test()
|
||||
{
|
||||
}
|
||||
}
|
||||
'
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before OrderedClassElementsFixer.
|
||||
* Must run after FinalInternalClassFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 66;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
if (\defined('T_ENUM') && $tokens->isAllTokenKindsFound([T_ENUM, T_PROTECTED])) { // @TODO: drop condition when PHP 8.1+ is required
|
||||
return true;
|
||||
}
|
||||
|
||||
return $tokens->isAllTokenKindsFound([T_CLASS, T_FINAL, T_PROTECTED]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$tokensAnalyzer = new TokensAnalyzer($tokens);
|
||||
$modifierKinds = [T_PUBLIC, T_PROTECTED, T_PRIVATE, T_FINAL, T_ABSTRACT, T_NS_SEPARATOR, T_STRING, CT::T_NULLABLE_TYPE, CT::T_ARRAY_TYPEHINT, T_STATIC, CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION];
|
||||
|
||||
if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required
|
||||
$modifierKinds[] = T_READONLY;
|
||||
}
|
||||
|
||||
$classesCandidate = [];
|
||||
$classElementTypes = ['method' => true, 'property' => true, 'const' => true];
|
||||
|
||||
foreach ($tokensAnalyzer->getClassyElements() as $index => $element) {
|
||||
$classIndex = $element['classIndex'];
|
||||
|
||||
if (!\array_key_exists($classIndex, $classesCandidate)) {
|
||||
$classesCandidate[$classIndex] = $this->isClassCandidate($tokens, $classIndex);
|
||||
}
|
||||
|
||||
if (false === $classesCandidate[$classIndex]) {
|
||||
continue; // not "final" class, "extends", is "anonymous", enum or uses trait
|
||||
}
|
||||
|
||||
if (!isset($classElementTypes[$element['type']])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$previous = $index;
|
||||
$isProtected = false;
|
||||
$isFinal = false;
|
||||
|
||||
do {
|
||||
$previous = $tokens->getPrevMeaningfulToken($previous);
|
||||
|
||||
if ($tokens[$previous]->isGivenKind(T_PROTECTED)) {
|
||||
$isProtected = $previous;
|
||||
} elseif ($tokens[$previous]->isGivenKind(T_FINAL)) {
|
||||
$isFinal = $previous;
|
||||
}
|
||||
} while ($tokens[$previous]->isGivenKind($modifierKinds));
|
||||
|
||||
if (false === $isProtected) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($isFinal && 'const' === $element['type']) {
|
||||
continue; // Final constants cannot be private
|
||||
}
|
||||
|
||||
$element['protected_index'] = $isProtected;
|
||||
$tokens[$element['protected_index']] = new Token([T_PRIVATE, 'private']);
|
||||
}
|
||||
}
|
||||
|
||||
private function isClassCandidate(Tokens $tokens, int $classIndex): bool
|
||||
{
|
||||
if (\defined('T_ENUM') && $tokens[$classIndex]->isGivenKind(T_ENUM)) { // @TODO: drop condition when PHP 8.1+ is required
|
||||
return true;
|
||||
}
|
||||
|
||||
$prevToken = $tokens[$tokens->getPrevMeaningfulToken($classIndex)];
|
||||
|
||||
if (!$prevToken->isGivenKind(T_FINAL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$classNameIndex = $tokens->getNextMeaningfulToken($classIndex); // move to class name as anonymous class is never "final"
|
||||
$classExtendsIndex = $tokens->getNextMeaningfulToken($classNameIndex); // move to possible "extends"
|
||||
|
||||
if ($tokens[$classExtendsIndex]->isGivenKind(T_EXTENDS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$tokens->isTokenKindFound(CT::T_USE_TRAIT)) {
|
||||
return true; // cheap test
|
||||
}
|
||||
|
||||
$classOpenIndex = $tokens->getNextTokenOfKind($classNameIndex, ['{']);
|
||||
$classCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classOpenIndex);
|
||||
$useIndex = $tokens->getNextTokenOfKind($classOpenIndex, [[CT::T_USE_TRAIT]]);
|
||||
|
||||
return null === $useIndex || $useIndex > $classCloseIndex;
|
||||
}
|
||||
}
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
<?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\ClassNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Preg;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use PhpCsFixer\Tokenizer\TokensAnalyzer;
|
||||
|
||||
/**
|
||||
* @author Gregor Harlan <gharlan@web.de>
|
||||
*/
|
||||
final class SelfAccessorFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Inside class or interface element `self` should be preferred to the class name itself.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
class Sample
|
||||
{
|
||||
const BAZ = 1;
|
||||
const BAR = Sample::BAZ;
|
||||
|
||||
public function getBar()
|
||||
{
|
||||
return Sample::BAR;
|
||||
}
|
||||
}
|
||||
'
|
||||
),
|
||||
],
|
||||
null,
|
||||
'Risky when using dynamic calls like get_called_class() or late static binding.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound([T_CLASS, T_INTERFACE]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$tokensAnalyzer = new TokensAnalyzer($tokens);
|
||||
|
||||
foreach ((new NamespacesAnalyzer())->getDeclarations($tokens) as $namespace) {
|
||||
for ($index = $namespace->getScopeStartIndex(); $index < $namespace->getScopeEndIndex(); ++$index) {
|
||||
if (!$tokens[$index]->isGivenKind([T_CLASS, T_INTERFACE]) || $tokensAnalyzer->isAnonymousClass($index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$nameIndex = $tokens->getNextTokenOfKind($index, [[T_STRING]]);
|
||||
$startIndex = $tokens->getNextTokenOfKind($nameIndex, ['{']);
|
||||
$endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $startIndex);
|
||||
|
||||
$name = $tokens[$nameIndex]->getContent();
|
||||
|
||||
$this->replaceNameOccurrences($tokens, $namespace->getFullName(), $name, $startIndex, $endIndex);
|
||||
|
||||
$index = $endIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace occurrences of the name of the classy element by "self" (if possible).
|
||||
*/
|
||||
private function replaceNameOccurrences(Tokens $tokens, string $namespace, string $name, int $startIndex, int $endIndex): void
|
||||
{
|
||||
$tokensAnalyzer = new TokensAnalyzer($tokens);
|
||||
$insideMethodSignatureUntil = null;
|
||||
|
||||
for ($i = $startIndex; $i < $endIndex; ++$i) {
|
||||
if ($i === $insideMethodSignatureUntil) {
|
||||
$insideMethodSignatureUntil = null;
|
||||
}
|
||||
|
||||
$token = $tokens[$i];
|
||||
|
||||
// skip anonymous classes
|
||||
if ($token->isGivenKind(T_CLASS) && $tokensAnalyzer->isAnonymousClass($i)) {
|
||||
$i = $tokens->getNextTokenOfKind($i, ['{']);
|
||||
$i = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $i);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->isGivenKind(T_FUNCTION)) {
|
||||
$i = $tokens->getNextTokenOfKind($i, ['(']);
|
||||
$insideMethodSignatureUntil = $tokens->getNextTokenOfKind($i, ['{', ';']);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$token->equals([T_STRING, $name], false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$nextToken = $tokens[$tokens->getNextMeaningfulToken($i)];
|
||||
if ($nextToken->isGivenKind(T_NS_SEPARATOR)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$classStartIndex = $i;
|
||||
$prevToken = $tokens[$tokens->getPrevMeaningfulToken($i)];
|
||||
if ($prevToken->isGivenKind(T_NS_SEPARATOR)) {
|
||||
$classStartIndex = $this->getClassStart($tokens, $i, $namespace);
|
||||
if (null === $classStartIndex) {
|
||||
continue;
|
||||
}
|
||||
$prevToken = $tokens[$tokens->getPrevMeaningfulToken($classStartIndex)];
|
||||
}
|
||||
if ($prevToken->isGivenKind(T_STRING) || $prevToken->isObjectOperator()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
$prevToken->isGivenKind([T_INSTANCEOF, T_NEW])
|
||||
|| $nextToken->isGivenKind(T_PAAMAYIM_NEKUDOTAYIM)
|
||||
|| (
|
||||
null !== $insideMethodSignatureUntil
|
||||
&& $i < $insideMethodSignatureUntil
|
||||
&& $prevToken->equalsAny(['(', ',', [CT::T_TYPE_COLON], [CT::T_NULLABLE_TYPE]])
|
||||
)
|
||||
) {
|
||||
for ($j = $classStartIndex; $j < $i; ++$j) {
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($j);
|
||||
}
|
||||
$tokens[$i] = new Token([T_STRING, 'self']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getClassStart(Tokens $tokens, int $index, string $namespace): ?int
|
||||
{
|
||||
$namespace = ('' !== $namespace ? '\\'.$namespace : '').'\\';
|
||||
|
||||
foreach (array_reverse(Preg::split('/(\\\\)/', $namespace, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE)) as $piece) {
|
||||
$index = $tokens->getPrevMeaningfulToken($index);
|
||||
if ('\\' === $piece) {
|
||||
if (!$tokens[$index]->isGivenKind(T_NS_SEPARATOR)) {
|
||||
return null;
|
||||
}
|
||||
} elseif (!$tokens[$index]->equals([T_STRING, $piece], false)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return $index;
|
||||
}
|
||||
}
|
||||
Vendored
+201
@@ -0,0 +1,201 @@
|
||||
<?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\ClassNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
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 SelfStaticAccessorFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* @var TokensAnalyzer
|
||||
*/
|
||||
private $tokensAnalyzer;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Inside a `final` class or anonymous class `self` should be preferred to `static`.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
final class Sample
|
||||
{
|
||||
private static $A = 1;
|
||||
|
||||
public function getBar()
|
||||
{
|
||||
return static::class.static::test().static::$A;
|
||||
}
|
||||
|
||||
private static function test()
|
||||
{
|
||||
return \'test\';
|
||||
}
|
||||
}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
final class Foo
|
||||
{
|
||||
public function bar()
|
||||
{
|
||||
return new static();
|
||||
}
|
||||
}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
final class Foo
|
||||
{
|
||||
public function isBar()
|
||||
{
|
||||
return $foo instanceof static;
|
||||
}
|
||||
}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
$a = new class() {
|
||||
public function getBar()
|
||||
{
|
||||
return static::class;
|
||||
}
|
||||
};
|
||||
'
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAllTokenKindsFound([T_CLASS, T_STATIC]) && $tokens->isAnyTokenKindsFound([T_DOUBLE_COLON, T_NEW, T_INSTANCEOF]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after FinalInternalClassFixer, FunctionToConstantFixer, PhpUnitTestCaseStaticMethodCallsFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return -10;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$this->tokensAnalyzer = new TokensAnalyzer($tokens);
|
||||
|
||||
$classIndex = $tokens->getNextTokenOfKind(0, [[T_CLASS]]);
|
||||
|
||||
while (null !== $classIndex) {
|
||||
if (
|
||||
$this->tokensAnalyzer->isAnonymousClass($classIndex)
|
||||
|| $tokens[$tokens->getPrevMeaningfulToken($classIndex)]->isGivenKind(T_FINAL)
|
||||
) {
|
||||
$classIndex = $this->fixClass($tokens, $classIndex);
|
||||
}
|
||||
|
||||
$classIndex = $tokens->getNextTokenOfKind($classIndex, [[T_CLASS]]);
|
||||
}
|
||||
}
|
||||
|
||||
private function fixClass(Tokens $tokens, int $index): int
|
||||
{
|
||||
$index = $tokens->getNextTokenOfKind($index, ['{']);
|
||||
$classOpenCount = 1;
|
||||
|
||||
while ($classOpenCount > 0) {
|
||||
++$index;
|
||||
|
||||
if ($tokens[$index]->equals('{')) {
|
||||
++$classOpenCount;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$index]->equals('}')) {
|
||||
--$classOpenCount;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$index]->isGivenKind(T_FUNCTION)) {
|
||||
// do not fix inside lambda
|
||||
if ($this->tokensAnalyzer->isLambda($index)) {
|
||||
// figure out where the lambda starts
|
||||
$index = $tokens->getNextTokenOfKind($index, ['{']);
|
||||
$openCount = 1;
|
||||
|
||||
do {
|
||||
$index = $tokens->getNextTokenOfKind($index, ['}', '{', [T_CLASS]]);
|
||||
if ($tokens[$index]->equals('}')) {
|
||||
--$openCount;
|
||||
} elseif ($tokens[$index]->equals('{')) {
|
||||
++$openCount;
|
||||
} else {
|
||||
$index = $this->fixClass($tokens, $index);
|
||||
}
|
||||
} while ($openCount > 0);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$index]->isGivenKind([T_NEW, T_INSTANCEOF])) {
|
||||
$index = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
if ($tokens[$index]->isGivenKind(T_STATIC)) {
|
||||
$tokens[$index] = new Token([T_STRING, 'self']);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$tokens[$index]->isGivenKind(T_STATIC)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$staticIndex = $index;
|
||||
$index = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
if (!$tokens[$index]->isGivenKind(T_DOUBLE_COLON)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens[$staticIndex] = new Token([T_STRING, 'self']);
|
||||
}
|
||||
|
||||
return $index;
|
||||
}
|
||||
}
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
<?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\ClassNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
||||
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
|
||||
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\Preg;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use PhpCsFixer\Tokenizer\TokensAnalyzer;
|
||||
|
||||
/**
|
||||
* Fixer for rules defined in PSR2 ¶4.2.
|
||||
*
|
||||
* @author Javier Spagnoletti <phansys@gmail.com>
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class SingleClassElementPerStatementFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before ClassAttributesSeparationFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 56;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'There MUST NOT be more than one property or constant declared per statement.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
final class Example
|
||||
{
|
||||
const FOO_1 = 1, FOO_2 = 2;
|
||||
private static $bar1 = array(1,2,3), $bar2 = [1,2,3];
|
||||
}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
final class Example
|
||||
{
|
||||
const FOO_1 = 1, FOO_2 = 2;
|
||||
private static $bar1 = array(1,2,3), $bar2 = [1,2,3];
|
||||
}
|
||||
',
|
||||
['elements' => ['property']]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$analyzer = new TokensAnalyzer($tokens);
|
||||
$elements = array_reverse($analyzer->getClassyElements(), true);
|
||||
|
||||
foreach ($elements as $index => $element) {
|
||||
if (!\in_array($element['type'], $this->configuration['elements'], true)) {
|
||||
continue; // not in configuration
|
||||
}
|
||||
|
||||
$this->fixElement($tokens, $element['type'], $index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
$values = ['const', 'property'];
|
||||
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('elements', 'List of strings which element should be modified.'))
|
||||
->setDefault($values)
|
||||
->setAllowedTypes(['array'])
|
||||
->setAllowedValues([new AllowedValueSubset($values)])
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function fixElement(Tokens $tokens, string $type, int $index): void
|
||||
{
|
||||
$tokensAnalyzer = new TokensAnalyzer($tokens);
|
||||
$repeatIndex = $index;
|
||||
|
||||
while (true) {
|
||||
$repeatIndex = $tokens->getNextMeaningfulToken($repeatIndex);
|
||||
$repeatToken = $tokens[$repeatIndex];
|
||||
|
||||
if ($tokensAnalyzer->isArray($repeatIndex)) {
|
||||
if ($repeatToken->isGivenKind(T_ARRAY)) {
|
||||
$repeatIndex = $tokens->getNextTokenOfKind($repeatIndex, ['(']);
|
||||
$repeatIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $repeatIndex);
|
||||
} else {
|
||||
$repeatIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $repeatIndex);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($repeatToken->equals(';')) {
|
||||
return; // no repeating found, no fixing needed
|
||||
}
|
||||
|
||||
if ($repeatToken->equals(',')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$start = $tokens->getPrevTokenOfKind($index, [';', '{', '}']);
|
||||
$this->expandElement(
|
||||
$tokens,
|
||||
$type,
|
||||
$tokens->getNextMeaningfulToken($start),
|
||||
$tokens->getNextTokenOfKind($index, [';'])
|
||||
);
|
||||
}
|
||||
|
||||
private function expandElement(Tokens $tokens, string $type, int $startIndex, int $endIndex): void
|
||||
{
|
||||
$divisionContent = null;
|
||||
|
||||
if ($tokens[$startIndex - 1]->isWhitespace()) {
|
||||
$divisionContent = $tokens[$startIndex - 1]->getContent();
|
||||
|
||||
if (Preg::match('#(\n|\r\n)#', $divisionContent, $matches)) {
|
||||
$divisionContent = $matches[0].trim($divisionContent, "\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
// iterate variables to split up
|
||||
for ($i = $endIndex - 1; $i > $startIndex; --$i) {
|
||||
$token = $tokens[$i];
|
||||
|
||||
if ($token->equals(')')) {
|
||||
$i = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $i);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_CLOSE)) {
|
||||
$i = $tokens->findBlockStart(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $i);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$tokens[$i]->equals(',')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens[$i] = new Token(';');
|
||||
|
||||
if ($tokens[$i + 1]->isWhitespace()) {
|
||||
$tokens->clearAt($i + 1);
|
||||
}
|
||||
|
||||
if (null !== $divisionContent && '' !== $divisionContent) {
|
||||
$tokens->insertAt($i + 1, new Token([T_WHITESPACE, $divisionContent]));
|
||||
}
|
||||
|
||||
// collect modifiers
|
||||
$sequence = $this->getModifiersSequences($tokens, $type, $startIndex, $endIndex);
|
||||
$tokens->insertAt($i + 2, $sequence);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Token[]
|
||||
*/
|
||||
private function getModifiersSequences(Tokens $tokens, string $type, int $startIndex, int $endIndex): array
|
||||
{
|
||||
if ('property' === $type) {
|
||||
$tokenKinds = [T_PUBLIC, T_PROTECTED, T_PRIVATE, T_STATIC, T_VAR, T_STRING, T_NS_SEPARATOR, CT::T_NULLABLE_TYPE, CT::T_ARRAY_TYPEHINT, CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION];
|
||||
|
||||
if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required
|
||||
$tokenKinds[] = T_READONLY;
|
||||
}
|
||||
} else {
|
||||
$tokenKinds = [T_PUBLIC, T_PROTECTED, T_PRIVATE, T_CONST];
|
||||
}
|
||||
|
||||
$sequence = [];
|
||||
|
||||
for ($i = $startIndex; $i < $endIndex - 1; ++$i) {
|
||||
if ($tokens[$i]->isComment()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$tokens[$i]->isWhitespace() && !$tokens[$i]->isGivenKind($tokenKinds)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$sequence[] = clone $tokens[$i];
|
||||
}
|
||||
|
||||
return $sequence;
|
||||
}
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
<?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\ClassNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Preg;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class SingleTraitInsertPerStatementFixer extends AbstractFixer
|
||||
{
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Each trait `use` must be done as single statement.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
final class Example
|
||||
{
|
||||
use Foo, Bar;
|
||||
}
|
||||
'
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before BracesFixer, SpaceAfterSemicolonFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 36;
|
||||
}
|
||||
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(CT::T_USE_TRAIT);
|
||||
}
|
||||
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
for ($index = \count($tokens) - 1; 1 < $index; --$index) {
|
||||
if ($tokens[$index]->isGivenKind(CT::T_USE_TRAIT)) {
|
||||
$candidates = $this->getCandidates($tokens, $index);
|
||||
if (\count($candidates) > 0) {
|
||||
$this->fixTraitUse($tokens, $index, $candidates);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $candidates ',' indices to fix
|
||||
*/
|
||||
private function fixTraitUse(Tokens $tokens, int $useTraitIndex, array $candidates): void
|
||||
{
|
||||
foreach ($candidates as $commaIndex) {
|
||||
$inserts = [
|
||||
new Token([CT::T_USE_TRAIT, 'use']),
|
||||
new Token([T_WHITESPACE, ' ']),
|
||||
];
|
||||
|
||||
$nextImportStartIndex = $tokens->getNextMeaningfulToken($commaIndex);
|
||||
|
||||
if ($tokens[$nextImportStartIndex - 1]->isWhitespace()) {
|
||||
if (1 === Preg::match('/\R/', $tokens[$nextImportStartIndex - 1]->getContent())) {
|
||||
array_unshift($inserts, clone $tokens[$useTraitIndex - 1]);
|
||||
}
|
||||
$tokens->clearAt($nextImportStartIndex - 1);
|
||||
}
|
||||
|
||||
$tokens[$commaIndex] = new Token(';');
|
||||
$tokens->insertAt($nextImportStartIndex, $inserts);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
private function getCandidates(Tokens $tokens, int $index): array
|
||||
{
|
||||
$indices = [];
|
||||
$index = $tokens->getNextTokenOfKind($index, [',', ';', '{']);
|
||||
|
||||
while (!$tokens[$index]->equals(';')) {
|
||||
if ($tokens[$index]->equals('{')) {
|
||||
return []; // do not fix use cases with grouping
|
||||
}
|
||||
|
||||
$indices[] = $index;
|
||||
$index = $tokens->getNextTokenOfKind($index, [',', ';', '{']);
|
||||
}
|
||||
|
||||
return array_reverse($indices);
|
||||
}
|
||||
}
|
||||
Vendored
+212
@@ -0,0 +1,212 @@
|
||||
<?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\ClassNotation;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Fixer for rules defined in PSR2 ¶4.3, ¶4.5.
|
||||
*
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class VisibilityRequiredFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Visibility MUST be declared on all properties and methods; `abstract` and `final` MUST be declared before the visibility; `static` MUST be declared after the visibility.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
class Sample
|
||||
{
|
||||
var $a;
|
||||
static protected $var_foo2;
|
||||
|
||||
function A()
|
||||
{
|
||||
}
|
||||
}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
class Sample
|
||||
{
|
||||
const SAMPLE = 1;
|
||||
}
|
||||
',
|
||||
['elements' => ['const']]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before ClassAttributesSeparationFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 56;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('elements', 'The structural elements to fix (PHP >= 7.1 required for `const`).'))
|
||||
->setAllowedTypes(['array'])
|
||||
->setAllowedValues([new AllowedValueSubset(['property', 'method', 'const'])])
|
||||
->setDefault(['property', 'method', 'const'])
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$tokensAnalyzer = new TokensAnalyzer($tokens);
|
||||
|
||||
$propertyTypeDeclarationKinds = [T_STRING, T_NS_SEPARATOR, CT::T_NULLABLE_TYPE, CT::T_ARRAY_TYPEHINT, CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION];
|
||||
|
||||
if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required
|
||||
$propertyReadOnlyType = T_READONLY;
|
||||
$propertyTypeDeclarationKinds[] = T_READONLY;
|
||||
} else {
|
||||
$propertyReadOnlyType = -999;
|
||||
}
|
||||
|
||||
$expectedKindsGeneric = [T_ABSTRACT, T_FINAL, T_PRIVATE, T_PROTECTED, T_PUBLIC, T_STATIC, T_VAR];
|
||||
$expectedKindsPropertyKinds = array_merge($expectedKindsGeneric, $propertyTypeDeclarationKinds);
|
||||
|
||||
foreach (array_reverse($tokensAnalyzer->getClassyElements(), true) as $index => $element) {
|
||||
if (!\in_array($element['type'], $this->configuration['elements'], true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$abstractFinalIndex = null;
|
||||
$visibilityIndex = null;
|
||||
$staticIndex = null;
|
||||
$typeIndex = null;
|
||||
$readOnlyIndex = null;
|
||||
$prevIndex = $tokens->getPrevMeaningfulToken($index);
|
||||
$expectedKinds = 'property' === $element['type']
|
||||
? $expectedKindsPropertyKinds
|
||||
: $expectedKindsGeneric
|
||||
;
|
||||
|
||||
while ($tokens[$prevIndex]->isGivenKind($expectedKinds)) {
|
||||
if ($tokens[$prevIndex]->isGivenKind([T_ABSTRACT, T_FINAL])) {
|
||||
$abstractFinalIndex = $prevIndex;
|
||||
} elseif ($tokens[$prevIndex]->isGivenKind(T_STATIC)) {
|
||||
$staticIndex = $prevIndex;
|
||||
} elseif ($tokens[$prevIndex]->isGivenKind($propertyReadOnlyType)) {
|
||||
$readOnlyIndex = $prevIndex;
|
||||
} elseif ($tokens[$prevIndex]->isGivenKind($propertyTypeDeclarationKinds)) {
|
||||
$typeIndex = $prevIndex;
|
||||
} else {
|
||||
$visibilityIndex = $prevIndex;
|
||||
}
|
||||
|
||||
$prevIndex = $tokens->getPrevMeaningfulToken($prevIndex);
|
||||
}
|
||||
|
||||
if (null !== $typeIndex) {
|
||||
$index = $typeIndex;
|
||||
}
|
||||
|
||||
if ($tokens[$prevIndex]->equals(',')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$swapIndex = $staticIndex ?? $readOnlyIndex; // "static" property cannot be "readonly", so there can always be at most one swap
|
||||
|
||||
if (null !== $swapIndex) {
|
||||
if ($this->isKeywordPlacedProperly($tokens, $swapIndex, $index)) {
|
||||
$index = $swapIndex;
|
||||
} else {
|
||||
$this->moveTokenAndEnsureSingleSpaceFollows($tokens, $swapIndex, $index);
|
||||
}
|
||||
}
|
||||
|
||||
if (null === $visibilityIndex) {
|
||||
$tokens->insertAt($index, [new Token([T_PUBLIC, 'public']), new Token([T_WHITESPACE, ' '])]);
|
||||
} else {
|
||||
if ($tokens[$visibilityIndex]->isGivenKind(T_VAR)) {
|
||||
$tokens[$visibilityIndex] = new Token([T_PUBLIC, 'public']);
|
||||
}
|
||||
if ($this->isKeywordPlacedProperly($tokens, $visibilityIndex, $index)) {
|
||||
$index = $visibilityIndex;
|
||||
} else {
|
||||
$this->moveTokenAndEnsureSingleSpaceFollows($tokens, $visibilityIndex, $index);
|
||||
}
|
||||
}
|
||||
|
||||
if (null === $abstractFinalIndex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->isKeywordPlacedProperly($tokens, $abstractFinalIndex, $index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->moveTokenAndEnsureSingleSpaceFollows($tokens, $abstractFinalIndex, $index);
|
||||
}
|
||||
}
|
||||
|
||||
private function isKeywordPlacedProperly(Tokens $tokens, int $keywordIndex, int $comparedIndex): bool
|
||||
{
|
||||
return $keywordIndex + 2 === $comparedIndex && ' ' === $tokens[$keywordIndex + 1]->getContent();
|
||||
}
|
||||
|
||||
private function moveTokenAndEnsureSingleSpaceFollows(Tokens $tokens, int $fromIndex, int $toIndex): void
|
||||
{
|
||||
$tokens->insertAt($toIndex, [$tokens[$fromIndex], new Token([T_WHITESPACE, ' '])]);
|
||||
$tokens->clearAt($fromIndex);
|
||||
|
||||
if ($tokens[$fromIndex + 1]->isWhitespace()) {
|
||||
$tokens->clearAt($fromIndex + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
<?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\ClassUsage;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Kuba Werłos <werlos@gmail.com>
|
||||
*/
|
||||
final class DateTimeImmutableFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Class `DateTimeImmutable` should be used instead of `DateTime`.',
|
||||
[new CodeSample("<?php\nnew DateTime();\n")],
|
||||
null,
|
||||
'Risky when the code relies on modifying `DateTime` objects or if any of the `date_create*` functions are overridden.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_STRING);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$functionsAnalyzer = new FunctionsAnalyzer();
|
||||
$functionMap = [
|
||||
'date_create' => 'date_create_immutable',
|
||||
'date_create_from_format' => 'date_create_immutable_from_format',
|
||||
];
|
||||
|
||||
$isInNamespace = false;
|
||||
$isImported = false; // e.g. use DateTime;
|
||||
|
||||
for ($index = 0, $limit = $tokens->count(); $index < $limit; ++$index) {
|
||||
$token = $tokens[$index];
|
||||
|
||||
if ($token->isGivenKind(T_NAMESPACE)) {
|
||||
$isInNamespace = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($isInNamespace && $token->isGivenKind(T_USE)) {
|
||||
$nextIndex = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
if ('datetime' !== strtolower($tokens[$nextIndex]->getContent())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$nextNextIndex = $tokens->getNextMeaningfulToken($nextIndex);
|
||||
|
||||
if ($tokens[$nextNextIndex]->equals(';')) {
|
||||
$isImported = true;
|
||||
}
|
||||
|
||||
$index = $nextNextIndex;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$token->isGivenKind(T_STRING)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$prevIndex = $tokens->getPrevMeaningfulToken($index);
|
||||
|
||||
if ($tokens[$prevIndex]->isGivenKind(T_FUNCTION)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$lowercaseContent = strtolower($token->getContent());
|
||||
|
||||
if ('datetime' === $lowercaseContent) {
|
||||
$this->fixClassUsage($tokens, $index, $isInNamespace, $isImported);
|
||||
$limit = $tokens->count(); // update limit, as fixing class usage may insert new token
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($functionMap[$lowercaseContent]) && $functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) {
|
||||
$tokens[$index] = new Token([T_STRING, $functionMap[$lowercaseContent]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function fixClassUsage(Tokens $tokens, int $index, bool $isInNamespace, bool $isImported): void
|
||||
{
|
||||
$nextIndex = $tokens->getNextMeaningfulToken($index);
|
||||
if ($tokens[$nextIndex]->isGivenKind(T_DOUBLE_COLON)) {
|
||||
$nextNextIndex = $tokens->getNextMeaningfulToken($nextIndex);
|
||||
if ($tokens[$nextNextIndex]->isGivenKind(T_STRING)) {
|
||||
$nextNextNextIndex = $tokens->getNextMeaningfulToken($nextNextIndex);
|
||||
if (!$tokens[$nextNextNextIndex]->equals('(')) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$isUsedAlone = false; // e.g. new DateTime();
|
||||
$isUsedWithLeadingBackslash = false; // e.g. new \DateTime();
|
||||
|
||||
$prevIndex = $tokens->getPrevMeaningfulToken($index);
|
||||
if ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) {
|
||||
$prevPrevIndex = $tokens->getPrevMeaningfulToken($prevIndex);
|
||||
if (!$tokens[$prevPrevIndex]->isGivenKind(T_STRING)) {
|
||||
$isUsedWithLeadingBackslash = true;
|
||||
}
|
||||
} elseif (!$tokens[$prevIndex]->isGivenKind(T_DOUBLE_COLON) && !$tokens[$prevIndex]->isObjectOperator()) {
|
||||
$isUsedAlone = true;
|
||||
}
|
||||
|
||||
if ($isUsedWithLeadingBackslash || $isUsedAlone && ($isInNamespace && $isImported || !$isInNamespace)) {
|
||||
$tokens[$index] = new Token([T_STRING, \DateTimeImmutable::class]);
|
||||
if ($isInNamespace && $isUsedAlone) {
|
||||
$tokens->insertAt($index, new Token([T_NS_SEPARATOR, '\\']));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+239
@@ -0,0 +1,239 @@
|
||||
<?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\Comment;
|
||||
|
||||
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\CommentsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use PhpCsFixer\Utils;
|
||||
|
||||
/**
|
||||
* @author Kuba Werłos <werlos@gmail.com>
|
||||
*/
|
||||
final class CommentToPhpdocFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private array $ignoredTags = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_COMMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before GeneralPhpdocAnnotationRemoveFixer, GeneralPhpdocTagRenameFixer, NoBlankLinesAfterPhpdocFixer, NoEmptyPhpdocFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocAlignFixer, PhpdocAnnotationWithoutDotFixer, PhpdocInlineTagNormalizerFixer, PhpdocLineSpanFixer, PhpdocNoAccessFixer, PhpdocNoAliasTagFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocNoUselessInheritdocFixer, PhpdocOrderByValueFixer, PhpdocOrderFixer, PhpdocReturnSelfReferenceFixer, PhpdocSeparationFixer, PhpdocSingleLineVarSpacingFixer, PhpdocSummaryFixer, PhpdocTagCasingFixer, PhpdocTagTypeFixer, PhpdocToCommentFixer, PhpdocToParamTypeFixer, PhpdocToPropertyTypeFixer, PhpdocToReturnTypeFixer, PhpdocTrimConsecutiveBlankLineSeparationFixer, PhpdocTrimFixer, PhpdocTypesOrderFixer, PhpdocVarAnnotationCorrectOrderFixer, PhpdocVarWithoutNameFixer.
|
||||
* Must run after AlignMultilineCommentFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
// Should be run before all other PHPDoc fixers
|
||||
return 26;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Comments with annotation should be docblock when used on structural elements.',
|
||||
[
|
||||
new CodeSample("<?php /* header */ \$x = true; /* @var bool \$isFoo */ \$isFoo = true;\n"),
|
||||
new CodeSample("<?php\n// @todo do something later\n\$foo = 1;\n\n// @var int \$a\n\$a = foo();\n", ['ignored_tags' => ['todo']]),
|
||||
],
|
||||
null,
|
||||
'Risky as new docblocks might mean more, e.g. a Doctrine entity might have a new column in database.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configure(array $configuration): void
|
||||
{
|
||||
parent::configure($configuration);
|
||||
|
||||
$this->ignoredTags = array_map(
|
||||
static function (string $tag): string {
|
||||
return strtolower($tag);
|
||||
},
|
||||
$this->configuration['ignored_tags']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('ignored_tags', 'List of ignored tags'))
|
||||
->setAllowedTypes(['array'])
|
||||
->setDefault([])
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$commentsAnalyzer = new CommentsAnalyzer();
|
||||
|
||||
for ($index = 0, $limit = \count($tokens); $index < $limit; ++$index) {
|
||||
$token = $tokens[$index];
|
||||
|
||||
if (!$token->isGivenKind(T_COMMENT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($commentsAnalyzer->isHeaderComment($tokens, $index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$commentsAnalyzer->isBeforeStructuralElement($tokens, $index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$commentIndices = $commentsAnalyzer->getCommentBlockIndices($tokens, $index);
|
||||
|
||||
if ($this->isCommentCandidate($tokens, $commentIndices)) {
|
||||
$this->fixComment($tokens, $commentIndices);
|
||||
}
|
||||
|
||||
$index = max($commentIndices);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $indices
|
||||
*/
|
||||
private function isCommentCandidate(Tokens $tokens, array $indices): bool
|
||||
{
|
||||
return array_reduce(
|
||||
$indices,
|
||||
function (bool $carry, int $index) use ($tokens): bool {
|
||||
if ($carry) {
|
||||
return true;
|
||||
}
|
||||
if (1 !== Preg::match('~(?:#|//|/\*+|\R(?:\s*\*)?)\s*\@([a-zA-Z0-9_\\\\-]+)(?=\s|\(|$)~', $tokens[$index]->getContent(), $matches)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !\in_array(strtolower($matches[1]), $this->ignoredTags, true);
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $indices
|
||||
*/
|
||||
private function fixComment(Tokens $tokens, array $indices): void
|
||||
{
|
||||
if (1 === \count($indices)) {
|
||||
$this->fixCommentSingleLine($tokens, reset($indices));
|
||||
} else {
|
||||
$this->fixCommentMultiLine($tokens, $indices);
|
||||
}
|
||||
}
|
||||
|
||||
private function fixCommentSingleLine(Tokens $tokens, int $index): void
|
||||
{
|
||||
$message = $this->getMessage($tokens[$index]->getContent());
|
||||
|
||||
if ('' !== trim(substr($message, 0, 1))) {
|
||||
$message = ' '.$message;
|
||||
}
|
||||
|
||||
if ('' !== trim(substr($message, -1))) {
|
||||
$message .= ' ';
|
||||
}
|
||||
|
||||
$tokens[$index] = new Token([T_DOC_COMMENT, '/**'.$message.'*/']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $indices
|
||||
*/
|
||||
private function fixCommentMultiLine(Tokens $tokens, array $indices): void
|
||||
{
|
||||
$startIndex = reset($indices);
|
||||
$indent = Utils::calculateTrailingWhitespaceIndent($tokens[$startIndex - 1]);
|
||||
|
||||
$newContent = '/**'.$this->whitespacesConfig->getLineEnding();
|
||||
$count = max($indices);
|
||||
|
||||
for ($index = $startIndex; $index <= $count; ++$index) {
|
||||
if (!$tokens[$index]->isComment()) {
|
||||
continue;
|
||||
}
|
||||
if (str_contains($tokens[$index]->getContent(), '*/')) {
|
||||
return;
|
||||
}
|
||||
$message = $this->getMessage($tokens[$index]->getContent());
|
||||
if ('' !== trim(substr($message, 0, 1))) {
|
||||
$message = ' '.$message;
|
||||
}
|
||||
$newContent .= $indent.' *'.$message.$this->whitespacesConfig->getLineEnding();
|
||||
}
|
||||
|
||||
for ($index = $startIndex; $index <= $count; ++$index) {
|
||||
$tokens->clearAt($index);
|
||||
}
|
||||
|
||||
$newContent .= $indent.' */';
|
||||
|
||||
$tokens->insertAt($startIndex, new Token([T_DOC_COMMENT, $newContent]));
|
||||
}
|
||||
|
||||
private function getMessage(string $content): string
|
||||
{
|
||||
if (str_starts_with($content, '#')) {
|
||||
return substr($content, 1);
|
||||
}
|
||||
if (str_starts_with($content, '//')) {
|
||||
return substr($content, 2);
|
||||
}
|
||||
|
||||
return rtrim(ltrim($content, '/*'), '*/');
|
||||
}
|
||||
}
|
||||
+454
@@ -0,0 +1,454 @@
|
||||
<?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\Comment;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException;
|
||||
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\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
|
||||
/**
|
||||
* @author Antonio J. García Lagar <aj@garcialagar.es>
|
||||
*/
|
||||
final class HeaderCommentFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public const HEADER_PHPDOC = 'PHPDoc';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public const HEADER_COMMENT = 'comment';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Add, replace or remove header comment.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace A\B;
|
||||
|
||||
echo 1;
|
||||
',
|
||||
[
|
||||
'header' => 'Made with love.',
|
||||
]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace A\B;
|
||||
|
||||
echo 1;
|
||||
',
|
||||
[
|
||||
'header' => 'Made with love.',
|
||||
'comment_type' => 'PHPDoc',
|
||||
'location' => 'after_open',
|
||||
'separate' => 'bottom',
|
||||
]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace A\B;
|
||||
|
||||
echo 1;
|
||||
',
|
||||
[
|
||||
'header' => 'Made with love.',
|
||||
'comment_type' => 'comment',
|
||||
'location' => 'after_declare_strict',
|
||||
]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Comment is not wanted here.
|
||||
*/
|
||||
|
||||
namespace A\B;
|
||||
|
||||
echo 1;
|
||||
',
|
||||
[
|
||||
'header' => '',
|
||||
]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isMonolithicPhp();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before SingleLineCommentStyleFixer.
|
||||
* Must run after DeclareStrictTypesFixer, NoBlankLinesAfterPhpdocFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
// When this fixer is configured with ["separate" => "bottom", "comment_type" => "PHPDoc"]
|
||||
// and the target file has no namespace or declare() construct,
|
||||
// the fixed header comment gets trimmed by NoBlankLinesAfterPhpdocFixer if we run before it.
|
||||
return -30;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$location = $this->configuration['location'];
|
||||
$locationIndices = [];
|
||||
|
||||
foreach (['after_open', 'after_declare_strict'] as $possibleLocation) {
|
||||
$locationIndex = $this->findHeaderCommentInsertionIndex($tokens, $possibleLocation);
|
||||
|
||||
if (!isset($locationIndices[$locationIndex]) || $possibleLocation === $location) {
|
||||
$locationIndices[$locationIndex] = $possibleLocation;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($locationIndices as $possibleLocation) {
|
||||
// figure out where the comment should be placed
|
||||
$headerNewIndex = $this->findHeaderCommentInsertionIndex($tokens, $possibleLocation);
|
||||
|
||||
// check if there is already a comment
|
||||
$headerCurrentIndex = $this->findHeaderCommentCurrentIndex($tokens, $headerNewIndex - 1);
|
||||
|
||||
if (null === $headerCurrentIndex) {
|
||||
if ('' === $this->configuration['header'] || $possibleLocation !== $location) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->insertHeader($tokens, $headerNewIndex);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$sameComment = $this->getHeaderAsComment() === $tokens[$headerCurrentIndex]->getContent();
|
||||
$expectedLocation = $possibleLocation === $location;
|
||||
|
||||
if (!$sameComment || !$expectedLocation) {
|
||||
if ($expectedLocation ^ $sameComment) {
|
||||
$this->removeHeader($tokens, $headerCurrentIndex);
|
||||
}
|
||||
|
||||
if ('' === $this->configuration['header']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($possibleLocation === $location) {
|
||||
$this->insertHeader($tokens, $headerNewIndex);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->fixWhiteSpaceAroundHeader($tokens, $headerCurrentIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
$fixerName = $this->getName();
|
||||
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('header', 'Proper header content.'))
|
||||
->setAllowedTypes(['string'])
|
||||
->setNormalizer(static function (Options $options, string $value) use ($fixerName): string {
|
||||
if ('' === trim($value)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (str_contains($value, '*/')) {
|
||||
throw new InvalidFixerConfigurationException($fixerName, 'Cannot use \'*/\' in header.');
|
||||
}
|
||||
|
||||
return $value;
|
||||
})
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('comment_type', 'Comment syntax type.'))
|
||||
->setAllowedValues([self::HEADER_PHPDOC, self::HEADER_COMMENT])
|
||||
->setDefault(self::HEADER_COMMENT)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('location', 'The location of the inserted header.'))
|
||||
->setAllowedValues(['after_open', 'after_declare_strict'])
|
||||
->setDefault('after_declare_strict')
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('separate', 'Whether the header should be separated from the file content with a new line.'))
|
||||
->setAllowedValues(['both', 'top', 'bottom', 'none'])
|
||||
->setDefault('both')
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enclose the given text in a comment block.
|
||||
*/
|
||||
private function getHeaderAsComment(): string
|
||||
{
|
||||
$lineEnding = $this->whitespacesConfig->getLineEnding();
|
||||
$comment = (self::HEADER_COMMENT === $this->configuration['comment_type'] ? '/*' : '/**').$lineEnding;
|
||||
$lines = explode("\n", str_replace("\r", '', $this->configuration['header']));
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$comment .= rtrim(' * '.$line).$lineEnding;
|
||||
}
|
||||
|
||||
return $comment.' */';
|
||||
}
|
||||
|
||||
private function findHeaderCommentCurrentIndex(Tokens $tokens, int $headerNewIndex): ?int
|
||||
{
|
||||
$index = $tokens->getNextNonWhitespace($headerNewIndex);
|
||||
|
||||
if (null === $index || !$tokens[$index]->isComment()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$next = $index + 1;
|
||||
|
||||
if (!isset($tokens[$next]) || \in_array($this->configuration['separate'], ['top', 'none'], true) || !$tokens[$index]->isGivenKind(T_DOC_COMMENT)) {
|
||||
return $index;
|
||||
}
|
||||
|
||||
if ($tokens[$next]->isWhitespace()) {
|
||||
if (!Preg::match('/^\h*\R\h*$/D', $tokens[$next]->getContent())) {
|
||||
return $index;
|
||||
}
|
||||
|
||||
++$next;
|
||||
}
|
||||
|
||||
if (!isset($tokens[$next]) || !$tokens[$next]->isClassy() && !$tokens[$next]->isGivenKind(T_FUNCTION)) {
|
||||
return $index;
|
||||
}
|
||||
|
||||
return $this->getHeaderAsComment() === $tokens[$index]->getContent() ? $index : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the index where the header comment must be inserted.
|
||||
*/
|
||||
private function findHeaderCommentInsertionIndex(Tokens $tokens, string $location): int
|
||||
{
|
||||
$openTagIndex = $tokens[0]->isGivenKind(T_OPEN_TAG) ? 0 : $tokens->getNextTokenOfKind(0, [[T_OPEN_TAG]]);
|
||||
|
||||
if (null === $openTagIndex) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ('after_open' === $location) {
|
||||
return $openTagIndex + 1;
|
||||
}
|
||||
|
||||
$index = $tokens->getNextMeaningfulToken($openTagIndex);
|
||||
|
||||
if (null === $index) {
|
||||
return $openTagIndex + 1; // file without meaningful tokens but an open tag, comment should always be placed directly after the open tag
|
||||
}
|
||||
|
||||
if (!$tokens[$index]->isGivenKind(T_DECLARE)) {
|
||||
return $openTagIndex + 1;
|
||||
}
|
||||
|
||||
$next = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
if (null === $next || !$tokens[$next]->equals('(')) {
|
||||
return $openTagIndex + 1;
|
||||
}
|
||||
|
||||
$next = $tokens->getNextMeaningfulToken($next);
|
||||
|
||||
if (null === $next || !$tokens[$next]->equals([T_STRING, 'strict_types'], false)) {
|
||||
return $openTagIndex + 1;
|
||||
}
|
||||
|
||||
$next = $tokens->getNextMeaningfulToken($next);
|
||||
|
||||
if (null === $next || !$tokens[$next]->equals('=')) {
|
||||
return $openTagIndex + 1;
|
||||
}
|
||||
|
||||
$next = $tokens->getNextMeaningfulToken($next);
|
||||
|
||||
if (null === $next || !$tokens[$next]->isGivenKind(T_LNUMBER)) {
|
||||
return $openTagIndex + 1;
|
||||
}
|
||||
|
||||
$next = $tokens->getNextMeaningfulToken($next);
|
||||
|
||||
if (null === $next || !$tokens[$next]->equals(')')) {
|
||||
return $openTagIndex + 1;
|
||||
}
|
||||
|
||||
$next = $tokens->getNextMeaningfulToken($next);
|
||||
|
||||
if (null === $next || !$tokens[$next]->equals(';')) { // don't insert after close tag
|
||||
return $openTagIndex + 1;
|
||||
}
|
||||
|
||||
return $next + 1;
|
||||
}
|
||||
|
||||
private function fixWhiteSpaceAroundHeader(Tokens $tokens, int $headerIndex): void
|
||||
{
|
||||
$lineEnding = $this->whitespacesConfig->getLineEnding();
|
||||
|
||||
// fix lines after header comment
|
||||
if (
|
||||
('both' === $this->configuration['separate'] || 'bottom' === $this->configuration['separate'])
|
||||
&& null !== $tokens->getNextMeaningfulToken($headerIndex)
|
||||
) {
|
||||
$expectedLineCount = 2;
|
||||
} else {
|
||||
$expectedLineCount = 1;
|
||||
}
|
||||
|
||||
if ($headerIndex === \count($tokens) - 1) {
|
||||
$tokens->insertAt($headerIndex + 1, new Token([T_WHITESPACE, str_repeat($lineEnding, $expectedLineCount)]));
|
||||
} else {
|
||||
$lineBreakCount = $this->getLineBreakCount($tokens, $headerIndex, 1);
|
||||
|
||||
if ($lineBreakCount < $expectedLineCount) {
|
||||
$missing = str_repeat($lineEnding, $expectedLineCount - $lineBreakCount);
|
||||
|
||||
if ($tokens[$headerIndex + 1]->isWhitespace()) {
|
||||
$tokens[$headerIndex + 1] = new Token([T_WHITESPACE, $missing.$tokens[$headerIndex + 1]->getContent()]);
|
||||
} else {
|
||||
$tokens->insertAt($headerIndex + 1, new Token([T_WHITESPACE, $missing]));
|
||||
}
|
||||
} elseif ($lineBreakCount > $expectedLineCount && $tokens[$headerIndex + 1]->isWhitespace()) {
|
||||
$newLinesToRemove = $lineBreakCount - $expectedLineCount;
|
||||
$tokens[$headerIndex + 1] = new Token([
|
||||
T_WHITESPACE,
|
||||
Preg::replace("/^\\R{{$newLinesToRemove}}/", '', $tokens[$headerIndex + 1]->getContent()),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// fix lines before header comment
|
||||
$expectedLineCount = 'both' === $this->configuration['separate'] || 'top' === $this->configuration['separate'] ? 2 : 1;
|
||||
$prev = $tokens->getPrevNonWhitespace($headerIndex);
|
||||
|
||||
$regex = '/\h$/';
|
||||
|
||||
if ($tokens[$prev]->isGivenKind(T_OPEN_TAG) && Preg::match($regex, $tokens[$prev]->getContent())) {
|
||||
$tokens[$prev] = new Token([T_OPEN_TAG, Preg::replace($regex, $lineEnding, $tokens[$prev]->getContent())]);
|
||||
}
|
||||
|
||||
$lineBreakCount = $this->getLineBreakCount($tokens, $headerIndex, -1);
|
||||
|
||||
if ($lineBreakCount < $expectedLineCount) {
|
||||
// because of the way the insert index was determined for header comment there cannot be an empty token here
|
||||
$tokens->insertAt($headerIndex, new Token([T_WHITESPACE, str_repeat($lineEnding, $expectedLineCount - $lineBreakCount)]));
|
||||
}
|
||||
}
|
||||
|
||||
private function getLineBreakCount(Tokens $tokens, int $index, int $direction): int
|
||||
{
|
||||
$whitespace = '';
|
||||
|
||||
for ($index += $direction; isset($tokens[$index]); $index += $direction) {
|
||||
$token = $tokens[$index];
|
||||
|
||||
if ($token->isWhitespace()) {
|
||||
$whitespace .= $token->getContent();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (-1 === $direction && $token->isGivenKind(T_OPEN_TAG)) {
|
||||
$whitespace .= $token->getContent();
|
||||
}
|
||||
|
||||
if ('' !== $token->getContent()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return substr_count($whitespace, "\n");
|
||||
}
|
||||
|
||||
private function removeHeader(Tokens $tokens, int $index): void
|
||||
{
|
||||
$prevIndex = $index - 1;
|
||||
$prevToken = $tokens[$prevIndex];
|
||||
$newlineRemoved = false;
|
||||
|
||||
if ($prevToken->isWhitespace()) {
|
||||
$content = $prevToken->getContent();
|
||||
|
||||
if (Preg::match('/\R/', $content)) {
|
||||
$newlineRemoved = true;
|
||||
}
|
||||
|
||||
$content = Preg::replace('/\R?\h*$/', '', $content);
|
||||
|
||||
$tokens->ensureWhitespaceAtIndex($prevIndex, 0, $content);
|
||||
}
|
||||
|
||||
$nextIndex = $index + 1;
|
||||
$nextToken = $tokens[$nextIndex] ?? null;
|
||||
|
||||
if (!$newlineRemoved && null !== $nextToken && $nextToken->isWhitespace()) {
|
||||
$content = Preg::replace('/^\R/', '', $nextToken->getContent());
|
||||
|
||||
$tokens->ensureWhitespaceAtIndex($nextIndex, 0, $content);
|
||||
}
|
||||
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($index);
|
||||
}
|
||||
|
||||
private function insertHeader(Tokens $tokens, int $index): void
|
||||
{
|
||||
$tokens->insertAt($index, new Token([self::HEADER_COMMENT === $this->configuration['comment_type'] ? T_COMMENT : T_DOC_COMMENT, $this->getHeaderAsComment()]));
|
||||
$this->fixWhiteSpaceAroundHeader($tokens, $index);
|
||||
}
|
||||
}
|
||||
Vendored
+98
@@ -0,0 +1,98 @@
|
||||
<?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\Comment;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author Filippo Tessarotto <zoeslam@gmail.com>
|
||||
*/
|
||||
final class MultilineCommentOpeningClosingFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'DocBlocks must start with two asterisks, multiline comments must start with a single asterisk, after the opening slash. Both must end with a single asterisk before the closing slash.',
|
||||
[
|
||||
new CodeSample(
|
||||
<<<'EOT'
|
||||
<?php
|
||||
|
||||
/******
|
||||
* Multiline comment with arbitrary asterisks count
|
||||
******/
|
||||
|
||||
/**\
|
||||
* Multiline comment that seems a DocBlock
|
||||
*/
|
||||
|
||||
/**
|
||||
* DocBlock with arbitrary asterisk count at the end
|
||||
**/
|
||||
|
||||
EOT
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound([T_COMMENT, T_DOC_COMMENT]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($tokens as $index => $token) {
|
||||
$originalContent = $token->getContent();
|
||||
|
||||
if (
|
||||
!$token->isGivenKind(T_DOC_COMMENT)
|
||||
&& !($token->isGivenKind(T_COMMENT) && str_starts_with($originalContent, '/*'))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$newContent = $originalContent;
|
||||
|
||||
// Fix opening
|
||||
if ($token->isGivenKind(T_COMMENT)) {
|
||||
$newContent = Preg::replace('/^\\/\\*{2,}(?!\\/)/', '/*', $newContent);
|
||||
}
|
||||
|
||||
// Fix closing
|
||||
$newContent = Preg::replace('/(?<!\\/)\\*{2,}\\/$/', '*/', $newContent);
|
||||
|
||||
if ($newContent !== $originalContent) {
|
||||
$tokens[$index] = new Token([$token->getId(), $newContent]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
<?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\Comment;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Preg;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class NoEmptyCommentFixer extends AbstractFixer
|
||||
{
|
||||
private const TYPE_HASH = 1;
|
||||
|
||||
private const TYPE_DOUBLE_SLASH = 2;
|
||||
|
||||
private const TYPE_SLASH_ASTERISK = 3;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before NoExtraBlankLinesFixer, NoTrailingWhitespaceFixer, NoWhitespaceInBlankLineFixer.
|
||||
* Must run after PhpdocToCommentFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'There should not be any empty comments.',
|
||||
[new CodeSample("<?php\n//\n#\n/* */\n")]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_COMMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
for ($index = 1, $count = \count($tokens); $index < $count; ++$index) {
|
||||
if (!$tokens[$index]->isGivenKind(T_COMMENT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[$blockStart, $index, $isEmpty] = $this->getCommentBlock($tokens, $index);
|
||||
if (false === $isEmpty) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for ($i = $blockStart; $i <= $index; ++$i) {
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the start index, end index and a flag stating if the comment block is empty.
|
||||
*
|
||||
* @param int $index T_COMMENT index
|
||||
*/
|
||||
private function getCommentBlock(Tokens $tokens, int $index): array
|
||||
{
|
||||
$commentType = $this->getCommentType($tokens[$index]->getContent());
|
||||
$empty = $this->isEmptyComment($tokens[$index]->getContent());
|
||||
|
||||
if (self::TYPE_SLASH_ASTERISK === $commentType) {
|
||||
return [$index, $index, $empty];
|
||||
}
|
||||
|
||||
$start = $index;
|
||||
$count = \count($tokens);
|
||||
++$index;
|
||||
|
||||
for (; $index < $count; ++$index) {
|
||||
if ($tokens[$index]->isComment()) {
|
||||
if ($commentType !== $this->getCommentType($tokens[$index]->getContent())) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ($empty) { // don't retest if already known the block not being empty
|
||||
$empty = $this->isEmptyComment($tokens[$index]->getContent());
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$tokens[$index]->isWhitespace() || $this->getLineBreakCount($tokens, $index, $index + 1) > 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return [$start, $index - 1, $empty];
|
||||
}
|
||||
|
||||
private function getCommentType(string $content): int
|
||||
{
|
||||
if (str_starts_with($content, '#')) {
|
||||
return self::TYPE_HASH;
|
||||
}
|
||||
|
||||
if ('*' === $content[1]) {
|
||||
return self::TYPE_SLASH_ASTERISK;
|
||||
}
|
||||
|
||||
return self::TYPE_DOUBLE_SLASH;
|
||||
}
|
||||
|
||||
private function getLineBreakCount(Tokens $tokens, int $whiteStart, int $whiteEnd): int
|
||||
{
|
||||
$lineCount = 0;
|
||||
for ($i = $whiteStart; $i < $whiteEnd; ++$i) {
|
||||
$lineCount += Preg::matchAll('/\R/u', $tokens[$i]->getContent(), $matches);
|
||||
}
|
||||
|
||||
return $lineCount;
|
||||
}
|
||||
|
||||
private function isEmptyComment(string $content): bool
|
||||
{
|
||||
static $mapper = [
|
||||
self::TYPE_HASH => '|^#\s*$|', // single line comment starting with '#'
|
||||
self::TYPE_SLASH_ASTERISK => '|^/\*[\s\*]*\*+/$|', // comment starting with '/*' and ending with '*/' (but not a PHPDoc)
|
||||
self::TYPE_DOUBLE_SLASH => '|^//\s*$|', // single line comment starting with '//'
|
||||
];
|
||||
|
||||
$type = $this->getCommentType($content);
|
||||
|
||||
return 1 === Preg::match($mapper[$type], $content);
|
||||
}
|
||||
}
|
||||
Vendored
+84
@@ -0,0 +1,84 @@
|
||||
<?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\Comment;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class NoTrailingWhitespaceInCommentFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'There MUST be no trailing spaces inside comment or PHPDoc.',
|
||||
[new CodeSample('<?php
|
||||
// This is '.'
|
||||
// a comment. '.'
|
||||
')]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after PhpdocNoUselessInheritdocFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound([T_COMMENT, T_DOC_COMMENT]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($tokens as $index => $token) {
|
||||
if ($token->isGivenKind(T_DOC_COMMENT)) {
|
||||
$tokens[$index] = new Token([T_DOC_COMMENT, Preg::replace('/(*ANY)[\h]+$/m', '', $token->getContent())]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->isGivenKind(T_COMMENT)) {
|
||||
if (str_starts_with($token->getContent(), '/*')) {
|
||||
$tokens[$index] = new Token([T_COMMENT, Preg::replace('/(*ANY)[\h]+$/m', '', $token->getContent())]);
|
||||
} elseif (isset($tokens[$index + 1]) && $tokens[$index + 1]->isWhitespace()) {
|
||||
$trimmedContent = ltrim($tokens[$index + 1]->getContent(), " \t");
|
||||
$tokens->ensureWhitespaceAtIndex($index + 1, 0, $trimmedContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+119
@@ -0,0 +1,119 @@
|
||||
<?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\Comment;
|
||||
|
||||
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 SingleLineCommentSpacingFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Single-line comments must have proper spacing.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
//comment 1
|
||||
#comment 2
|
||||
/*comment 3*/
|
||||
'
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after PhpdocToCommentFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_COMMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
for ($index = \count($tokens) - 1; 0 <= $index; --$index) {
|
||||
$token = $tokens[$index];
|
||||
|
||||
if (!$token->isGivenKind(T_COMMENT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$content = $token->getContent();
|
||||
$contentLength = \strlen($content);
|
||||
|
||||
if ('/' === $content[0]) {
|
||||
if ($contentLength < 3) {
|
||||
continue; // cheap check for "//"
|
||||
}
|
||||
|
||||
if ('*' === $content[1]) { // slash asterisk comment
|
||||
if ($contentLength < 5 || '*' === $content[2] || str_contains($content, "\n")) {
|
||||
continue; // cheap check for "/**/", comment that looks like a PHPDoc, or multi line comment
|
||||
}
|
||||
|
||||
$newContent = rtrim(substr($content, 0, -2)).' '.substr($content, -2);
|
||||
$newContent = $this->fixCommentLeadingSpace($newContent, '/*');
|
||||
} else { // double slash comment
|
||||
$newContent = $this->fixCommentLeadingSpace($content, '//');
|
||||
}
|
||||
} else { // hash comment
|
||||
if ($contentLength < 2 || '[' === $content[1]) { // cheap check for "#" or annotation (like) comment
|
||||
continue;
|
||||
}
|
||||
|
||||
$newContent = $this->fixCommentLeadingSpace($content, '#');
|
||||
}
|
||||
|
||||
if ($newContent !== $content) {
|
||||
$tokens[$index] = new Token([T_COMMENT, $newContent]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fix space between comment open and leading text
|
||||
private function fixCommentLeadingSpace(string $content, string $prefix): string
|
||||
{
|
||||
if (0 !== Preg::match(sprintf('@^%s\h+.*$@', preg_quote($prefix, '@')), $content)) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
$position = \strlen($prefix);
|
||||
|
||||
return substr($content, 0, $position).' '.substr($content, $position);
|
||||
}
|
||||
}
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
<?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\Comment;
|
||||
|
||||
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\Preg;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Filippo Tessarotto <zoeslam@gmail.com>
|
||||
*/
|
||||
final class SingleLineCommentStyleFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $asteriskEnabled;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $hashEnabled;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configure(array $configuration): void
|
||||
{
|
||||
parent::configure($configuration);
|
||||
|
||||
$this->asteriskEnabled = \in_array('asterisk', $this->configuration['comment_types'], true);
|
||||
$this->hashEnabled = \in_array('hash', $this->configuration['comment_types'], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Single-line comments and multi-line comments with only one line of actual content should use the `//` syntax.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
/* asterisk comment */
|
||||
$a = 1;
|
||||
|
||||
# hash comment
|
||||
$b = 2;
|
||||
|
||||
/*
|
||||
* multi-line
|
||||
* comment
|
||||
*/
|
||||
$c = 3;
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
/* first comment */
|
||||
$a = 1;
|
||||
|
||||
/*
|
||||
* second comment
|
||||
*/
|
||||
$b = 2;
|
||||
|
||||
/*
|
||||
* third
|
||||
* comment
|
||||
*/
|
||||
$c = 3;
|
||||
',
|
||||
['comment_types' => ['asterisk']]
|
||||
),
|
||||
new CodeSample(
|
||||
"<?php # comment\n",
|
||||
['comment_types' => ['hash']]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after HeaderCommentFixer, NoUselessReturnFixer, PhpdocToCommentFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return -31;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_COMMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->isGivenKind(T_COMMENT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$content = $token->getContent();
|
||||
$commentContent = substr($content, 2, -2) ?: '';
|
||||
|
||||
if ($this->hashEnabled && str_starts_with($content, '#')) {
|
||||
if (isset($content[1]) && '[' === $content[1]) {
|
||||
continue; // This might be an attribute on PHP8, do not change
|
||||
}
|
||||
|
||||
$tokens[$index] = new Token([$token->getId(), '//'.substr($content, 1)]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
!$this->asteriskEnabled
|
||||
|| str_contains($commentContent, '?>')
|
||||
|| !str_starts_with($content, '/*')
|
||||
|| 1 === Preg::match('/[^\s\*].*\R.*[^\s\*]/s', $commentContent)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$nextTokenIndex = $index + 1;
|
||||
if (isset($tokens[$nextTokenIndex])) {
|
||||
$nextToken = $tokens[$nextTokenIndex];
|
||||
if (!$nextToken->isWhitespace() || 1 !== Preg::match('/\R/', $nextToken->getContent())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens[$nextTokenIndex] = new Token([$nextToken->getId(), ltrim($nextToken->getContent(), " \t")]);
|
||||
}
|
||||
|
||||
$content = '//';
|
||||
if (1 === Preg::match('/[^\s\*]/', $commentContent)) {
|
||||
$content = '// '.Preg::replace('/[\s\*]*([^\s\*](?:.+[^\s\*])?)[\s\*]*/', '\1', $commentContent);
|
||||
}
|
||||
$tokens[$index] = new Token([$token->getId(), $content]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('comment_types', 'List of comment types to fix'))
|
||||
->setAllowedTypes(['array'])
|
||||
->setAllowedValues([new AllowedValueSubset(['asterisk', 'hash'])])
|
||||
->setDefault(['asterisk', 'hash'])
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
<?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;
|
||||
|
||||
use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
|
||||
|
||||
/**
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
interface ConfigurableFixerInterface extends FixerInterface
|
||||
{
|
||||
/**
|
||||
* Set configuration.
|
||||
*
|
||||
* New configuration must override current one, not patch it.
|
||||
* Using empty array makes fixer to use default configuration
|
||||
* (or reset configuration from previously configured back to default one).
|
||||
*
|
||||
* Some fixers may have no configuration, then - simply don't implement this interface.
|
||||
* Other ones may have configuration that will change behavior of fixer,
|
||||
* eg `php_unit_strict` fixer allows to configure which methods should be fixed.
|
||||
* Finally, some fixers need configuration to work, eg `header_comment`.
|
||||
*
|
||||
* @param array $configuration configuration depends on Fixer
|
||||
*
|
||||
* @throws InvalidFixerConfigurationException
|
||||
*/
|
||||
public function configure(array $configuration): void;
|
||||
|
||||
/**
|
||||
* Defines the available configuration options of the fixer.
|
||||
*/
|
||||
public function getConfigurationDefinition(): FixerConfigurationResolverInterface;
|
||||
}
|
||||
+305
@@ -0,0 +1,305 @@
|
||||
<?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\ConstantNotation;
|
||||
|
||||
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\Analyzer\Analysis\NamespaceAnalysis;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use PhpCsFixer\Tokenizer\TokensAnalyzer;
|
||||
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
|
||||
|
||||
/**
|
||||
* @author Filippo Tessarotto <zoeslam@gmail.com>
|
||||
*/
|
||||
final class NativeConstantInvocationFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* @var array<string, true>
|
||||
*/
|
||||
private array $constantsToEscape = [];
|
||||
|
||||
/**
|
||||
* @var array<string, true>
|
||||
*/
|
||||
private array $caseInsensitiveConstantsToEscape = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Add leading `\` before constant invocation of internal constant to speed up resolving. Constant name match is case-sensitive, except for `null`, `false` and `true`.',
|
||||
[
|
||||
new CodeSample("<?php var_dump(PHP_VERSION, M_PI, MY_CUSTOM_PI);\n"),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
namespace space1 {
|
||||
echo PHP_VERSION;
|
||||
}
|
||||
namespace {
|
||||
echo M_PI;
|
||||
}
|
||||
',
|
||||
['scope' => 'namespaced']
|
||||
),
|
||||
new CodeSample(
|
||||
"<?php var_dump(PHP_VERSION, M_PI, MY_CUSTOM_PI);\n",
|
||||
[
|
||||
'include' => [
|
||||
'MY_CUSTOM_PI',
|
||||
],
|
||||
]
|
||||
),
|
||||
new CodeSample(
|
||||
"<?php var_dump(PHP_VERSION, M_PI, MY_CUSTOM_PI);\n",
|
||||
[
|
||||
'fix_built_in' => false,
|
||||
'include' => [
|
||||
'MY_CUSTOM_PI',
|
||||
],
|
||||
]
|
||||
),
|
||||
new CodeSample(
|
||||
"<?php var_dump(PHP_VERSION, M_PI, MY_CUSTOM_PI);\n",
|
||||
[
|
||||
'exclude' => [
|
||||
'M_PI',
|
||||
],
|
||||
]
|
||||
),
|
||||
],
|
||||
null,
|
||||
'Risky when any of the constants are namespaced or overridden.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before GlobalNamespaceImportFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 10;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_STRING);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configure(array $configuration): void
|
||||
{
|
||||
parent::configure($configuration);
|
||||
|
||||
$uniqueConfiguredExclude = array_unique($this->configuration['exclude']);
|
||||
|
||||
// Case-sensitive constants handling
|
||||
$constantsToEscape = array_values($this->configuration['include']);
|
||||
|
||||
if (true === $this->configuration['fix_built_in']) {
|
||||
$getDefinedConstants = get_defined_constants(true);
|
||||
unset($getDefinedConstants['user']);
|
||||
foreach ($getDefinedConstants as $constants) {
|
||||
$constantsToEscape = array_merge($constantsToEscape, array_keys($constants));
|
||||
}
|
||||
}
|
||||
|
||||
$constantsToEscape = array_diff(
|
||||
array_unique($constantsToEscape),
|
||||
$uniqueConfiguredExclude
|
||||
);
|
||||
|
||||
// Case-insensitive constants handling
|
||||
static $caseInsensitiveConstants = ['null', 'false', 'true'];
|
||||
$caseInsensitiveConstantsToEscape = [];
|
||||
|
||||
foreach ($constantsToEscape as $constantIndex => $constant) {
|
||||
$loweredConstant = strtolower($constant);
|
||||
if (\in_array($loweredConstant, $caseInsensitiveConstants, true)) {
|
||||
$caseInsensitiveConstantsToEscape[] = $loweredConstant;
|
||||
unset($constantsToEscape[$constantIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
$caseInsensitiveConstantsToEscape = array_diff(
|
||||
array_unique($caseInsensitiveConstantsToEscape),
|
||||
array_map(
|
||||
static fn (string $function): string => strtolower($function),
|
||||
$uniqueConfiguredExclude,
|
||||
),
|
||||
);
|
||||
|
||||
// Store the cache
|
||||
$this->constantsToEscape = array_fill_keys($constantsToEscape, true);
|
||||
ksort($this->constantsToEscape);
|
||||
|
||||
$this->caseInsensitiveConstantsToEscape = array_fill_keys($caseInsensitiveConstantsToEscape, true);
|
||||
ksort($this->caseInsensitiveConstantsToEscape);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
if ('all' === $this->configuration['scope']) {
|
||||
$this->fixConstantInvocations($tokens, 0, \count($tokens) - 1);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$namespaces = (new NamespacesAnalyzer())->getDeclarations($tokens);
|
||||
|
||||
// 'scope' is 'namespaced' here
|
||||
/** @var NamespaceAnalysis $namespace */
|
||||
foreach (array_reverse($namespaces) as $namespace) {
|
||||
if ($namespace->isGlobalNamespace()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->fixConstantInvocations($tokens, $namespace->getScopeStartIndex(), $namespace->getScopeEndIndex());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
$constantChecker = static function (array $value): bool {
|
||||
foreach ($value as $constantName) {
|
||||
if (!\is_string($constantName) || '' === trim($constantName) || trim($constantName) !== $constantName) {
|
||||
throw new InvalidOptionsException(sprintf(
|
||||
'Each element must be a non-empty, trimmed string, got "%s" instead.',
|
||||
get_debug_type($constantName)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('fix_built_in', 'Whether to fix constants returned by `get_defined_constants`. User constants are not accounted in this list and must be specified in the include one.'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(true)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('include', 'List of additional constants to fix.'))
|
||||
->setAllowedTypes(['array'])
|
||||
->setAllowedValues([$constantChecker])
|
||||
->setDefault([])
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('exclude', 'List of constants to ignore.'))
|
||||
->setAllowedTypes(['array'])
|
||||
->setAllowedValues([$constantChecker])
|
||||
->setDefault(['null', 'false', 'true'])
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('scope', 'Only fix constant invocations that are made within a namespace or fix all.'))
|
||||
->setAllowedValues(['all', 'namespaced'])
|
||||
->setDefault('all')
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('strict', 'Whether leading `\` of constant invocation not meant to have it should be removed.'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(true)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function fixConstantInvocations(Tokens $tokens, int $startIndex, int $endIndex): void
|
||||
{
|
||||
$useDeclarations = (new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens);
|
||||
$useConstantDeclarations = [];
|
||||
|
||||
foreach ($useDeclarations as $use) {
|
||||
if ($use->isConstant()) {
|
||||
$useConstantDeclarations[$use->getShortName()] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$tokenAnalyzer = new TokensAnalyzer($tokens);
|
||||
|
||||
for ($index = $endIndex; $index > $startIndex; --$index) {
|
||||
$token = $tokens[$index];
|
||||
|
||||
// test if we are at a constant call
|
||||
if (!$token->isGivenKind(T_STRING)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$tokenAnalyzer->isConstantInvocation($index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokenContent = $token->getContent();
|
||||
$prevIndex = $tokens->getPrevMeaningfulToken($index);
|
||||
|
||||
if (!isset($this->constantsToEscape[$tokenContent]) && !isset($this->caseInsensitiveConstantsToEscape[strtolower($tokenContent)])) {
|
||||
if (false === $this->configuration['strict']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$prevPrevIndex = $tokens->getPrevMeaningfulToken($prevIndex);
|
||||
|
||||
if ($tokens[$prevPrevIndex]->isGivenKind(T_STRING)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($prevIndex);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($useConstantDeclarations[$tokenContent])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens->insertAt($index, new Token([T_NS_SEPARATOR, '\\']));
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* @author Kuba Werłos <werlos@gmail.com>
|
||||
*/
|
||||
interface DeprecatedFixerInterface extends FixerInterface
|
||||
{
|
||||
/**
|
||||
* Returns names of fixers to use instead, if any.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getSuccessorsNames(): array;
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
<?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\DoctrineAnnotation;
|
||||
|
||||
use Doctrine\Common\Annotations\DocLexer;
|
||||
use PhpCsFixer\AbstractDoctrineAnnotationFixer;
|
||||
use PhpCsFixer\Doctrine\Annotation\Tokens;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
|
||||
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
|
||||
/**
|
||||
* Forces the configured operator for assignment in arrays in Doctrine Annotations.
|
||||
*/
|
||||
final class DoctrineAnnotationArrayAssignmentFixer extends AbstractDoctrineAnnotationFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Doctrine annotations must use configured operator for assignment in arrays.',
|
||||
[
|
||||
new CodeSample(
|
||||
"<?php\n/**\n * @Foo({bar : \"baz\"})\n */\nclass Bar {}\n"
|
||||
),
|
||||
new CodeSample(
|
||||
"<?php\n/**\n * @Foo({bar = \"baz\"})\n */\nclass Bar {}\n",
|
||||
['operator' => ':']
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before DoctrineAnnotationSpacesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
$options = parent::createConfigurationDefinition()->getOptions();
|
||||
|
||||
$operator = new FixerOptionBuilder('operator', 'The operator to use.');
|
||||
$options[] = $operator
|
||||
->setAllowedValues(['=', ':'])
|
||||
->setDefault('=')
|
||||
->getOption()
|
||||
;
|
||||
|
||||
return new FixerConfigurationResolver($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function fixAnnotations(Tokens $doctrineAnnotationTokens): void
|
||||
{
|
||||
$scopes = [];
|
||||
foreach ($doctrineAnnotationTokens as $token) {
|
||||
if ($token->isType(DocLexer::T_OPEN_PARENTHESIS)) {
|
||||
$scopes[] = 'annotation';
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->isType(DocLexer::T_OPEN_CURLY_BRACES)) {
|
||||
$scopes[] = 'array';
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->isType([DocLexer::T_CLOSE_PARENTHESIS, DocLexer::T_CLOSE_CURLY_BRACES])) {
|
||||
array_pop($scopes);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('array' === end($scopes) && $token->isType([DocLexer::T_EQUALS, DocLexer::T_COLON])) {
|
||||
$token->setContent($this->configuration['operator']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
<?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\DoctrineAnnotation;
|
||||
|
||||
use Doctrine\Common\Annotations\DocLexer;
|
||||
use PhpCsFixer\AbstractDoctrineAnnotationFixer;
|
||||
use PhpCsFixer\Doctrine\Annotation\Token;
|
||||
use PhpCsFixer\Doctrine\Annotation\Tokens;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
|
||||
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
|
||||
/**
|
||||
* Adds braces to Doctrine annotations when missing.
|
||||
*/
|
||||
final class DoctrineAnnotationBracesFixer extends AbstractDoctrineAnnotationFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Doctrine annotations without arguments must use the configured syntax.',
|
||||
[
|
||||
new CodeSample(
|
||||
"<?php\n/**\n * @Foo()\n */\nclass Bar {}\n"
|
||||
),
|
||||
new CodeSample(
|
||||
"<?php\n/**\n * @Foo\n */\nclass Bar {}\n",
|
||||
['syntax' => 'with_braces']
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver(array_merge(
|
||||
parent::createConfigurationDefinition()->getOptions(),
|
||||
[
|
||||
(new FixerOptionBuilder('syntax', 'Whether to add or remove braces.'))
|
||||
->setAllowedValues(['with_braces', 'without_braces'])
|
||||
->setDefault('without_braces')
|
||||
->getOption(),
|
||||
]
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function fixAnnotations(Tokens $doctrineAnnotationTokens): void
|
||||
{
|
||||
if ('without_braces' === $this->configuration['syntax']) {
|
||||
$this->removesBracesFromAnnotations($doctrineAnnotationTokens);
|
||||
} else {
|
||||
$this->addBracesToAnnotations($doctrineAnnotationTokens);
|
||||
}
|
||||
}
|
||||
|
||||
private function addBracesToAnnotations(Tokens $tokens): void
|
||||
{
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$tokens[$index]->isType(DocLexer::T_AT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$braceIndex = $tokens->getNextMeaningfulToken($index + 1);
|
||||
if (null !== $braceIndex && $tokens[$braceIndex]->isType(DocLexer::T_OPEN_PARENTHESIS)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens->insertAt($index + 2, new Token(DocLexer::T_OPEN_PARENTHESIS, '('));
|
||||
$tokens->insertAt($index + 3, new Token(DocLexer::T_CLOSE_PARENTHESIS, ')'));
|
||||
}
|
||||
}
|
||||
|
||||
private function removesBracesFromAnnotations(Tokens $tokens): void
|
||||
{
|
||||
for ($index = 0, $max = \count($tokens); $index < $max; ++$index) {
|
||||
if (!$tokens[$index]->isType(DocLexer::T_AT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$openBraceIndex = $tokens->getNextMeaningfulToken($index + 1);
|
||||
if (null === $openBraceIndex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$tokens[$openBraceIndex]->isType(DocLexer::T_OPEN_PARENTHESIS)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$closeBraceIndex = $tokens->getNextMeaningfulToken($openBraceIndex);
|
||||
if (null === $closeBraceIndex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$tokens[$closeBraceIndex]->isType(DocLexer::T_CLOSE_PARENTHESIS)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for ($currentIndex = $index + 2; $currentIndex <= $closeBraceIndex; ++$currentIndex) {
|
||||
$tokens[$currentIndex]->clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
<?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\DoctrineAnnotation;
|
||||
|
||||
use Doctrine\Common\Annotations\DocLexer;
|
||||
use PhpCsFixer\AbstractDoctrineAnnotationFixer;
|
||||
use PhpCsFixer\Doctrine\Annotation\Tokens;
|
||||
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;
|
||||
|
||||
final class DoctrineAnnotationIndentationFixer extends AbstractDoctrineAnnotationFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Doctrine annotations must be indented with four spaces.',
|
||||
[
|
||||
new CodeSample("<?php\n/**\n * @Foo(\n * foo=\"foo\"\n * )\n */\nclass Bar {}\n"),
|
||||
new CodeSample(
|
||||
"<?php\n/**\n * @Foo({@Bar,\n * @Baz})\n */\nclass Bar {}\n",
|
||||
['indent_mixed_lines' => true]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver(array_merge(
|
||||
parent::createConfigurationDefinition()->getOptions(),
|
||||
[
|
||||
(new FixerOptionBuilder('indent_mixed_lines', 'Whether to indent lines that have content before closing parenthesis.'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(false)
|
||||
->getOption(),
|
||||
]
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function fixAnnotations(Tokens $doctrineAnnotationTokens): void
|
||||
{
|
||||
$annotationPositions = [];
|
||||
for ($index = 0, $max = \count($doctrineAnnotationTokens); $index < $max; ++$index) {
|
||||
if (!$doctrineAnnotationTokens[$index]->isType(DocLexer::T_AT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$annotationEndIndex = $doctrineAnnotationTokens->getAnnotationEnd($index);
|
||||
if (null === $annotationEndIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
$annotationPositions[] = [$index, $annotationEndIndex];
|
||||
$index = $annotationEndIndex;
|
||||
}
|
||||
|
||||
$indentLevel = 0;
|
||||
foreach ($doctrineAnnotationTokens as $index => $token) {
|
||||
if (!$token->isType(DocLexer::T_NONE) || !str_contains($token->getContent(), "\n")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->indentationCanBeFixed($doctrineAnnotationTokens, $index, $annotationPositions)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$braces = $this->getLineBracesCount($doctrineAnnotationTokens, $index);
|
||||
$delta = $braces[0] - $braces[1];
|
||||
$mixedBraces = 0 === $delta && $braces[0] > 0;
|
||||
$extraIndentLevel = 0;
|
||||
|
||||
if ($indentLevel > 0 && ($delta < 0 || $mixedBraces)) {
|
||||
--$indentLevel;
|
||||
|
||||
if (true === $this->configuration['indent_mixed_lines'] && $this->isClosingLineWithMeaningfulContent($doctrineAnnotationTokens, $index)) {
|
||||
$extraIndentLevel = 1;
|
||||
}
|
||||
}
|
||||
|
||||
$token->setContent(Preg::replace(
|
||||
'/(\n( +\*)?) *$/',
|
||||
'$1'.str_repeat(' ', 4 * ($indentLevel + $extraIndentLevel) + 1),
|
||||
$token->getContent()
|
||||
));
|
||||
|
||||
if ($delta > 0 || $mixedBraces) {
|
||||
++$indentLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
private function getLineBracesCount(Tokens $tokens, int $index): array
|
||||
{
|
||||
$opening = 0;
|
||||
$closing = 0;
|
||||
|
||||
while (isset($tokens[++$index])) {
|
||||
$token = $tokens[$index];
|
||||
if ($token->isType(DocLexer::T_NONE) && str_contains($token->getContent(), "\n")) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ($token->isType([DocLexer::T_OPEN_PARENTHESIS, DocLexer::T_OPEN_CURLY_BRACES])) {
|
||||
++$opening;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$token->isType([DocLexer::T_CLOSE_PARENTHESIS, DocLexer::T_CLOSE_CURLY_BRACES])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($opening > 0) {
|
||||
--$opening;
|
||||
} else {
|
||||
++$closing;
|
||||
}
|
||||
}
|
||||
|
||||
return [$opening, $closing];
|
||||
}
|
||||
|
||||
private function isClosingLineWithMeaningfulContent(Tokens $tokens, int $index): bool
|
||||
{
|
||||
while (isset($tokens[++$index])) {
|
||||
$token = $tokens[$index];
|
||||
if ($token->isType(DocLexer::T_NONE)) {
|
||||
if (str_contains($token->getContent(), "\n")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
return !$token->isType([DocLexer::T_CLOSE_PARENTHESIS, DocLexer::T_CLOSE_CURLY_BRACES]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array<int>> $annotationPositions Pairs of begin and end indices of main annotations
|
||||
*/
|
||||
private function indentationCanBeFixed(Tokens $tokens, int $newLineTokenIndex, array $annotationPositions): bool
|
||||
{
|
||||
foreach ($annotationPositions as $position) {
|
||||
if ($newLineTokenIndex >= $position[0] && $newLineTokenIndex <= $position[1]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for ($index = $newLineTokenIndex + 1, $max = \count($tokens); $index < $max; ++$index) {
|
||||
$token = $tokens[$index];
|
||||
|
||||
if (str_contains($token->getContent(), "\n")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $tokens[$index]->isType(DocLexer::T_AT);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+301
@@ -0,0 +1,301 @@
|
||||
<?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\DoctrineAnnotation;
|
||||
|
||||
use Doctrine\Common\Annotations\DocLexer;
|
||||
use PhpCsFixer\AbstractDoctrineAnnotationFixer;
|
||||
use PhpCsFixer\Doctrine\Annotation\Token;
|
||||
use PhpCsFixer\Doctrine\Annotation\Tokens;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Fixes spaces around commas and assignment operators in Doctrine annotations.
|
||||
*/
|
||||
final class DoctrineAnnotationSpacesFixer extends AbstractDoctrineAnnotationFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Fixes spaces in Doctrine annotations.',
|
||||
[
|
||||
new CodeSample(
|
||||
"<?php\n/**\n * @Foo ( )\n */\nclass Bar {}\n\n/**\n * @Foo(\"bar\" ,\"baz\")\n */\nclass Bar2 {}\n\n/**\n * @Foo(foo = \"foo\", bar = {\"foo\":\"foo\", \"bar\"=\"bar\"})\n */\nclass Bar3 {}\n"
|
||||
),
|
||||
new CodeSample(
|
||||
"<?php\n/**\n * @Foo(foo = \"foo\", bar = {\"foo\":\"foo\", \"bar\"=\"bar\"})\n */\nclass Bar {}\n",
|
||||
['after_array_assignments_equals' => false, 'before_array_assignments_equals' => false]
|
||||
),
|
||||
],
|
||||
'There must not be any space around parentheses; commas must be preceded by no space and followed by one space; there must be no space around named arguments assignment operator; there must be one space around array assignment operator.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after DoctrineAnnotationArrayAssignmentFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver(array_merge(
|
||||
parent::createConfigurationDefinition()->getOptions(),
|
||||
[
|
||||
(new FixerOptionBuilder('around_parentheses', 'Whether to fix spaces around parentheses.'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(true)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('around_commas', 'Whether to fix spaces around commas.'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(true)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('before_argument_assignments', 'Whether to add, remove or ignore spaces before argument assignment operator.'))
|
||||
->setAllowedTypes(['null', 'bool'])
|
||||
->setDefault(false)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('after_argument_assignments', 'Whether to add, remove or ignore spaces after argument assignment operator.'))
|
||||
->setAllowedTypes(['null', 'bool'])
|
||||
->setDefault(false)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('before_array_assignments_equals', 'Whether to add, remove or ignore spaces before array `=` assignment operator.'))
|
||||
->setAllowedTypes(['null', 'bool'])
|
||||
->setDefault(true)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('after_array_assignments_equals', 'Whether to add, remove or ignore spaces after array assignment `=` operator.'))
|
||||
->setAllowedTypes(['null', 'bool'])
|
||||
->setDefault(true)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('before_array_assignments_colon', 'Whether to add, remove or ignore spaces before array `:` assignment operator.'))
|
||||
->setAllowedTypes(['null', 'bool'])
|
||||
->setDefault(true)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('after_array_assignments_colon', 'Whether to add, remove or ignore spaces after array assignment `:` operator.'))
|
||||
->setAllowedTypes(['null', 'bool'])
|
||||
->setDefault(true)
|
||||
->getOption(),
|
||||
]
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function fixAnnotations(Tokens $doctrineAnnotationTokens): void
|
||||
{
|
||||
if (true === $this->configuration['around_parentheses']) {
|
||||
$this->fixSpacesAroundParentheses($doctrineAnnotationTokens);
|
||||
}
|
||||
|
||||
if (true === $this->configuration['around_commas']) {
|
||||
$this->fixSpacesAroundCommas($doctrineAnnotationTokens);
|
||||
}
|
||||
|
||||
if (
|
||||
null !== $this->configuration['before_argument_assignments']
|
||||
|| null !== $this->configuration['after_argument_assignments']
|
||||
|| null !== $this->configuration['before_array_assignments_equals']
|
||||
|| null !== $this->configuration['after_array_assignments_equals']
|
||||
|| null !== $this->configuration['before_array_assignments_colon']
|
||||
|| null !== $this->configuration['after_array_assignments_colon']
|
||||
) {
|
||||
$this->fixAroundAssignments($doctrineAnnotationTokens);
|
||||
}
|
||||
}
|
||||
|
||||
private function fixSpacesAroundParentheses(Tokens $tokens): void
|
||||
{
|
||||
$inAnnotationUntilIndex = null;
|
||||
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (null !== $inAnnotationUntilIndex) {
|
||||
if ($index === $inAnnotationUntilIndex) {
|
||||
$inAnnotationUntilIndex = null;
|
||||
|
||||
continue;
|
||||
}
|
||||
} elseif ($tokens[$index]->isType(DocLexer::T_AT)) {
|
||||
$endIndex = $tokens->getAnnotationEnd($index);
|
||||
if (null !== $endIndex) {
|
||||
$inAnnotationUntilIndex = $endIndex + 1;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (null === $inAnnotationUntilIndex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$token->isType([DocLexer::T_OPEN_PARENTHESIS, DocLexer::T_CLOSE_PARENTHESIS])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->isType(DocLexer::T_OPEN_PARENTHESIS)) {
|
||||
$token = $tokens[$index - 1];
|
||||
if ($token->isType(DocLexer::T_NONE)) {
|
||||
$token->clear();
|
||||
}
|
||||
|
||||
$token = $tokens[$index + 1];
|
||||
} else {
|
||||
$token = $tokens[$index - 1];
|
||||
}
|
||||
|
||||
if ($token->isType(DocLexer::T_NONE)) {
|
||||
if (str_contains($token->getContent(), "\n")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$token->clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function fixSpacesAroundCommas(Tokens $tokens): void
|
||||
{
|
||||
$inAnnotationUntilIndex = null;
|
||||
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (null !== $inAnnotationUntilIndex) {
|
||||
if ($index === $inAnnotationUntilIndex) {
|
||||
$inAnnotationUntilIndex = null;
|
||||
|
||||
continue;
|
||||
}
|
||||
} elseif ($tokens[$index]->isType(DocLexer::T_AT)) {
|
||||
$endIndex = $tokens->getAnnotationEnd($index);
|
||||
if (null !== $endIndex) {
|
||||
$inAnnotationUntilIndex = $endIndex;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (null === $inAnnotationUntilIndex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$token->isType(DocLexer::T_COMMA)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$token = $tokens[$index - 1];
|
||||
if ($token->isType(DocLexer::T_NONE)) {
|
||||
$token->clear();
|
||||
}
|
||||
|
||||
if ($index < \count($tokens) - 1 && !Preg::match('/^\s/', $tokens[$index + 1]->getContent())) {
|
||||
$tokens->insertAt($index + 1, new Token(DocLexer::T_NONE, ' '));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function fixAroundAssignments(Tokens $tokens): void
|
||||
{
|
||||
$beforeArguments = $this->configuration['before_argument_assignments'];
|
||||
$afterArguments = $this->configuration['after_argument_assignments'];
|
||||
$beforeArraysEquals = $this->configuration['before_array_assignments_equals'];
|
||||
$afterArraysEquals = $this->configuration['after_array_assignments_equals'];
|
||||
$beforeArraysColon = $this->configuration['before_array_assignments_colon'];
|
||||
$afterArraysColon = $this->configuration['after_array_assignments_colon'];
|
||||
|
||||
$scopes = [];
|
||||
foreach ($tokens as $index => $token) {
|
||||
$endScopeType = end($scopes);
|
||||
if (false !== $endScopeType && $token->isType($endScopeType)) {
|
||||
array_pop($scopes);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$index]->isType(DocLexer::T_AT)) {
|
||||
$scopes[] = DocLexer::T_CLOSE_PARENTHESIS;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$index]->isType(DocLexer::T_OPEN_CURLY_BRACES)) {
|
||||
$scopes[] = DocLexer::T_CLOSE_CURLY_BRACES;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (DocLexer::T_CLOSE_PARENTHESIS === $endScopeType && $token->isType(DocLexer::T_EQUALS)) {
|
||||
$this->updateSpacesAfter($tokens, $index, $afterArguments);
|
||||
$this->updateSpacesBefore($tokens, $index, $beforeArguments);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (DocLexer::T_CLOSE_CURLY_BRACES === $endScopeType) {
|
||||
if ($token->isType(DocLexer::T_EQUALS)) {
|
||||
$this->updateSpacesAfter($tokens, $index, $afterArraysEquals);
|
||||
$this->updateSpacesBefore($tokens, $index, $beforeArraysEquals);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->isType(DocLexer::T_COLON)) {
|
||||
$this->updateSpacesAfter($tokens, $index, $afterArraysColon);
|
||||
$this->updateSpacesBefore($tokens, $index, $beforeArraysColon);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function updateSpacesAfter(Tokens $tokens, int $index, ?bool $insert): void
|
||||
{
|
||||
$this->updateSpacesAt($tokens, $index + 1, $index + 1, $insert);
|
||||
}
|
||||
|
||||
private function updateSpacesBefore(Tokens $tokens, int $index, ?bool $insert): void
|
||||
{
|
||||
$this->updateSpacesAt($tokens, $index - 1, $index, $insert);
|
||||
}
|
||||
|
||||
private function updateSpacesAt(Tokens $tokens, int $index, int $insertIndex, ?bool $insert): void
|
||||
{
|
||||
if (null === $insert) {
|
||||
return;
|
||||
}
|
||||
|
||||
$token = $tokens[$index];
|
||||
if ($insert) {
|
||||
if (!$token->isType(DocLexer::T_NONE)) {
|
||||
$tokens->insertAt($insertIndex, $token = new Token());
|
||||
}
|
||||
|
||||
$token->setContent(' ');
|
||||
} elseif ($token->isType(DocLexer::T_NONE)) {
|
||||
$token->clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?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;
|
||||
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
interface FixerInterface
|
||||
{
|
||||
/**
|
||||
* Check if the fixer is a candidate for given Tokens collection.
|
||||
*
|
||||
* Fixer is a candidate when the collection contains tokens that may be fixed
|
||||
* during fixer work. This could be considered as some kind of bloom filter.
|
||||
* When this method returns true then to the Tokens collection may or may not
|
||||
* need a fixing, but when this method returns false then the Tokens collection
|
||||
* need no fixing for sure.
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool;
|
||||
|
||||
/**
|
||||
* Check if fixer is risky or not.
|
||||
*
|
||||
* Risky fixer could change code behavior!
|
||||
*/
|
||||
public function isRisky(): bool;
|
||||
|
||||
/**
|
||||
* Fixes a file.
|
||||
*
|
||||
* @param \SplFileInfo $file A \SplFileInfo instance
|
||||
* @param Tokens $tokens Tokens collection
|
||||
*/
|
||||
public function fix(\SplFileInfo $file, Tokens $tokens): void;
|
||||
|
||||
/**
|
||||
* Returns the definition of the fixer.
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface;
|
||||
|
||||
/**
|
||||
* Returns the name of the fixer.
|
||||
*
|
||||
* The name must be all lowercase and without any spaces.
|
||||
*
|
||||
* @return string The name of the fixer
|
||||
*/
|
||||
public function getName(): string;
|
||||
|
||||
/**
|
||||
* Returns the priority of the fixer.
|
||||
*
|
||||
* The default priority is 0 and higher priorities are executed first.
|
||||
*/
|
||||
public function getPriority(): int;
|
||||
|
||||
/**
|
||||
* Returns true if the file is supported by this fixer.
|
||||
*
|
||||
* @return bool true if the file is supported by this fixer, false otherwise
|
||||
*/
|
||||
public function supports(\SplFileInfo $file): bool;
|
||||
}
|
||||
Vendored
+236
@@ -0,0 +1,236 @@
|
||||
<?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\FunctionNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Gregor Harlan
|
||||
*/
|
||||
final class CombineNestedDirnameFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Replace multiple nested calls of `dirname` by only one call with second `$level` parameter. Requires PHP >= 7.0.',
|
||||
[
|
||||
new CodeSample(
|
||||
"<?php\ndirname(dirname(dirname(\$path)));\n"
|
||||
),
|
||||
],
|
||||
null,
|
||||
'Risky when the function `dirname` is overridden.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_STRING);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before MethodArgumentSpaceFixer, NoSpacesInsideParenthesisFixer.
|
||||
* Must run after DirConstantFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 35;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
for ($index = $tokens->count() - 1; 0 <= $index; --$index) {
|
||||
$dirnameInfo = $this->getDirnameInfo($tokens, $index);
|
||||
|
||||
if (!$dirnameInfo) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$prev = $tokens->getPrevMeaningfulToken($dirnameInfo['indexes'][0]);
|
||||
|
||||
if (!$tokens[$prev]->equals('(')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$prev = $tokens->getPrevMeaningfulToken($prev);
|
||||
$firstArgumentEnd = $dirnameInfo['end'];
|
||||
$dirnameInfoArray = [$dirnameInfo];
|
||||
|
||||
while ($dirnameInfo = $this->getDirnameInfo($tokens, $prev, $firstArgumentEnd)) {
|
||||
$dirnameInfoArray[] = $dirnameInfo;
|
||||
$prev = $tokens->getPrevMeaningfulToken($dirnameInfo['indexes'][0]);
|
||||
|
||||
if (!$tokens[$prev]->equals('(')) {
|
||||
break;
|
||||
}
|
||||
|
||||
$prev = $tokens->getPrevMeaningfulToken($prev);
|
||||
$firstArgumentEnd = $dirnameInfo['end'];
|
||||
}
|
||||
|
||||
if (\count($dirnameInfoArray) > 1) {
|
||||
$this->combineDirnames($tokens, $dirnameInfoArray);
|
||||
}
|
||||
|
||||
$index = $prev;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $index Index of `dirname`
|
||||
* @param null|int $firstArgumentEndIndex Index of last token of first argument of `dirname` call
|
||||
*
|
||||
* @return array{indexes: list<int>, secondArgument?: int, levels: int, end: int}|bool `false` when it is not a (supported) `dirname` call, an array with info about the dirname call otherwise
|
||||
*/
|
||||
private function getDirnameInfo(Tokens $tokens, int $index, ?int $firstArgumentEndIndex = null)
|
||||
{
|
||||
if (!$tokens[$index]->equals([T_STRING, 'dirname'], false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(new FunctionsAnalyzer())->isGlobalFunctionCall($tokens, $index)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$info = ['indexes' => []];
|
||||
$prev = $tokens->getPrevMeaningfulToken($index);
|
||||
|
||||
if ($tokens[$prev]->isGivenKind(T_NS_SEPARATOR)) {
|
||||
$info['indexes'][] = $prev;
|
||||
}
|
||||
|
||||
$info['indexes'][] = $index;
|
||||
|
||||
// opening parenthesis "("
|
||||
$next = $tokens->getNextMeaningfulToken($index);
|
||||
$info['indexes'][] = $next;
|
||||
|
||||
if (null !== $firstArgumentEndIndex) {
|
||||
$next = $tokens->getNextMeaningfulToken($firstArgumentEndIndex);
|
||||
} else {
|
||||
$next = $tokens->getNextMeaningfulToken($next);
|
||||
|
||||
if ($tokens[$next]->equals(')')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (!$tokens[$next]->equalsAny([',', ')'])) {
|
||||
$blockType = Tokens::detectBlockType($tokens[$next]);
|
||||
|
||||
if (null !== $blockType) {
|
||||
$next = $tokens->findBlockEnd($blockType['type'], $next);
|
||||
}
|
||||
|
||||
$next = $tokens->getNextMeaningfulToken($next);
|
||||
}
|
||||
}
|
||||
|
||||
$info['indexes'][] = $next;
|
||||
|
||||
if ($tokens[$next]->equals(',')) {
|
||||
$next = $tokens->getNextMeaningfulToken($next);
|
||||
$info['indexes'][] = $next;
|
||||
}
|
||||
|
||||
if ($tokens[$next]->equals(')')) {
|
||||
$info['levels'] = 1;
|
||||
$info['end'] = $next;
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
if (!$tokens[$next]->isGivenKind(T_LNUMBER)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$info['secondArgument'] = $next;
|
||||
$info['levels'] = (int) $tokens[$next]->getContent();
|
||||
|
||||
$next = $tokens->getNextMeaningfulToken($next);
|
||||
|
||||
if ($tokens[$next]->equals(',')) {
|
||||
$info['indexes'][] = $next;
|
||||
$next = $tokens->getNextMeaningfulToken($next);
|
||||
}
|
||||
|
||||
if (!$tokens[$next]->equals(')')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$info['indexes'][] = $next;
|
||||
$info['end'] = $next;
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array{indexes: list<int>, secondArgument?: int, levels: int, end: int}> $dirnameInfoArray
|
||||
*/
|
||||
private function combineDirnames(Tokens $tokens, array $dirnameInfoArray): void
|
||||
{
|
||||
$outerDirnameInfo = array_pop($dirnameInfoArray);
|
||||
$levels = $outerDirnameInfo['levels'];
|
||||
|
||||
foreach ($dirnameInfoArray as $dirnameInfo) {
|
||||
$levels += $dirnameInfo['levels'];
|
||||
|
||||
foreach ($dirnameInfo['indexes'] as $index) {
|
||||
$tokens->removeLeadingWhitespace($index);
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($index);
|
||||
}
|
||||
}
|
||||
|
||||
$levelsToken = new Token([T_LNUMBER, (string) $levels]);
|
||||
|
||||
if (isset($outerDirnameInfo['secondArgument'])) {
|
||||
$tokens[$outerDirnameInfo['secondArgument']] = $levelsToken;
|
||||
} else {
|
||||
$prev = $tokens->getPrevMeaningfulToken($outerDirnameInfo['end']);
|
||||
$items = [];
|
||||
|
||||
if (!$tokens[$prev]->equals(',')) {
|
||||
$items = [new Token(','), new Token([T_WHITESPACE, ' '])];
|
||||
}
|
||||
|
||||
$items[] = $levelsToken;
|
||||
$tokens->insertAt($outerDirnameInfo['end'], $items);
|
||||
}
|
||||
}
|
||||
}
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
<?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\FunctionNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class DateTimeCreateFromFormatCallFixer extends AbstractFixer
|
||||
{
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'The first argument of `DateTime::createFromFormat` method must start with `!`.',
|
||||
[
|
||||
new CodeSample("<?php \\DateTime::createFromFormat('Y-m-d', '2022-02-11');\n"),
|
||||
],
|
||||
"Consider this code:
|
||||
`DateTime::createFromFormat('Y-m-d', '2022-02-11')`.
|
||||
What value will be returned? '2022-02-11 00:00:00.0'? No, actual return value has 'H:i:s' section like '2022-02-11 16:55:37.0'.
|
||||
Change 'Y-m-d' to '!Y-m-d', return value will be '2022-02-11 00:00:00.0'.
|
||||
So, adding `!` to format string will make return value more intuitive.",
|
||||
'Risky when depending on the actual timings being used even when not explicit set in format.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after NoUselessConcatOperatorFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_DOUBLE_COLON);
|
||||
}
|
||||
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$argumentsAnalyzer = new ArgumentsAnalyzer();
|
||||
$namespacesAnalyzer = new NamespacesAnalyzer();
|
||||
$namespaceUsesAnalyzer = new NamespaceUsesAnalyzer();
|
||||
|
||||
foreach ($namespacesAnalyzer->getDeclarations($tokens) as $namespace) {
|
||||
$scopeStartIndex = $namespace->getScopeStartIndex();
|
||||
$useDeclarations = $namespaceUsesAnalyzer->getDeclarationsInNamespace($tokens, $namespace);
|
||||
|
||||
for ($index = $namespace->getScopeEndIndex(); $index > $scopeStartIndex; --$index) {
|
||||
if (!$tokens[$index]->isGivenKind(T_DOUBLE_COLON)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$functionNameIndex = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
if (!$tokens[$functionNameIndex]->equals([T_STRING, 'createFromFormat'], false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$tokens[$tokens->getNextMeaningfulToken($functionNameIndex)]->equals('(')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$classNameIndex = $tokens->getPrevMeaningfulToken($index);
|
||||
|
||||
if (!$tokens[$classNameIndex]->equalsAny([[T_STRING, 'DateTime'], [T_STRING, 'DateTimeImmutable']], false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$preClassNameIndex = $tokens->getPrevMeaningfulToken($classNameIndex);
|
||||
|
||||
if ($tokens[$preClassNameIndex]->isGivenKind(T_NS_SEPARATOR)) {
|
||||
if ($tokens[$tokens->getPrevMeaningfulToken($preClassNameIndex)]->isGivenKind(T_STRING)) {
|
||||
continue;
|
||||
}
|
||||
} elseif (!$namespace->isGlobalNamespace()) {
|
||||
continue;
|
||||
} else {
|
||||
foreach ($useDeclarations as $useDeclaration) {
|
||||
foreach (['datetime', 'datetimeimmutable'] as $name) {
|
||||
if ($name === strtolower($useDeclaration->getShortName()) && $name !== strtolower($useDeclaration->getFullName())) {
|
||||
continue 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$openIndex = $tokens->getNextTokenOfKind($functionNameIndex, ['(']);
|
||||
$closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex);
|
||||
|
||||
$argumentIndex = $this->getFirstArgumentTokenIndex($tokens, $argumentsAnalyzer->getArguments($tokens, $openIndex, $closeIndex));
|
||||
|
||||
if (null === $argumentIndex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$format = $tokens[$argumentIndex]->getContent();
|
||||
|
||||
if (\strlen($format) < 3) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$offset = 'b' === $format[0] || 'B' === $format[0] ? 2 : 1;
|
||||
|
||||
if ('!' === $format[$offset]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens->clearAt($argumentIndex);
|
||||
$tokens->insertAt($argumentIndex, new Token([T_CONSTANT_ENCAPSED_STRING, substr_replace($format, '!', $offset, 0)]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, int> $arguments
|
||||
*/
|
||||
private function getFirstArgumentTokenIndex(Tokens $tokens, array $arguments): ?int
|
||||
{
|
||||
if (2 !== \count($arguments)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$argumentStartIndex = array_key_first($arguments);
|
||||
$argumentEndIndex = $arguments[$argumentStartIndex];
|
||||
$argumentStartIndex = $tokens->getNextMeaningfulToken($argumentStartIndex - 1);
|
||||
|
||||
if (
|
||||
$argumentStartIndex !== $argumentEndIndex
|
||||
&& $tokens->getNextMeaningfulToken($argumentStartIndex) <= $argumentEndIndex
|
||||
) {
|
||||
return null; // argument is not a simple single string
|
||||
}
|
||||
|
||||
return !$tokens[$argumentStartIndex]->isGivenKind(T_CONSTANT_ENCAPSED_STRING)
|
||||
? null // first argument is not a string
|
||||
: $argumentStartIndex;
|
||||
}
|
||||
}
|
||||
Vendored
+126
@@ -0,0 +1,126 @@
|
||||
<?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\FunctionNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFopenFlagFixer;
|
||||
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 FopenFlagOrderFixer extends AbstractFopenFlagFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Order the flags in `fopen` calls, `b` and `t` must be last.',
|
||||
[new CodeSample("<?php\n\$a = fopen(\$foo, 'br+');\n")],
|
||||
null,
|
||||
'Risky when the function `fopen` is overridden.'
|
||||
);
|
||||
}
|
||||
|
||||
protected function fixFopenFlagToken(Tokens $tokens, int $argumentStartIndex, int $argumentEndIndex): void
|
||||
{
|
||||
$argumentFlagIndex = null;
|
||||
|
||||
for ($i = $argumentStartIndex; $i <= $argumentEndIndex; ++$i) {
|
||||
if ($tokens[$i]->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (null !== $argumentFlagIndex) {
|
||||
return; // multiple meaningful tokens found, no candidate for fixing
|
||||
}
|
||||
|
||||
$argumentFlagIndex = $i;
|
||||
}
|
||||
|
||||
// check if second argument is candidate
|
||||
if (null === $argumentFlagIndex || !$tokens[$argumentFlagIndex]->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$content = $tokens[$argumentFlagIndex]->getContent();
|
||||
$contentQuote = $content[0]; // `'`, `"`, `b` or `B`
|
||||
|
||||
if ('b' === $contentQuote || 'B' === $contentQuote) {
|
||||
$binPrefix = $contentQuote;
|
||||
$contentQuote = $content[1]; // `'` or `"`
|
||||
$mode = substr($content, 2, -1);
|
||||
} else {
|
||||
$binPrefix = '';
|
||||
$mode = substr($content, 1, -1);
|
||||
}
|
||||
|
||||
$modeLength = \strlen($mode);
|
||||
if ($modeLength < 2) {
|
||||
return; // nothing to sort
|
||||
}
|
||||
|
||||
if (false === $this->isValidModeString($mode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$split = $this->sortFlags(Preg::split('#([^\+]\+?)#', $mode, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE));
|
||||
$newContent = $binPrefix.$contentQuote.implode('', $split).$contentQuote;
|
||||
|
||||
if ($content !== $newContent) {
|
||||
$tokens[$argumentFlagIndex] = new Token([T_CONSTANT_ENCAPSED_STRING, $newContent]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $flags
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private function sortFlags(array $flags): array
|
||||
{
|
||||
usort(
|
||||
$flags,
|
||||
static function (string $flag1, string $flag2): int {
|
||||
if ($flag1 === $flag2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ('b' === $flag1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ('b' === $flag2) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ('t' === $flag1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ('t' === $flag2) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return $flag1 < $flag2 ? -1 : 1;
|
||||
}
|
||||
);
|
||||
|
||||
return $flags;
|
||||
}
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
<?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\FunctionNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFopenFlagFixer;
|
||||
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 FopenFlagsFixer extends AbstractFopenFlagFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'The flags in `fopen` calls must omit `t`, and `b` must be omitted or included consistently.',
|
||||
[
|
||||
new CodeSample("<?php\n\$a = fopen(\$foo, 'rwt');\n"),
|
||||
new CodeSample("<?php\n\$a = fopen(\$foo, 'rwt');\n", ['b_mode' => false]),
|
||||
],
|
||||
null,
|
||||
'Risky when the function `fopen` is overridden.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('b_mode', 'The `b` flag must be used (`true`) or omitted (`false`).'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(true)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function fixFopenFlagToken(Tokens $tokens, int $argumentStartIndex, int $argumentEndIndex): void
|
||||
{
|
||||
$argumentFlagIndex = null;
|
||||
|
||||
for ($i = $argumentStartIndex; $i <= $argumentEndIndex; ++$i) {
|
||||
if ($tokens[$i]->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (null !== $argumentFlagIndex) {
|
||||
return; // multiple meaningful tokens found, no candidate for fixing
|
||||
}
|
||||
|
||||
$argumentFlagIndex = $i;
|
||||
}
|
||||
|
||||
// check if second argument is candidate
|
||||
if (null === $argumentFlagIndex || !$tokens[$argumentFlagIndex]->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$content = $tokens[$argumentFlagIndex]->getContent();
|
||||
$contentQuote = $content[0]; // `'`, `"`, `b` or `B`
|
||||
|
||||
if ('b' === $contentQuote || 'B' === $contentQuote) {
|
||||
$binPrefix = $contentQuote;
|
||||
$contentQuote = $content[1]; // `'` or `"`
|
||||
$mode = substr($content, 2, -1);
|
||||
} else {
|
||||
$binPrefix = '';
|
||||
$mode = substr($content, 1, -1);
|
||||
}
|
||||
|
||||
if (false === $this->isValidModeString($mode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$mode = str_replace('t', '', $mode);
|
||||
|
||||
if (true === $this->configuration['b_mode']) {
|
||||
if (!str_contains($mode, 'b')) {
|
||||
$mode .= 'b';
|
||||
}
|
||||
} else {
|
||||
$mode = str_replace('b', '', $mode);
|
||||
}
|
||||
|
||||
$newContent = $binPrefix.$contentQuote.$mode.$contentQuote;
|
||||
|
||||
if ($content !== $newContent) {
|
||||
$tokens[$argumentFlagIndex] = new Token([T_CONSTANT_ENCAPSED_STRING, $newContent]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user