Create New Item
Item Type
File
Folder
Item Name
Search file in folder and subfolders...
Are you sure want to rename?
tipuloidea
/
back
/
vendor
/
friendsofphp
/
php-cs-fixer
/
src
/
Fixer
/
Whitespace
:
StatementIndentationFixer.php
Advanced Search
Upload
New Item
Settings
Back
Back Up
Advanced Editor
Save
<?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; } }