Create New Item
Item Type
File
Folder
Item Name
Search file in folder and subfolders...
Are you sure want to rename?
peripherad
/
back
/
vendor
/
friendsofphp
/
php-cs-fixer
/
src
/
Fixer
/
ClassNotation
:
OrderedTraitsFixer.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\ClassNotation; use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; use PhpCsFixer\Fixer\ConfigurableFixerTrait; 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\CT; use PhpCsFixer\Tokenizer\Tokens; /** * @phpstan-type _AutogeneratedInputConfiguration array{ * case_sensitive?: bool, * } * @phpstan-type _AutogeneratedComputedConfiguration array{ * case_sensitive: bool, * } * * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> * * @no-named-arguments Parameter names are not covered by the backward compatibility promise. */ final class OrderedTraitsFixer extends AbstractFixer implements ConfigurableFixerInterface { /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ use ConfigurableFixerTrait; public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( 'Trait `use` statements must be sorted alphabetically.', [ new CodeSample("<?php class Foo { \nuse Z; use A; }\n"), new CodeSample( "<?php class Foo { \nuse Aaa; use AA; }\n", [ 'case_sensitive' => true, ], ), ], null, 'Risky when depending on order of the imports.', ); } public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(CT::T_USE_TRAIT); } public function isRisky(): bool { return true; } protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ (new FixerOptionBuilder('case_sensitive', 'Whether the sorting should be case sensitive.')) ->setAllowedTypes(['bool']) ->setDefault(false) ->getOption(), ]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($this->findUseStatementsGroups($tokens) as $uses) { $this->sortUseStatements($tokens, $uses); } } /** * @return iterable<array<int, Tokens>> */ private function findUseStatementsGroups(Tokens $tokens): iterable { $uses = []; for ($index = 1, $max = \count($tokens); $index < $max; ++$index) { $token = $tokens[$index]; if ($token->isWhitespace() || $token->isComment()) { continue; } if (!$token->isGivenKind(CT::T_USE_TRAIT)) { if (\count($uses) > 0) { yield $uses; $uses = []; } continue; } $startIndex = $tokens->getNextNonWhitespace($tokens->getPrevMeaningfulToken($index)); \assert(\is_int($startIndex)); $endIndex = $tokens->getNextTokenOfKind($index, [';', '{']); if ($tokens[$endIndex]->equals('{')) { $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $endIndex); } $use = []; for ($i = $startIndex; $i <= $endIndex; ++$i) { $use[] = $tokens[$i]; } $uses[$startIndex] = Tokens::fromArray($use); $index = $endIndex; } } /** * @param array<int, Tokens> $uses */ private function sortUseStatements(Tokens $tokens, array $uses): void { foreach ($uses as $use) { $this->sortMultipleTraitsInStatement($use); } $this->sort($tokens, $uses); } private function sortMultipleTraitsInStatement(Tokens $use): void { $traits = []; $indexOfName = null; $name = []; for ($index = 0, $max = \count($use); $index < $max; ++$index) { $token = $use[$index]; if ($token->isGivenKind([\T_STRING, \T_NS_SEPARATOR])) { $name[] = $token; if (null === $indexOfName) { $indexOfName = $index; } continue; } if ($token->equalsAny([',', ';', '{'])) { \assert(null !== $indexOfName); $traits[$indexOfName] = Tokens::fromArray($name); $name = []; $indexOfName = null; } if ($token->equals('{')) { $index = $use->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); } } $this->sort($use, $traits); } /** * @param array<int, Tokens> $elements */ private function sort(Tokens $tokens, array $elements): void { $toTraitName = static function (Tokens $use): string { $string = ''; foreach ($use as $token) { if ($token->equalsAny([';', '{'])) { break; } if ($token->isGivenKind([\T_NS_SEPARATOR, \T_STRING])) { $string .= $token->getContent(); } } return ltrim($string, '\\'); }; $sortedElements = $elements; uasort( $sortedElements, fn (Tokens $useA, Tokens $useB): int => true === $this->configuration['case_sensitive'] ? $toTraitName($useA) <=> $toTraitName($useB) : strcasecmp($toTraitName($useA), $toTraitName($useB)), ); $sortedElements = array_combine( array_keys($elements), array_values($sortedElements), ); $beforeOverrideCount = $tokens->count(); foreach (array_reverse($sortedElements, true) as $index => $tokensToInsert) { $tokens->overrideRange( $index, $index + \count($elements[$index]) - 1, $tokensToInsert, ); } if ($beforeOverrideCount < $tokens->count()) { $tokens->clearEmptyTokens(); } } }