Missing dependancies
This commit is contained in:
+203
@@ -0,0 +1,203 @@
|
||||
<?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\Whitespace;
|
||||
|
||||
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\CT;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class ArrayIndentationFixer extends AbstractFixer implements WhitespacesAwareFixerInterface
|
||||
{
|
||||
use Indentation;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Each element of an array must be indented exactly once.',
|
||||
[
|
||||
new CodeSample("<?php\n\$foo = [\n 'bar' => [\n 'baz' => true,\n ],\n];\n"),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before AlignMultilineCommentFixer, BinaryOperatorSpacesFixer.
|
||||
* Must run after MethodArgumentSpaceFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 29;
|
||||
}
|
||||
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$lastIndent = '';
|
||||
$scopes = [];
|
||||
$previousLineInitialIndent = '';
|
||||
$previousLineNewIndent = '';
|
||||
|
||||
foreach ($tokens as $index => $token) {
|
||||
$currentScope = [] !== $scopes ? \count($scopes) - 1 : null;
|
||||
|
||||
if ($token->isComment()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
$token->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)
|
||||
|| ($token->equals('(') && $tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_ARRAY))
|
||||
) {
|
||||
$endIndex = $tokens->findBlockEnd(
|
||||
$token->equals('(') ? Tokens::BLOCK_TYPE_PARENTHESIS_BRACE : Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE,
|
||||
$index
|
||||
);
|
||||
|
||||
$scopes[] = [
|
||||
'type' => 'array',
|
||||
'end_index' => $endIndex,
|
||||
'initial_indent' => $lastIndent,
|
||||
];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->isNewLineToken($tokens, $index)) {
|
||||
$lastIndent = $this->extractIndent($this->computeNewLineContent($tokens, $index));
|
||||
}
|
||||
|
||||
if (null === $currentScope) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->isWhitespace()) {
|
||||
if (!Preg::match('/\R/', $token->getContent())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('array' === $scopes[$currentScope]['type']) {
|
||||
$indent = false;
|
||||
|
||||
for ($searchEndIndex = $index + 1; $searchEndIndex < $scopes[$currentScope]['end_index']; ++$searchEndIndex) {
|
||||
$searchEndToken = $tokens[$searchEndIndex];
|
||||
|
||||
if (
|
||||
(!$searchEndToken->isWhitespace() && !$searchEndToken->isComment())
|
||||
|| ($searchEndToken->isWhitespace() && Preg::match('/\R/', $searchEndToken->getContent()))
|
||||
) {
|
||||
$indent = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$content = Preg::replace(
|
||||
'/(\R+)\h*$/',
|
||||
'$1'.$scopes[$currentScope]['initial_indent'].($indent ? $this->whitespacesConfig->getIndent() : ''),
|
||||
$token->getContent()
|
||||
);
|
||||
|
||||
$previousLineInitialIndent = $this->extractIndent($token->getContent());
|
||||
$previousLineNewIndent = $this->extractIndent($content);
|
||||
} else {
|
||||
$content = Preg::replace(
|
||||
'/(\R)'.preg_quote($scopes[$currentScope]['initial_indent'], '/').'(\h*)$/',
|
||||
'$1'.$scopes[$currentScope]['new_indent'].'$2',
|
||||
$token->getContent()
|
||||
);
|
||||
}
|
||||
|
||||
$tokens[$index] = new Token([T_WHITESPACE, $content]);
|
||||
$lastIndent = $this->extractIndent($content);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($index === $scopes[$currentScope]['end_index']) {
|
||||
while ([] !== $scopes && $index === $scopes[$currentScope]['end_index']) {
|
||||
array_pop($scopes);
|
||||
--$currentScope;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->equals(',')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('expression' !== $scopes[$currentScope]['type']) {
|
||||
$endIndex = $this->findExpressionEndIndex($tokens, $index, $scopes[$currentScope]['end_index']);
|
||||
|
||||
if ($endIndex === $index) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$scopes[] = [
|
||||
'type' => 'expression',
|
||||
'end_index' => $endIndex,
|
||||
'initial_indent' => $previousLineInitialIndent,
|
||||
'new_indent' => $previousLineNewIndent,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function findExpressionEndIndex(Tokens $tokens, int $index, int $parentScopeEndIndex): int
|
||||
{
|
||||
$endIndex = null;
|
||||
|
||||
for ($searchEndIndex = $index + 1; $searchEndIndex < $parentScopeEndIndex; ++$searchEndIndex) {
|
||||
$searchEndToken = $tokens[$searchEndIndex];
|
||||
|
||||
if ($searchEndToken->equalsAny(['(', '{']) || $searchEndToken->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) {
|
||||
$type = Tokens::detectBlockType($searchEndToken);
|
||||
$searchEndIndex = $tokens->findBlockEnd(
|
||||
$type['type'],
|
||||
$searchEndIndex
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($searchEndToken->equals(',')) {
|
||||
$endIndex = $tokens->getPrevMeaningfulToken($searchEndIndex);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $endIndex ?? $tokens->getPrevMeaningfulToken($parentScopeEndIndex);
|
||||
}
|
||||
}
|
||||
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\Whitespace;
|
||||
|
||||
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\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use PhpCsFixer\Tokenizer\TokensAnalyzer;
|
||||
|
||||
/**
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
* @author Andreas Möller <am@localheinz.com>
|
||||
*/
|
||||
final class BlankLineBeforeStatementFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
|
||||
{
|
||||
/**
|
||||
* @var array<string, int>
|
||||
*/
|
||||
private static array $tokenMap = [
|
||||
'break' => T_BREAK,
|
||||
'case' => T_CASE,
|
||||
'continue' => T_CONTINUE,
|
||||
'declare' => T_DECLARE,
|
||||
'default' => T_DEFAULT,
|
||||
'do' => T_DO,
|
||||
'exit' => T_EXIT,
|
||||
'for' => T_FOR,
|
||||
'foreach' => T_FOREACH,
|
||||
'goto' => T_GOTO,
|
||||
'if' => T_IF,
|
||||
'include' => T_INCLUDE,
|
||||
'include_once' => T_INCLUDE_ONCE,
|
||||
'phpdoc' => T_DOC_COMMENT,
|
||||
'require' => T_REQUIRE,
|
||||
'require_once' => T_REQUIRE_ONCE,
|
||||
'return' => T_RETURN,
|
||||
'switch' => T_SWITCH,
|
||||
'throw' => T_THROW,
|
||||
'try' => T_TRY,
|
||||
'while' => T_WHILE,
|
||||
'yield' => T_YIELD,
|
||||
'yield_from' => T_YIELD_FROM,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var list<int>
|
||||
*/
|
||||
private array $fixTokenMap = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configure(array $configuration): void
|
||||
{
|
||||
parent::configure($configuration);
|
||||
|
||||
$this->fixTokenMap = [];
|
||||
|
||||
foreach ($this->configuration['statements'] as $key) {
|
||||
$this->fixTokenMap[$key] = self::$tokenMap[$key];
|
||||
}
|
||||
|
||||
$this->fixTokenMap = array_values($this->fixTokenMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'An empty line feed must precede any configured statement.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
function A() {
|
||||
echo 1;
|
||||
return 1;
|
||||
}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
switch ($foo) {
|
||||
case 42:
|
||||
$bar->process();
|
||||
break;
|
||||
case 44:
|
||||
break;
|
||||
}
|
||||
',
|
||||
[
|
||||
'statements' => ['break'],
|
||||
]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
foreach ($foo as $bar) {
|
||||
if ($bar->isTired()) {
|
||||
$bar->sleep();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
',
|
||||
[
|
||||
'statements' => ['continue'],
|
||||
]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
$i = 0;
|
||||
do {
|
||||
echo $i;
|
||||
} while ($i > 0);
|
||||
',
|
||||
[
|
||||
'statements' => ['do'],
|
||||
]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
if ($foo === false) {
|
||||
exit(0);
|
||||
} else {
|
||||
$bar = 9000;
|
||||
exit(1);
|
||||
}
|
||||
',
|
||||
[
|
||||
'statements' => ['exit'],
|
||||
]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
a:
|
||||
|
||||
if ($foo === false) {
|
||||
goto a;
|
||||
} else {
|
||||
$bar = 9000;
|
||||
goto b;
|
||||
}
|
||||
',
|
||||
[
|
||||
'statements' => ['goto'],
|
||||
]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
$a = 9000;
|
||||
if (true) {
|
||||
$foo = $bar;
|
||||
}
|
||||
',
|
||||
[
|
||||
'statements' => ['if'],
|
||||
]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
|
||||
if (true) {
|
||||
$foo = $bar;
|
||||
return;
|
||||
}
|
||||
',
|
||||
[
|
||||
'statements' => ['return'],
|
||||
]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
$a = 9000;
|
||||
switch ($a) {
|
||||
case 42:
|
||||
break;
|
||||
}
|
||||
',
|
||||
[
|
||||
'statements' => ['switch'],
|
||||
]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
if (null === $a) {
|
||||
$foo->bar();
|
||||
throw new \UnexpectedValueException("A cannot be null.");
|
||||
}
|
||||
',
|
||||
[
|
||||
'statements' => ['throw'],
|
||||
]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
$a = 9000;
|
||||
try {
|
||||
$foo->bar();
|
||||
} catch (\Exception $exception) {
|
||||
$a = -1;
|
||||
}
|
||||
',
|
||||
[
|
||||
'statements' => ['try'],
|
||||
]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
|
||||
if (true) {
|
||||
$foo = $bar;
|
||||
yield $foo;
|
||||
}
|
||||
',
|
||||
[
|
||||
'statements' => ['yield'],
|
||||
]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after NoExtraBlankLinesFixer, NoUselessReturnFixer, ReturnAssignmentFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return -21;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound($this->fixTokenMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$analyzer = new TokensAnalyzer($tokens);
|
||||
|
||||
for ($index = $tokens->count() - 1; $index > 0; --$index) {
|
||||
$token = $tokens[$index];
|
||||
|
||||
if (!$token->isGivenKind($this->fixTokenMap)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->isGivenKind(T_WHILE) && $analyzer->isWhilePartOfDoWhile($index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$prevNonWhitespace = $tokens->getPrevNonWhitespace($index);
|
||||
|
||||
if ($this->shouldAddBlankLine($tokens, $prevNonWhitespace)) {
|
||||
$this->insertBlankLine($tokens, $index);
|
||||
}
|
||||
|
||||
$index = $prevNonWhitespace;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('statements', 'List of statements which must be preceded by an empty line.'))
|
||||
->setAllowedTypes(['array'])
|
||||
->setAllowedValues([new AllowedValueSubset(array_keys(self::$tokenMap))])
|
||||
->setDefault([
|
||||
'break',
|
||||
'continue',
|
||||
'declare',
|
||||
'return',
|
||||
'throw',
|
||||
'try',
|
||||
])
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function shouldAddBlankLine(Tokens $tokens, int $prevNonWhitespace): bool
|
||||
{
|
||||
$prevNonWhitespaceToken = $tokens[$prevNonWhitespace];
|
||||
|
||||
if ($prevNonWhitespaceToken->isComment()) {
|
||||
for ($j = $prevNonWhitespace - 1; $j >= 0; --$j) {
|
||||
if (str_contains($tokens[$j]->getContent(), "\n")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($tokens[$j]->isWhitespace() || $tokens[$j]->isComment()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $tokens[$j]->equalsAny([';', '}']);
|
||||
}
|
||||
}
|
||||
|
||||
return $prevNonWhitespaceToken->equalsAny([';', '}']);
|
||||
}
|
||||
|
||||
private function insertBlankLine(Tokens $tokens, int $index): void
|
||||
{
|
||||
$prevIndex = $index - 1;
|
||||
$prevToken = $tokens[$prevIndex];
|
||||
$lineEnding = $this->whitespacesConfig->getLineEnding();
|
||||
|
||||
if ($prevToken->isWhitespace()) {
|
||||
$newlinesCount = substr_count($prevToken->getContent(), "\n");
|
||||
|
||||
if (0 === $newlinesCount) {
|
||||
$tokens[$prevIndex] = new Token([T_WHITESPACE, rtrim($prevToken->getContent(), " \t").$lineEnding.$lineEnding]);
|
||||
} elseif (1 === $newlinesCount) {
|
||||
$tokens[$prevIndex] = new Token([T_WHITESPACE, $lineEnding.$prevToken->getContent()]);
|
||||
}
|
||||
} else {
|
||||
$tokens->insertAt($index, new Token([T_WHITESPACE, $lineEnding.$lineEnding]));
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+192
@@ -0,0 +1,192 @@
|
||||
<?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\Whitespace;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
|
||||
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 Sander Verkuil <s.verkuil@pm.me>
|
||||
*/
|
||||
final class BlankLineBetweenImportGroupsFixer extends AbstractFixer implements WhitespacesAwareFixerInterface
|
||||
{
|
||||
private const IMPORT_TYPE_CLASS = 'class';
|
||||
|
||||
private const IMPORT_TYPE_CONST = 'const';
|
||||
|
||||
private const IMPORT_TYPE_FUNCTION = 'function';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Putting blank lines between `use` statement groups.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
|
||||
use function AAC;
|
||||
use const AAB;
|
||||
use AAA;
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
use const AAAA;
|
||||
use const BBB;
|
||||
use Bar;
|
||||
use AAC;
|
||||
use Acme;
|
||||
use function CCC\AA;
|
||||
use function DDD;
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
use const BBB;
|
||||
use const AAAA;
|
||||
use Acme;
|
||||
use AAC;
|
||||
use Bar;
|
||||
use function DDD;
|
||||
use function CCC\AA;
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
use const AAAA;
|
||||
use const BBB;
|
||||
use Acme;
|
||||
use function DDD;
|
||||
use AAC;
|
||||
use function CCC\AA;
|
||||
use Bar;
|
||||
'
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after OrderedImportsFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return -40;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_USE);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$tokensAnalyzer = new TokensAnalyzer($tokens);
|
||||
$namespacesImports = $tokensAnalyzer->getImportUseIndexes(true);
|
||||
|
||||
foreach (array_reverse($namespacesImports) as $uses) {
|
||||
$this->walkOverUses($tokens, $uses);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $uses
|
||||
*/
|
||||
private function walkOverUses(Tokens $tokens, array $uses): void
|
||||
{
|
||||
$usesCount = \count($uses);
|
||||
|
||||
if ($usesCount < 2) {
|
||||
return; // nothing to fix
|
||||
}
|
||||
|
||||
$previousType = null;
|
||||
|
||||
for ($i = $usesCount - 1; $i >= 0; --$i) {
|
||||
$index = $uses[$i];
|
||||
$startIndex = $tokens->getNextMeaningfulToken($index + 1);
|
||||
$endIndex = $tokens->getNextTokenOfKind($startIndex, [';', [T_CLOSE_TAG]]);
|
||||
|
||||
if ($tokens[$startIndex]->isGivenKind(CT::T_CONST_IMPORT)) {
|
||||
$type = self::IMPORT_TYPE_CONST;
|
||||
} elseif ($tokens[$startIndex]->isGivenKind(CT::T_FUNCTION_IMPORT)) {
|
||||
$type = self::IMPORT_TYPE_FUNCTION;
|
||||
} else {
|
||||
$type = self::IMPORT_TYPE_CLASS;
|
||||
}
|
||||
|
||||
if (null !== $previousType && $type !== $previousType) {
|
||||
$this->ensureLine($tokens, $endIndex + 1);
|
||||
}
|
||||
|
||||
$previousType = $type;
|
||||
}
|
||||
}
|
||||
|
||||
private function ensureLine(Tokens $tokens, int $index): void
|
||||
{
|
||||
static $lineEnding;
|
||||
|
||||
if (null === $lineEnding) {
|
||||
$lineEnding = $this->whitespacesConfig->getLineEnding();
|
||||
$lineEnding .= $lineEnding;
|
||||
}
|
||||
|
||||
$index = $this->getInsertIndex($tokens, $index);
|
||||
|
||||
if ($tokens[$index]->isWhitespace()) {
|
||||
$tokens[$index] = new Token([T_WHITESPACE, $lineEnding]);
|
||||
} else {
|
||||
$tokens->insertSlices([$index + 1 => [new Token([T_WHITESPACE, $lineEnding])]]);
|
||||
}
|
||||
}
|
||||
|
||||
private function getInsertIndex(Tokens $tokens, int $index): int
|
||||
{
|
||||
$tokensCount = \count($tokens);
|
||||
|
||||
for (; $index < $tokensCount - 1; ++$index) {
|
||||
if (!$tokens[$index]->isWhitespace() && !$tokens[$index]->isComment()) {
|
||||
return $index - 1;
|
||||
}
|
||||
|
||||
$content = $tokens[$index]->getContent();
|
||||
|
||||
if (str_contains($content, "\n")) {
|
||||
return $index;
|
||||
}
|
||||
}
|
||||
|
||||
return $index;
|
||||
}
|
||||
}
|
||||
Vendored
+80
@@ -0,0 +1,80 @@
|
||||
<?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\Whitespace;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Jack Cherng <jfcherng@gmail.com>
|
||||
*/
|
||||
final class CompactNullableTypehintFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Remove extra spaces in a nullable typehint.',
|
||||
[
|
||||
new CodeSample(
|
||||
"<?php\nfunction sample(? string \$str): ? string\n{}\n"
|
||||
),
|
||||
],
|
||||
'Rule is applied only in a PHP 7.1+ environment.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(CT::T_NULLABLE_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
static $typehintKinds = [
|
||||
CT::T_ARRAY_TYPEHINT,
|
||||
T_CALLABLE,
|
||||
T_NS_SEPARATOR,
|
||||
T_STRING,
|
||||
];
|
||||
|
||||
for ($index = $tokens->count() - 1; $index >= 0; --$index) {
|
||||
if (!$tokens[$index]->isGivenKind(CT::T_NULLABLE_TYPE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// remove whitespaces only if there are only whitespaces
|
||||
// between '?' and the variable type
|
||||
if (
|
||||
$tokens[$index + 1]->isWhitespace()
|
||||
&& $tokens[$index + 2]->isGivenKind($typehintKinds)
|
||||
) {
|
||||
$tokens->removeTrailingWhitespace($index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
<?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\Whitespace;
|
||||
|
||||
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\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\FixerDefinition\VersionSpecification;
|
||||
use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
|
||||
use PhpCsFixer\Preg;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Gregor Harlan
|
||||
*/
|
||||
final class HeredocIndentationFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Heredoc/nowdoc content must be properly indented. Requires PHP >= 7.3.',
|
||||
[
|
||||
new VersionSpecificCodeSample(
|
||||
<<<'SAMPLE'
|
||||
<?php
|
||||
$a = <<<EOD
|
||||
abc
|
||||
def
|
||||
EOD;
|
||||
|
||||
SAMPLE
|
||||
,
|
||||
new VersionSpecification(70300)
|
||||
),
|
||||
new VersionSpecificCodeSample(
|
||||
<<<'SAMPLE'
|
||||
<?php
|
||||
$a = <<<'EOD'
|
||||
abc
|
||||
def
|
||||
EOD;
|
||||
|
||||
SAMPLE
|
||||
,
|
||||
new VersionSpecification(70300)
|
||||
),
|
||||
new VersionSpecificCodeSample(
|
||||
<<<'SAMPLE'
|
||||
<?php
|
||||
$a = <<<'EOD'
|
||||
abc
|
||||
def
|
||||
EOD;
|
||||
|
||||
SAMPLE
|
||||
,
|
||||
new VersionSpecification(70300),
|
||||
['indentation' => 'same_as_start']
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after BracesFixer, StatementIndentationFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return -26;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_START_HEREDOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('indentation', 'Whether the indentation should be the same as in the start token line or one level more.'))
|
||||
->setAllowedValues(['start_plus_one', 'same_as_start'])
|
||||
->setDefault('start_plus_one')
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
for ($index = \count($tokens) - 1; 0 <= $index; --$index) {
|
||||
if (!$tokens[$index]->isGivenKind(T_END_HEREDOC)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$end = $index;
|
||||
$index = $tokens->getPrevTokenOfKind($index, [[T_START_HEREDOC]]);
|
||||
|
||||
$this->fixIndentation($tokens, $index, $end);
|
||||
}
|
||||
}
|
||||
|
||||
private function fixIndentation(Tokens $tokens, int $start, int $end): void
|
||||
{
|
||||
$indent = WhitespacesAnalyzer::detectIndent($tokens, $start);
|
||||
|
||||
if ('start_plus_one' === $this->configuration['indentation']) {
|
||||
$indent .= $this->whitespacesConfig->getIndent();
|
||||
}
|
||||
|
||||
Preg::match('/^\h*/', $tokens[$end]->getContent(), $matches);
|
||||
$currentIndent = $matches[0];
|
||||
$currentIndentLength = \strlen($currentIndent);
|
||||
|
||||
$content = $indent.substr($tokens[$end]->getContent(), $currentIndentLength);
|
||||
$tokens[$end] = new Token([T_END_HEREDOC, $content]);
|
||||
|
||||
if ($end === $start + 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
for ($index = $end - 1, $last = true; $index > $start; --$index, $last = false) {
|
||||
if (!$tokens[$index]->isGivenKind([T_ENCAPSED_AND_WHITESPACE, T_WHITESPACE])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$content = $tokens[$index]->getContent();
|
||||
|
||||
if ('' !== $currentIndent) {
|
||||
$content = Preg::replace('/(?<=\v)(?!'.$currentIndent.')\h+/', '', $content);
|
||||
}
|
||||
|
||||
$regexEnd = $last && !$currentIndent ? '(?!\v|$)' : '(?!\v)';
|
||||
$content = Preg::replace('/(?<=\v)'.$currentIndent.$regexEnd.'/', $indent, $content);
|
||||
|
||||
$tokens[$index] = new Token([$tokens[$index]->getId(), $content]);
|
||||
}
|
||||
|
||||
++$index;
|
||||
|
||||
if (!$tokens[$index]->isGivenKind(T_ENCAPSED_AND_WHITESPACE)) {
|
||||
$tokens->insertAt($index, new Token([T_ENCAPSED_AND_WHITESPACE, $indent]));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$content = $tokens[$index]->getContent();
|
||||
|
||||
if (!\in_array($content[0], ["\r", "\n"], true) && (!$currentIndent || str_starts_with($content, $currentIndent))) {
|
||||
$content = $indent.substr($content, $currentIndentLength);
|
||||
} elseif ($currentIndent) {
|
||||
$content = Preg::replace('/^(?!'.$currentIndent.')\h+/', '', $content);
|
||||
}
|
||||
|
||||
$tokens[$index] = new Token([T_ENCAPSED_AND_WHITESPACE, $content]);
|
||||
}
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
<?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\Whitespace;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Preg;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* Fixer for rules defined in PSR2 ¶2.4.
|
||||
*
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class IndentationTypeFixer extends AbstractFixer implements WhitespacesAwareFixerInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $indent;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Code MUST use configured indentation type.',
|
||||
[
|
||||
new CodeSample("<?php\n\nif (true) {\n\techo 'Hello!';\n}\n"),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before PhpdocIndentFixer.
|
||||
* Must run after ClassAttributesSeparationFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 50;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound([T_COMMENT, T_DOC_COMMENT, T_WHITESPACE]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$this->indent = $this->whitespacesConfig->getIndent();
|
||||
|
||||
foreach ($tokens as $index => $token) {
|
||||
if ($token->isComment()) {
|
||||
$tokens[$index] = $this->fixIndentInComment($tokens, $index);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->isWhitespace()) {
|
||||
$tokens[$index] = $this->fixIndentToken($tokens, $index);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function fixIndentInComment(Tokens $tokens, int $index): Token
|
||||
{
|
||||
$content = Preg::replace('/^(?:(?<! ) {1,3})?\t/m', '\1 ', $tokens[$index]->getContent(), -1, $count);
|
||||
|
||||
// Also check for more tabs.
|
||||
while (0 !== $count) {
|
||||
$content = Preg::replace('/^(\ +)?\t/m', '\1 ', $content, -1, $count);
|
||||
}
|
||||
|
||||
$indent = $this->indent;
|
||||
|
||||
// change indent to expected one
|
||||
$content = Preg::replaceCallback('/^(?: )+/m', function (array $matches) use ($indent): string {
|
||||
return $this->getExpectedIndent($matches[0], $indent);
|
||||
}, $content);
|
||||
|
||||
return new Token([$tokens[$index]->getId(), $content]);
|
||||
}
|
||||
|
||||
private function fixIndentToken(Tokens $tokens, int $index): Token
|
||||
{
|
||||
$content = $tokens[$index]->getContent();
|
||||
$previousTokenHasTrailingLinebreak = false;
|
||||
|
||||
// @TODO this can be removed when we have a transformer for "T_OPEN_TAG" to "T_OPEN_TAG + T_WHITESPACE"
|
||||
if (str_contains($tokens[$index - 1]->getContent(), "\n")) {
|
||||
$content = "\n".$content;
|
||||
$previousTokenHasTrailingLinebreak = true;
|
||||
}
|
||||
|
||||
$indent = $this->indent;
|
||||
$newContent = Preg::replaceCallback(
|
||||
'/(\R)(\h+)/', // find indent
|
||||
function (array $matches) use ($indent): string {
|
||||
// normalize mixed indent
|
||||
$content = Preg::replace('/(?:(?<! ) {1,3})?\t/', ' ', $matches[2]);
|
||||
|
||||
// change indent to expected one
|
||||
return $matches[1].$this->getExpectedIndent($content, $indent);
|
||||
},
|
||||
$content
|
||||
);
|
||||
|
||||
if ($previousTokenHasTrailingLinebreak) {
|
||||
$newContent = substr($newContent, 1);
|
||||
}
|
||||
|
||||
return new Token([T_WHITESPACE, $newContent]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string mixed
|
||||
*/
|
||||
private function getExpectedIndent(string $content, string $indent): string
|
||||
{
|
||||
if ("\t" === $indent) {
|
||||
$content = str_replace(' ', $indent, $content);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
+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\Whitespace;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Preg;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* Fixer for rules defined in PSR2 ¶2.2.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class LineEndingFixer extends AbstractFixer implements WhitespacesAwareFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'All PHP files must use same line ending.',
|
||||
[
|
||||
new CodeSample(
|
||||
"<?php \$b = \" \$a \r\n 123\"; \$a = <<<TEST\r\nAAAAA \r\n |\r\nTEST;\n"
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$ending = $this->whitespacesConfig->getLineEnding();
|
||||
|
||||
for ($index = 0, $count = \count($tokens); $index < $count; ++$index) {
|
||||
$token = $tokens[$index];
|
||||
|
||||
if ($token->isGivenKind(T_ENCAPSED_AND_WHITESPACE)) {
|
||||
if ($tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(T_END_HEREDOC)) {
|
||||
$tokens[$index] = new Token([
|
||||
$token->getId(),
|
||||
Preg::replace(
|
||||
'#\R#',
|
||||
$ending,
|
||||
$token->getContent()
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->isGivenKind([T_CLOSE_TAG, T_COMMENT, T_DOC_COMMENT, T_OPEN_TAG, T_START_HEREDOC, T_WHITESPACE])) {
|
||||
$tokens[$index] = new Token([
|
||||
$token->getId(),
|
||||
Preg::replace(
|
||||
'#\R#',
|
||||
$ending,
|
||||
$token->getContent()
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+222
@@ -0,0 +1,222 @@
|
||||
<?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\Whitespace;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Preg;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Vladimir Boliev <voff.web@gmail.com>
|
||||
*/
|
||||
final class MethodChainingIndentationFixer extends AbstractFixer implements WhitespacesAwareFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Method chaining MUST be properly indented. Method chaining with different levels of indentation is not supported.',
|
||||
[new CodeSample("<?php\n\$user->setEmail('voff.web@gmail.com')\n ->setPassword('233434');\n")]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound(Token::getObjectOperatorKinds());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$lineEnding = $this->whitespacesConfig->getLineEnding();
|
||||
|
||||
for ($index = 1, $count = \count($tokens); $index < $count; ++$index) {
|
||||
if (!$tokens[$index]->isObjectOperator()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$endParenthesisIndex = $tokens->getNextTokenOfKind($index, ['(', ';', ',', [T_CLOSE_TAG]]);
|
||||
|
||||
if (null === $endParenthesisIndex || !$tokens[$endParenthesisIndex]->equals('(')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->canBeMovedToNextLine($index, $tokens)) {
|
||||
$newline = new Token([T_WHITESPACE, $lineEnding]);
|
||||
|
||||
if ($tokens[$index - 1]->isWhitespace()) {
|
||||
$tokens[$index - 1] = $newline;
|
||||
} else {
|
||||
$tokens->insertAt($index, $newline);
|
||||
++$index;
|
||||
++$endParenthesisIndex;
|
||||
}
|
||||
}
|
||||
|
||||
$currentIndent = $this->getIndentAt($tokens, $index - 1);
|
||||
|
||||
if (null === $currentIndent) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$expectedIndent = $this->getExpectedIndentAt($tokens, $index);
|
||||
|
||||
if ($currentIndent !== $expectedIndent) {
|
||||
$tokens[$index - 1] = new Token([T_WHITESPACE, $lineEnding.$expectedIndent]);
|
||||
}
|
||||
|
||||
$endParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $endParenthesisIndex);
|
||||
|
||||
for ($searchIndex = $index + 1; $searchIndex < $endParenthesisIndex; ++$searchIndex) {
|
||||
$searchToken = $tokens[$searchIndex];
|
||||
|
||||
if (!$searchToken->isWhitespace()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$content = $searchToken->getContent();
|
||||
|
||||
if (!Preg::match('/\R/', $content)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$content = Preg::replace(
|
||||
'/(\R)'.$currentIndent.'(\h*)$/D',
|
||||
'$1'.$expectedIndent.'$2',
|
||||
$content
|
||||
);
|
||||
|
||||
$tokens[$searchIndex] = new Token([$searchToken->getId(), $content]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $index index of the first token on the line to indent
|
||||
*/
|
||||
private function getExpectedIndentAt(Tokens $tokens, int $index): string
|
||||
{
|
||||
$index = $tokens->getPrevMeaningfulToken($index);
|
||||
$indent = $this->whitespacesConfig->getIndent();
|
||||
|
||||
for ($i = $index; $i >= 0; --$i) {
|
||||
if ($tokens[$i]->equals(')')) {
|
||||
$i = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $i);
|
||||
}
|
||||
|
||||
$currentIndent = $this->getIndentAt($tokens, $i);
|
||||
if (null === $currentIndent) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->currentLineRequiresExtraIndentLevel($tokens, $i, $index)) {
|
||||
return $currentIndent.$indent;
|
||||
}
|
||||
|
||||
return $currentIndent;
|
||||
}
|
||||
|
||||
return $indent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $index position of the object operator token ("->" or "?->")
|
||||
*/
|
||||
private function canBeMovedToNextLine(int $index, Tokens $tokens): bool
|
||||
{
|
||||
$prevMeaningful = $tokens->getPrevMeaningfulToken($index);
|
||||
$hasCommentBefore = false;
|
||||
|
||||
for ($i = $index - 1; $i > $prevMeaningful; --$i) {
|
||||
if ($tokens[$i]->isComment()) {
|
||||
$hasCommentBefore = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$i]->isWhitespace() && 1 === Preg::match('/\R/', $tokens[$i]->getContent())) {
|
||||
return $hasCommentBefore;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $index index of the indentation token
|
||||
*/
|
||||
private function getIndentAt(Tokens $tokens, int $index): ?string
|
||||
{
|
||||
if (1 === Preg::match('/\R{1}(\h*)$/', $this->getIndentContentAt($tokens, $index), $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getIndentContentAt(Tokens $tokens, int $index): string
|
||||
{
|
||||
if (!$tokens[$index]->isGivenKind([T_WHITESPACE, T_INLINE_HTML])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$content = $tokens[$index]->getContent();
|
||||
|
||||
if ($tokens[$index]->isWhitespace() && $tokens[$index - 1]->isGivenKind(T_OPEN_TAG)) {
|
||||
$content = $tokens[$index - 1]->getContent().$content;
|
||||
}
|
||||
|
||||
if (Preg::match('/\R/', $content)) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $start index of first meaningful token on previous line
|
||||
* @param int $end index of last token on previous line
|
||||
*/
|
||||
private function currentLineRequiresExtraIndentLevel(Tokens $tokens, int $start, int $end): bool
|
||||
{
|
||||
$firstMeaningful = $tokens->getNextMeaningfulToken($start);
|
||||
|
||||
if ($tokens[$firstMeaningful]->isObjectOperator()) {
|
||||
$thirdMeaningful = $tokens->getNextMeaningfulToken($tokens->getNextMeaningfulToken($firstMeaningful));
|
||||
|
||||
return
|
||||
$tokens[$thirdMeaningful]->equals('(')
|
||||
&& $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $thirdMeaningful) > $end
|
||||
;
|
||||
}
|
||||
|
||||
return
|
||||
!$tokens[$end]->equals(')')
|
||||
|| $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $end) >= $start
|
||||
;
|
||||
}
|
||||
}
|
||||
+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\Whitespace;
|
||||
|
||||
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;
|
||||
use PhpCsFixer\Utils;
|
||||
|
||||
/**
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class NoExtraBlankLinesFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private static array $availableTokens = [
|
||||
'attribute',
|
||||
'break',
|
||||
'case',
|
||||
'continue',
|
||||
'curly_brace_block',
|
||||
'default',
|
||||
'extra',
|
||||
'parenthesis_brace_block',
|
||||
'return',
|
||||
'square_brace_block',
|
||||
'switch',
|
||||
'throw',
|
||||
'use',
|
||||
'use_trait',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<int, string> key is token id, value is name of callback
|
||||
*/
|
||||
private array $tokenKindCallbackMap;
|
||||
|
||||
/**
|
||||
* @var array<string, string> token prototype, value is name of callback
|
||||
*/
|
||||
private array $tokenEqualsMap;
|
||||
|
||||
private Tokens $tokens;
|
||||
|
||||
private TokensAnalyzer $tokensAnalyzer;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configure(array $configuration): void
|
||||
{
|
||||
if (isset($configuration['tokens']) && \in_array('use_trait', $configuration['tokens'], true)) {
|
||||
Utils::triggerDeprecation(new \RuntimeException('Option "tokens: use_trait" used in `no_extra_blank_lines` rule is deprecated, use the rule `class_attributes_separation` with `elements: trait_import` instead.'));
|
||||
}
|
||||
|
||||
parent::configure($configuration);
|
||||
|
||||
$tokensConfiguration = $this->configuration['tokens'];
|
||||
|
||||
$this->tokenEqualsMap = [];
|
||||
|
||||
if (\in_array('curly_brace_block', $tokensConfiguration, true)) {
|
||||
$this->tokenEqualsMap['{'] = 'fixStructureOpenCloseIfMultiLine'; // i.e. not: CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN
|
||||
}
|
||||
|
||||
if (\in_array('parenthesis_brace_block', $tokensConfiguration, true)) {
|
||||
$this->tokenEqualsMap['('] = 'fixStructureOpenCloseIfMultiLine'; // i.e. not: CT::T_BRACE_CLASS_INSTANTIATION_OPEN
|
||||
}
|
||||
|
||||
static $configMap = [
|
||||
'attribute' => [CT::T_ATTRIBUTE_CLOSE, 'fixAfterToken'],
|
||||
'break' => [T_BREAK, 'fixAfterToken'],
|
||||
'case' => [T_CASE, 'fixAfterCaseToken'],
|
||||
'continue' => [T_CONTINUE, 'fixAfterToken'],
|
||||
'default' => [T_DEFAULT, 'fixAfterToken'],
|
||||
'extra' => [T_WHITESPACE, 'removeMultipleBlankLines'],
|
||||
'return' => [T_RETURN, 'fixAfterToken'],
|
||||
'square_brace_block' => [CT::T_ARRAY_SQUARE_BRACE_OPEN, 'fixStructureOpenCloseIfMultiLine'],
|
||||
'switch' => [T_SWITCH, 'fixAfterToken'],
|
||||
'throw' => [T_THROW, 'fixAfterThrowToken'],
|
||||
'use' => [T_USE, 'removeBetweenUse'],
|
||||
'use_trait' => [CT::T_USE_TRAIT, 'removeBetweenUse'],
|
||||
];
|
||||
|
||||
$this->tokenKindCallbackMap = [];
|
||||
|
||||
foreach ($tokensConfiguration as $config) {
|
||||
if (isset($configMap[$config])) {
|
||||
$this->tokenKindCallbackMap[$configMap[$config][0]] = $configMap[$config][1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Removes extra blank lines and/or blank lines following configuration.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
|
||||
$foo = array("foo");
|
||||
|
||||
|
||||
$bar = "bar";
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
|
||||
switch ($foo) {
|
||||
case 41:
|
||||
echo "foo";
|
||||
break;
|
||||
|
||||
case 42:
|
||||
break;
|
||||
}
|
||||
',
|
||||
['tokens' => ['break']]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
|
||||
for ($i = 0; $i < 9000; ++$i) {
|
||||
if (true) {
|
||||
continue;
|
||||
|
||||
}
|
||||
}
|
||||
',
|
||||
['tokens' => ['continue']]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
|
||||
for ($i = 0; $i < 9000; ++$i) {
|
||||
|
||||
echo $i;
|
||||
|
||||
}
|
||||
',
|
||||
['tokens' => ['curly_brace_block']]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
|
||||
$foo = array("foo");
|
||||
|
||||
|
||||
$bar = "bar";
|
||||
',
|
||||
['tokens' => ['extra']]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
|
||||
$foo = array(
|
||||
|
||||
"foo"
|
||||
|
||||
);
|
||||
',
|
||||
['tokens' => ['parenthesis_brace_block']]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
|
||||
function foo($bar)
|
||||
{
|
||||
return $bar;
|
||||
|
||||
}
|
||||
',
|
||||
['tokens' => ['return']]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
|
||||
$foo = [
|
||||
|
||||
"foo"
|
||||
|
||||
];
|
||||
',
|
||||
['tokens' => ['square_brace_block']]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
|
||||
function foo($bar)
|
||||
{
|
||||
throw new \Exception("Hello!");
|
||||
|
||||
}
|
||||
',
|
||||
['tokens' => ['throw']]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
|
||||
namespace Foo;
|
||||
|
||||
use Bar\Baz;
|
||||
|
||||
use Baz\Bar;
|
||||
|
||||
class Bar
|
||||
{
|
||||
}
|
||||
',
|
||||
['tokens' => ['use']]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
switch($a) {
|
||||
|
||||
case 1:
|
||||
|
||||
default:
|
||||
|
||||
echo 3;
|
||||
}
|
||||
',
|
||||
['tokens' => ['switch', 'case', 'default']]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before BlankLineBeforeStatementFixer.
|
||||
* Must run after ClassAttributesSeparationFixer, CombineConsecutiveUnsetsFixer, EmptyLoopBodyFixer, EmptyLoopConditionFixer, FunctionToConstantFixer, ModernizeStrposFixer, NoEmptyCommentFixer, NoEmptyPhpdocFixer, NoEmptyStatementFixer, NoUnusedImportsFixer, NoUselessElseFixer, NoUselessReturnFixer, NoUselessSprintfFixer, StringLengthToEmptyFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return -20;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$this->tokens = $tokens;
|
||||
$this->tokensAnalyzer = new TokensAnalyzer($this->tokens);
|
||||
|
||||
for ($index = $tokens->getSize() - 1; $index > 0; --$index) {
|
||||
$this->fixByToken($tokens[$index], $index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('tokens', 'List of tokens to fix.'))
|
||||
->setAllowedTypes(['array'])
|
||||
->setAllowedValues([new AllowedValueSubset(self::$availableTokens)])
|
||||
->setDefault(['extra'])
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function fixByToken(Token $token, int $index): void
|
||||
{
|
||||
foreach ($this->tokenKindCallbackMap as $kind => $callback) {
|
||||
if (!$token->isGivenKind($kind)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->{$callback}($index);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->tokenEqualsMap as $equals => $callback) {
|
||||
if (!$token->equals($equals)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->{$callback}($index);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private function removeBetweenUse(int $index): void
|
||||
{
|
||||
$next = $this->tokens->getNextTokenOfKind($index, [';', [T_CLOSE_TAG]]);
|
||||
|
||||
if (null === $next || $this->tokens[$next]->isGivenKind(T_CLOSE_TAG)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$nextUseCandidate = $this->tokens->getNextMeaningfulToken($next);
|
||||
|
||||
if (null === $nextUseCandidate || !$this->tokens[$nextUseCandidate]->isGivenKind($this->tokens[$index]->getId()) || !$this->containsLinebreak($index, $nextUseCandidate)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->removeEmptyLinesAfterLineWithTokenAt($next);
|
||||
}
|
||||
|
||||
private function removeMultipleBlankLines(int $index): void
|
||||
{
|
||||
$expected = $this->tokens[$index - 1]->isGivenKind(T_OPEN_TAG) && 1 === Preg::match('/\R$/', $this->tokens[$index - 1]->getContent()) ? 1 : 2;
|
||||
|
||||
$parts = Preg::split('/(.*\R)/', $this->tokens[$index]->getContent(), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
|
||||
$count = \count($parts);
|
||||
|
||||
if ($count > $expected) {
|
||||
$this->tokens[$index] = new Token([T_WHITESPACE, implode('', \array_slice($parts, 0, $expected)).rtrim($parts[$count - 1], "\r\n")]);
|
||||
}
|
||||
}
|
||||
|
||||
private function fixAfterToken(int $index): void
|
||||
{
|
||||
for ($i = $index - 1; $i > 0; --$i) {
|
||||
if ($this->tokens[$i]->isGivenKind(T_FUNCTION) && $this->tokensAnalyzer->isLambda($i)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->tokens[$i]->isGivenKind(T_CLASS) && $this->tokensAnalyzer->isAnonymousClass($i)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->tokens[$i]->isWhitespace() && str_contains($this->tokens[$i]->getContent(), "\n")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->removeEmptyLinesAfterLineWithTokenAt($index);
|
||||
}
|
||||
|
||||
private function fixAfterCaseToken(int $index): void
|
||||
{
|
||||
if (\defined('T_ENUM')) { // @TODO: drop condition when PHP 8.1+ is required
|
||||
$enumSwitchIndex = $this->tokens->getPrevTokenOfKind($index, [[T_SWITCH], [T_ENUM]]);
|
||||
|
||||
if (!$this->tokens[$enumSwitchIndex]->isGivenKind(T_SWITCH)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->removeEmptyLinesAfterLineWithTokenAt($index);
|
||||
}
|
||||
|
||||
private function fixAfterThrowToken(int $index): void
|
||||
{
|
||||
if ($this->tokens[$this->tokens->getPrevMeaningfulToken($index)]->equalsAny([';', '{', '}', ':', [T_OPEN_TAG]])) {
|
||||
$this->fixAfterToken($index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove white line(s) after the index of a block type,
|
||||
* but only if the block is not on one line.
|
||||
*
|
||||
* @param int $index body start
|
||||
*/
|
||||
private function fixStructureOpenCloseIfMultiLine(int $index): void
|
||||
{
|
||||
$blockTypeInfo = Tokens::detectBlockType($this->tokens[$index]);
|
||||
$bodyEnd = $this->tokens->findBlockEnd($blockTypeInfo['type'], $index);
|
||||
|
||||
for ($i = $bodyEnd - 1; $i >= $index; --$i) {
|
||||
if (str_contains($this->tokens[$i]->getContent(), "\n")) {
|
||||
$this->removeEmptyLinesAfterLineWithTokenAt($i);
|
||||
$this->removeEmptyLinesAfterLineWithTokenAt($index);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function removeEmptyLinesAfterLineWithTokenAt(int $index): void
|
||||
{
|
||||
// find the line break
|
||||
$tokenCount = \count($this->tokens);
|
||||
for ($end = $index; $end < $tokenCount; ++$end) {
|
||||
if (
|
||||
$this->tokens[$end]->equals('}')
|
||||
|| str_contains($this->tokens[$end]->getContent(), "\n")
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($end === $tokenCount) {
|
||||
return; // not found, early return
|
||||
}
|
||||
|
||||
$ending = $this->whitespacesConfig->getLineEnding();
|
||||
|
||||
for ($i = $end; $i < $tokenCount && $this->tokens[$i]->isWhitespace(); ++$i) {
|
||||
$content = $this->tokens[$i]->getContent();
|
||||
|
||||
if (substr_count($content, "\n") < 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$newContent = Preg::replace('/^.*\R(\h*)$/s', $ending.'$1', $content);
|
||||
|
||||
$this->tokens[$i] = new Token([T_WHITESPACE, $newContent]);
|
||||
}
|
||||
}
|
||||
|
||||
private function containsLinebreak(int $startIndex, int $endIndex): bool
|
||||
{
|
||||
for ($i = $endIndex; $i > $startIndex; --$i) {
|
||||
if (Preg::match('/\R/', $this->tokens[$i]->getContent())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Vendored
+111
@@ -0,0 +1,111 @@
|
||||
<?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\Whitespace;
|
||||
|
||||
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\Tokens;
|
||||
|
||||
/**
|
||||
* @author Javier Spagnoletti <phansys@gmail.com>
|
||||
*/
|
||||
final class NoSpacesAroundOffsetFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'There MUST NOT be spaces around offset braces.',
|
||||
[
|
||||
new CodeSample("<?php\n\$sample = \$b [ 'a' ] [ 'b' ];\n"),
|
||||
new CodeSample("<?php\n\$sample = \$b [ 'a' ] [ 'b' ];\n", ['positions' => ['inside']]),
|
||||
new CodeSample("<?php\n\$sample = \$b [ 'a' ] [ 'b' ];\n", ['positions' => ['outside']]),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound(['[', CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->equalsAny(['[', [CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN]])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (\in_array('inside', $this->configuration['positions'], true)) {
|
||||
if ($token->equals('[')) {
|
||||
$endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, $index);
|
||||
} else {
|
||||
$endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE, $index);
|
||||
}
|
||||
|
||||
// remove space after opening `[` or `{`
|
||||
if ($tokens[$index + 1]->isWhitespace(" \t")) {
|
||||
$tokens->clearAt($index + 1);
|
||||
}
|
||||
|
||||
// remove space before closing `]` or `}`
|
||||
if ($tokens[$endIndex - 1]->isWhitespace(" \t")) {
|
||||
$tokens->clearAt($endIndex - 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (\in_array('outside', $this->configuration['positions'], true)) {
|
||||
$prevNonWhitespaceIndex = $tokens->getPrevNonWhitespace($index);
|
||||
if ($tokens[$prevNonWhitespaceIndex]->isComment()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens->removeLeadingWhitespace($index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
$values = ['inside', 'outside'];
|
||||
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('positions', 'Whether spacing should be fixed inside and/or outside the offset braces.'))
|
||||
->setAllowedTypes(['array'])
|
||||
->setAllowedValues([new AllowedValueSubset($values)])
|
||||
->setDefault($values)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
Vendored
+111
@@ -0,0 +1,111 @@
|
||||
<?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\Whitespace;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* Fixer for rules defined in PSR2 ¶4.3, ¶4.6, ¶5.
|
||||
*
|
||||
* @author Marc Aubé
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class NoSpacesInsideParenthesisFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'There MUST NOT be a space after the opening parenthesis. There MUST NOT be a space before the closing parenthesis.',
|
||||
[
|
||||
new CodeSample("<?php\nif ( \$a ) {\n foo( );\n}\n"),
|
||||
new CodeSample(
|
||||
"<?php
|
||||
function foo( \$bar, \$baz )
|
||||
{
|
||||
}\n"
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before FunctionToConstantFixer, GetClassToClassKeywordFixer, StringLengthToEmptyFixer.
|
||||
* Must run after CombineConsecutiveIssetsFixer, CombineNestedDirnameFixer, IncrementStyleFixer, LambdaNotUsedImportFixer, ModernizeStrposFixer, NoUselessSprintfFixer, PowToExponentiationFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound('(');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
foreach ($tokens as $index => $token) {
|
||||
if (!$token->equals('(')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$prevIndex = $tokens->getPrevMeaningfulToken($index);
|
||||
|
||||
// ignore parenthesis for T_ARRAY
|
||||
if (null !== $prevIndex && $tokens[$prevIndex]->isGivenKind(T_ARRAY)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index);
|
||||
|
||||
// remove space after opening `(`
|
||||
if (!$tokens[$tokens->getNextNonWhitespace($index)]->isComment()) {
|
||||
$this->removeSpaceAroundToken($tokens, $index + 1);
|
||||
}
|
||||
|
||||
// remove space before closing `)` if it is not `list($a, $b, )` case
|
||||
if (!$tokens[$tokens->getPrevMeaningfulToken($endIndex)]->equals(',')) {
|
||||
$this->removeSpaceAroundToken($tokens, $endIndex - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove spaces from token at a given index.
|
||||
*/
|
||||
private function removeSpaceAroundToken(Tokens $tokens, int $index): void
|
||||
{
|
||||
$token = $tokens[$index];
|
||||
|
||||
if ($token->isWhitespace() && !str_contains($token->getContent(), "\n")) {
|
||||
$tokens->clearAt($index);
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+113
@@ -0,0 +1,113 @@
|
||||
<?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\Whitespace;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Fixer for rules defined in PSR2 ¶2.3.
|
||||
*
|
||||
* Don't add trailing spaces at the end of non-blank lines.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class NoTrailingWhitespaceFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Remove trailing whitespace at the end of non-blank lines.',
|
||||
[new CodeSample("<?php\n\$a = 1; \n")]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after CombineConsecutiveIssetsFixer, CombineConsecutiveUnsetsFixer, EmptyLoopBodyFixer, EmptyLoopConditionFixer, FunctionToConstantFixer, ModernizeStrposFixer, NoEmptyCommentFixer, NoEmptyPhpdocFixer, NoEmptyStatementFixer, NoUnneededControlParenthesesFixer, NoUselessElseFixer, StringLengthToEmptyFixer, TernaryToElvisOperatorFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
for ($index = \count($tokens) - 1; $index >= 0; --$index) {
|
||||
$token = $tokens[$index];
|
||||
if (
|
||||
$token->isGivenKind(T_OPEN_TAG)
|
||||
&& $tokens->offsetExists($index + 1)
|
||||
&& $tokens[$index + 1]->isWhitespace()
|
||||
&& 1 === Preg::match('/(.*)\h$/', $token->getContent(), $openTagMatches)
|
||||
&& 1 === Preg::match('/^(\R)(.*)$/s', $tokens[$index + 1]->getContent(), $whitespaceMatches)
|
||||
) {
|
||||
$tokens[$index] = new Token([T_OPEN_TAG, $openTagMatches[1].$whitespaceMatches[1]]);
|
||||
$tokens->ensureWhitespaceAtIndex($index + 1, 0, $whitespaceMatches[2]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$token->isWhitespace()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$lines = Preg::split('/(\\R+)/', $token->getContent(), -1, PREG_SPLIT_DELIM_CAPTURE);
|
||||
$linesSize = \count($lines);
|
||||
|
||||
// fix only multiline whitespaces or singleline whitespaces at the end of file
|
||||
if ($linesSize > 1 || !isset($tokens[$index + 1])) {
|
||||
if (!$tokens[$index - 1]->isGivenKind(T_OPEN_TAG) || 1 !== Preg::match('/(.*)\R$/', $tokens[$index - 1]->getContent())) {
|
||||
$lines[0] = rtrim($lines[0], " \t");
|
||||
}
|
||||
|
||||
for ($i = 1; $i < $linesSize; ++$i) {
|
||||
$trimmedLine = rtrim($lines[$i], " \t");
|
||||
if ('' !== $trimmedLine) {
|
||||
$lines[$i] = $trimmedLine;
|
||||
}
|
||||
}
|
||||
|
||||
$content = implode('', $lines);
|
||||
if ('' !== $content) {
|
||||
$tokens[$index] = new Token([$token->getId(), $content]);
|
||||
} else {
|
||||
$tokens->clearAt($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\Whitespace;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Preg;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class NoWhitespaceInBlankLineFixer extends AbstractFixer implements WhitespacesAwareFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Remove trailing whitespace at the end of blank lines.',
|
||||
[new CodeSample("<?php\n \n\$a = 1;\n")]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after AssignNullCoalescingToCoalesceEqualFixer, CombineConsecutiveIssetsFixer, CombineConsecutiveUnsetsFixer, FunctionToConstantFixer, NoEmptyCommentFixer, NoEmptyPhpdocFixer, NoEmptyStatementFixer, NoUselessElseFixer, NoUselessReturnFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return -19;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
// skip first as it cannot be a white space token
|
||||
for ($i = 1, $count = \count($tokens); $i < $count; ++$i) {
|
||||
if ($tokens[$i]->isWhitespace()) {
|
||||
$this->fixWhitespaceToken($tokens, $i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function fixWhitespaceToken(Tokens $tokens, int $index): void
|
||||
{
|
||||
$content = $tokens[$index]->getContent();
|
||||
$lines = Preg::split("/(\r\n|\n)/", $content);
|
||||
$lineCount = \count($lines);
|
||||
|
||||
if (
|
||||
// fix T_WHITESPACES with at least 3 lines (eg `\n \n`)
|
||||
$lineCount > 2
|
||||
// and T_WHITESPACES with at least 2 lines at the end of file or after open tag with linebreak
|
||||
|| ($lineCount > 0 && (!isset($tokens[$index + 1]) || $tokens[$index - 1]->isGivenKind(T_OPEN_TAG)))
|
||||
) {
|
||||
$lMax = isset($tokens[$index + 1]) ? $lineCount - 1 : $lineCount;
|
||||
|
||||
$lStart = 1;
|
||||
if ($tokens[$index - 1]->isGivenKind(T_OPEN_TAG) && "\n" === substr($tokens[$index - 1]->getContent(), -1)) {
|
||||
$lStart = 0;
|
||||
}
|
||||
|
||||
for ($l = $lStart; $l < $lMax; ++$l) {
|
||||
$lines[$l] = Preg::replace('/^\h+$/', '', $lines[$l]);
|
||||
}
|
||||
$content = implode($this->whitespacesConfig->getLineEnding(), $lines);
|
||||
$tokens->ensureWhitespaceAtIndex($index, 0, $content);
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+76
@@ -0,0 +1,76 @@
|
||||
<?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\Whitespace;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* A file must always end with a line endings character.
|
||||
*
|
||||
* Fixer for rules defined in PSR2 ¶2.2.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class SingleBlankLineAtEofFixer extends AbstractFixer implements WhitespacesAwareFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'A PHP file without end tag must always end with a single empty line feed.',
|
||||
[
|
||||
new CodeSample("<?php\n\$a = 1;"),
|
||||
new CodeSample("<?php\n\$a = 1;\n\n"),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
// must run last to be sure the file is properly formatted before it runs
|
||||
return -50;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$count = $tokens->count();
|
||||
|
||||
if ($count > 0 && !$tokens[$count - 1]->isGivenKind([T_INLINE_HTML, T_CLOSE_TAG, T_OPEN_TAG])) {
|
||||
$tokens->ensureWhitespaceAtIndex($count - 1, 1, $this->whitespacesConfig->getLineEnding());
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+615
@@ -0,0 +1,615 @@
|
||||
<?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\Whitespace;
|
||||
|
||||
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\Analyzer\AlternativeSyntaxAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class StatementIndentationFixer extends AbstractFixer implements WhitespacesAwareFixerInterface
|
||||
{
|
||||
use Indentation;
|
||||
|
||||
private AlternativeSyntaxAnalyzer $alternativeSyntaxAnalyzer;
|
||||
|
||||
private bool $bracesFixerCompatibility;
|
||||
|
||||
public function __construct(bool $bracesFixerCompatibility = false)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->bracesFixerCompatibility = $bracesFixerCompatibility;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Each statement must be indented.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
if ($baz == true) {
|
||||
echo "foo";
|
||||
}
|
||||
else {
|
||||
echo "bar";
|
||||
}
|
||||
'
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before HeredocIndentationFixer.
|
||||
* Must run after ClassAttributesSeparationFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return parent::getPriority();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$this->alternativeSyntaxAnalyzer = new AlternativeSyntaxAnalyzer();
|
||||
|
||||
$blockSignatureFirstTokens = [
|
||||
T_USE,
|
||||
T_IF,
|
||||
T_ELSE,
|
||||
T_ELSEIF,
|
||||
T_FOR,
|
||||
T_FOREACH,
|
||||
T_WHILE,
|
||||
T_SWITCH,
|
||||
T_CASE,
|
||||
T_DEFAULT,
|
||||
T_TRY,
|
||||
T_FUNCTION,
|
||||
T_CLASS,
|
||||
T_INTERFACE,
|
||||
T_TRAIT,
|
||||
T_EXTENDS,
|
||||
T_IMPLEMENTS,
|
||||
];
|
||||
if (\defined('T_MATCH')) { // @TODO: drop condition when PHP 8.0+ is required
|
||||
$blockSignatureFirstTokens[] = T_MATCH;
|
||||
}
|
||||
|
||||
$blockFirstTokens = ['{', [CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN], [CT::T_USE_TRAIT], [CT::T_GROUP_IMPORT_BRACE_OPEN]];
|
||||
if (\defined('T_ATTRIBUTE')) { // @TODO: drop condition when PHP 8.0+ is required
|
||||
$blockFirstTokens[] = [T_ATTRIBUTE];
|
||||
}
|
||||
|
||||
$endIndex = \count($tokens) - 1;
|
||||
if ($tokens[$endIndex]->isWhitespace()) {
|
||||
--$endIndex;
|
||||
}
|
||||
|
||||
$lastIndent = $this->getLineIndentationWithBracesCompatibility(
|
||||
$tokens,
|
||||
0,
|
||||
$this->extractIndent($this->computeNewLineContent($tokens, 0)),
|
||||
);
|
||||
|
||||
/**
|
||||
* @var list<array{
|
||||
* type: 'block'|'block_signature'|'statement',
|
||||
* skip: bool,
|
||||
* end_index: int,
|
||||
* end_index_inclusive: bool,
|
||||
* initial_indent: string,
|
||||
* is_indented_block: bool,
|
||||
* }> $scopes
|
||||
*/
|
||||
$scopes = [
|
||||
[
|
||||
'type' => 'block',
|
||||
'skip' => false,
|
||||
'end_index' => $endIndex,
|
||||
'end_index_inclusive' => true,
|
||||
'initial_indent' => $lastIndent,
|
||||
'is_indented_block' => false,
|
||||
],
|
||||
];
|
||||
|
||||
$previousLineInitialIndent = '';
|
||||
$previousLineNewIndent = '';
|
||||
$alternativeBlockStarts = [];
|
||||
$caseBlockStarts = [];
|
||||
|
||||
foreach ($tokens as $index => $token) {
|
||||
$currentScope = \count($scopes) - 1;
|
||||
|
||||
if (
|
||||
$token->equalsAny($blockFirstTokens)
|
||||
|| ($token->equals('(') && !$tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_ARRAY))
|
||||
|| isset($alternativeBlockStarts[$index])
|
||||
|| isset($caseBlockStarts[$index])
|
||||
) {
|
||||
$endIndexInclusive = true;
|
||||
|
||||
if ($token->isGivenKind([T_EXTENDS, T_IMPLEMENTS])) {
|
||||
$endIndex = $tokens->getNextTokenOfKind($index, ['{']);
|
||||
} elseif ($token->isGivenKind(CT::T_USE_TRAIT)) {
|
||||
$endIndex = $tokens->getNextTokenOfKind($index, [';']);
|
||||
} elseif ($token->equals(':')) {
|
||||
if (isset($caseBlockStarts[$index])) {
|
||||
[$endIndex, $endIndexInclusive] = $this->findCaseBlockEnd($tokens, $index);
|
||||
} else {
|
||||
$endIndex = $this->alternativeSyntaxAnalyzer->findAlternativeSyntaxBlockEnd($tokens, $alternativeBlockStarts[$index]);
|
||||
}
|
||||
} elseif ($token->isGivenKind(CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN)) {
|
||||
$endIndex = $tokens->getNextTokenOfKind($index, [[CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE]]);
|
||||
} elseif ($token->isGivenKind(CT::T_GROUP_IMPORT_BRACE_OPEN)) {
|
||||
$endIndex = $tokens->getNextTokenOfKind($index, [[CT::T_GROUP_IMPORT_BRACE_CLOSE]]);
|
||||
} elseif ($token->equals('{')) {
|
||||
$endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index);
|
||||
} elseif ($token->equals('(')) {
|
||||
$endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index);
|
||||
} else {
|
||||
$endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ATTRIBUTE, $index);
|
||||
}
|
||||
|
||||
if ('block_signature' === $scopes[$currentScope]['type']) {
|
||||
$initialIndent = $scopes[$currentScope]['initial_indent'];
|
||||
} else {
|
||||
$initialIndent = $this->getLineIndentationWithBracesCompatibility($tokens, $index, $lastIndent);
|
||||
}
|
||||
|
||||
$skip = false;
|
||||
if ($this->bracesFixerCompatibility) {
|
||||
$prevIndex = $tokens->getPrevMeaningfulToken($index);
|
||||
if (null !== $prevIndex) {
|
||||
$prevIndex = $tokens->getPrevMeaningfulToken($prevIndex);
|
||||
}
|
||||
if (null !== $prevIndex && $tokens[$prevIndex]->isGivenKind([T_FUNCTION, T_FN])) {
|
||||
$skip = true;
|
||||
}
|
||||
}
|
||||
|
||||
$scopes[] = [
|
||||
'type' => 'block',
|
||||
'skip' => $skip,
|
||||
'end_index' => $endIndex,
|
||||
'end_index_inclusive' => $endIndexInclusive,
|
||||
'initial_indent' => $initialIndent,
|
||||
'is_indented_block' => true,
|
||||
];
|
||||
++$currentScope;
|
||||
|
||||
while ($index >= $scopes[$currentScope]['end_index']) {
|
||||
array_pop($scopes);
|
||||
|
||||
--$currentScope;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->isGivenKind($blockSignatureFirstTokens)) {
|
||||
for ($endIndex = $index + 1, $max = \count($tokens); $endIndex < $max; ++$endIndex) {
|
||||
if ($tokens[$endIndex]->equals('(')) {
|
||||
$endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $endIndex);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$endIndex]->equalsAny(['{', ';', [T_DOUBLE_ARROW], [T_IMPLEMENTS]])) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ($tokens[$endIndex]->equals(':')) {
|
||||
if ($token->isGivenKind([T_CASE, T_DEFAULT])) {
|
||||
$caseBlockStarts[$endIndex] = $index;
|
||||
} else {
|
||||
$alternativeBlockStarts[$endIndex] = $index;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$scopes[] = [
|
||||
'type' => 'block_signature',
|
||||
'skip' => false,
|
||||
'end_index' => $endIndex,
|
||||
'end_index_inclusive' => true,
|
||||
'initial_indent' => $this->getLineIndentationWithBracesCompatibility($tokens, $index, $lastIndent),
|
||||
'is_indented_block' => $token->isGivenKind([T_EXTENDS, T_IMPLEMENTS]),
|
||||
];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
$token->isWhitespace()
|
||||
|| ($index > 0 && $tokens[$index - 1]->isGivenKind(T_OPEN_TAG))
|
||||
) {
|
||||
$previousOpenTagContent = $tokens[$index - 1]->isGivenKind(T_OPEN_TAG)
|
||||
? Preg::replace('/\S/', '', $tokens[$index - 1]->getContent())
|
||||
: ''
|
||||
;
|
||||
|
||||
$content = $previousOpenTagContent.($token->isWhitespace() ? $token->getContent() : '');
|
||||
|
||||
if (!Preg::match('/\R/', $content)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$nextToken = $tokens[$index + 1] ?? null;
|
||||
|
||||
if (
|
||||
$this->bracesFixerCompatibility
|
||||
&& null !== $nextToken
|
||||
&& $nextToken->isComment()
|
||||
&& !$this->isCommentWithFixableIndentation($tokens, $index + 1)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('block' === $scopes[$currentScope]['type'] || 'block_signature' === $scopes[$currentScope]['type']) {
|
||||
$indent = false;
|
||||
|
||||
if ($scopes[$currentScope]['is_indented_block']) {
|
||||
$firstNonWhitespaceTokenIndex = null;
|
||||
$nextNewlineIndex = null;
|
||||
for ($searchIndex = $index + 1, $max = \count($tokens); $searchIndex < $max; ++$searchIndex) {
|
||||
$searchToken = $tokens[$searchIndex];
|
||||
|
||||
if (!$searchToken->isWhitespace()) {
|
||||
if (null === $firstNonWhitespaceTokenIndex) {
|
||||
$firstNonWhitespaceTokenIndex = $searchIndex;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Preg::match('/\R/', $searchToken->getContent())) {
|
||||
$nextNewlineIndex = $searchIndex;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->isCommentForControlSructureContinuation($tokens, $index + 1)) {
|
||||
$endIndex = $scopes[$currentScope]['end_index'];
|
||||
|
||||
if (!$scopes[$currentScope]['end_index_inclusive']) {
|
||||
++$endIndex;
|
||||
}
|
||||
|
||||
if (
|
||||
(null !== $firstNonWhitespaceTokenIndex && $firstNonWhitespaceTokenIndex < $endIndex)
|
||||
|| (null !== $nextNewlineIndex && $nextNewlineIndex < $endIndex)
|
||||
) {
|
||||
$indent = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$previousLineInitialIndent = $this->extractIndent($content);
|
||||
|
||||
if ($scopes[$currentScope]['skip']) {
|
||||
$whitespaces = $previousLineInitialIndent;
|
||||
} else {
|
||||
$whitespaces = $scopes[$currentScope]['initial_indent'].($indent ? $this->whitespacesConfig->getIndent() : '');
|
||||
}
|
||||
|
||||
$content = Preg::replace(
|
||||
'/(\R+)\h*$/',
|
||||
'$1'.$whitespaces,
|
||||
$content
|
||||
);
|
||||
|
||||
$previousLineNewIndent = $this->extractIndent($content);
|
||||
} else {
|
||||
$content = Preg::replace(
|
||||
'/(\R)'.$scopes[$currentScope]['initial_indent'].'(\h*)$/D',
|
||||
'$1'.$scopes[$currentScope]['new_indent'].'$2',
|
||||
$content
|
||||
);
|
||||
}
|
||||
|
||||
$lastIndent = $this->extractIndent($content);
|
||||
|
||||
if ('' !== $previousOpenTagContent) {
|
||||
$content = Preg::replace("/^{$previousOpenTagContent}/", '', $content);
|
||||
}
|
||||
|
||||
if ('' !== $content) {
|
||||
$tokens->ensureWhitespaceAtIndex($index, 0, $content);
|
||||
} elseif ($token->isWhitespace()) {
|
||||
$tokens->clearAt($index);
|
||||
}
|
||||
|
||||
if (null !== $nextToken && $nextToken->isComment()) {
|
||||
$tokens[$index + 1] = new Token([
|
||||
$nextToken->getId(),
|
||||
Preg::replace(
|
||||
'/(\R)'.preg_quote($previousLineInitialIndent, '/').'(\h*\S+.*)/',
|
||||
'$1'.$previousLineNewIndent.'$2',
|
||||
$nextToken->getContent()
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($token->isWhitespace()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->isNewLineToken($tokens, $index)) {
|
||||
$lastIndent = $this->extractIndent($this->computeNewLineContent($tokens, $index));
|
||||
}
|
||||
|
||||
while ($index >= $scopes[$currentScope]['end_index']) {
|
||||
array_pop($scopes);
|
||||
|
||||
if ([] === $scopes) {
|
||||
return;
|
||||
}
|
||||
|
||||
--$currentScope;
|
||||
}
|
||||
|
||||
if ($token->isComment() || $token->equalsAny([';', ',', '}', [T_OPEN_TAG], [T_CLOSE_TAG], [CT::T_ATTRIBUTE_CLOSE]])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('statement' !== $scopes[$currentScope]['type'] && 'block_signature' !== $scopes[$currentScope]['type']) {
|
||||
$endIndex = $this->findStatementEndIndex($tokens, $index, $scopes[$currentScope]['end_index']);
|
||||
|
||||
if ($endIndex === $index) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$scopes[] = [
|
||||
'type' => 'statement',
|
||||
'skip' => false,
|
||||
'end_index' => $endIndex,
|
||||
'end_index_inclusive' => false,
|
||||
'initial_indent' => $previousLineInitialIndent,
|
||||
'new_indent' => $previousLineNewIndent,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function findStatementEndIndex(Tokens $tokens, int $index, int $parentScopeEndIndex): int
|
||||
{
|
||||
$endIndex = null;
|
||||
|
||||
for ($searchEndIndex = $index; $searchEndIndex <= $parentScopeEndIndex; ++$searchEndIndex) {
|
||||
$searchEndToken = $tokens[$searchEndIndex];
|
||||
|
||||
if ($searchEndToken->equalsAny(['(', '{', [CT::T_ARRAY_SQUARE_BRACE_OPEN]])) {
|
||||
if ($searchEndToken->equals('(')) {
|
||||
$blockType = Tokens::BLOCK_TYPE_PARENTHESIS_BRACE;
|
||||
} elseif ($searchEndToken->equals('{')) {
|
||||
$blockType = Tokens::BLOCK_TYPE_CURLY_BRACE;
|
||||
} else {
|
||||
$blockType = Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE;
|
||||
}
|
||||
|
||||
$searchEndIndex = $tokens->findBlockEnd($blockType, $searchEndIndex);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($searchEndToken->equalsAny([';', ',', '}', [T_CLOSE_TAG]])) {
|
||||
$endIndex = $tokens->getPrevNonWhitespace($searchEndIndex);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $endIndex ?? $tokens->getPrevMeaningfulToken($parentScopeEndIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{int, bool}
|
||||
*/
|
||||
private function findCaseBlockEnd(Tokens $tokens, int $index): array
|
||||
{
|
||||
for ($max = \count($tokens); $index < $max; ++$index) {
|
||||
if ($tokens[$index]->isGivenKind(T_SWITCH)) {
|
||||
$braceIndex = $tokens->getNextMeaningfulToken(
|
||||
$tokens->findBlockEnd(
|
||||
Tokens::BLOCK_TYPE_PARENTHESIS_BRACE,
|
||||
$tokens->getNextMeaningfulToken($index)
|
||||
)
|
||||
);
|
||||
|
||||
if ($tokens[$braceIndex]->equals(':')) {
|
||||
$index = $this->alternativeSyntaxAnalyzer->findAlternativeSyntaxBlockEnd($tokens, $index);
|
||||
} else {
|
||||
$index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $braceIndex);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$index]->equals('{')) {
|
||||
$index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$index]->equalsAny([[T_CASE], [T_DEFAULT]])) {
|
||||
return [$index, true];
|
||||
}
|
||||
|
||||
if ($tokens[$index]->equalsAny(['}', [T_ENDSWITCH]])) {
|
||||
return [$tokens->getPrevNonWhitespace($index), false];
|
||||
}
|
||||
}
|
||||
|
||||
throw new \LogicException('End of case block not found.');
|
||||
}
|
||||
|
||||
private function getLineIndentationWithBracesCompatibility(Tokens $tokens, int $index, string $regularIndent): string
|
||||
{
|
||||
if (
|
||||
$this->bracesFixerCompatibility
|
||||
&& $tokens[$index]->isGivenKind(T_OPEN_TAG)
|
||||
&& Preg::match('/\R/', $tokens[$index]->getContent())
|
||||
&& isset($tokens[$index + 1])
|
||||
&& $tokens[$index + 1]->isWhitespace()
|
||||
&& Preg::match('/\h+$/D', $tokens[$index + 1]->getContent())
|
||||
) {
|
||||
return Preg::replace('/.*?(\h+)$/sD', '$1', $tokens[$index + 1]->getContent());
|
||||
}
|
||||
|
||||
return $regularIndent;
|
||||
}
|
||||
|
||||
private function isCommentForControlSructureContinuation(Tokens $tokens, int $index): bool
|
||||
{
|
||||
if (!isset($tokens[$index], $tokens[$index + 1])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$tokens[$index]->isComment() || 1 !== Preg::match('~^(//|#)~', $tokens[$index]->getContent())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$tokens[$index + 1]->isWhitespace() || 1 !== Preg::match('/\R/', $tokens[$index + 1]->getContent())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$prevIndex = $tokens->getPrevMeaningfulToken($index);
|
||||
if (null !== $prevIndex && $tokens[$prevIndex]->equals('{')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$index = $tokens->getNextMeaningfulToken($index + 1);
|
||||
|
||||
if (null === $index || !$tokens[$index]->equals('}')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$index = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
return null !== $index && $tokens[$index]->equalsAny([[T_ELSE], [T_ELSEIF], ',']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the token at given index is a comment whose indentation
|
||||
* can be fixed.
|
||||
*
|
||||
* Indentation of a comment is not changed when the comment is part of a
|
||||
* multi-line message whose lines are all single-line comments and at least
|
||||
* one line has meaningful content.
|
||||
*/
|
||||
private function isCommentWithFixableIndentation(Tokens $tokens, int $index): bool
|
||||
{
|
||||
if (!$tokens[$index]->isComment()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (str_starts_with($tokens[$index]->getContent(), '/*')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$indent = preg_quote($this->whitespacesConfig->getIndent(), '~');
|
||||
|
||||
if (1 === Preg::match("~^(//|#)({$indent}.*)?$~", $tokens[$index]->getContent())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$firstCommentIndex = $index;
|
||||
while (true) {
|
||||
$i = $this->getSiblingContinuousSingleLineComment($tokens, $firstCommentIndex, false);
|
||||
if (null === $i) {
|
||||
break;
|
||||
}
|
||||
|
||||
$firstCommentIndex = $i;
|
||||
}
|
||||
|
||||
$lastCommentIndex = $index;
|
||||
while (true) {
|
||||
$i = $this->getSiblingContinuousSingleLineComment($tokens, $lastCommentIndex, true);
|
||||
if (null === $i) {
|
||||
break;
|
||||
}
|
||||
|
||||
$lastCommentIndex = $i;
|
||||
}
|
||||
|
||||
if ($firstCommentIndex === $lastCommentIndex) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for ($i = $firstCommentIndex + 1; $i < $lastCommentIndex; ++$i) {
|
||||
if (!$tokens[$i]->isWhitespace() && !$tokens[$i]->isComment()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function getSiblingContinuousSingleLineComment(Tokens $tokens, int $index, bool $after): ?int
|
||||
{
|
||||
$siblingIndex = $index;
|
||||
do {
|
||||
if ($after) {
|
||||
$siblingIndex = $tokens->getNextTokenOfKind($siblingIndex, [[T_COMMENT]]);
|
||||
} else {
|
||||
$siblingIndex = $tokens->getPrevTokenOfKind($siblingIndex, [[T_COMMENT]]);
|
||||
}
|
||||
|
||||
if (null === $siblingIndex) {
|
||||
return null;
|
||||
}
|
||||
} while (str_starts_with($tokens[$siblingIndex]->getContent(), '/*'));
|
||||
|
||||
$newLines = 0;
|
||||
for ($i = min($siblingIndex, $index) + 1, $max = max($siblingIndex, $index); $i < $max; ++$i) {
|
||||
if ($tokens[$i]->isWhitespace() && Preg::match('/\R/', $tokens[$i]->getContent())) {
|
||||
if (1 === $newLines || Preg::match('/\R.*\R/', $tokens[$i]->getContent())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
++$newLines;
|
||||
}
|
||||
}
|
||||
|
||||
return $siblingIndex;
|
||||
}
|
||||
}
|
||||
+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\Whitespace;
|
||||
|
||||
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\Preg;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class TypesSpacesFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
public function configure(array $configuration): void
|
||||
{
|
||||
parent::configure($configuration);
|
||||
|
||||
if (!isset($this->configuration['space_multiple_catch'])) {
|
||||
$this->configuration['space_multiple_catch'] = $this->configuration['space'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'A single space or none should be around union type and intersection type operators.',
|
||||
[
|
||||
new CodeSample(
|
||||
"<?php\ntry\n{\n new Foo();\n} catch (ErrorA | ErrorB \$e) {\necho'error';}\n"
|
||||
),
|
||||
new CodeSample(
|
||||
"<?php\ntry\n{\n new Foo();\n} catch (ErrorA|ErrorB \$e) {\necho'error';}\n",
|
||||
['space' => 'single']
|
||||
),
|
||||
new VersionSpecificCodeSample(
|
||||
"<?php\nfunction foo(int | string \$x)\n{\n}\n",
|
||||
new VersionSpecification(80000)
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound([CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('space', 'spacing to apply around union type and intersection type operators.'))
|
||||
->setAllowedValues(['none', 'single'])
|
||||
->setDefault('none')
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('space_multiple_catch', 'spacing to apply around type operator when catching exceptions of multiple types, use `null` to follow the value configured for `space`.'))
|
||||
->setAllowedValues(['none', 'single', null])
|
||||
->setDefault(null)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$tokenCount = $tokens->count() - 1;
|
||||
|
||||
for ($index = 0; $index < $tokenCount; ++$index) {
|
||||
if ($tokens[$index]->isGivenKind([CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION])) {
|
||||
$tokenCount += $this->fixSpacing($tokens, $index, 'single' === $this->configuration['space']);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$index]->isGivenKind(T_CATCH)) {
|
||||
while (true) {
|
||||
$index = $tokens->getNextTokenOfKind($index, [')', [CT::T_TYPE_ALTERNATION]]);
|
||||
|
||||
if ($tokens[$index]->equals(')')) {
|
||||
break;
|
||||
}
|
||||
|
||||
$tokenCount += $this->fixSpacing($tokens, $index, 'single' === $this->configuration['space_multiple_catch']);
|
||||
}
|
||||
|
||||
// implicit continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function fixSpacing(Tokens $tokens, int $index, bool $singleSpace): int
|
||||
{
|
||||
if (!$singleSpace) {
|
||||
$this->ensureNoSpace($tokens, $index + 1);
|
||||
$this->ensureNoSpace($tokens, $index - 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
$addedTokenCount = 0;
|
||||
$addedTokenCount += $this->ensureSingleSpace($tokens, $index + 1, 0);
|
||||
$addedTokenCount += $this->ensureSingleSpace($tokens, $index - 1, 1);
|
||||
|
||||
return $addedTokenCount;
|
||||
}
|
||||
|
||||
private function ensureSingleSpace(Tokens $tokens, int $index, int $offset): int
|
||||
{
|
||||
if (!$tokens[$index]->isWhitespace()) {
|
||||
$tokens->insertSlices([$index + $offset => new Token([T_WHITESPACE, ' '])]);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (' ' !== $tokens[$index]->getContent() && 1 !== Preg::match('/\R/', $tokens[$index]->getContent())) {
|
||||
$tokens[$index] = new Token([T_WHITESPACE, ' ']);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function ensureNoSpace(Tokens $tokens, int $index): void
|
||||
{
|
||||
if ($tokens[$index]->isWhitespace() && 1 !== Preg::match('/\R/', $tokens[$index]->getContent())) {
|
||||
$tokens->clearAt($index);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user