Browse Source

feat: Introduce `native_type_declaration_casing` fixer (#7330)

SpacePossum 1 year ago
parent
commit
e067339478

+ 8 - 1
doc/list.rst

@@ -1372,9 +1372,16 @@ List of Available Rules
 
    Native type declarations for functions should use the correct case.
 
-   Part of rule sets `@PhpCsFixer <./ruleSets/PhpCsFixer.rst>`_ `@Symfony <./ruleSets/Symfony.rst>`_
+   *warning deprecated*   Use ``native_type_declaration_casing`` instead.
 
    `Source PhpCsFixer\\Fixer\\Casing\\NativeFunctionTypeDeclarationCasingFixer <./../src/Fixer/Casing/NativeFunctionTypeDeclarationCasingFixer.php>`_
+-  `native_type_declaration_casing <./rules/casing/native_type_declaration_casing.rst>`_
+
+   Native type declarations should be used in the correct case.
+
+   Part of rule sets `@PhpCsFixer <./ruleSets/PhpCsFixer.rst>`_ `@Symfony <./ruleSets/Symfony.rst>`_
+
+   `Source PhpCsFixer\\Fixer\\Casing\\NativeTypeDeclarationCasingFixer <./../src/Fixer/Casing/NativeTypeDeclarationCasingFixer.php>`_
 -  `new_with_braces <./rules/operator/new_with_braces.rst>`_
 
    All instances created with ``new`` keyword must (not) be followed by braces.

+ 1 - 1
doc/ruleSets/Symfony.rst

@@ -61,7 +61,7 @@ Rules
   ``['on_multiline' => 'ignore']``
 
 - `native_function_casing <./../rules/casing/native_function_casing.rst>`_
-- `native_function_type_declaration_casing <./../rules/casing/native_function_type_declaration_casing.rst>`_
+- `native_type_declaration_casing <./../rules/casing/native_type_declaration_casing.rst>`_
 - `no_alias_language_construct_call <./../rules/alias/no_alias_language_construct_call.rst>`_
 - `no_alternative_syntax <./../rules/control_structure/no_alternative_syntax.rst>`_
 - `no_binary_string <./../rules/string_notation/no_binary_string.rst>`_

+ 8 - 9
doc/rules/casing/native_function_type_declaration_casing.rst

@@ -4,6 +4,14 @@ Rule ``native_function_type_declaration_casing``
 
 Native type declarations for functions should use the correct case.
 
+Warning
+-------
+
+This rule is deprecated and will be removed in the next major version
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You should use ``native_type_declaration_casing`` instead.
+
 Examples
 --------
 
@@ -64,12 +72,3 @@ Example #4
     {
         return 'hi!';
     }
-
-Rule sets
----------
-
-The rule is part of the following rule sets:
-
-- `@PhpCsFixer <./../../ruleSets/PhpCsFixer.rst>`_
-- `@Symfony <./../../ruleSets/Symfony.rst>`_
-

+ 47 - 0
doc/rules/casing/native_type_declaration_casing.rst

@@ -0,0 +1,47 @@
+=======================================
+Rule ``native_type_declaration_casing``
+=======================================
+
+Native type declarations should be used in the correct case.
+
+Examples
+--------
+
+Example #1
+~~~~~~~~~~
+
+.. code-block:: diff
+
+   --- Original
+   +++ New
+    <?php
+    class Bar {
+   -    public function Foo(CALLABLE $bar): INT
+   +    public function Foo(callable $bar): int
+        {
+            return 1;
+        }
+    }
+
+Example #2
+~~~~~~~~~~
+
+.. code-block:: diff
+
+   --- Original
+   +++ New
+    <?php
+    class Foo
+    {
+   -    const INT BAR = 1;
+   +    const int BAR = 1;
+    }
+
+Rule sets
+---------
+
+The rule is part of the following rule sets:
+
+- `@PhpCsFixer <./../../ruleSets/PhpCsFixer.rst>`_
+- `@Symfony <./../../ruleSets/Symfony.rst>`_
+

+ 4 - 1
doc/rules/index.rst

@@ -138,9 +138,12 @@ Casing
 - `native_function_casing <./casing/native_function_casing.rst>`_
 
   Function defined by PHP should be called using the correct casing.
-- `native_function_type_declaration_casing <./casing/native_function_type_declaration_casing.rst>`_
+- `native_function_type_declaration_casing <./casing/native_function_type_declaration_casing.rst>`_ *(deprecated)*
 
   Native type declarations for functions should use the correct case.
+- `native_type_declaration_casing <./casing/native_type_declaration_casing.rst>`_
+
+  Native type declarations should be used in the correct case.
 
 Cast Notation
 -------------

+ 11 - 110
src/Fixer/Casing/NativeFunctionTypeDeclarationCasingFixer.php

@@ -14,80 +14,17 @@ declare(strict_types=1);
 
 namespace PhpCsFixer\Fixer\Casing;
 
-use PhpCsFixer\AbstractFixer;
+use PhpCsFixer\AbstractProxyFixer;
+use PhpCsFixer\Fixer\DeprecatedFixerInterface;
 use PhpCsFixer\FixerDefinition\CodeSample;
 use PhpCsFixer\FixerDefinition\FixerDefinition;
 use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
-use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis;
-use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
-use PhpCsFixer\Tokenizer\Token;
-use PhpCsFixer\Tokenizer\Tokens;
 
-final class NativeFunctionTypeDeclarationCasingFixer extends AbstractFixer
+/**
+ * @deprecated in favor of NativeTypeDeclarationCasingFixer
+ */
+final class NativeFunctionTypeDeclarationCasingFixer extends AbstractProxyFixer implements DeprecatedFixerInterface
 {
-    /**
-     * https://secure.php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration.
-     *
-     * self     PHP 5.0
-     * array    PHP 5.1
-     * callable PHP 5.4
-     * bool     PHP 7.0
-     * float    PHP 7.0
-     * int      PHP 7.0
-     * string   PHP 7.0
-     * iterable PHP 7.1
-     * void     PHP 7.1
-     * object   PHP 7.2
-     * static   PHP 8.0 (return type only)
-     * mixed    PHP 8.0
-     * false    PHP 8.0 (union return type only)
-     * null     PHP 8.0 (union return type only)
-     * never    PHP 8.1 (return type only)
-     * true     PHP 8.2 (standalone type: https://wiki.php.net/rfc/true-type)
-     * false    PHP 8.2 (standalone type: https://wiki.php.net/rfc/null-false-standalone-types)
-     * null     PHP 8.2 (standalone type: https://wiki.php.net/rfc/null-false-standalone-types)
-     *
-     * @var array<string, true>
-     */
-    private array $hints;
-
-    private FunctionsAnalyzer $functionsAnalyzer;
-
-    public function __construct()
-    {
-        parent::__construct();
-
-        $this->hints = [
-            'array' => true,
-            'bool' => true,
-            'callable' => true,
-            'float' => true,
-            'int' => true,
-            'iterable' => true,
-            'object' => true,
-            'self' => true,
-            'string' => true,
-            'void' => true,
-        ];
-
-        if (\PHP_VERSION_ID >= 8_00_00) {
-            $this->hints['false'] = true;
-            $this->hints['mixed'] = true;
-            $this->hints['null'] = true;
-            $this->hints['static'] = true;
-        }
-
-        if (\PHP_VERSION_ID >= 8_01_00) {
-            $this->hints['never'] = true;
-        }
-
-        if (\PHP_VERSION_ID >= 8_02_00) {
-            $this->hints['true'] = true;
-        }
-
-        $this->functionsAnalyzer = new FunctionsAnalyzer();
-    }
-
     public function getDefinition(): FixerDefinitionInterface
     {
         return new FixerDefinition(
@@ -107,51 +44,15 @@ final class NativeFunctionTypeDeclarationCasingFixer extends AbstractFixer
         );
     }
 
-    public function isCandidate(Tokens $tokens): bool
+    public function getSuccessorsNames(): array
     {
-        return $tokens->isAnyTokenKindsFound([T_FUNCTION, T_FN]);
+        return array_keys($this->proxyFixers);
     }
 
-    protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
+    protected function createProxyFixers(): array
     {
-        for ($index = $tokens->count() - 1; $index >= 0; --$index) {
-            if ($tokens[$index]->isGivenKind([T_FUNCTION, T_FN])) {
-                $this->fixFunctionReturnType($tokens, $index);
-                $this->fixFunctionArgumentTypes($tokens, $index);
-            }
-        }
-    }
-
-    private function fixFunctionArgumentTypes(Tokens $tokens, int $index): void
-    {
-        foreach ($this->functionsAnalyzer->getFunctionArguments($tokens, $index) as $argument) {
-            $this->fixArgumentType($tokens, $argument->getTypeAnalysis());
-        }
-    }
-
-    private function fixFunctionReturnType(Tokens $tokens, int $index): void
-    {
-        $this->fixArgumentType($tokens, $this->functionsAnalyzer->getFunctionReturnType($tokens, $index));
-    }
-
-    private function fixArgumentType(Tokens $tokens, ?TypeAnalysis $type = null): void
-    {
-        if (null === $type) {
-            return;
-        }
-
-        for ($index = $type->getStartIndex(); $index <= $type->getEndIndex(); ++$index) {
-            if ($tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(T_NS_SEPARATOR)) {
-                continue;
-            }
-
-            $lowerCasedName = strtolower($tokens[$index]->getContent());
-
-            if (!isset($this->hints[$lowerCasedName])) {
-                continue;
-            }
+        $fixer = new NativeTypeDeclarationCasingFixer();
 
-            $tokens[$index] = new Token([$tokens[$index]->getId(), $lowerCasedName]);
-        }
+        return [$fixer];
     }
 }

+ 360 - 0
src/Fixer/Casing/NativeTypeDeclarationCasingFixer.php

@@ -0,0 +1,360 @@
+<?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\Casing;
+
+use PhpCsFixer\AbstractFixer;
+use PhpCsFixer\FixerDefinition\CodeSample;
+use PhpCsFixer\FixerDefinition\FixerDefinition;
+use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
+use PhpCsFixer\FixerDefinition\VersionSpecification;
+use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
+use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis;
+use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
+use PhpCsFixer\Tokenizer\CT;
+use PhpCsFixer\Tokenizer\Token;
+use PhpCsFixer\Tokenizer\Tokens;
+use PhpCsFixer\Tokenizer\TokensAnalyzer;
+
+final class NativeTypeDeclarationCasingFixer extends AbstractFixer
+{
+    /*
+     * https://wiki.php.net/rfc/typed_class_constants
+     * Supported types
+     * Class constant type declarations support all type declarations supported by PHP,
+     * except `void`, `callable`, `never`.
+     *
+     * array
+     * bool
+     * callable
+     * float
+     * int
+     * iterable
+     * object
+     * mixed
+     * parent
+     * self
+     * string
+     * any class or interface name -> not native, so not applicable for this Fixer
+     * ?type -> not native, `?` has no casing, so not applicable for this Fixer
+     *
+     * Not in the list referenced but supported:
+     * null
+     * static
+     */
+    private const CLASS_CONST_SUPPORTED_HINTS = [
+        'array' => true,
+        'bool' => true,
+        'float' => true,
+        'int' => true,
+        'iterable' => true,
+        'mixed' => true,
+        'null' => true,
+        'object' => true,
+        'parent' => true,
+        'self' => true,
+        'string' => true,
+        'static' => true,
+    ];
+
+    private const CLASS_PROPERTY_SUPPORTED_HINTS = [
+        'array' => true,
+        'bool' => true,
+        'float' => true,
+        'int' => true,
+        'iterable' => true,
+        'mixed' => true,
+        'null' => true,
+        'object' => true,
+        'parent' => true,
+        'self' => true,
+        'static' => true,
+        'string' => true,
+    ];
+
+    private const TYPE_SEPARATION_TYPES = [
+        CT::T_TYPE_ALTERNATION,
+        CT::T_TYPE_INTERSECTION,
+        CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN,
+        CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE,
+    ];
+
+    /**
+     * https://secure.php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration.
+     *
+     * self     PHP 5.0
+     * array    PHP 5.1
+     * callable PHP 5.4
+     * bool     PHP 7.0
+     * float    PHP 7.0
+     * int      PHP 7.0
+     * string   PHP 7.0
+     * iterable PHP 7.1
+     * void     PHP 7.1
+     * object   PHP 7.2
+     * static   PHP 8.0 (return type only)
+     * mixed    PHP 8.0
+     * false    PHP 8.0 (union return type only)
+     * null     PHP 8.0 (union return type only)
+     * never    PHP 8.1 (return type only)
+     * true     PHP 8.2 (standalone type: https://wiki.php.net/rfc/true-type)
+     * false    PHP 8.2 (standalone type: https://wiki.php.net/rfc/null-false-standalone-types)
+     * null     PHP 8.2 (standalone type: https://wiki.php.net/rfc/null-false-standalone-types)
+     *
+     * @var array<string, true>
+     */
+    private array $functionTypeHints;
+
+    private FunctionsAnalyzer $functionsAnalyzer;
+
+    /**
+     * @var list<list<int>>
+     */
+    private array $propertyTypeModifiers;
+
+    public function __construct()
+    {
+        parent::__construct();
+
+        $this->propertyTypeModifiers = [[T_PRIVATE], [T_PROTECTED], [T_PUBLIC]];
+
+        $this->functionTypeHints = [
+            'array' => true,
+            'bool' => true,
+            'callable' => true,
+            'float' => true,
+            'int' => true,
+            'iterable' => true,
+            'object' => true,
+            'self' => true,
+            'string' => true,
+            'void' => true,
+        ];
+
+        if (\PHP_VERSION_ID >= 8_00_00) {
+            $this->functionTypeHints['false'] = true;
+            $this->functionTypeHints['mixed'] = true;
+            $this->functionTypeHints['null'] = true;
+            $this->functionTypeHints['static'] = true;
+        }
+
+        if (\PHP_VERSION_ID >= 8_01_00) {
+            $this->functionTypeHints['never'] = true;
+
+            $this->propertyTypeModifiers[] = [T_READONLY];
+        }
+
+        if (\PHP_VERSION_ID >= 8_02_00) {
+            $this->functionTypeHints['true'] = true;
+        }
+
+        $this->functionsAnalyzer = new FunctionsAnalyzer();
+    }
+
+    public function getDefinition(): FixerDefinitionInterface
+    {
+        return new FixerDefinition(
+            'Native type declarations should be used in the correct case.',
+            [
+                new CodeSample(
+                    "<?php\nclass Bar {\n    public function Foo(CALLABLE \$bar): INT\n    {\n        return 1;\n    }\n}\n"
+                ),
+                new VersionSpecificCodeSample(
+                    "<?php\nclass Foo\n{\n    const INT BAR = 1;\n}\n",
+                    new VersionSpecification(8_03_00),
+                ),
+            ]
+        );
+    }
+
+    public function isCandidate(Tokens $tokens): bool
+    {
+        $classyFound = $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds());
+
+        return
+            $tokens->isAnyTokenKindsFound([T_FUNCTION, T_FN])
+            || ($classyFound && $tokens->isTokenKindFound(T_STRING))
+            || (
+                \PHP_VERSION_ID >= 8_03_00
+                && $tokens->isTokenKindFound(T_CONST)
+                && $classyFound
+            );
+    }
+
+    protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
+    {
+        $this->fixFunctions($tokens);
+        $this->fixClassConstantsAndProperties($tokens);
+    }
+
+    private function fixFunctions(Tokens $tokens): void
+    {
+        for ($index = $tokens->count() - 1; $index >= 0; --$index) {
+            if ($tokens[$index]->isGivenKind([T_FUNCTION, T_FN])) {
+                $this->fixFunctionReturnType($tokens, $index);
+                $this->fixFunctionArgumentTypes($tokens, $index);
+            }
+        }
+    }
+
+    private function fixFunctionArgumentTypes(Tokens $tokens, int $index): void
+    {
+        foreach ($this->functionsAnalyzer->getFunctionArguments($tokens, $index) as $argument) {
+            $this->fixArgumentType($tokens, $argument->getTypeAnalysis());
+        }
+    }
+
+    private function fixFunctionReturnType(Tokens $tokens, int $index): void
+    {
+        $this->fixArgumentType($tokens, $this->functionsAnalyzer->getFunctionReturnType($tokens, $index));
+    }
+
+    private function fixArgumentType(Tokens $tokens, ?TypeAnalysis $type = null): void
+    {
+        if (null === $type) {
+            return;
+        }
+
+        for ($index = $type->getStartIndex(); $index <= $type->getEndIndex(); ++$index) {
+            if ($tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(T_NS_SEPARATOR)) {
+                continue;
+            }
+
+            $this->fixCasing($this->functionTypeHints, $tokens, $index);
+        }
+    }
+
+    private function fixClassConstantsAndProperties(Tokens $tokens): void
+    {
+        $analyzer = new TokensAnalyzer($tokens);
+        $elements = array_reverse($analyzer->getClassyElements(), true);
+
+        foreach ($elements as $index => $element) {
+            if ('const' === $element['type']) {
+                if (\PHP_VERSION_ID >= 8_03_00 && !$this->isConstWithoutType($tokens, $index)) {
+                    foreach ($this->getNativeTypeHintCandidatesForConstant($tokens, $index) as $nativeTypeHintIndex) {
+                        $this->fixCasing($this::CLASS_CONST_SUPPORTED_HINTS, $tokens, $nativeTypeHintIndex);
+                    }
+                }
+
+                continue;
+            }
+
+            if ('property' === $element['type']) {
+                foreach ($this->getNativeTypeHintCandidatesForProperty($tokens, $index) as $nativeTypeHintIndex) {
+                    $this->fixCasing($this::CLASS_PROPERTY_SUPPORTED_HINTS, $tokens, $nativeTypeHintIndex);
+                }
+            }
+        }
+    }
+
+    /** @return iterable<int> */
+    private function getNativeTypeHintCandidatesForConstant(Tokens $tokens, int $index): iterable
+    {
+        $constNameIndex = $this->getConstNameIndex($tokens, $index);
+        $index = $this->getFirstIndexOfType($tokens, $index);
+
+        do {
+            $typeEnd = $this->getTypeEnd($tokens, $index, $constNameIndex);
+
+            if ($typeEnd === $index) {
+                yield $index;
+            }
+
+            do {
+                $index = $tokens->getNextMeaningfulToken($index);
+            } while ($tokens[$index]->isGivenKind(self::TYPE_SEPARATION_TYPES));
+        } while ($index < $constNameIndex);
+    }
+
+    private function isConstWithoutType(Tokens $tokens, int $index): bool
+    {
+        $index = $tokens->getNextMeaningfulToken($index);
+
+        return $tokens[$index]->isGivenKind(T_STRING) && $tokens[$tokens->getNextMeaningfulToken($index)]->equals('=');
+    }
+
+    private function getConstNameIndex(Tokens $tokens, int $index): int
+    {
+        return $tokens->getPrevMeaningfulToken(
+            $tokens->getNextTokenOfKind($index, ['=']),
+        );
+    }
+
+    /** @return iterable<int> */
+    private function getNativeTypeHintCandidatesForProperty(Tokens $tokens, int $index): iterable
+    {
+        $propertyNameIndex = $index;
+        $index = $tokens->getPrevTokenOfKind($index, $this->propertyTypeModifiers);
+
+        $index = $this->getFirstIndexOfType($tokens, $index);
+
+        do {
+            $typeEnd = $this->getTypeEnd($tokens, $index, $propertyNameIndex);
+
+            if ($typeEnd === $index) {
+                yield $index;
+            }
+
+            do {
+                $index = $tokens->getNextMeaningfulToken($index);
+            } while ($tokens[$index]->isGivenKind(self::TYPE_SEPARATION_TYPES));
+        } while ($index < $propertyNameIndex);
+
+        return [];
+    }
+
+    private function getFirstIndexOfType(Tokens $tokens, int $index): int
+    {
+        $index = $tokens->getNextMeaningfulToken($index);
+
+        if ($tokens[$index]->isGivenKind(CT::T_NULLABLE_TYPE)) {
+            $index = $tokens->getNextMeaningfulToken($index);
+        }
+
+        if ($tokens[$index]->isGivenKind(CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN)) {
+            $index = $tokens->getNextMeaningfulToken($index);
+        }
+
+        return $index;
+    }
+
+    private function getTypeEnd(Tokens $tokens, int $index, int $upperLimit): int
+    {
+        if (!$tokens[$index]->isGivenKind([T_STRING, T_NS_SEPARATOR])) {
+            return $index; // callable, array, self, static, etc.
+        }
+
+        $endIndex = $index;
+        while ($tokens[$index]->isGivenKind([T_STRING, T_NS_SEPARATOR]) && $index < $upperLimit) {
+            $endIndex = $index;
+            $index = $tokens->getNextMeaningfulToken($index);
+        }
+
+        return $endIndex;
+    }
+
+    /**
+     * @param array<string, true> $supportedTypeHints
+     */
+    private function fixCasing(array $supportedTypeHints, Tokens $tokens, int $index): void
+    {
+        $typeContent = $tokens[$index]->getContent();
+        $typeContentLower = strtolower($typeContent);
+
+        if (isset($supportedTypeHints[$typeContentLower]) && $typeContent !== $typeContentLower) {
+            $tokens[$index] = new Token([$tokens[$index]->getId(), $typeContentLower]);
+        }
+    }
+}

