File "BlankLineBeforeStatementFixer.php"
Full Path: /var/www/html/back/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/BlankLineBeforeStatementFixer.php
File size: 12.61 KB
MIME-type: text/x-php
Charset: utf-8
<?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\ConfigurableFixerTrait;
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;
/**
* @phpstan-type _AutogeneratedInputConfiguration array{
* statements?: list<'break'|'case'|'continue'|'declare'|'default'|'do'|'exit'|'for'|'foreach'|'goto'|'if'|'include'|'include_once'|'phpdoc'|'require'|'require_once'|'return'|'switch'|'throw'|'try'|'while'|'yield'|'yield_from'>,
* }
* @phpstan-type _AutogeneratedComputedConfiguration array{
* statements: list<'break'|'case'|'continue'|'declare'|'default'|'do'|'exit'|'for'|'foreach'|'goto'|'if'|'include'|'include_once'|'phpdoc'|'require'|'require_once'|'return'|'switch'|'throw'|'try'|'while'|'yield'|'yield_from'>,
* }
*
* @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
*
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
* @author Andreas Möller <am@localheinz.com>
*
* @no-named-arguments Parameter names are not covered by the backward compatibility promise.
*/
final class BlankLineBeforeStatementFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
{
/** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */
use ConfigurableFixerTrait;
/**
* @var array<string, int>
*/
private const TOKEN_MAP = [
'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 = [];
public function getDefinition(): FixerDefinitionInterface
{
return new FixerDefinition(
'An empty line feed must precede any configured statement.',
[
new CodeSample(
<<<'PHP'
<?php
function A() {
echo 1;
return 1;
}
PHP,
),
new CodeSample(
<<<'PHP'
<?php
switch ($foo) {
case 42:
$bar->process();
break;
case 44:
break;
}
PHP,
[
'statements' => ['break'],
],
),
new CodeSample(
<<<'PHP'
<?php
foreach ($foo as $bar) {
if ($bar->isTired()) {
$bar->sleep();
continue;
}
}
PHP,
[
'statements' => ['continue'],
],
),
new CodeSample(
<<<'PHP'
<?php
$i = 0;
do {
echo $i;
} while ($i > 0);
PHP,
[
'statements' => ['do'],
],
),
new CodeSample(
<<<'PHP'
<?php
if ($foo === false) {
exit(0);
} else {
$bar = 9000;
exit(1);
}
PHP,
[
'statements' => ['exit'],
],
),
new CodeSample(
<<<'PHP'
<?php
a:
if ($foo === false) {
goto a;
} else {
$bar = 9000;
goto b;
}
PHP,
[
'statements' => ['goto'],
],
),
new CodeSample(
<<<'PHP'
<?php
$a = 9000;
if (true) {
$foo = $bar;
}
PHP,
[
'statements' => ['if'],
],
),
new CodeSample(
<<<'PHP'
<?php
if (true) {
$foo = $bar;
return;
}
PHP,
[
'statements' => ['return'],
],
),
new CodeSample(
<<<'PHP'
<?php
$a = 9000;
switch ($a) {
case 42:
break;
}
PHP,
[
'statements' => ['switch'],
],
),
new CodeSample(
<<<'PHP'
<?php
if (null === $a) {
$foo->bar();
throw new \UnexpectedValueException("A cannot be null.");
}
PHP,
[
'statements' => ['throw'],
],
),
new CodeSample(
<<<'PHP'
<?php
$a = 9000;
try {
$foo->bar();
} catch (\Exception $exception) {
$a = -1;
}
PHP,
[
'statements' => ['try'],
],
),
new CodeSample(
<<<'PHP'
<?php
function getValues() {
yield 1;
yield 2;
// comment
yield 3;
}
PHP,
[
'statements' => ['yield'],
],
),
],
);
}
/**
* {@inheritdoc}
*
* Must run after NoExtraBlankLinesFixer, NoUselessElseFixer, NoUselessReturnFixer, ReturnAssignmentFixer, YieldFromArrayToYieldsFixer.
*/
public function getPriority(): int
{
return -21;
}
public function isCandidate(Tokens $tokens): bool
{
return $tokens->isAnyTokenKindsFound($this->fixTokenMap);
}
protected function configurePostNormalisation(): void
{
$fixTokenMap = [];
foreach ($this->configuration['statements'] as $key) {
$fixTokenMap[$key] = self::TOKEN_MAP[$key];
}
$this->fixTokenMap = array_values($fixTokenMap);
}
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;
}
if ($token->isGivenKind(\T_CASE) && $analyzer->isEnumCase($index)) {
continue;
}
$insertBlankLineIndex = $this->getInsertBlankLineIndex($tokens, $index);
$prevNonWhitespace = $tokens->getPrevNonWhitespace($insertBlankLineIndex);
if ($this->shouldAddBlankLine($tokens, $prevNonWhitespace)) {
$this->insertBlankLine($tokens, $insertBlankLineIndex);
}
$index = $prevNonWhitespace;
}
}
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
{
return new FixerConfigurationResolver([
(new FixerOptionBuilder('statements', 'List of statements which must be preceded by an empty line.'))
->setAllowedTypes(['string[]'])
->setAllowedValues([new AllowedValueSubset(array_keys(self::TOKEN_MAP))])
->setDefault([
'break',
'continue',
'declare',
'return',
'throw',
'try',
])
->getOption(),
]);
}
private function getInsertBlankLineIndex(Tokens $tokens, int $index): int
{
while ($index > 0) {
if ($tokens[$index - 1]->isWhitespace() && substr_count($tokens[$index - 1]->getContent(), "\n") > 1) {
break;
}
$prevIndex = $tokens->getPrevNonWhitespace($index);
if (!$tokens[$prevIndex]->isComment()) {
break;
}
if (!$tokens[$prevIndex - 1]->isWhitespace()) {
break;
}
if (1 !== substr_count($tokens[$prevIndex - 1]->getContent(), "\n")) {
break;
}
$index = $prevIndex;
}
return $index;
}
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]));
}
}
}