Skip to content

Commit

Permalink
OrderedTraitsFixer - Introduction
Browse files Browse the repository at this point in the history
  • Loading branch information
julienfalque authored and SpacePossum committed Jan 29, 2020
1 parent 73a632c commit f1bf4bc
Show file tree
Hide file tree
Showing 4 changed files with 468 additions and 0 deletions.
4 changes: 4 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1352,6 +1352,10 @@ Choose from the list of available rules:
- ``order`` (``'alpha'``, ``'length'``): how the interfaces should be ordered;
defaults to ``'alpha'``

* **ordered_traits** [@PhpCsFixer]

Trait ``use`` statements must be sorted alphabetically.

* **php_unit_construct** [@Symfony:risky, @PhpCsFixer:risky]

PHPUnit assertion method calls like ``->assertSame(true, $foo)`` should be
Expand Down
183 changes: 183 additions & 0 deletions src/Fixer/ClassNotation/OrderedTraitsFixer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
<?php

/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <[email protected]>
* Dariusz Rumiński <[email protected]>
*
* 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\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Tokens;

final class OrderedTraitsFixer extends AbstractFixer
{
/**
* {@inheritdoc}
*/
public function getDefinition()
{
return new FixerDefinition(
'Trait `use` statements must be sorted alphabetically.',
[
new CodeSample("<?php class Foo { \nuse Z; use A; }\n"),
]
);
}

/**
* {@inheritdoc}
*/
public function isCandidate(Tokens $tokens)
{
return $tokens->isTokenKindFound(CT::T_USE_TRAIT);
}

/**
* {@inheritdoc}
*/
protected function applyFix(\SplFileInfo $file, Tokens $tokens)
{
foreach ($this->findUseStatementsGroups($tokens) as $uses) {
$this->sortUseStatements($tokens, $uses);
}
}

/**
* @return iterable<array<int, Tokens>>
*/
private function findUseStatementsGroups(Tokens $tokens)
{
$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;
}

$endIndex = $tokens->getNextTokenOfKind($index, [';', '{']);

if ($tokens[$endIndex]->equals('{')) {
$endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $endIndex);
}

$use = [];
for ($i = $index; $i <= $endIndex; ++$i) {
$use[] = $tokens[$i];
}

$uses[$index] = Tokens::fromArray($use);

$index = $endIndex;
}
}

/**
* @param array<int, Tokens> $uses
*/
private function sortUseStatements(Tokens $tokens, array $uses)
{
foreach ($uses as $use) {
$this->sortMultipleTraitsInStatement($use);
}

$this->sort($tokens, $uses);
}

private function sortMultipleTraitsInStatement(Tokens $use)
{
$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)
{
/**
* @return string
*/
$toTraitName = static function (Tokens $use) {
$string = '';

foreach ($use as $token) {
if ($token->equalsAny([';', '{'])) {
break;
}

if ($token->equalsAny([[T_NS_SEPARATOR], [T_STRING]])) {
$string .= $token->getContent();
}
}

return ltrim($string, '\\');
};

$sortedElements = $elements;
uasort($sortedElements, static function (Tokens $useA, Tokens $useB) use ($toTraitName) {
return strcasecmp($toTraitName($useA), $toTraitName($useB));
});

$sortedElements = array_combine(
array_keys($elements),
array_values($sortedElements)
);

foreach (array_reverse($sortedElements, true) as $index => $tokensToInsert) {
$tokens->overrideRange(
$index,
$index + \count($elements[$index]) - 1,
$tokensToInsert
);
}
}
}
1 change: 1 addition & 0 deletions src/RuleSet.php
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ final class RuleSet implements RuleSetInterface
'no_useless_else' => true,
'no_useless_return' => true,
'ordered_class_elements' => true,
'ordered_traits' => true,
'php_unit_internal_class' => true,
'php_unit_method_casing' => true,
'php_unit_test_class_requires_covers' => true,
Expand Down
Loading

0 comments on commit f1bf4bc

Please sign in to comment.