SpacePossum 4 years ago
parent
commit
3057facfa4

+ 14 - 14
README.rst

@@ -413,7 +413,7 @@ Choose from the list of available rules:
 
   Calling ``unset`` on multiple items should be done in one call.
 
-* **combine_nested_dirname** [@PHP70Migration:risky, @PHP71Migration:risky, @PHP8Migration:risky]
+* **combine_nested_dirname** [@PHP70Migration:risky, @PHP71Migration:risky, @PHP80Migration:risky]
 
   Replace multiple nested calls of ``dirname`` by only one call with second
   ``$level`` parameter. Requires PHP >= 7.0.
@@ -456,7 +456,7 @@ Choose from the list of available rules:
   - ``space`` (``'none'``, ``'single'``): spacing to apply around the equal sign;
     defaults to ``'none'``
 
-* **declare_strict_types** [@PHP70Migration:risky, @PHP71Migration:risky, @PHP8Migration:risky]
+* **declare_strict_types** [@PHP70Migration:risky, @PHP71Migration:risky, @PHP80Migration:risky]
 
   Force strict types declaration in all files. Requires PHP >= 7.0.
 
@@ -768,7 +768,7 @@ Choose from the list of available rules:
   - ``separate`` (``'both'``, ``'bottom'``, ``'none'``, ``'top'``): whether the header should be
     separated from the file content with a new line; defaults to ``'both'``
 
-* **heredoc_indentation** [@PHP73Migration, @PHP8Migration]
+* **heredoc_indentation** [@PHP73Migration, @PHP80Migration]
 
   Heredoc/nowdoc content must be properly indented. Requires PHP >= 7.3.
 
@@ -776,7 +776,7 @@ Choose from the list of available rules:
 
   Convert ``heredoc`` to ``nowdoc`` where possible.
 
-* **implode_call** [@Symfony:risky, @PhpCsFixer:risky, @PHP8Migration:risky]
+* **implode_call** [@Symfony:risky, @PhpCsFixer:risky, @PHP80Migration:risky]
 
   Function ``implode`` must be called with 2 arguments in the documented
   order.
@@ -972,7 +972,7 @@ Choose from the list of available rules:
 
   All instances created with new keyword must be followed by braces.
 
-* **no_alias_functions** [@Symfony:risky, @PhpCsFixer:risky, @PHP8Migration:risky]
+* **no_alias_functions** [@Symfony:risky, @PhpCsFixer:risky, @PHP80Migration:risky]
 
   Master functions shall be used instead of aliases.
 
@@ -1180,7 +1180,7 @@ Choose from the list of available rules:
   - ``namespaces`` (``bool``): remove unneeded curly braces from bracketed
     namespaces; defaults to ``false``
 
-* **no_unneeded_final_method** [@Symfony:risky, @PhpCsFixer:risky, @PHP8Migration:risky]
+* **no_unneeded_final_method** [@Symfony:risky, @PhpCsFixer:risky, @PHP80Migration:risky]
 
   A ``final`` class must not have ``final`` methods and ``private`` methods must
   not be ``final``.
@@ -1194,7 +1194,7 @@ Choose from the list of available rules:
 
   *Risky rule: modifies the signature of functions; therefore risky when using systems (such as some Symfony components) that rely on those (for example through reflection).*
 
-* **no_unset_cast** [@PhpCsFixer, @PHP8Migration]
+* **no_unset_cast** [@PhpCsFixer, @PHP80Migration]
 
   Variables must be set ``null`` instead of using ``(unset)`` casting.
 
@@ -1230,7 +1230,7 @@ Choose from the list of available rules:
 
   Remove trailing whitespace at the end of blank lines.
 
-* **non_printable_character** [@Symfony:risky, @PhpCsFixer:risky, @PHP70Migration:risky, @PHP71Migration:risky, @PHP8Migration:risky]
+* **non_printable_character** [@Symfony:risky, @PhpCsFixer:risky, @PHP70Migration:risky, @PHP71Migration:risky, @PHP80Migration:risky]
 
   Remove Zero-width space (ZWSP), Non-breaking space (NBSP) and other
   invisible unicode symbols.
