diff --git a/SlevomatCodingStandard/Sniffs/Namespaces/AlphabeticallySortedUsesSniff.php b/SlevomatCodingStandard/Sniffs/Namespaces/AlphabeticallySortedUsesSniff.php index d6bbd0436..cadfdf853 100644 --- a/SlevomatCodingStandard/Sniffs/Namespaces/AlphabeticallySortedUsesSniff.php +++ b/SlevomatCodingStandard/Sniffs/Namespaces/AlphabeticallySortedUsesSniff.php @@ -4,6 +4,7 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; +use PHP_CodeSniffer\Util\Common; use SlevomatCodingStandard\Helpers\CommentHelper; use SlevomatCodingStandard\Helpers\NamespaceHelper; use SlevomatCodingStandard\Helpers\StringHelper; @@ -22,6 +23,9 @@ use function sprintf; use function strcasecmp; use function strcmp; +use function strcoll; +use function strlen; +use function substr; use function uasort; use const T_OPEN_TAG; use const T_SEMICOLON; @@ -32,11 +36,20 @@ class AlphabeticallySortedUsesSniff implements Sniff public const CODE_INCORRECT_ORDER = 'IncorrectlyOrderedUses'; + public const INVALID_ORDER = 'InvalidOrder'; + + private const SUPPORTED_ORDERING_METHODS = [ + 'dictionary', + 'caseInsensitive', + 'caseSensitive', + 'locale', + ]; + /** @var bool */ public $psr12Compatible = true; - /** @var bool */ - public $caseSensitive = false; + /** @var string */ + public $order = 'caseInsensitive'; /** * @return array @@ -54,6 +67,19 @@ public function register(): array */ public function process(File $phpcsFile, $openTagPointer): void { + if (!in_array($this->order, self::SUPPORTED_ORDERING_METHODS, true)) { + $error = sprintf( + "'%s' is not a valid order for %s. Pick one of: %s", + $this->order, + Common::getSniffCode(self::class), + implode(', ', self::SUPPORTED_ORDERING_METHODS) + ); + + $phpcsFile->addError($error, $openTagPointer, self::INVALID_ORDER); + + return; + } + if (TokenHelper::findPrevious($phpcsFile, T_OPEN_TAG, $openTagPointer - 1) !== null) { return; } @@ -201,11 +227,50 @@ private function compareUseStatements(UseStatement $a, UseStatement $b): int private function compare(string $a, string $b): int { - if ($this->caseSensitive) { - return strcmp($a, $b); + switch ($this->order) { + case 'caseSensitive': + return strcmp($a, $b); + case 'locale': + return strcoll($a, $b); + case 'dictionary': + return $this->dictionaryCompare($a, $b); } + // default is 'caseInsensitive' return strcasecmp($a, $b); } + /** + * Lexicographical namespace string compare. + * + * Example: + * + * use Doctrine\ORM\Query; + * use Doctrine\ORM\Query\Expr; + * use Doctrine\ORM\QueryBuilder; + * + * @param string $a first namespace string + * @param string $b second namespace string + */ + private function dictionaryCompare(string $a, string $b): int + { + $min = min(strlen($a), strlen($b)); + + for ($i = 0; $i < $min; $i++) { + if ($a[$i] === $b[$i]) { + continue; + } + + if ($a[$i] < $b[$i]) { + return -1; + } + + if ($a[$i] > $b[$i]) { + return 1; + } + } + + return strcmp(substr($a, $min), substr($b, $min)); + } + } diff --git a/tests/Sniffs/Namespaces/AlphabeticallySortedUsesSniffTest.php b/tests/Sniffs/Namespaces/AlphabeticallySortedUsesSniffTest.php index 4cafae835..d09ef9d73 100644 --- a/tests/Sniffs/Namespaces/AlphabeticallySortedUsesSniffTest.php +++ b/tests/Sniffs/Namespaces/AlphabeticallySortedUsesSniffTest.php @@ -45,7 +45,7 @@ public function testCorrectOrderOfSimilarNamespacesCaseSensitive(): void { self::assertNoSniffErrorInFile( self::checkFile(__DIR__ . '/data/correctOrderSimilarNamespacesCaseSensitive.php', [ - 'caseSensitive' => true, + 'order' => 'caseSensitive', ]) ); } @@ -66,6 +66,32 @@ public function testPatrikOrder(): void self::assertNoSniffErrorInFile($report); } + public function testUnknownOrder(): void + { + self::assertSniffError( + self::checkFile( + __DIR__ . '/data/alphabeticalPatrik.php', + ['order' => 'mySpecialOrder'] + ), + 1, + AlphabeticallySortedUsesSniff::INVALID_ORDER, + "'mySpecialOrder' is not a valid order for SlevomatCodingStandard.Namespaces.AlphabeticallySortedUses. Pick one of: dictionary, caseInsensitive, caseSensitive, locale" + ); + } + + public function testDictionaryOrder(): void + { + self::assertSniffError( + self::checkFile( + __DIR__ . '/data/dictionary.php', + ['order' => 'dictionary'] + ), + 17, + AlphabeticallySortedUsesSniff::CODE_INCORRECT_ORDER, + 'Expr' + ); + } + public function testFixable(): void { $report = self::checkFile( diff --git a/tests/Sniffs/Namespaces/data/dictionary.php b/tests/Sniffs/Namespaces/data/dictionary.php new file mode 100644 index 000000000..dd16d7d85 --- /dev/null +++ b/tests/Sniffs/Namespaces/data/dictionary.php @@ -0,0 +1,22 @@ +