Missing dependancies

This commit is contained in:
tokslaw7
2023-06-21 12:19:22 +00:00
parent fbf3f180c2
commit 421f25c80d
2356 changed files with 342670 additions and 4743 deletions
@@ -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);
}
}
@@ -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]));
}
}
}
@@ -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;
}
}
@@ -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);
}
}
}
}
@@ -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]);
}
}
@@ -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;
}
}
@@ -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()
),
]);
}
}
}
}
@@ -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
;
}
}
@@ -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;
}
}
@@ -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(),
]);
}
}
@@ -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);
}
}
}
@@ -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);
}
}
}
}
}
@@ -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);
}
}
}
@@ -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());
}
}
}
@@ -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;
}
}
@@ -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);
}
}
}