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
/
LanguageConstruct
:
SingleSpaceAroundConstructFixer.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\LanguageConstruct; use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; use PhpCsFixer\Fixer\ConfigurableFixerTrait; 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\Preg; use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\FCT; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; /** * @phpstan-type _AutogeneratedInputConfiguration array{ * constructs_contain_a_single_space?: list<'yield_from'>, * constructs_followed_by_a_single_space?: list<'abstract'|'as'|'attribute'|'break'|'case'|'catch'|'class'|'clone'|'comment'|'const'|'const_import'|'continue'|'do'|'echo'|'else'|'elseif'|'enum'|'extends'|'final'|'finally'|'for'|'foreach'|'function'|'function_import'|'global'|'goto'|'if'|'implements'|'include'|'include_once'|'instanceof'|'insteadof'|'interface'|'match'|'named_argument'|'namespace'|'new'|'open_tag_with_echo'|'php_doc'|'php_open'|'print'|'private'|'private_set'|'protected'|'protected_set'|'public'|'public_set'|'readonly'|'require'|'require_once'|'return'|'static'|'switch'|'throw'|'trait'|'try'|'type_colon'|'use'|'use_lambda'|'use_trait'|'var'|'while'|'yield'|'yield_from'>, * constructs_preceded_by_a_single_space?: list<'as'|'else'|'elseif'|'use_lambda'>, * } * @phpstan-type _AutogeneratedComputedConfiguration array{ * constructs_contain_a_single_space: list<'yield_from'>, * constructs_followed_by_a_single_space: list<'abstract'|'as'|'attribute'|'break'|'case'|'catch'|'class'|'clone'|'comment'|'const'|'const_import'|'continue'|'do'|'echo'|'else'|'elseif'|'enum'|'extends'|'final'|'finally'|'for'|'foreach'|'function'|'function_import'|'global'|'goto'|'if'|'implements'|'include'|'include_once'|'instanceof'|'insteadof'|'interface'|'match'|'named_argument'|'namespace'|'new'|'open_tag_with_echo'|'php_doc'|'php_open'|'print'|'private'|'private_set'|'protected'|'protected_set'|'public'|'public_set'|'readonly'|'require'|'require_once'|'return'|'static'|'switch'|'throw'|'trait'|'try'|'type_colon'|'use'|'use_lambda'|'use_trait'|'var'|'while'|'yield'|'yield_from'>, * constructs_preceded_by_a_single_space: list<'as'|'else'|'elseif'|'use_lambda'>, * } * * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> * * @author Andreas Möller <am@localheinz.com> * @author Dariusz Rumiński <dariusz.ruminski@gmail.com> * * @no-named-arguments Parameter names are not covered by the backward compatibility promise. */ final class SingleSpaceAroundConstructFixer extends AbstractFixer implements ConfigurableFixerInterface { /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ use ConfigurableFixerTrait; /** * @var array<string, int> */ private const TOKEN_MAP_CONTAIN_A_SINGLE_SPACE = [ // for now, only one case - but we are ready to extend it, when we learn about new cases to cover 'yield_from' => \T_YIELD_FROM, ]; /** * @var array<string, int> */ private const TOKEN_MAP_PRECEDED_BY_A_SINGLE_SPACE = [ 'as' => \T_AS, 'else' => \T_ELSE, 'elseif' => \T_ELSEIF, 'use_lambda' => CT::T_USE_LAMBDA, ]; /** * @var array<string, int> */ private const TOKEN_MAP_FOLLOWED_BY_A_SINGLE_SPACE = [ 'abstract' => \T_ABSTRACT, 'as' => \T_AS, 'attribute' => CT::T_ATTRIBUTE_CLOSE, 'break' => \T_BREAK, 'case' => \T_CASE, 'catch' => \T_CATCH, 'class' => \T_CLASS, 'clone' => \T_CLONE, 'comment' => \T_COMMENT, 'const' => \T_CONST, 'const_import' => CT::T_CONST_IMPORT, 'continue' => \T_CONTINUE, 'do' => \T_DO, 'echo' => \T_ECHO, 'else' => \T_ELSE, 'elseif' => \T_ELSEIF, 'enum' => FCT::T_ENUM, 'extends' => \T_EXTENDS, 'final' => \T_FINAL, 'finally' => \T_FINALLY, 'for' => \T_FOR, 'foreach' => \T_FOREACH, 'function' => \T_FUNCTION, 'function_import' => CT::T_FUNCTION_IMPORT, 'global' => \T_GLOBAL, 'goto' => \T_GOTO, 'if' => \T_IF, 'implements' => \T_IMPLEMENTS, 'include' => \T_INCLUDE, 'include_once' => \T_INCLUDE_ONCE, 'instanceof' => \T_INSTANCEOF, 'insteadof' => \T_INSTEADOF, 'interface' => \T_INTERFACE, 'match' => FCT::T_MATCH, 'named_argument' => CT::T_NAMED_ARGUMENT_COLON, 'namespace' => \T_NAMESPACE, 'new' => \T_NEW, 'open_tag_with_echo' => \T_OPEN_TAG_WITH_ECHO, 'php_doc' => \T_DOC_COMMENT, 'php_open' => \T_OPEN_TAG, 'print' => \T_PRINT, 'private' => \T_PRIVATE, 'private_set' => FCT::T_PRIVATE_SET, 'protected' => \T_PROTECTED, 'protected_set' => FCT::T_PROTECTED_SET, 'public' => \T_PUBLIC, 'public_set' => FCT::T_PUBLIC_SET, 'readonly' => FCT::T_READONLY, 'require' => \T_REQUIRE, 'require_once' => \T_REQUIRE_ONCE, 'return' => \T_RETURN, 'static' => \T_STATIC, 'switch' => \T_SWITCH, 'throw' => \T_THROW, 'trait' => \T_TRAIT, 'try' => \T_TRY, 'type_colon' => CT::T_TYPE_COLON, 'use' => \T_USE, 'use_lambda' => CT::T_USE_LAMBDA, 'use_trait' => CT::T_USE_TRAIT, 'var' => \T_VAR, 'while' => \T_WHILE, 'yield' => \T_YIELD, 'yield_from' => \T_YIELD_FROM, ]; /** * @var array<string, int> */ private array $fixTokenMapFollowedByASingleSpace = []; /** * @var array<string, int> */ private array $fixTokenMapContainASingleSpace = []; /** * @var array<string, int> */ private array $fixTokenMapPrecededByASingleSpace = []; public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( 'Ensures a single space after language constructs.', [ new CodeSample( <<<'PHP' <?php throw new \Exception(); PHP, ), new CodeSample( <<<'PHP' <?php function foo() { yield from baz(); } PHP, [ 'constructs_contain_a_single_space' => [ 'yield_from', ], 'constructs_followed_by_a_single_space' => [ 'yield_from', ], ], ), new CodeSample( <<<'PHP' <?php $foo = function& ()use($bar) { }; PHP, [ 'constructs_preceded_by_a_single_space' => [ 'use_lambda', ], 'constructs_followed_by_a_single_space' => [ 'use_lambda', ], ], ), new CodeSample( <<<'PHP' <?php echo "Hello!"; PHP, [ 'constructs_followed_by_a_single_space' => [ 'echo', ], ], ), new CodeSample( <<<'PHP' <?php yield from baz(); PHP, [ 'constructs_followed_by_a_single_space' => [ 'yield_from', ], ], ), ], ); } /** * {@inheritdoc} * * Must run before BracesFixer, FunctionDeclarationFixer. * Must run after ArraySyntaxFixer, ModernizeStrposFixer. */ public function getPriority(): int { return 36; } public function isCandidate(Tokens $tokens): bool { $tokenKinds = [ ...array_values($this->fixTokenMapContainASingleSpace), ...array_values($this->fixTokenMapPrecededByASingleSpace), ...array_values($this->fixTokenMapFollowedByASingleSpace), ]; return $tokens->isAnyTokenKindsFound($tokenKinds); } protected function configurePostNormalisation(): void { $this->fixTokenMapContainASingleSpace = []; foreach ($this->configuration['constructs_contain_a_single_space'] as $key) { $this->fixTokenMapContainASingleSpace[$key] = self::TOKEN_MAP_CONTAIN_A_SINGLE_SPACE[$key]; } $this->fixTokenMapPrecededByASingleSpace = []; foreach ($this->configuration['constructs_preceded_by_a_single_space'] as $key) { $this->fixTokenMapPrecededByASingleSpace[$key] = self::TOKEN_MAP_PRECEDED_BY_A_SINGLE_SPACE[$key]; } $this->fixTokenMapFollowedByASingleSpace = []; foreach ($this->configuration['constructs_followed_by_a_single_space'] as $key) { $this->fixTokenMapFollowedByASingleSpace[$key] = self::TOKEN_MAP_FOLLOWED_BY_A_SINGLE_SPACE[$key]; } if (isset($this->fixTokenMapFollowedByASingleSpace['public'])) { $this->fixTokenMapFollowedByASingleSpace['constructor_public'] = CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC; } if (isset($this->fixTokenMapFollowedByASingleSpace['protected'])) { $this->fixTokenMapFollowedByASingleSpace['constructor_protected'] = CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED; } if (isset($this->fixTokenMapFollowedByASingleSpace['private'])) { $this->fixTokenMapFollowedByASingleSpace['constructor_private'] = CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE; } } protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $tokenKindsContainASingleSpace = array_values($this->fixTokenMapContainASingleSpace); for ($index = $tokens->count() - 1; $index > 0; --$index) { if ($tokens[$index]->isGivenKind($tokenKindsContainASingleSpace)) { $token = $tokens[$index]; if ( $token->isGivenKind(\T_YIELD_FROM) && 'yield from' !== strtolower($token->getContent()) ) { $tokens[$index] = new Token([\T_YIELD_FROM, Preg::replace( '/\s+/', ' ', $token->getContent(), )]); } } } $tokenKindsPrecededByASingleSpace = array_values($this->fixTokenMapPrecededByASingleSpace); for ($index = $tokens->count() - 1; $index > 0; --$index) { if ($tokens[$index]->isGivenKind($tokenKindsPrecededByASingleSpace)) { if (!$this->isFullLineCommentBefore($tokens, $index)) { $tokens->ensureWhitespaceAtIndex($index - 1, 1, ' '); } } } $tokenKindsFollowedByASingleSpace = array_values($this->fixTokenMapFollowedByASingleSpace); for ($index = $tokens->count() - 2; $index >= 0; --$index) { $token = $tokens[$index]; if (!$token->isGivenKind($tokenKindsFollowedByASingleSpace)) { continue; } $whitespaceTokenIndex = $index + 1; if ($tokens[$whitespaceTokenIndex]->equalsAny([',', ':', ';', ')', [CT::T_ARRAY_SQUARE_BRACE_CLOSE], [CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE]])) { continue; } if ( $token->isGivenKind(\T_STATIC) && !$tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind([\T_FN, \T_FUNCTION, \T_NS_SEPARATOR, \T_STRING, \T_VARIABLE, CT::T_ARRAY_TYPEHINT, CT::T_NULLABLE_TYPE]) ) { continue; } if ($token->isGivenKind(\T_OPEN_TAG)) { if ($tokens[$whitespaceTokenIndex]->isGivenKind(\T_WHITESPACE) && !str_contains($tokens[$whitespaceTokenIndex]->getContent(), "\n") && !str_contains($token->getContent(), "\n")) { $tokens->clearAt($whitespaceTokenIndex); } continue; } if ($token->isGivenKind(\T_CLASS) && $tokens[$tokens->getNextMeaningfulToken($index)]->equals('(')) { continue; } if ($token->isGivenKind([\T_EXTENDS, \T_IMPLEMENTS]) && $this->isMultilineExtendsOrImplementsWithMoreThanOneAncestor($tokens, $index)) { continue; } if ($token->isGivenKind(\T_RETURN) && $this->isMultiLineReturn($tokens, $index)) { continue; } if ($token->isGivenKind(\T_CONST) && $this->isMultilineCommaSeparatedConstant($tokens, $index)) { continue; } if ($token->isComment() || $token->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) { if ($tokens[$whitespaceTokenIndex]->isGivenKind(\T_WHITESPACE) && str_contains($tokens[$whitespaceTokenIndex]->getContent(), "\n")) { continue; } } if ($tokens[$whitespaceTokenIndex]->isWhitespace() && str_contains($tokens[$whitespaceTokenIndex]->getContent(), "\n")) { $nextNextToken = $tokens[$whitespaceTokenIndex + 1]; if ( $nextNextToken->isGivenKind(FCT::T_ATTRIBUTE) || $nextNextToken->isComment() && str_starts_with($nextNextToken->getContent(), '#[') ) { continue; } if ($nextNextToken->isGivenKind(\T_DOC_COMMENT)) { continue; } } $tokens->ensureWhitespaceAtIndex($whitespaceTokenIndex, 0, ' '); } } protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { $tokenMapContainASingleSpaceKeys = array_keys(self::TOKEN_MAP_CONTAIN_A_SINGLE_SPACE); $tokenMapPrecededByASingleSpaceKeys = array_keys(self::TOKEN_MAP_PRECEDED_BY_A_SINGLE_SPACE); $tokenMapFollowedByASingleSpaceKeys = array_keys(self::TOKEN_MAP_FOLLOWED_BY_A_SINGLE_SPACE); return new FixerConfigurationResolver([ (new FixerOptionBuilder('constructs_contain_a_single_space', 'List of constructs which must contain a single space.')) ->setAllowedTypes(['string[]']) ->setAllowedValues([new AllowedValueSubset($tokenMapContainASingleSpaceKeys)]) ->setDefault($tokenMapContainASingleSpaceKeys) ->getOption(), (new FixerOptionBuilder('constructs_preceded_by_a_single_space', 'List of constructs which must be preceded by a single space.')) ->setAllowedTypes(['string[]']) ->setAllowedValues([new AllowedValueSubset($tokenMapPrecededByASingleSpaceKeys)]) ->setDefault(['as', 'use_lambda']) ->getOption(), (new FixerOptionBuilder('constructs_followed_by_a_single_space', 'List of constructs which must be followed by a single space.')) ->setAllowedTypes(['string[]']) ->setAllowedValues([new AllowedValueSubset($tokenMapFollowedByASingleSpaceKeys)]) ->setDefault($tokenMapFollowedByASingleSpaceKeys) ->getOption(), ]); } private function isMultiLineReturn(Tokens $tokens, int $index): bool { ++$index; $tokenFollowingReturn = $tokens[$index]; if ( !$tokenFollowingReturn->isGivenKind(\T_WHITESPACE) || !str_contains($tokenFollowingReturn->getContent(), "\n") ) { return false; } $nestedCount = 0; for ($indexEnd = \count($tokens) - 1, ++$index; $index < $indexEnd; ++$index) { if (str_contains($tokens[$index]->getContent(), "\n")) { return true; } if ($tokens[$index]->equals('{')) { ++$nestedCount; } elseif ($tokens[$index]->equals('}')) { --$nestedCount; } elseif (0 === $nestedCount && $tokens[$index]->equalsAny([';', [\T_CLOSE_TAG]])) { break; } } return false; } private function isMultilineExtendsOrImplementsWithMoreThanOneAncestor(Tokens $tokens, int $index): bool { $hasMoreThanOneAncestor = false; while (true) { ++$index; $token = $tokens[$index]; if ($token->equals(',')) { $hasMoreThanOneAncestor = true; continue; } if ($token->equals('{')) { return false; } if ($hasMoreThanOneAncestor && str_contains($token->getContent(), "\n")) { return true; } } return LogicException('Not reachable code was reached.'); // @phpstan-ignore deadCode.unreachable } private function isMultilineCommaSeparatedConstant(Tokens $tokens, int $constantIndex): bool { $isMultilineConstant = false; $hasMoreThanOneConstant = false; $index = $constantIndex; while (!$tokens[$index]->equalsAny([';', [\T_CLOSE_TAG]])) { ++$index; $isMultilineConstant = $isMultilineConstant || str_contains($tokens[$index]->getContent(), "\n"); if ($tokens[$index]->equals(',')) { $hasMoreThanOneConstant = true; } $blockType = Tokens::detectBlockType($tokens[$index]); if (null !== $blockType && true === $blockType['isStart']) { $index = $tokens->findBlockEnd($blockType['type'], $index); } } return $hasMoreThanOneConstant && $isMultilineConstant; } private function isFullLineCommentBefore(Tokens $tokens, int $index): bool { $beforeIndex = $tokens->getPrevNonWhitespace($index); if (!$tokens[$beforeIndex]->isGivenKind(\T_COMMENT)) { return false; } $content = $tokens[$beforeIndex]->getContent(); return str_starts_with($content, '#') || str_starts_with($content, '//'); } }