+ 1 - 1
src/RuleSet/Sets/SymfonySet.php

@@ -77,7 +77,7 @@ final class SymfonySet extends AbstractRuleSetDescription
                 'on_multiline' => 'ignore',
             ],
             'native_function_casing' => true,
-            'native_function_type_declaration_casing' => true,
+            'native_type_declaration_casing' => true,
             'no_alias_language_construct_call' => true,
             'no_alternative_syntax' => true,
             'no_binary_string' => true,

+ 8 - 246
tests/Fixer/Casing/NativeFunctionTypeDeclarationCasingFixerTest.php

@@ -14,6 +14,7 @@ declare(strict_types=1);
 
 namespace PhpCsFixer\Tests\Fixer\Casing;
 
+use PhpCsFixer\Fixer\DeprecatedFixerInterface;
 use PhpCsFixer\Tests\Test\AbstractFixerTestCase;
 
 /**
@@ -23,253 +24,14 @@ use PhpCsFixer\Tests\Test\AbstractFixerTestCase;
  */
 final class NativeFunctionTypeDeclarationCasingFixerTest extends AbstractFixerTestCase
 {
-    /**
-     * @dataProvider provideFixCases
-     */
-    public function testFix(string $expected, ?string $input = null): void
+    public function testFunctionIsDeprecatedProperly(): void
     {
-        $this->doTest($expected, $input);
-    }
-
-    public static function provideFixCases(): iterable
-    {
-        yield [
-            '<?php
-class Foo
-{
-    private function Bar(array $bar) {
-        return false;
-    }
-}
-',
-            '<?php
-class Foo
-{
-    private function Bar(ARRAY $bar) {
-        return false;
-    }
-}
-',
-        ];
-
-        yield [
-            '<?php
-interface Foo
-{
-    public function Bar(array $bar);
-}
-',
-            '<?php
-interface Foo
-{
-    public function Bar(ArrAY $bar);
-}
-',
-        ];
-
-        yield [
-            '<?php
-function Foo(/**/array/**/$bar) {
-    return false;
-}
-',
-            '<?php
-function Foo(/**/ARRAY/**/$bar) {
-    return false;
-}
-',
-        ];
-
-        yield [
-            '<?php
-class Bar { function Foo(array $a, callable $b, self $c) {} }
-                ',
-            '<?php
-class Bar { function Foo(ARRAY $a, CALLABLE $b, Self $c) {} }
-                ',
-        ];
-
-        yield [
-            '<?php
-function Foo(INTEGER $a) {}
-                ',
-        ];
-
-        yield [
-            '<?php function Foo(
-                    String\A $x,
-                    B\String\C $y
-                ) {}',
-        ];
-
-        yield [
-            '<?php final class Foo1 { final public function Foo(bool $A, float $B, int $C, string $D): int {} }',
-            '<?php final class Foo1 { final public function Foo(BOOL $A, FLOAT $B, INT $C, STRING $D): INT {} }',
-        ];
-
-        yield [
-            '<?php function Foo(bool $A, float $B, int $C, string $D): int {}',
-            '<?php function Foo(BOOL $A, FLOAT $B, INT $C, STRING $D): INT {}',
-        ];
-
-        yield [
-            '<?php function Foo(): Foo\A { return new Foo(); }',
-        ];
-
-        yield [
-            '<?php trait XYZ { function Foo(iterable $A): void {} }',
-            '<?php trait XYZ { function Foo(ITERABLE $A): VOID {} }',
-        ];
-
-        yield [
-            '<?php function Foo(iterable $A): void {}',
-            '<?php function Foo(ITERABLE $A): VOID {}',
-        ];
-
-        yield [
-            '<?php function Foo(?int $A): void {}',
-            '<?php function Foo(?INT $A): VOID {}',
-        ];
-
-        yield [
-            '<?php function Foo(string $A): ?/* */int {}',
-            '<?php function Foo(STRING $A): ?/* */INT {}',
-        ];
-
-        yield [
-            '<?php function Foo(object $A): void {}',
-            '<?php function Foo(OBJECT $A): VOID {}',
-        ];
-
-        yield [
-            '<?php return function (callable $c) {};',
-            '<?php return function (CALLABLE $c) {};',
-        ];
-
-        yield [
-            '<?php return fn (callable $c): int => 1;',
-            '<?php return fn (CALLABLE $c): INT => 1;',
-        ];
-    }
-
-    /**
-     * @dataProvider provideFix80Cases
-     *
-     * @requires PHP 8.0
-     */
-    public function testFix80(string $expected, string $input): void
-    {
-        $this->doTest($expected, $input);
-    }
-
-    public static function provideFix80Cases(): iterable
-    {
-        yield [
-            '<?php class T { public function Foo(object $A): static {}}',
-            '<?php class T { public function Foo(object $A): StatiC {}}',
-        ];
-
-        yield [
-            '<?php class T { public function Foo(object $A): ?static {}}',
-            '<?php class T { public function Foo(object $A): ?StatiC {}}',
-        ];
-
-        yield [
-            '<?php class T { public function Foo(mixed $A): mixed {}}',
-            '<?php class T { public function Foo(Mixed $A): MIXED {}}',
-        ];
-
-        yield 'mixed in arrow function' => [
-            '<?php return fn (mixed $c): mixed => 1;',
-            '<?php return fn (MiXeD $c): MIXED => 1;',
-        ];
-
-        yield [
-            '<?php function foo(int|bool $x) {}',
-            '<?php function foo(INT|BOOL $x) {}',
-        ];
-
-        yield [
-            '<?php function foo(int | bool $x) {}',
-            '<?php function foo(INT | BOOL $x) {}',
-        ];
-
-        yield [
-            '<?php function foo(): int|bool {}',
-            '<?php function foo(): INT|BOOL {}',
-        ];
-
-        yield 'return type string|false' => [
-            '<?php function foo(): string|false {}',
-            '<?php function foo(): string|FALSE {}',
-        ];
-
-        yield 'return type string|null' => [
-            '<?php function foo(): string|null {}',
-            '<?php function foo(): string|NULL {}',
-        ];
-
-        yield 'union types in arrow function' => [
-            '<?php return fn (string|null $c): int|null => 1;',
-            '<?php return fn (string|NULL $c): INT|NULL => 1;',
-        ];
-    }
-
-    /**
-     * @dataProvider provideFix81Cases
-     *
-     * @requires PHP 8.1
-     */
-    public function testFix81(string $expected, string $input): void
-    {
-        $this->doTest($expected, $input);
-    }
-
-    public static function provideFix81Cases(): iterable
-    {
-        yield 'return type `never`' => [
-            '<?php class T { public function Foo(object $A): never {die;}}',
-            '<?php class T { public function Foo(object $A): NEVER {die;}}',
-        ];
-    }
-
-    /**
-     * @dataProvider provideFix82Cases
-     *
-     * @requires PHP 8.2
-     */
-    public function testFix82(string $expected, string $input): void
-    {
-        $this->doTest($expected, $input);
-    }
-
-    public static function provideFix82Cases(): iterable
-    {
-        yield 'disjunctive normal form types in arrow function' => [
-            '<?php return fn ((A&B)|C|null $c): (X&Y)|Z|null => 1;',
-            '<?php return fn ((A&B)|C|Null $c): (X&Y)|Z|NULL => 1;',
-        ];
-
-        foreach (['true', 'false', 'null'] as $type) {
-            yield sprintf('standalone type `%s` in class method', $type) => [
-                sprintf('<?php class T { public function Foo(%s $A): %1$s {return $A;}}', $type),
-                sprintf('<?php class T { public function Foo(%s $A): %1$s {return $A;}}', strtoupper($type)),
-            ];
-
-            yield sprintf('standalone type `%s` in function', $type) => [
-                sprintf('<?php function Foo(%s $A): %1$s {return $A;}', $type),
-                sprintf('<?php function Foo(%s $A): %1$s {return $A;}', strtoupper($type)),
-            ];
-
-            yield sprintf('standalone type `%s` in closure', $type) => [
-                sprintf('<?php array_filter([], function (%s $A): %1$s {return $A;});', $type),
-                sprintf('<?php array_filter([], function (%s $A): %1$s {return $A;});', strtoupper($type)),
-            ];
+        $fixer = $this->fixer;
 
-            yield sprintf('standalone type `%s` in arrow function', $type) => [
-                sprintf('<?php array_filter([], fn (%s $A): %1$s => $A);', $type),
-                sprintf('<?php array_filter([], fn (%s $A): %1$s => $A);', strtoupper($type)),
-            ];
-        }
+        self::assertInstanceOf(DeprecatedFixerInterface::class, $fixer);
+        self::assertSame(
+            ['native_type_declaration_casing'],
+            $fixer->getSuccessorsNames(),
+        );
     }
 }