@@ -1242,7 +1242,7 @@ Choose from the list of available rules:
   - ``use_escape_sequences_in_strings`` (``bool``): whether characters should be
     replaced with escape sequences in strings; defaults to ``false``
 
-* **normalize_index_brace** [@Symfony, @PhpCsFixer, @PHP8Migration]
+* **normalize_index_brace** [@Symfony, @PhpCsFixer, @PHP80Migration]
 
   Array index should always be written by using square braces.
 
@@ -1658,7 +1658,7 @@ Choose from the list of available rules:
   ``@var`` and ``@type`` annotations of classy properties should not contain
   the name.
 
-* **pow_to_exponentiation** [@PHP56Migration:risky, @PHP70Migration:risky, @PHP71Migration:risky, @PHP8Migration:risky]
+* **pow_to_exponentiation** [@PHP56Migration:risky, @PHP70Migration:risky, @PHP71Migration:risky, @PHP80Migration:risky]
 
   Converts ``pow`` to the ``**`` operator.
 
@@ -1691,7 +1691,7 @@ Choose from the list of available rules:
 
   *Risky rule: this fixer may change your class name, which will break the code that depends on the old name.*
 
-* **random_api_migration** [@PHP70Migration:risky, @PHP71Migration:risky, @PHP8Migration:risky]
+* **random_api_migration** [@PHP70Migration:risky, @PHP71Migration:risky, @PHP80Migration:risky]
 
   Replaces ``rand``, ``srand``, ``getrandmax`` functions calls with their ``mt_*``
   analogs.
@@ -1862,7 +1862,7 @@ Choose from the list of available rules:
 
   Standardize spaces around ternary operator.
 
-* **ternary_to_null_coalescing** [@PHP70Migration, @PHP71Migration, @PHP73Migration, @PHP8Migration]
+* **ternary_to_null_coalescing** [@PHP70Migration, @PHP71Migration, @PHP73Migration, @PHP80Migration]
 
   Use ``null`` coalescing operator ``??`` where possible. Requires PHP >= 7.0.
 
@@ -1884,7 +1884,7 @@ Choose from the list of available rules:
 
   Unary operators should be placed adjacent to their operands.
 
-* **visibility_required** [@PSR2, @Symfony, @PhpCsFixer, @PHP71Migration, @PHP73Migration, @PHP8Migration]
+* **visibility_required** [@PSR2, @Symfony, @PhpCsFixer, @PHP71Migration, @PHP73Migration, @PHP80Migration]
 
   Visibility MUST be declared on all properties and methods; ``abstract``
   and ``final`` MUST be declared before the visibility; ``static`` MUST be
@@ -1896,7 +1896,7 @@ Choose from the list of available rules:
     elements to fix (PHP >= 7.1 required for ``const``); defaults to
     ``['property', 'method']``
 
-* **void_return** [@PHP71Migration:risky, @PHP8Migration:risky]
+* **void_return** [@PHP71Migration:risky, @PHP80Migration:risky]
 
   Add ``void`` return type to functions with missing or empty return
   statements, but priority is given to ``@return`` annotations. Requires

+ 15 - 4
src/Fixer/Casing/NativeFunctionTypeDeclarationCasingFixer.php

@@ -40,6 +40,7 @@ final class NativeFunctionTypeDeclarationCasingFixer extends AbstractFixer
      * iterable PHP 7.1.0
      * void     PHP 7.1.0
      * object   PHP 7.2.0
+     * static   PHP 8.0.0 (return type only)
      *
      * @var array<string, true>
      */
@@ -86,6 +87,10 @@ final class NativeFunctionTypeDeclarationCasingFixer extends AbstractFixer
             $this->hints = array_merge($this->hints, ['object' => true]);
         }
 
+        if (\PHP_VERSION_ID >= 80000) {
+            $this->hints = array_merge($this->hints, ['static' => true]);
+        }
+
         $this->functionsAnalyzer = new FunctionsAnalyzer();
     }
 
