Browse Source

feat: Configurable case sensitivity for more ordering fixers (#7021)

Aleksey Polyvanyi 1 year ago
parent
commit
5f3892b03b

+ 25 - 0
dev-tools/phpstan/baseline.php

@@ -151,6 +151,21 @@ $ignoreErrors[] = [
 	'count' => 1,
 	'path' => __DIR__ . '/../../src/Fixer/ClassNotation/OrderedClassElementsFixer.php',
 ];
+$ignoreErrors[] = [
+    'message' => '#^Only booleans are allowed in a ternary operator condition, mixed given\\.$#',
+    'count' => 1,
+    'path' => __DIR__ . '/../../src/Fixer/ClassNotation/OrderedInterfacesFixer.php',
+];
+$ignoreErrors[] = [
+    'message' => '#^Only booleans are allowed in a ternary operator condition, mixed given\\.$#',
+    'count' => 1,
+    'path' => __DIR__ . '/../../src/Fixer/ClassNotation/OrderedTraitsFixer.php',
+];
+$ignoreErrors[] = [
+    'message' => '#^Only booleans are allowed in a ternary operator condition, mixed given\\.$#',
+    'count' => 1,
+    'path' => __DIR__ . '/../../src/Fixer/ClassNotation/OrderedTypesFixer.php',
+];
 $ignoreErrors[] = [
 	'message' => '#^Method PhpCsFixer\\\\Fixer\\\\Comment\\\\NoEmptyCommentFixer\\:\\:getCommentBlock\\(\\) return type has no value type specified in iterable type array\\.$#',
 	'count' => 1,
@@ -236,6 +251,11 @@ $ignoreErrors[] = [
 	'count' => 1,
 	'path' => __DIR__ . '/../../src/Fixer/Import/OrderedImportsFixer.php',
 ];
+$ignoreErrors[] = [
+    'message' => '#^Only booleans are allowed in a ternary operator condition, mixed given\\.$#',
+    'count' => 2,
+    'path' => __DIR__ . '/../../src/Fixer/Import/OrderedImportsFixer.php',
+];
 $ignoreErrors[] = [
 	'message' => '#^Method PhpCsFixer\\\\Fixer\\\\Import\\\\SingleImportPerStatementFixer\\:\\:getGroupDeclaration\\(\\) return type has no value type specified in iterable type array\\.$#',
 	'count' => 1,
@@ -361,6 +381,11 @@ $ignoreErrors[] = [
 	'count' => 1,
 	'path' => __DIR__ . '/../../src/Fixer/Phpdoc/PhpdocTrimConsecutiveBlankLineSeparationFixer.php',
 ];
+$ignoreErrors[] = [
+    'message' => '#^Only booleans are allowed in a ternary operator condition, mixed given\\.$#',
+    'count' => 1,
+    'path' => __DIR__ . '/../../src/Fixer/Phpdoc/PhpdocTypesOrderFixer.php',
+];
 $ignoreErrors[] = [
 	'message' => '#^Method PhpCsFixer\\\\Fixer\\\\Strict\\\\StrictParamFixer\\:\\:fixFunction\\(\\) has parameter \\$functionParams with no value type specified in iterable type array\\.$#',
 	'count' => 1,

+ 24 - 0
doc/list.rst

@@ -1994,6 +1994,10 @@ List of Available Rules
      | Defines the order of import types.
      | Allowed types: ``array`` and ``null``
      | Default value: ``null``
+   - | ``case_sensitive``
+     | Whether the sorting should be case sensitive.
+     | Allowed types: ``bool``
+     | Default value: ``false``
 
 
    Part of rule sets `@PER <./ruleSets/PER.rst>`_ `@PER-CS1.0 <./ruleSets/PER-CS1.0.rst>`_ `@PSR12 <./ruleSets/PSR12.rst>`_ `@PhpCsFixer <./ruleSets/PhpCsFixer.rst>`_ `@Symfony <./ruleSets/Symfony.rst>`_
@@ -2013,6 +2017,10 @@ List of Available Rules
      | Which direction the interfaces should be ordered.
      | Allowed values: ``'ascend'`` and ``'descend'``
      | Default value: ``'ascend'``
+   - | ``case_sensitive``
+     | Whether the sorting should be case sensitive.
+     | Allowed types: ``bool``
+     | Default value: ``false``
 
 
    `Source PhpCsFixer\\Fixer\\ClassNotation\\OrderedInterfacesFixer <./../src/Fixer/ClassNotation/OrderedInterfacesFixer.php>`_
@@ -2022,6 +2030,14 @@ List of Available Rules
 
    *warning risky* Risky when depending on order of the imports.
 
+   Configuration options:
+
+   - | ``case_sensitive``
+     | Whether the sorting should be case sensitive.
+     | Allowed types: ``bool``
+     | Default value: ``false``
+
+
    Part of rule sets `@PhpCsFixer:risky <./ruleSets/PhpCsFixerRisky.rst>`_ `@Symfony:risky <./ruleSets/SymfonyRisky.rst>`_
 
    `Source PhpCsFixer\\Fixer\\ClassNotation\\OrderedTraitsFixer <./../src/Fixer/ClassNotation/OrderedTraitsFixer.php>`_
@@ -2039,6 +2055,10 @@ List of Available Rules
      | Forces the position of ``null`` (overrides ``sort_algorithm``).
      | Allowed values: ``'always_first'``, ``'always_last'`` and ``'none'``
      | Default value: ``'always_first'``
+   - | ``case_sensitive``
+     | Whether the sorting should be case sensitive.
+     | Allowed types: ``bool``
+     | Default value: ``false``
 
 
    `Source PhpCsFixer\\Fixer\\ClassNotation\\OrderedTypesFixer <./../src/Fixer/ClassNotation/OrderedTypesFixer.php>`_
@@ -2410,6 +2430,10 @@ List of Available Rules
      | Forces the position of ``null`` (overrides ``sort_algorithm``).
      | Allowed values: ``'always_first'``, ``'always_last'`` and ``'none'``
      | Default value: ``'always_first'``
+   - | ``case_sensitive``
+     | Whether the sorting should be case sensitive.
+     | Allowed types: ``bool``
+     | Default value: ``false``
 
 
    Part of rule sets `@PhpCsFixer <./ruleSets/PhpCsFixer.rst>`_ `@Symfony <./ruleSets/Symfony.rst>`_

+ 43 - 0
doc/rules/class_notation/ordered_interfaces.rst

@@ -25,6 +25,15 @@ Allowed values: ``'ascend'`` and ``'descend'``
 
 Default value: ``'ascend'``
 
+``case_sensitive``
+~~~~~~~~~~~~~~~~~~
+
+Whether the sorting should be case sensitive.
+
+Allowed types: ``bool``
+
+Default value: ``false``
+
 Examples
 --------
 
@@ -95,3 +104,37 @@ With configuration: ``['order' => 'length', 'direction' => 'descend']``.
 
    -interface ExampleB extends MuchLonger, Short, Longer {}
    +interface ExampleB extends MuchLonger, Longer, Short {}
+
+Example #5
+~~~~~~~~~~
+
+With configuration: ``['order' => 'alpha']``.
+
+.. code-block:: diff
+
+   --- Original
+   +++ New
+    <?php
+
+   -final class ExampleA implements IgnorecaseB, IgNoReCaSeA, IgnoreCaseC {}
+   +final class ExampleA implements IgNoReCaSeA, IgnorecaseB, IgnoreCaseC {}
+
+   -interface ExampleB extends IgnorecaseB, IgNoReCaSeA, IgnoreCaseC {}
+   +interface ExampleB extends IgNoReCaSeA, IgnorecaseB, IgnoreCaseC {}
+
+Example #6
+~~~~~~~~~~
+
+With configuration: ``['order' => 'alpha', 'case_sensitive' => true]``.
+
+.. code-block:: diff
+
+   --- Original
+   +++ New
+    <?php
+
+   -final class ExampleA implements Casesensitivea, CaseSensitiveA, CasesensitiveA {}
+   +final class ExampleA implements CaseSensitiveA, CasesensitiveA, Casesensitivea {}
+
+   -interface ExampleB extends Casesensitivea, CaseSensitiveA, CasesensitiveA {}
+   +interface ExampleB extends CaseSensitiveA, CasesensitiveA, Casesensitivea {}

+ 27 - 0
doc/rules/class_notation/ordered_traits.rst

@@ -12,12 +12,26 @@ Using this rule is risky
 
 Risky when depending on order of the imports.
 
+Configuration
+-------------
+
+``case_sensitive``
+~~~~~~~~~~~~~~~~~~
+
+Whether the sorting should be case sensitive.
+
+Allowed types: ``bool``
+
+Default value: ``false``
+
 Examples
 --------
 
 Example #1
 ~~~~~~~~~~
 
+*Default* configuration.
+
 .. code-block:: diff
 
    --- Original
@@ -26,6 +40,19 @@ Example #1
    -use Z; use A; }
    +use A; use Z; }
 
+Example #2
+~~~~~~~~~~
+
+With configuration: ``['case_sensitive' => true]``.
+
+.. code-block:: diff
+
+   --- Original
+   +++ New
+    <?php class Foo { 
+   -use Aaa; use AA; }
+   +use AA; use Aaa; }
+
 Rule sets
 ---------
 

+ 26 - 1
doc/rules/class_notation/ordered_types.rst

@@ -25,6 +25,15 @@ Allowed values: ``'always_first'``, ``'always_last'`` and ``'none'``
 
 Default value: ``'always_first'``
 
+``case_sensitive``
+~~~~~~~~~~~~~~~~~~
+
+Whether the sorting should be case sensitive.
+
+Allowed types: ``bool``
+
+Default value: ``false``
+
 Examples
 --------
 
@@ -50,6 +59,22 @@ Example #1
 Example #2
 ~~~~~~~~~~
 
+With configuration: ``['case_sensitive' => true]``.
+
+.. code-block:: diff
+
+   --- Original
+   +++ New
+    <?php
+    interface Foo
+    {
+   -    public function bar(\Aaa|\AA $foo): string|int;
+   +    public function bar(\AA|\Aaa $foo): int|string;
+    }
+
+Example #3
+~~~~~~~~~~
+
 With configuration: ``['null_adjustment' => 'always_last']``.
 
 .. code-block:: diff
@@ -66,7 +91,7 @@ With configuration: ``['null_adjustment' => 'always_last']``.
    +    public function foo(\Countable&\Stringable $obj): int;
     }
 
