| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- <?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\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;
- final class OrderedTraitsFixer extends AbstractFixer implements ConfigurableFixerInterface
- {
- 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));
- $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([',', ';', '{'])) {
- $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();
- }
- }
- }
|