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
/
ControlStructure
:
NoBreakCommentFixer.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\ControlStructure; use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; use PhpCsFixer\Fixer\ConfigurableFixerTrait; 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\WhitespacesAnalyzer; use PhpCsFixer\Tokenizer\FCT; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; use Symfony\Component\OptionsResolver\Options; /** * Fixer for rule defined in PSR2 ¶5.2. * * @phpstan-type _AutogeneratedInputConfiguration array{ * comment_text?: string, * } * @phpstan-type _AutogeneratedComputedConfiguration array{ * comment_text: string, * } * * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> * * @no-named-arguments Parameter names are not covered by the backward compatibility promise. */ final class NoBreakCommentFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface { /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ use ConfigurableFixerTrait; private const STRUCTURE_KINDS = [\T_FOR, \T_FOREACH, \T_WHILE, \T_IF, \T_ELSEIF, \T_SWITCH, \T_FUNCTION, FCT::T_MATCH]; public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( 'There must be a comment when fall-through is intentional in a non-empty case body.', [ new CodeSample( <<<'PHP' <?php switch ($foo) { case 1: foo(); case 2: bar(); // no break break; case 3: baz(); } PHP, ), new CodeSample( <<<'PHP' <?php switch ($foo) { case 1: foo(); case 2: foo(); } PHP, ['comment_text' => 'some comment'], ), ], 'Adds a "no break" comment before fall-through cases, and removes it if there is no fall-through.', ); } public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(\T_SWITCH); } /** * {@inheritdoc} * * Must run after NoUselessElseFixer. */ public function getPriority(): int { return 0; } protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ (new FixerOptionBuilder('comment_text', 'The text to use in the added comment and to detect it.')) ->setAllowedTypes(['string']) ->setAllowedValues([ static function (string $value): bool { if (Preg::match('/\R/', $value)) { throw new InvalidOptionsException('The comment text must not contain new lines.'); } return true; }, ]) ->setNormalizer(static fn (Options $options, string $value): string => rtrim($value)) ->setDefault('no break') ->getOption(), ]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { for ($index = \count($tokens) - 1; $index >= 0; --$index) { if ($tokens[$index]->isGivenKind(\T_DEFAULT)) { if ($tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(\T_DOUBLE_ARROW)) { continue; // this is "default" from "match" } } elseif (!$tokens[$index]->isGivenKind(\T_CASE)) { continue; } $this->fixCase($tokens, $tokens->getNextTokenOfKind($index, [':', ';'])); } } private function fixCase(Tokens $tokens, int $casePosition): void { $empty = true; $fallThrough = true; $commentPosition = null; for ($i = $casePosition + 1, $max = \count($tokens); $i < $max; ++$i) { if ($tokens[$i]->isGivenKind([...self::STRUCTURE_KINDS, \T_ELSE, \T_DO, \T_CLASS])) { $empty = false; $i = $this->getStructureEnd($tokens, $i); continue; } if ($tokens[$i]->isGivenKind([\T_BREAK, \T_CONTINUE, \T_RETURN, \T_EXIT, \T_GOTO])) { $fallThrough = false; continue; } if ($tokens[$i]->isGivenKind(\T_THROW)) { $previousIndex = $tokens->getPrevMeaningfulToken($i); if ($previousIndex === $casePosition || $tokens[$previousIndex]->equalsAny(['{', ';', '}', [\T_OPEN_TAG]])) { $fallThrough = false; } continue; } if ($tokens[$i]->equals('}') || $tokens[$i]->isGivenKind(\T_ENDSWITCH)) { if (null !== $commentPosition) { $this->removeComment($tokens, $commentPosition); } break; } if ($this->isNoBreakComment($tokens[$i])) { $commentPosition = $i; continue; } if ($tokens[$i]->isGivenKind([\T_CASE, \T_DEFAULT])) { if (!$empty && $fallThrough) { if (null !== $commentPosition && $tokens->getPrevNonWhitespace($i) !== $commentPosition) { $this->removeComment($tokens, $commentPosition); $commentPosition = null; } if (null === $commentPosition) { $this->insertCommentAt($tokens, $i); } else { $text = $this->configuration['comment_text']; $tokens[$commentPosition] = new Token([ $tokens[$commentPosition]->getId(), str_ireplace($text, $text, $tokens[$commentPosition]->getContent()), ]); $this->ensureNewLineAt($tokens, $commentPosition); } } elseif (null !== $commentPosition) { $this->removeComment($tokens, $commentPosition); } break; } if (!$tokens[$i]->isGivenKind([\T_COMMENT, \T_WHITESPACE])) { $empty = false; } } } private function isNoBreakComment(Token $token): bool { if (!$token->isComment()) { return false; } $text = preg_quote($this->configuration['comment_text'], '~'); return Preg::match("~^((//|#)\\s*{$text}\\s*)|(/\\*\\*?\\s*{$text}(\\s+.*)*\\*/)$~i", $token->getContent()); } private function insertCommentAt(Tokens $tokens, int $casePosition): void { $lineEnding = $this->whitespacesConfig->getLineEnding(); $newlinePosition = $this->ensureNewLineAt($tokens, $casePosition); $newlineToken = $tokens[$newlinePosition]; $nbNewlines = substr_count($newlineToken->getContent(), $lineEnding); if ($newlineToken->isGivenKind(\T_OPEN_TAG) && Preg::match('/\R/', $newlineToken->getContent())) { ++$nbNewlines; } elseif ($tokens[$newlinePosition - 1]->isGivenKind(\T_OPEN_TAG) && Preg::match('/\R/', $tokens[$newlinePosition - 1]->getContent())) { ++$nbNewlines; if (!Preg::match('/\R/', $newlineToken->getContent())) { $tokens[$newlinePosition] = new Token([$newlineToken->getId(), $lineEnding.$newlineToken->getContent()]); } } if ($nbNewlines > 1) { Preg::match('/^(.*?)(\R\h*)$/s', $newlineToken->getContent(), $matches); $indent = WhitespacesAnalyzer::detectIndent($tokens, $newlinePosition - 1); $tokens[$newlinePosition] = new Token([$newlineToken->getId(), $matches[1].$lineEnding.$indent]); $tokens->insertAt(++$newlinePosition, new Token([\T_WHITESPACE, $matches[2]])); } $tokens->insertAt($newlinePosition, new Token([\T_COMMENT, '// '.$this->configuration['comment_text']])); $this->ensureNewLineAt($tokens, $newlinePosition); } /** * @return int The newline token position */ private function ensureNewLineAt(Tokens $tokens, int $position): int { $lineEnding = $this->whitespacesConfig->getLineEnding(); $content = $lineEnding.WhitespacesAnalyzer::detectIndent($tokens, $position); $whitespaceToken = $tokens[$position - 1]; if (!$whitespaceToken->isGivenKind(\T_WHITESPACE)) { if ($whitespaceToken->isGivenKind(\T_OPEN_TAG)) { $content = Preg::replace('/\R/', '', $content); if (!Preg::match('/\R/', $whitespaceToken->getContent())) { $tokens[$position - 1] = new Token([\T_OPEN_TAG, Preg::replace('/\s+$/', $lineEnding, $whitespaceToken->getContent())]); } } if ('' !== $content) { $tokens->insertAt($position, new Token([\T_WHITESPACE, $content])); return $position; } return $position - 1; } if ($tokens[$position - 2]->isGivenKind(\T_OPEN_TAG) && Preg::match('/\R/', $tokens[$position - 2]->getContent())) { $content = Preg::replace('/^\R/', '', $content); } if (!Preg::match('/\R/', $whitespaceToken->getContent())) { $tokens[$position - 1] = new Token([\T_WHITESPACE, $content]); } return $position - 1; } private function removeComment(Tokens $tokens, int $commentPosition): void { if ($tokens[$tokens->getPrevNonWhitespace($commentPosition)]->isGivenKind(\T_OPEN_TAG)) { $whitespacePosition = $commentPosition + 1; $regex = '/^\R\h*/'; } else { $whitespacePosition = $commentPosition - 1; $regex = '/\R\h*$/'; } $whitespaceToken = $tokens[$whitespacePosition]; if ($whitespaceToken->isGivenKind(\T_WHITESPACE)) { $content = Preg::replace($regex, '', $whitespaceToken->getContent()); $tokens->ensureWhitespaceAtIndex($whitespacePosition, 0, $content); } $tokens->clearTokenAndMergeSurroundingWhitespace($commentPosition); } private function getStructureEnd(Tokens $tokens, int $position): int { $initialToken = $tokens[$position]; if ($initialToken->isGivenKind(self::STRUCTURE_KINDS)) { $position = $tokens->findBlockEnd( Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $tokens->getNextTokenOfKind($position, ['(']), ); } elseif ($initialToken->isGivenKind(\T_CLASS)) { $openParenthesisPosition = $tokens->getNextMeaningfulToken($position); if ('(' === $tokens[$openParenthesisPosition]->getContent()) { $position = $tokens->findBlockEnd( Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesisPosition, ); } } if ($initialToken->isGivenKind(\T_FUNCTION)) { $position = $tokens->getNextTokenOfKind($position, ['{']); } else { $position = $tokens->getNextMeaningfulToken($position); } if ('{' !== $tokens[$position]->getContent()) { return $tokens->getNextTokenOfKind($position, [';']); } $position = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $position); if ($initialToken->isGivenKind(\T_DO)) { $position = $tokens->findBlockEnd( Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $tokens->getNextTokenOfKind($position, ['(']), ); return $tokens->getNextTokenOfKind($position, [';']); } return $position; } }