-Example #3
+Example #4
 ~~~~~~~~~~
 
 With configuration: ``['sort_algorithm' => 'none', 'null_adjustment' => 'always_last']``.

+ 26 - 3
doc/rules/import/ordered_imports.rst

@@ -26,6 +26,15 @@ Allowed types: ``array`` and ``null``
 
 Default value: ``null``
 
+``case_sensitive``
+~~~~~~~~~~~~~~~~~~
+
+Whether the sorting should be case sensitive.
+
+Allowed types: ``bool``
+
+Default value: ``false``
+
 Examples
 --------
 
@@ -48,6 +57,20 @@ Example #1
 Example #2
 ~~~~~~~~~~
 
+With configuration: ``['case_sensitive' => true]``.
+
+.. code-block:: diff
+
+   --- Original
+   +++ New
+    <?php
+   +use const AA;
+    use function Aaa;
+   -use const AA;
+
+Example #3
+~~~~~~~~~~
+
 With configuration: ``['sort_algorithm' => 'length']``.
 
 .. code-block:: diff
@@ -63,7 +86,7 @@ With configuration: ``['sort_algorithm' => 'length']``.
    -use Acme;
    -use Bar;
 
-Example #3
+Example #4
 ~~~~~~~~~~
 
 With configuration: ``['sort_algorithm' => 'length', 'imports_order' => ['const', 'class', 'function']]``.
