OrderedTraitsFixer.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4. * This file is part of PHP CS Fixer.
  5. *
  6. * (c) Fabien Potencier <fabien@symfony.com>
  7. * Dariusz Rumiński <dariusz.ruminski@gmail.com>
  8. *
  9. * This source file is subject to the MIT license that is bundled
  10. * with this source code in the file LICENSE.
  11. */
  12. namespace PhpCsFixer\Fixer\ClassNotation;
  13. use PhpCsFixer\AbstractFixer;
  14. use PhpCsFixer\Fixer\ConfigurableFixerInterface;
  15. use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
  16. use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
  17. use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
  18. use PhpCsFixer\FixerDefinition\CodeSample;
  19. use PhpCsFixer\FixerDefinition\FixerDefinition;
  20. use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
  21. use PhpCsFixer\Tokenizer\CT;
  22. use PhpCsFixer\Tokenizer\Tokens;
  23. final class OrderedTraitsFixer extends AbstractFixer implements ConfigurableFixerInterface
  24. {
  25. public function getDefinition(): FixerDefinitionInterface
  26. {
  27. return new FixerDefinition(
  28. 'Trait `use` statements must be sorted alphabetically.',
  29. [
  30. new CodeSample("<?php class Foo { \nuse Z; use A; }\n"),
  31. new CodeSample(
  32. "<?php class Foo { \nuse Aaa; use AA; }\n",
  33. [
  34. 'case_sensitive' => true,
  35. ]
  36. ),
  37. ],
  38. null,
  39. 'Risky when depending on order of the imports.'
  40. );
  41. }
  42. public function isCandidate(Tokens $tokens): bool
  43. {
  44. return $tokens->isTokenKindFound(CT::T_USE_TRAIT);
  45. }
  46. public function isRisky(): bool
  47. {
  48. return true;
  49. }
  50. protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
  51. {
  52. return new FixerConfigurationResolver([
  53. (new FixerOptionBuilder('case_sensitive', 'Whether the sorting should be case sensitive.'))
  54. ->setAllowedTypes(['bool'])
  55. ->setDefault(false)
  56. ->getOption(),
  57. ]);
  58. }
  59. protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
  60. {
  61. foreach ($this->findUseStatementsGroups($tokens) as $uses) {
  62. $this->sortUseStatements($tokens, $uses);
  63. }
  64. }
  65. /**
  66. * @return iterable<array<int, Tokens>>
  67. */
  68. private function findUseStatementsGroups(Tokens $tokens): iterable
  69. {
  70. $uses = [];
  71. for ($index = 1, $max = \count($tokens); $index < $max; ++$index) {
  72. $token = $tokens[$index];
  73. if ($token->isWhitespace() || $token->isComment()) {
  74. continue;
  75. }
  76. if (!$token->isGivenKind(CT::T_USE_TRAIT)) {
  77. if (\count($uses) > 0) {
  78. yield $uses;
  79. $uses = [];
  80. }
  81. continue;
  82. }
  83. $startIndex = $tokens->getNextNonWhitespace($tokens->getPrevMeaningfulToken($index));
  84. $endIndex = $tokens->getNextTokenOfKind($index, [';', '{']);
  85. if ($tokens[$endIndex]->equals('{')) {
  86. $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $endIndex);
  87. }
  88. $use = [];
  89. for ($i = $startIndex; $i <= $endIndex; ++$i) {
  90. $use[] = $tokens[$i];
  91. }
  92. $uses[$startIndex] = Tokens::fromArray($use);
  93. $index = $endIndex;
  94. }
  95. }
  96. /**
  97. * @param array<int, Tokens> $uses
  98. */
  99. private function sortUseStatements(Tokens $tokens, array $uses): void
  100. {
  101. foreach ($uses as $use) {
  102. $this->sortMultipleTraitsInStatement($use);
  103. }
  104. $this->sort($tokens, $uses);
  105. }
  106. private function sortMultipleTraitsInStatement(Tokens $use): void
  107. {
  108. $traits = [];
  109. $indexOfName = null;
  110. $name = [];
  111. for ($index = 0, $max = \count($use); $index < $max; ++$index) {
  112. $token = $use[$index];
  113. if ($token->isGivenKind([T_STRING, T_NS_SEPARATOR])) {
  114. $name[] = $token;
  115. if (null === $indexOfName) {
  116. $indexOfName = $index;
  117. }
  118. continue;
  119. }
  120. if ($token->equalsAny([',', ';', '{'])) {
  121. $traits[$indexOfName] = Tokens::fromArray($name);
  122. $name = [];
  123. $indexOfName = null;
  124. }
  125. if ($token->equals('{')) {
  126. $index = $use->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index);
  127. }
  128. }
  129. $this->sort($use, $traits);
  130. }
  131. /**
  132. * @param array<int, Tokens> $elements
  133. */
  134. private function sort(Tokens $tokens, array $elements): void
  135. {
  136. $toTraitName = static function (Tokens $use): string {
  137. $string = '';
  138. foreach ($use as $token) {
  139. if ($token->equalsAny([';', '{'])) {
  140. break;
  141. }
  142. if ($token->isGivenKind([T_NS_SEPARATOR, T_STRING])) {
  143. $string .= $token->getContent();
  144. }
  145. }
  146. return ltrim($string, '\\');
  147. };
  148. $sortedElements = $elements;
  149. uasort(
  150. $sortedElements,
  151. fn (Tokens $useA, Tokens $useB): int => true === $this->configuration['case_sensitive']
  152. ? $toTraitName($useA) <=> $toTraitName($useB)
  153. : strcasecmp($toTraitName($useA), $toTraitName($useB))
  154. );
  155. $sortedElements = array_combine(
  156. array_keys($elements),
  157. array_values($sortedElements)
  158. );
  159. $beforeOverrideCount = $tokens->count();
  160. foreach (array_reverse($sortedElements, true) as $index => $tokensToInsert) {
  161. $tokens->overrideRange(
  162. $index,
  163. $index + \count($elements[$index]) - 1,
  164. $tokensToInsert
  165. );
  166. }
  167. if ($beforeOverrideCount < $tokens->count()) {
  168. $tokens->clearEmptyTokens();
  169. }
  170. }
  171. }