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
/
Semicolon
:
MultilineWhitespaceBeforeSemicolonsFixer.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\Semicolon; 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\Tokenizer\Analyzer\WhitespacesAnalyzer; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; /** * @phpstan-type _AutogeneratedInputConfiguration array{ * strategy?: 'new_line_for_chained_calls'|'no_multi_line', * } * @phpstan-type _AutogeneratedComputedConfiguration array{ * strategy: 'new_line_for_chained_calls'|'no_multi_line', * } * * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> * * @author Graham Campbell <hello@gjcampbell.co.uk> * @author Egidijus Girčys <e.gircys@gmail.com> * * @no-named-arguments Parameter names are not covered by the backward compatibility promise. */ final class MultilineWhitespaceBeforeSemicolonsFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface { /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ use ConfigurableFixerTrait; /** * @internal */ public const STRATEGY_NO_MULTI_LINE = 'no_multi_line'; /** * @internal */ public const STRATEGY_NEW_LINE_FOR_CHAINED_CALLS = 'new_line_for_chained_calls'; public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( 'Forbid multi-line whitespace before the closing semicolon or move the semicolon to the new line for chained calls.', [ new CodeSample( <<<'PHP' <?php function foo() { return 1 + 2 ; } PHP, ), new CodeSample( <<<'PHP' <?php $object->method1() ->method2() ->method(3); PHP, ['strategy' => self::STRATEGY_NEW_LINE_FOR_CHAINED_CALLS], ), ], ); } /** * {@inheritdoc} * * Must run before SpaceAfterSemicolonFixer. * Must run after CombineConsecutiveIssetsFixer, GetClassToClassKeywordFixer, NoEmptyStatementFixer, SimplifiedIfReturnFixer, SingleImportPerStatementFixer. */ public function getPriority(): int { return 0; } public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(';'); } protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ (new FixerOptionBuilder( 'strategy', 'Forbid multi-line whitespace or move the semicolon to the new line for chained calls.', )) ->setAllowedValues([self::STRATEGY_NO_MULTI_LINE, self::STRATEGY_NEW_LINE_FOR_CHAINED_CALLS]) ->setDefault(self::STRATEGY_NO_MULTI_LINE) ->getOption(), ]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $lineEnding = $this->whitespacesConfig->getLineEnding(); for ($index = 0, $count = \count($tokens); $index < $count; ++$index) { if ($tokens[$index]->isGivenKind(\T_CONST)) { $index = $tokens->getNextTokenOfKind($index, [';']); continue; } if (!$tokens[$index]->equals(';')) { continue; } $previousIndex = $index - 1; $previous = $tokens[$previousIndex]; $indent = $this->findWhitespaceBeforeFirstCall($index, $tokens); if (self::STRATEGY_NEW_LINE_FOR_CHAINED_CALLS === $this->configuration['strategy'] && null !== $indent) { if ($previous->isWhitespace() && $previous->getContent() === $lineEnding.$indent) { continue; } // unset whitespace and semicolon if ($previous->isWhitespace()) { $tokens->clearAt($previousIndex); } $tokens->clearAt($index); // find the line ending token index after the semicolon $index = $this->getNewLineIndex($index, $tokens); // appended new line to the last method call $newline = new Token([\T_WHITESPACE, $lineEnding.$indent]); // insert the new line with indented semicolon $tokens->insertAt($index++, [$newline, new Token(';')]); } else { if (!$previous->isWhitespace() || !str_contains($previous->getContent(), "\n")) { continue; } $content = $previous->getContent(); if (str_starts_with($content, $lineEnding) && $tokens[$index - 2]->isComment()) { // if there is comment between closing parenthesis and semicolon // unset whitespace and semicolon $tokens->clearAt($previousIndex); $tokens->clearAt($index); // find the significant token index before the semicolon $significantTokenIndex = $this->getPreviousSignificantTokenIndex($index, $tokens); // insert the semicolon $tokens->insertAt($significantTokenIndex + 1, [new Token(';')]); } else { // if there is whitespace between closing bracket and semicolon, just remove it $tokens->clearAt($previousIndex); } } } } /** * Find the index for the next new line. Return the given index when there's no new line. */ private function getNewLineIndex(int $index, Tokens $tokens): int { $lineEnding = $this->whitespacesConfig->getLineEnding(); for ($index, $count = \count($tokens); $index < $count; ++$index) { if (!$tokens[$index]->isWhitespace() && !$tokens[$index]->isComment()) { break; } if (false !== strstr($tokens[$index]->getContent(), $lineEnding)) { return $index; } } return $index; } /** * Find the index for the previous significant token. Return the given index when there's no significant token. */ private function getPreviousSignificantTokenIndex(int $index, Tokens $tokens): int { $stopTokens = [ \T_LNUMBER, \T_DNUMBER, \T_STRING, \T_VARIABLE, \T_CONSTANT_ENCAPSED_STRING, ]; for ($index; $index > 0; --$index) { if ($tokens[$index]->isGivenKind($stopTokens) || $tokens[$index]->equals(')')) { return $index; } } return $index; } /** * Checks if the semicolon closes a multiline call and returns the whitespace of the first call at $index. * i.e. it will return the whitespace marked with '____' in the example underneath. * * .. * ____$this->methodCall() * ->anotherCall(); * .. */ private function findWhitespaceBeforeFirstCall(int $index, Tokens $tokens): ?string { $isMultilineCall = false; $prevIndex = $tokens->getPrevMeaningfulToken($index); while (!$tokens[$prevIndex]->equalsAny([';', ':', '{', '}', [\T_OPEN_TAG], [\T_OPEN_TAG_WITH_ECHO], [\T_ELSE]])) { $index = $prevIndex; $prevIndex = $tokens->getPrevMeaningfulToken($index); $blockType = Tokens::detectBlockType($tokens[$index]); if (null !== $blockType && !$blockType['isStart']) { $prevIndex = $tokens->findBlockStart($blockType['type'], $index); continue; } if ($tokens[$index]->isObjectOperator() || $tokens[$index]->isGivenKind(\T_DOUBLE_COLON)) { $prevIndex = $tokens->getPrevMeaningfulToken($index); $isMultilineCall = $isMultilineCall || $tokens->isPartialCodeMultiline($prevIndex, $index); } } return $isMultilineCall ? WhitespacesAnalyzer::detectIndent($tokens, $index) : null; } }