@@ -86,7 +109,7 @@ With configuration: ``['sort_algorithm' => 'length', 'imports_order' => ['const'
     use function CCC\AA;
    -use function DDD;
 
-Example #4
+Example #5
 ~~~~~~~~~~
 
 With configuration: ``['sort_algorithm' => 'alpha', 'imports_order' => ['const', 'class', 'function']]``.
@@ -109,7 +132,7 @@ With configuration: ``['sort_algorithm' => 'alpha', 'imports_order' => ['const',
     use function DDD;
    -use function CCC\AA;
 
-Example #5
+Example #6
 ~~~~~~~~~~
 
 With configuration: ``['sort_algorithm' => 'none', 'imports_order' => ['const', 'class', 'function']]``.

+ 24 - 0
doc/rules/phpdoc/phpdoc_types_order.rst

@@ -25,6 +25,15 @@ Allowed values: ``'always_first'``, ``'always_last'`` and ``'none'``
 
 Default value: ``'always_first'``
 
+``case_sensitive``
+~~~~~~~~~~~~~~~~~~
+
+Whether the sorting should be case sensitive.
+
+Allowed types: ``bool``
+
+Default value: ``false``
+
 Examples
 --------
 
@@ -103,6 +112,21 @@ With configuration: ``['sort_algorithm' => 'alpha', 'null_adjustment' => 'none']
    + * @param \Foo|int|null|string $bar
      */
 
+Example #6
+~~~~~~~~~~
+
+With configuration: ``['case_sensitive' => true]``.
+
+.. code-block:: diff
+
+   --- Original
+   +++ New
+    <?php
+    /**
+   - * @param Aaa|AA $bar
+   + * @param AA|Aaa $bar
+     */
+
 Rule sets
 ---------
 

+ 22 - 1
src/Fixer/ClassNotation/OrderedInterfacesFixer.php

@@ -91,6 +91,19 @@ final class OrderedInterfacesFixer extends AbstractFixer implements Configurable
                         self::OPTION_DIRECTION => self::DIRECTION_DESCEND,
                     ]
                 ),
+                new CodeSample(
+                    "<?php\n\nfinal class ExampleA implements IgnorecaseB, IgNoReCaSeA, IgnoreCaseC {}\n\ninterface ExampleB extends IgnorecaseB, IgNoReCaSeA, IgnoreCaseC {}\n",
+                    [
+                        self::OPTION_ORDER => self::ORDER_ALPHA,
+                    ]
+                ),
+                new CodeSample(
+                    "<?php\n\nfinal class ExampleA implements Casesensitivea, CaseSensitiveA, CasesensitiveA {}\n\ninterface ExampleB extends Casesensitivea, CaseSensitiveA, CasesensitiveA {}\n",
+                    [
+                        self::OPTION_ORDER => self::ORDER_ALPHA,
+                        'case_sensitive' => true,
+                    ]
+                ),
             ],
         );
     }