@@ -162,16 +167,22 @@ final class NativeFunctionTypeDeclarationCasingFixer extends AbstractFixer
             return;
         }
 
-        $argumentIndex = $type->getStartIndex();
-        if ($argumentIndex !== $type->getEndIndex()) {
-            return; // the type to fix are always unqualified and so are always composed as one token
+        $argumentStartIndex = $type->getStartIndex();
+        $argumentExpectedEndIndex = $type->isNullable()
+            ? $tokens->getNextMeaningfulToken($argumentStartIndex)
+            : $argumentStartIndex
+        ;
+
+        if ($argumentExpectedEndIndex !== $type->getEndIndex()) {
+            return; // the type to fix is always unqualified and so is always composed of one token and possible a nullable '?' one
         }
 
         $lowerCasedName = strtolower($type->getName());
+
         if (!isset($this->hints[$lowerCasedName])) {
             return; // check of type is of interest based on name (slower check than previous index based)
         }
 
-        $tokens[$argumentIndex] = new Token([$tokens[$argumentIndex]->getId(), $lowerCasedName]);
+        $tokens[$argumentExpectedEndIndex] = new Token([$tokens[$argumentExpectedEndIndex]->getId(), $lowerCasedName]);
     }
 }

+ 34 - 8
src/Fixer/FunctionNotation/PhpdocToReturnTypeFixer.php

@@ -125,6 +125,19 @@ function bar() {}
                     new VersionSpecification(70100),
                     ['scalar_types' => false]
                 ),
+                new VersionSpecificCodeSample(
+                    '<?php
+final class Foo {
+    /**
+     * @return static
+     */
+    public function create($prototype) {
+        return new static($prototype);
+    }
+}
+',
+                    new VersionSpecification(80000)
+                ),
             ],
             null,
             'This rule is EXPERIMENTAL and [1] is not covered with backward compatibility promise. [2] `@return` annotation is mandatory for the fixer to make changes, signatures of methods without it (no docblock, inheritdocs) will not be fixed. [3] Manual actions are required if inherited signatures are not properly documented. [4] `@inheritdocs` support is under construction.'
@@ -189,11 +202,13 @@ function bar() {}
             }
 
             $funcName = $tokens->getNextMeaningfulToken($index);
+
             if ($tokens[$funcName]->equalsAny($this->excludeFuncNames, false)) {
                 continue;
             }
 
             $returnTypeAnnotation = $this->findReturnAnnotations($tokens, $index);
+
             if (1 !== \count($returnTypeAnnotation)) {
                 continue;
             }