+ 600 - 0
tests/Fixer/Casing/NativeTypeDeclarationCasingFixerTest.php

@@ -0,0 +1,600 @@
+<?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\Tests\Fixer\Casing;
+
+use PhpCsFixer\Tests\Test\AbstractFixerTestCase;
+
+/**
+ * @internal
+ *
+ * @covers \PhpCsFixer\Fixer\Casing\NativeTypeDeclarationCasingFixer
+ */
+final class NativeTypeDeclarationCasingFixerTest extends AbstractFixerTestCase
+{
+    /**
+     * @dataProvider provideFixCases
+     */
+    public function testFix(string $expected, ?string $input = null): void
+    {
+        $this->doTest($expected, $input);
+    }
+
+    public static function provideFixCases(): iterable
+    {
+        yield [
+            '<?php
+                function A(int $a): void {}
+
+                class Foo
+                {
+                    private bool $c = false;
+                    private bool $d = false;
+
+                    public function B(int $a): bool { return $this->c || $this->d; }
+                }
+
+                function C(float $a): array { return [$a];}
+                function D(array $a): array { return [$a];}
+            ',
+            '<?php
+                function A(INT $a): VOID {}
+
+                class Foo
+                {
+                    private BOOL $c = false;
+                    private BOOL $d = false;
+
+                    public function B(INT $a): BOOL { return $this->c || $this->d; }
+                }
+
+                function C(FLOAT $a): ARRAY { return [$a];}
+                function D(ARRAY $a): ARRAY { return [$a];}
+            ',
+        ];
+    }
+
+    /**
+     * @dataProvider provideFixFunctionTypesCases
+     */
+    public function testFixFunctionTypes(string $expected, ?string $input = null): void
+    {
+        $this->doTest($expected, $input);
+    }
+
+    public static function provideFixFunctionTypesCases(): iterable
+    {
+        yield [
+            '<?php
+class Foo
+{
+    private function Bar(array $bar) {
+        return false;
+    }
+}
+',
+            '<?php
+class Foo
+{
+    private function Bar(ARRAY $bar) {
+        return false;
+    }
+}
+',
+        ];
+
+        yield [
+            '<?php
+interface Foo
+{
+    public function Bar(array $bar);
+}
+',
+            '<?php
+interface Foo
+{
+    public function Bar(ArrAY $bar);
+}
+',
+        ];
+
+        yield [
+            '<?php
+function Foo(/**/array/**/$bar) {
+    return false;
+}
+',
+            '<?php
+function Foo(/**/ARRAY/**/$bar) {
+    return false;
+}
+',
+        ];
+
+        yield [
+            '<?php
+class Bar { function Foo(array $a, callable $b, self $c) {} }
+                ',
+            '<?php
+class Bar { function Foo(ARRAY $a, CALLABLE $b, Self $c) {} }
+                ',
+        ];
+
+        yield [
+            '<?php
+function Foo(INTEGER $a) {}
+                ',
+        ];
+
+        yield [
+            '<?php function Foo(
+                    String\A $x,
+                    B\String\C $y
+                ) {}',
+        ];
+
+        yield [
+            '<?php final class Foo1 { final public function Foo(bool $A, float $B, int $C, string $D): int {} }',
+            '<?php final class Foo1 { final public function Foo(BOOL $A, FLOAT $B, INT $C, STRING $D): INT {} }',
+        ];
+
+        yield [
+            '<?php function Foo(bool $A, float $B, int $C, string $D): int {}',
+            '<?php function Foo(BOOL $A, FLOAT $B, INT $C, STRING $D): INT {}',
+        ];
+
+        yield [
+            '<?php function Foo(): Foo\A { return new Foo(); }',
+        ];
+
+        yield [
+            '<?php trait XYZ { function Foo(iterable $A): void {} }',
+            '<?php trait XYZ { function Foo(ITERABLE $A): VOID {} }',
+        ];
+
+        yield [
+            '<?php function Foo(iterable $A): void {}',
+            '<?php function Foo(ITERABLE $A): VOID {}',
+        ];
+
+        yield [
+            '<?php function Foo(?int $A): void {}',
+            '<?php function Foo(?INT $A): VOID {}',
+        ];
+
+        yield [
+            '<?php function Foo(string $A): ?/* */int {}',
+            '<?php function Foo(STRING $A): ?/* */INT {}',
+        ];
+
+        yield [
+            '<?php function Foo(object $A): void {}',
+            '<?php function Foo(OBJECT $A): VOID {}',
+        ];
+
+        yield [
+            '<?php return function (callable $c) {};',
+            '<?php return function (CALLABLE $c) {};',
+        ];
+
+        yield [
+            '<?php return fn (callable $c): int => 1;',
+            '<?php return fn (CALLABLE $c): INT => 1;',
+        ];
+    }
+
+    /**
+     * @dataProvider provideFixFunctionTypes80Cases
+     *
+     * @requires PHP 8.0
+     */
+    public function testFixFunctionTypes80(string $expected, string $input): void
+    {
+        $this->doTest($expected, $input);
+    }
+
+    public static function provideFixFunctionTypes80Cases(): iterable
+    {
+        yield [
+            '<?php class T { public function Foo(object $A): static {}}',
+            '<?php class T { public function Foo(object $A): StatiC {}}',
+        ];
+
+        yield [
+            '<?php class T { public function Foo(object $A): ?static {}}',
+            '<?php class T { public function Foo(object $A): ?StatiC {}}',
+        ];
+
+        yield [
+            '<?php class T { public function Foo(mixed $A): mixed {}}',
+            '<?php class T { public function Foo(Mixed $A): MIXED {}}',
+        ];
+
+        yield 'mixed in arrow function' => [
+            '<?php return fn (mixed $c): mixed => 1;',
+            '<?php return fn (MiXeD $c): MIXED => 1;',
+        ];
+
+        yield [
+            '<?php function foo(int|bool $x) {}',
+            '<?php function foo(INT|BOOL $x) {}',
+        ];
+
+        yield [
+            '<?php function foo(int | bool $x) {}',
+            '<?php function foo(INT | BOOL $x) {}',
+        ];
+
+        yield [
+            '<?php function foo(): int|bool {}',
+            '<?php function foo(): INT|BOOL {}',
+        ];
+
+        yield 'return type string|false' => [
+            '<?php function foo(): string|false {}',
+            '<?php function foo(): string|FALSE {}',
+        ];
+
+        yield 'return type string|null' => [
+            '<?php function foo(): string|null {}',
+            '<?php function foo(): string|NULL {}',
+        ];
+
+        yield 'union types in arrow function' => [
+            '<?php return fn (string|null $c): int|null => 1;',
+            '<?php return fn (string|NULL $c): INT|NULL => 1;',
+        ];
+    }
+
+    /**
+     * @dataProvider provideFixFunctionTypes81Cases
+     *
+     * @requires PHP 8.1
+     */
+    public function testFixFunctionTypes81(string $expected, string $input): void
+    {
+        $this->doTest($expected, $input);
+    }
+
+    public static function provideFixFunctionTypes81Cases(): iterable
+    {
+        yield 'return type `never`' => [
+            '<?php class T { public function Foo(object $A): never {die;}}',
+            '<?php class T { public function Foo(object $A): NEVER {die;}}',
+        ];
+    }
+
+    /**
+     * @dataProvider provideFixFunctionTypes82Cases
+     *
+     * @requires PHP 8.2
+     */
+    public function testFixFunctionTypes82(string $expected, string $input): void
+    {
+        $this->doTest($expected, $input);
+    }
+
+    public static function provideFixFunctionTypes82Cases(): iterable
+    {
+        yield 'disjunctive normal form types in arrow function' => [
+            '<?php return fn ((A&B)|C|null $c): (X&Y)|Z|null => 1;',
+            '<?php return fn ((A&B)|C|Null $c): (X&Y)|Z|NULL => 1;',
+        ];
+
+        yield 'iterable: disjunctive normal form types in arrow function' => [
+            '<?php return fn (iterable|C|null $c): (X&Y)|Z|null => 1;',
+            '<?php return fn (ITERABLE|C|Null $c): (X&Y)|Z|NULL => 1;',
+        ];
+
+        foreach (['true', 'false', 'null'] as $type) {
+            yield sprintf('standalone type `%s` in class method', $type) => [
+                sprintf('<?php class T { public function Foo(%s $A): %1$s {return $A;}}', $type),
+                sprintf('<?php class T { public function Foo(%s $A): %1$s {return $A;}}', strtoupper($type)),
+            ];
+
+            yield sprintf('standalone type `%s` in function', $type) => [
+                sprintf('<?php function Foo(%s $A): %1$s {return $A;}', $type),
+                sprintf('<?php function Foo(%s $A): %1$s {return $A;}', strtoupper($type)),
+            ];
+
+            yield sprintf('standalone type `%s` in closure', $type) => [
+                sprintf('<?php array_filter([], function (%s $A): %1$s {return $A;});', $type),
+                sprintf('<?php array_filter([], function (%s $A): %1$s {return $A;});', strtoupper($type)),
+            ];
+
+            yield sprintf('standalone type `%s` in arrow function', $type) => [
+                sprintf('<?php array_filter([], fn (%s $A): %1$s => $A);', $type),
+                sprintf('<?php array_filter([], fn (%s $A): %1$s => $A);', strtoupper($type)),
+            ];
+        }
+    }
+
+    /**
+     * @dataProvider provideFixConstantsCases
+     *
+     * @requires PHP 8.3
+     */
+    public function testFixConstants(string $expected, string $input = null): void
+    {
+        $this->doTest($expected, $input);
+    }
+
+    public static function provideFixConstantsCases(): iterable
+    {
+        yield 'simple case' => [
+            '<?php
+                class Foo
+                {
+                    const int FOO = 6;
+                }
+            ',
+            '<?php
+                class Foo
+                {
+                    const INT FOO = 6;
+                }
+            ',
+        ];
+
+        yield 'single types' => [
+            '<?php
+                class Foo
+                {
+                    const int SOME_INT = 3;
+                    const array SOME_ARRAY = [7];
+                    const float SOME_FLOAT = 1.23;
+                    const iterable SOME_ITERABLE = [1, 2];
+                    const mixed SOME_MIXED = 1;
+                    const null SOME_NULL = NULL;
+                    const string SOME_STRING = "X";
+                }
+            ',
+            '<?php
+                class Foo
+                {
+                    const INT SOME_INT = 3;
+                    const ARRAY SOME_ARRAY = [7];
+                    const Float SOME_FLOAT = 1.23;
+                    const ITERABLE SOME_ITERABLE = [1, 2];
+                    const MIXED SOME_MIXED = 1;
+                    const NULL SOME_NULL = NULL;
+                    const STRING SOME_STRING = "X";
+                }
+            ',
+        ];
+
+        yield 'nullables `self`, `parent` and `object`' => [
+            '<?php
+                class Foo extends FooParent
+                {
+                    const ?object SOME_OBJECT = NULL;
+                    const ?parent SOME_PARENT = NULL;
+                    const ?self SOME_SELF = NULL;
+                    const ?int/* x */A/* y */= 3;
+
+                    const ?BAR B = null;
+                    const ?BAR C = null;
+                    const ?\BAR D = null;
+                    const ?\INT\B E = null;
+                    public const ?INT\A/* x */X=C;
+                }
+            ',
+            '<?php
+                class Foo extends FooParent
+                {
+                    const ?OBJECT SOME_OBJECT = NULL;
+                    const ?PARENT SOME_PARENT = NULL;
+                    const ?Self SOME_SELF = NULL;
+                    const ?INT/* x */A/* y */= 3;
+
+                    const ?BAR B = null;
+                    const ?BAR C = null;
+                    const ?\BAR D = null;
+                    const ?\INT\B E = null;
+                    public const ?INT\A/* x */X=C;
+                }
+            ',
+        ];
+
+        yield 'simple `|`' => [
+            '<?php class Foo1 {
+                const D|float BAR = 1.0;
+            }',
+            '<?php class Foo1 {
+                const D|Float BAR = 1.0;
+            }',
+        ];
+
+        yield 'multiple `|`' => [
+            '<?php class Foo2 {
+                const int|array|null|A\B|\C\D|float BAR_1 = NULL;
+            }',
+            '<?php class Foo2 {
+                const INT|ARRAY|NULL|A\B|\C\D|FLOAT BAR_1 = NULL;
+            }',
+        ];
+
+        yield 'handle mix of `|` and `(X&Y)`' => [
+            '<?php
+                class Foo extends FooParent
+                {
+                    private const Z|A\S\T\R|int|float|iterable SOMETHING = 123;
+                    protected const \A\B|(Foo & Stringable )|null|\X D = null;
+
+                    public const Foo&Stringable G = V::A;
+                }
+            ',
+            '<?php
+                class Foo extends FooParent
+                {
+                    private const Z|A\S\T\R|INT|FLOAT|ITERABLE SOMETHING = 123;
+                    protected const \A\B|(Foo & Stringable )|NULL|\X D = null;
+
+                    public const Foo&Stringable G = V::A;
+                }
+            ',
+        ];
+
+        yield 'interface, nullable int' => [
+            '<?php interface SomeInterface {
+                const ?int FOO = 1;
+            }',
+            '<?php interface SomeInterface {
+                const ?INT FOO = 1;
+            }',
+        ];
+
+        yield 'trait, string' => [
+            '<?php trait T {
+                const string TEST = E::TEST;
+            }',
+            '<?php trait T {
+                const STRING TEST = E::TEST;
+            }',
+        ];
+
+        yield 'enum, int' => [
+            '<?php enum E: string {
+                case Hearts = "H";
+
+                const int TEST = 789;
+                const self A = self::Hearts;
+                const static B = self::Hearts;
+            }',
+            '<?php enum E: string {
+                case Hearts = "H";
+
+                const INT TEST = 789;
+                const SELF A = self::Hearts;
+                const STATIC B = self::Hearts;
+            }',
+        ];
+
+        yield 'do not fix' => [
+            '<?php class Foo {
+                PUBLIC CONST FOO&STRINGABLE G = A::B;
+            }
+
+            CONST A = 1;',
+        ];
+    }
+
+    public function testDoNotFixConstCases(): void
+    {
+        $this->doTest(
+            '<?php
+                class Foo
+                {
+                    const A = 1;
+                    const B = [];
+                    const INT = "A"; // class constant; INT is the name of the const, not the type
+                    const FLOAT=1.2;
+                }
+
+                const INT = "A"; // outside class; INT is the name of the const, not the type
+            ',
+        );
+    }
+
+    /**
+     * @dataProvider provideFixClassPropertiesCases
+     */
+    public function testFixClassProperties(string $expected, ?string $input = null): void
+    {
+        $this->doTest($expected, $input);
+    }
+
+    public static function provideFixClassPropertiesCases(): iterable
+    {
+        yield 'class properties single type' => [
+            '<?php
+                class D{}
+
+                $a = new class extends D {
+                    private array $ax;
+                    private bool $bx = false;
+                    private float $cx = 3.14;
+                    private int $dx = 667;
+                    private iterable $ex = [];
+                    private mixed $f;
+                    private object $g;
+                    private parent $h;
+                    private self $i;
+                    private static $j;
+                    private ?string $k;
+
+                    private $INT = 1;
+                    private FOO $bar;
+                    private A\INT\B $z;
+                };
+            ',
+            '<?php
+                class D{}
+
+                $a = new class extends D {
+                    private ARRAY $ax;
+                    private BOOL $bx = false;
+                    private FLOAT $cx = 3.14;
+                    private INT $dx = 667;
+                    private ITERABLE $ex = [];
+                    private MIXED $f;
+                    private OBJECT $g;
+                    private PARENT $h;
+                    private Self $i;
+                    private STatic $j;
+                    private ?STRIng $k;
+
+                    private $INT = 1;
+                    private FOO $bar;
+                    private A\INT\B $z;
+                };
+            ',
+        ];
+
+        if (\PHP_VERSION_ID >= 8_00_00) {
+            yield 'union Types' => [
+                '<?php $a = new class {
+                    private null|int|bool $a4 = false;
+                };',
+                '<?php $a = new class {
+                    private NULL|INT|BOOL $a4 = false;
+                };',
+            ];
+        }
+
+        if (\PHP_VERSION_ID >= 8_01_00) {
+            yield 'class readonly property' => [
+                '<?php class Z {
+                    private readonly array $ax;
+                };',
+                '<?php class Z {
+                    private readonly ARRAY $ax;
+                };',
+            ];
+        }
+
+        if (\PHP_VERSION_ID >= 8_02_00) {
+            yield 'intersection Types' => [
+                '<?php $a = new class {
+                    private (A&B)|int|D $d5;
+                    private (A\STRING\B&B\INT\C)|int|(A&B) $e6;
+                };',
+                '<?php $a = new class {
+                    private (A&B)|INT|D $d5;
+                    private (A\STRING\B&B\INT\C)|int|(A&B) $e6;
+                };',
+            ];
+        }
+    }
+}

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