@@ -153,7 +166,11 @@ final class OrderedInterfacesFixer extends AbstractFixer implements Configurable
             usort($interfaces, function (array $first, array $second): int {
                 $score = self::ORDER_LENGTH === $this->configuration[self::OPTION_ORDER]
                     ? \strlen($first['normalized']) - \strlen($second['normalized'])
-                    : strcasecmp($first['normalized'], $second['normalized']);
+                    : (
+                        $this->configuration['case_sensitive']
+                        ? strcmp($first['normalized'], $second['normalized'])
+                        : strcasecmp($first['normalized'], $second['normalized'])
+                    );
 
                 if (self::DIRECTION_DESCEND === $this->configuration[self::OPTION_DIRECTION]) {
                     $score *= -1;
@@ -197,6 +214,10 @@ final class OrderedInterfacesFixer extends AbstractFixer implements Configurable
                 ->setAllowedValues(self::SUPPORTED_DIRECTION_OPTIONS)
                 ->setDefault(self::DIRECTION_ASCEND)
                 ->getOption(),
+            (new FixerOptionBuilder('case_sensitive', 'Whether the sorting should be case sensitive.'))
+                ->setAllowedTypes(['bool'])
+                ->setDefault(false)
+                ->getOption(),
         ]);
     }
 

+ 27 - 2
src/Fixer/ClassNotation/OrderedTraitsFixer.php

@@ -15,13 +15,17 @@ declare(strict_types=1);
 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
+final class OrderedTraitsFixer extends AbstractFixer implements ConfigurableFixerInterface
 {
     public function getDefinition(): FixerDefinitionInterface
     {
@@ -29,6 +33,12 @@ final class OrderedTraitsFixer extends AbstractFixer
             '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.'
@@ -45,6 +55,16 @@ final class OrderedTraitsFixer extends AbstractFixer
         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) {
@@ -163,7 +183,12 @@ final class OrderedTraitsFixer extends AbstractFixer
         };
 
         $sortedElements = $elements;
-        uasort($sortedElements, static fn (Tokens $useA, Tokens $useB): int => strcasecmp($toTraitName($useA), $toTraitName($useB)));
+        uasort(
+            $sortedElements,
+            fn (Tokens $useA, Tokens $useB): int => $this->configuration['case_sensitive']
+                ? strcmp($toTraitName($useA), $toTraitName($useB))
+                : strcasecmp($toTraitName($useA), $toTraitName($useB))
+        );
 
         $sortedElements = array_combine(
             array_keys($elements),

+ 17 - 1
src/Fixer/ClassNotation/OrderedTypesFixer.php

@@ -56,6 +56,18 @@ try {
                 new VersionSpecificCodeSample(
                     '<?php
 interface Foo
+{
+    public function bar(\Aaa|\AA $foo): string|int;
+}
+',
+                    new VersionSpecification(8_00_00),
+                    [
+                        'case_sensitive' => true,
+                    ]
+                ),
+                new VersionSpecificCodeSample(
+                    '<?php
+interface Foo
 {
     public function bar(null|string|int $foo): string|int;
 
@@ -109,6 +121,10 @@ interface Bar
                 ->setAllowedValues(['always_first', 'always_last', 'none'])
                 ->setDefault('always_first')
                 ->getOption(),
+            (new FixerOptionBuilder('case_sensitive', 'Whether the sorting should be case sensitive.'))
+                ->setAllowedTypes(['bool'])
+                ->setDefault(false)
+                ->getOption(),
         ]);
     }
 
@@ -353,7 +369,7 @@ interface Bar
             }
 
             if ('alpha' === $this->configuration['sort_algorithm']) {
-                return strcasecmp($a, $b);
+                return $this->configuration['case_sensitive'] ? strcmp($a, $b) : strcasecmp($a, $b);
             }
 
             return 0;

Some files were not shown because too many files changed in this diff