File "StatementIndentationFixer.php"
Full Path: /var/www/html/back/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/StatementIndentationFixer.php
File size: 31.59 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\IndentationTrait;
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
use PhpCsFixer\Preg;
use PhpCsFixer\Tokenizer\Analyzer\AlternativeSyntaxAnalyzer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\FCT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @phpstan-type _AutogeneratedInputConfiguration array{
* stick_comment_to_next_continuous_control_statement?: bool,
* }
* @phpstan-type _AutogeneratedComputedConfiguration array{
* stick_comment_to_next_continuous_control_statement: bool,
* }
*
* @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
*
* @no-named-arguments Parameter names are not covered by the backward compatibility promise.
*/
final class StatementIndentationFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
{
/** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */
use ConfigurableFixerTrait;
use IndentationTrait;
private const BLOCK_SIGNATURE_FIRST_TOKENS = [
\T_USE,
\T_IF,
\T_ELSE,
\T_ELSEIF,
\T_FOR,
\T_FOREACH,
\T_WHILE,
\T_DO,
\T_SWITCH,
\T_CASE,
\T_DEFAULT,
\T_TRY,
\T_CLASS,
\T_INTERFACE,
\T_TRAIT,
\T_EXTENDS,
\T_IMPLEMENTS,
\T_CONST,
FCT::T_MATCH,
];
private const CONTROL_STRUCTURE_POSSIBIBLY_WITHOUT_BRACES_TOKENS = [
\T_IF,
\T_ELSE,
\T_ELSEIF,
\T_FOR,
\T_FOREACH,
\T_WHILE,
\T_DO,
];
private const BLOCK_FIRST_TOKENS = ['{', [CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN], [CT::T_USE_TRAIT], [CT::T_GROUP_IMPORT_BRACE_OPEN], [CT::T_PROPERTY_HOOK_BRACE_OPEN], [FCT::T_ATTRIBUTE]];
private const PROPERTY_KEYWORDS = [\T_VAR, \T_PUBLIC, \T_PROTECTED, \T_PRIVATE, \T_STATIC, FCT::T_READONLY];
private AlternativeSyntaxAnalyzer $alternativeSyntaxAnalyzer;
private bool $bracesFixerCompatibility;
public function __construct(bool $bracesFixerCompatibility = false)
{
parent::__construct();
$this->bracesFixerCompatibility = $bracesFixerCompatibility;
}
public function getDefinition(): FixerDefinitionInterface
{
return new FixerDefinition(
'Each statement must be indented.',
[
new CodeSample(
<<<'PHP'
<?php
if ($baz == true) {
echo "foo";
}
else {
echo "bar";
}
PHP,
),
new CodeSample(
<<<'PHP'
<?php
// foo
if ($foo) {
echo "foo";
// this is treated as comment of `if` block, as `stick_comment_to_next_continuous_control_statement` is disabled
} else {
$aaa = 1;
}
PHP,
['stick_comment_to_next_continuous_control_statement' => false],
),
new CodeSample(
<<<'PHP'
<?php
// foo
if ($foo) {
echo "foo";
// this is treated as comment of `elseif(1)` block, as `stick_comment_to_next_continuous_control_statement` is enabled
} elseif(1) {
echo "bar";
} elseif(2) {
// this is treated as comment of `elseif(2)` block, as the only content of that block
} elseif(3) {
$aaa = 1;
// this is treated as comment of `elseif(3)` block, as it is a comment in the final block
}
PHP,
['stick_comment_to_next_continuous_control_statement' => true],
),
],
);
}
/**
* {@inheritdoc}
*
* Must run before HeredocIndentationFixer.
* Must run after BracesPositionFixer, ClassAttributesSeparationFixer, CurlyBracesPositionFixer, FullyQualifiedStrictTypesFixer, GlobalNamespaceImportFixer, MethodArgumentSpaceFixer, NoUselessElseFixer, YieldFromArrayToYieldsFixer.
*/
public function getPriority(): int
{
return -3;
}
public function isCandidate(Tokens $tokens): bool
{
return true;
}
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
{
return new FixerConfigurationResolver([
(new FixerOptionBuilder('stick_comment_to_next_continuous_control_statement', 'Last comment of code block counts as comment for next block.'))
->setAllowedTypes(['bool'])
->setDefault(false)
->getOption(),
]);
}
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
{
$this->alternativeSyntaxAnalyzer = new AlternativeSyntaxAnalyzer();
$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,
* new_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 = '';
$noBracesBlockStarts = [];
$alternativeBlockStarts = [];
$caseBlockStarts = [];
foreach ($tokens as $index => $token) {
$currentScope = \count($scopes) - 1;
if (isset($noBracesBlockStarts[$index])) {
$scopes[] = [
'type' => 'block',
'skip' => false,
'end_index' => $this->findStatementEndIndex($tokens, $index, \count($tokens) - 1) + 1,
'end_index_inclusive' => true,
'initial_indent' => $this->getLineIndentationWithBracesCompatibility($tokens, $index, $lastIndent),
'is_indented_block' => true,
];
++$currentScope;
}
if (
$token->equalsAny(self::BLOCK_FIRST_TOKENS)
|| ($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);
} elseif ($this->alternativeSyntaxAnalyzer->belongsToAlternativeSyntax($tokens, $index)) {
$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);
} elseif ($token->isGivenKind(CT::T_PROPERTY_HOOK_BRACE_OPEN)) {
$endIndex = $tokens->getNextTokenOfKind($index, [[CT::T_PROPERTY_HOOK_BRACE_CLOSE]]);
} 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(CT::T_ARRAY_SQUARE_BRACE_OPEN)
|| ($token->equals('(') && $tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(\T_ARRAY))
) {
$blockType = $token->equals('(') ? Tokens::BLOCK_TYPE_PARENTHESIS_BRACE : Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE;
$scopes[] = [
'type' => 'statement',
'skip' => true,
'end_index' => $tokens->findBlockEnd($blockType, $index),
'end_index_inclusive' => true,
'initial_indent' => $previousLineInitialIndent,
'new_indent' => $previousLineNewIndent,
'is_indented_block' => false,
];
continue;
}
$isPropertyStart = $this->isPropertyStart($tokens, $index);
if ($isPropertyStart || $token->isGivenKind(self::BLOCK_SIGNATURE_FIRST_TOKENS)) {
$lastWhitespaceIndex = null;
$closingParenthesisIndex = null;
for ($endIndex = $index + 1, $max = \count($tokens); $endIndex < $max; ++$endIndex) {
$endToken = $tokens[$endIndex];
if ($endToken->equals('(')) {
$closingParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $endIndex);
$endIndex = $closingParenthesisIndex;
continue;
}
if ($endToken->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) {
$endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $endIndex);
continue;
}
if ($endToken->equalsAny(['{', ';', [\T_DOUBLE_ARROW], [\T_IMPLEMENTS]])) {
break;
}
if ($endToken->equals(':')) {
if ($token->isGivenKind([\T_CASE, \T_DEFAULT])) {
$caseBlockStarts[$endIndex] = $index;
} else {
$alternativeBlockStarts[$endIndex] = $index;
}
break;
}
if (!$token->isGivenKind(self::CONTROL_STRUCTURE_POSSIBIBLY_WITHOUT_BRACES_TOKENS)) {
continue;
}
if ($endToken->isWhitespace()) {
$lastWhitespaceIndex = $endIndex;
continue;
}
if (!$endToken->isComment()) {
$noBraceBlockStartIndex = $lastWhitespaceIndex ?? $endIndex;
$noBracesBlockStarts[$noBraceBlockStartIndex] = true;
$endIndex = $closingParenthesisIndex ?? $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' => $isPropertyStart || $token->isGivenKind([\T_EXTENDS, \T_IMPLEMENTS, \T_CONST]),
];
continue;
}
if ($token->isGivenKind(\T_FUNCTION)) {
$endIndex = $index + 1;
for ($max = \count($tokens); $endIndex < $max; ++$endIndex) {
if ($tokens[$endIndex]->equals('(')) {
$endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $endIndex);
continue;
}
if ($tokens[$endIndex]->equalsAny(['{', ';'])) {
break;
}
}
$scopes[] = [
'type' => 'block_signature',
'skip' => false,
'end_index' => $endIndex,
'end_index_inclusive' => true,
'initial_indent' => $this->getLineIndentationWithBracesCompatibility($tokens, $index, $lastIndent),
'is_indented_block' => false,
];
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;
}
}
$endIndex = $scopes[$currentScope]['end_index'];
if (!$scopes[$currentScope]['end_index_inclusive']) {
++$endIndex;
}
if (
(null !== $firstNonWhitespaceTokenIndex && $firstNonWhitespaceTokenIndex < $endIndex)
|| (null !== $nextNewlineIndex && $nextNewlineIndex < $endIndex)
) {
if (
// do we touch whitespace directly before comment...
$tokens[$firstNonWhitespaceTokenIndex]->isGivenKind(\T_COMMENT)
// ...and afterwards, there is only comment or `}`
&& $tokens[$tokens->getNextMeaningfulToken($firstNonWhitespaceTokenIndex)]->equals('}')
) {
if (
// ... and the comment was only content in docblock
$tokens[$tokens->getPrevMeaningfulToken($firstNonWhitespaceTokenIndex)]->equals('{')
) {
$indent = true;
} else {
// or it was dedicated comment for next control loop
// ^^ we need to check if there is a control group afterwards, and in that case don't make extra indent level
$nextIndex = $tokens->getNextMeaningfulToken($firstNonWhitespaceTokenIndex);
$nextNextIndex = $tokens->getNextMeaningfulToken($nextIndex);
if (null !== $nextNextIndex && $tokens[$nextNextIndex]->isGivenKind([\T_ELSE, \T_ELSEIF])) {
$indent = true !== $this->configuration['stick_comment_to_next_continuous_control_statement'];
} else {
$indent = true;
}
}
} else {
$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,
'is_indented_block' => true,
];
}
}
}
private function findStatementEndIndex(Tokens $tokens, int $index, int $parentScopeEndIndex): int
{
$endIndex = null;
$ifLevel = 0;
$doWhileLevel = 0;
for ($searchEndIndex = $index; $searchEndIndex <= $parentScopeEndIndex; ++$searchEndIndex) {
$searchEndToken = $tokens[$searchEndIndex];
if (
$searchEndToken->isGivenKind(\T_IF)
&& !$tokens[$tokens->getPrevMeaningfulToken($searchEndIndex)]->isGivenKind(\T_ELSE)
) {
++$ifLevel;
continue;
}
if ($searchEndToken->isGivenKind(\T_DO)) {
++$doWhileLevel;
continue;
}
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);
$searchEndToken = $tokens[$searchEndIndex];
}
if (!$searchEndToken->equalsAny([';', ',', '}', [\T_CLOSE_TAG]])) {
continue;
}
$controlStructureContinuationIndex = $tokens->getNextMeaningfulToken($searchEndIndex);
if (
$ifLevel > 0
&& null !== $controlStructureContinuationIndex
&& $tokens[$controlStructureContinuationIndex]->isGivenKind([\T_ELSE, \T_ELSEIF])
) {
if (
$tokens[$controlStructureContinuationIndex]->isGivenKind(\T_ELSE)
&& !$tokens[$tokens->getNextMeaningfulToken($controlStructureContinuationIndex)]->isGivenKind(\T_IF)
) {
--$ifLevel;
}
$searchEndIndex = $controlStructureContinuationIndex;
continue;
}
if (
$doWhileLevel > 0
&& null !== $controlStructureContinuationIndex
&& $tokens[$controlStructureContinuationIndex]->isGivenKind(\T_WHILE)
) {
--$doWhileLevel;
$searchEndIndex = $controlStructureContinuationIndex;
continue;
}
$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]->isGivenKind([\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;
}
/**
* Returns whether the token at given index is the last token in a property
* declaration before the type or the name of that property.
*/
private function isPropertyStart(Tokens $tokens, int $index): bool
{
$nextIndex = $tokens->getNextMeaningfulToken($index);
if (
null === $nextIndex
|| $tokens[$nextIndex]->isGivenKind(self::PROPERTY_KEYWORDS)
|| $tokens[$nextIndex]->isGivenKind([\T_CONST, \T_FUNCTION])
) {
return false;
}
while ($tokens[$index]->isGivenKind(self::PROPERTY_KEYWORDS)) {
if ($tokens[$index]->isGivenKind([\T_VAR, \T_PUBLIC, \T_PROTECTED, \T_PRIVATE])) {
return true;
}
$index = $tokens->getPrevMeaningfulToken($index);
}
return false;
}
/**
* 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 (Preg::match("~^(//|#)({$indent}.*)?$~", $tokens[$index]->getContent())) {
return false;
}
$firstCommentIndex = $index;
while (true) {
$firstCommentCandidateIndex = $this->getSiblingContinuousSingleLineComment($tokens, $firstCommentIndex, false);
if (null === $firstCommentCandidateIndex) {
break;
}
$firstCommentIndex = $firstCommentCandidateIndex;
}
$lastCommentIndex = $index;
while (true) {
$lastCommentCandidateIndex = $this->getSiblingContinuousSingleLineComment($tokens, $lastCommentIndex, true);
if (null === $lastCommentCandidateIndex) {
break;
}
$lastCommentIndex = $lastCommentCandidateIndex;
}
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;
}
}