@@ -233,7 +248,7 @@ function bar() {}
             }
 
             if ('static' === $returnType) {
-                $returnType = 'self';
+                $returnType = \PHP_VERSION_ID < 80000 ? 'self' : 'static';
             }
 
             if (isset($this->skippedTypes[$returnType])) {
@@ -299,11 +314,14 @@ function bar() {}
         static $specialTypes = [
             'array' => [CT::T_ARRAY_TYPEHINT, 'array'],
             'callable' => [T_CALLABLE, 'callable'],
+            'static' => [T_STATIC, 'static'],
         ];
+
         $newTokens = [
             new Token([CT::T_TYPE_COLON, ':']),
             new Token([T_WHITESPACE, ' ']),
         ];
+
         if (true === $isNullable) {
             $newTokens[] = new Token([CT::T_NULLABLE_TYPE, '?']);
         }
@@ -311,15 +329,23 @@ function bar() {}
         if (isset($specialTypes[$returnType])) {
             $newTokens[] = new Token($specialTypes[$returnType]);
         } else {
-            foreach (explode('\\', $returnType) as $nsIndex => $value) {
-                if (0 === $nsIndex && '' === $value) {
-                    continue;
-                }
+            $returnTypeUnqualified = ltrim($returnType, '\\');
+
+            if (isset($this->scalarTypes[$returnTypeUnqualified]) || isset($this->versionSpecificTypes[$returnTypeUnqualified])) {
+                // 'scalar's, 'void', 'iterable' and 'object' must be unqualified
+                $newTokens[] = new Token([T_STRING, $returnTypeUnqualified]);
+            } else {
+                foreach (explode('\\', $returnType) as $nsIndex => $value) {
+                    if (0 === $nsIndex && '' === $value) {
+                        continue;
+                    }
+
+                    if (0 < $nsIndex) {
+                        $newTokens[] = new Token([T_NS_SEPARATOR, '\\']);
+                    }
 
-                if (0 < $nsIndex) {
-                    $newTokens[] = new Token([T_NS_SEPARATOR, '\\']);
+                    $newTokens[] = new Token([T_STRING, $value]);
                 }
-                $newTokens[] = new Token([T_STRING, $value]);
             }
         }
 

+ 2 - 2
src/RuleSet.php

@@ -326,12 +326,12 @@ final class RuleSet implements RuleSetInterface
             '@PHP71Migration' => true,
             'heredoc_indentation' => true,
         ],
-        '@PHP8Migration' => [
+        '@PHP80Migration' => [
             '@PHP73Migration' => true,
             'no_unset_cast' => true,
             'normalize_index_brace' => true,
         ],
-        '@PHP8Migration:risky' => [
+        '@PHP80Migration:risky' => [
             '@PHP71Migration:risky' => true,
             'implode_call' => true,
             'no_alias_functions' => ['sets' => ['@all']],

+ 34 - 0
tests/Fixer/Casing/NativeFunctionTypeDeclarationCasingFixerTest.php

@@ -169,6 +169,14 @@ function Foo(INTEGER $a) {}
                 '<?php function Foo(iterable $A): void {}',
                 '<?php function Foo(ITERABLE $A): VOID {}',
             ],
+            [
+                '<?php function Foo(?int $A): void {}',
+                '<?php function Foo(?INT $A): VOID {}',
+            ],
+            [
+                '<?php function Foo(string $A): ?/* */int {}',
+                '<?php function Foo(STRING $A): ?/* */INT {}',
+            ],
         ];
     }
 
@@ -193,4 +201,30 @@ function Foo(INTEGER $a) {}
             ],
         ];
     }
+
+    /**
+     * @param string $expected
+     * @param string $input
+     *
+     * @dataProvider provideFix80Cases
+     * @requires PHP 8.0
+     */
+    public function testFix80($expected, $input)
+    {
+        $this->doTest($expected, $input);
+    }
+
+    public function provideFix80Cases()
+    {
+        return [
+            [
+                '<?php class T { public function Foo(object $A): static {}}',
+                '<?php class T { public function Foo(object $A): StatiC {}}',
+            ],
+            [
+                '<?php class T { public function Foo(object $A): ?static {}}',
+                '<?php class T { public function Foo(object $A): ?StatiC {}}',
+            ],
+        ];
+    }
 }

+ 76 - 8
tests/Fixer/FunctionNotation/PhpdocToReturnTypeFixerTest.php

@@ -20,6 +20,7 @@ use PhpCsFixer\Tests\Test\AbstractFixerTestCase;
  * @internal
  *
  * @covers \PhpCsFixer\Fixer\FunctionNotation\PhpdocToReturnTypeFixer
+ * @requires PHP 7.0
  */
 final class PhpdocToReturnTypeFixerTest extends AbstractFixerTestCase
 {
@@ -32,10 +33,7 @@ final class PhpdocToReturnTypeFixerTest extends AbstractFixerTestCase
      */
     public function testFix($expected, $input = null, $versionSpecificFix = null, array $config = [])
     {
-        if (
-            (null !== $input && \PHP_VERSION_ID < 70000)
-            || (null !== $versionSpecificFix && \PHP_VERSION_ID < $versionSpecificFix)
-        ) {
+        if (null !== $versionSpecificFix && \PHP_VERSION_ID < $versionSpecificFix) {
             $expected = $input;
             $input = null;
         }
@@ -47,7 +45,7 @@ final class PhpdocToReturnTypeFixerTest extends AbstractFixerTestCase
 
     public function provideFixCases()
     {
-        return [
+        $tests = [
             'no phpdoc return' => [
                 '<?php function my_foo() {}',
             ],
@@ -143,6 +141,10 @@ final class PhpdocToReturnTypeFixerTest extends AbstractFixerTestCase
                 '<?php /** @return bool */ function my_foo(): bool {}',
                 '<?php /** @return bool */ function my_foo() {}',
             ],
+            'fix scalar types by default, bool, make unqualified' => [
+                '<?php /** @return \bool */ function my_foo(): bool {}',
+                '<?php /** @return \bool */ function my_foo() {}',
+            ],
             'fix scalar types by default, false' => [
                 '<?php /** @return false */ function my_foo(): bool {}',
                 '<?php /** @return false */ function my_foo() {}',
@@ -177,17 +179,18 @@ final class PhpdocToReturnTypeFixerTest extends AbstractFixerTestCase
                     }
                 ',
             ],
-            'report static as self' => [
+            'nullable self accessor' => [
                 '<?php
                     class Foo {
-                        /** @return static */ function my_foo(): self {}
+                        /** @return self|null */ function my_foo(): ?self {}
                     }
                 ',
                 '<?php
                     class Foo {
-                        /** @return static */ function my_foo() {}
+                        /** @return self|null */ function my_foo() {}
                     }
                 ',
+                70100,
             ],
             'skip resource special type' => [
                 '<?php /** @return resource */ function my_foo() {}',
@@ -257,6 +260,25 @@ final class PhpdocToReturnTypeFixerTest extends AbstractFixerTestCase
                 ',
             ],
         ];
+
+        foreach ($tests as $index => $test) {
+            yield $index => $test;
+        }
+
+        if (\PHP_VERSION_ID < 80000) {
+            yield 'report static as self' => [
+                '<?php
+                    class Foo {
+                        /** @return static */ function my_foo(): self {}
+                    }
+                ',
+                '<?php
+                    class Foo {
+                        /** @return static */ function my_foo() {}
+                    }
+                ',
+            ];
+        }
     }
 
     /**
@@ -281,4 +303,50 @@ final class PhpdocToReturnTypeFixerTest extends AbstractFixerTestCase
             ],
         ];
     }
+
+    /**
+     * @param string      $expected
+     * @param null|string $input
+     *
+     * @dataProvider provideFixPhp80Cases
+     * @requires PHP 8.0
+     */
+    public function testFixPhp80($expected, $input = null)
+    {
+        $this->doTest($expected, $input);
+    }
+
+    public function provideFixPhp80Cases()
+    {
+        return [
+            'static' => [
+                '<?php
+                    final class Foo {
+                        /** @return static */
+                        public function something(): static {}
+                    }
+                ',
+                '<?php
+                    final class Foo {
+                        /** @return static */
+                        public function something() {}
+                    }
+                ',
+            ],
+            'nullable static' => [
+                '<?php
+                    final class Foo {
+                        /** @return null|static */
+                        public function something(): ?static {}
+                    }
+                ',
+                '<?php
+                    final class Foo {
+                        /** @return null|static */
+                        public function something() {}
+                    }
+                ',
+            ],
+        ];
+    }
 }

+ 6 - 0
tests/Fixtures/Integration/set/@PHP80Migration-risky.test

@@ -0,0 +1,6 @@
+--TEST--
+Integration of @PHP80Migration:risky.
+--RULESET--
+{"@PHP80Migration:risky": true}
+--REQUIREMENTS--
+{"php": 80000}

+ 0 - 0
tests/Fixtures/Integration/set/@PHP8Migration-risky.test-in.php → tests/Fixtures/Integration/set/@PHP80Migration-risky.test-in.php


+ 0 - 0
tests/Fixtures/Integration/set/@PHP8Migration-risky.test-out.php → tests/Fixtures/Integration/set/@PHP80Migration-risky.test-out.php


+ 0 - 6
tests/Fixtures/Integration/set/@PHP8Migration-risky.test

@@ -1,6 +0,0 @@
---TEST--
-Integration of @PHP8Migration:risky.
---RULESET--
-{"@PHP8Migration:risky": true}
---REQUIREMENTS--
-{"php": 80000}

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