Просмотр исходного кода

minor #4593 Ensure compatibility with PHP 7.4 typed properties (julienfalque)

This PR was merged into the 2.15 branch.

Discussion
----------

Ensure compatibility with PHP 7.4 typed properties

Related to #4382.

Commits
-------

3cd398eb Ensure compatibility with PHP 7.4 typed properties
Dariusz Ruminski 5 лет назад
Родитель
Сommit
189994880e

+ 3 - 2
README.rst

@@ -1065,7 +1065,8 @@ Choose from the list of available rules:
 
 * **no_null_property_initialization** [@PhpCsFixer]
 
-  Properties MUST not be explicitly initialized with ``null``.
+  Properties MUST not be explicitly initialized with ``null`` except when
+  they have a type declaration (PHP 7.4).
 
 * **no_php4_constructor**
 
@@ -1169,7 +1170,7 @@ Choose from the list of available rules:
 
   Properties should be set to ``null`` instead of using ``unset``.
 
-  *Risky rule: changing variables to ``null`` instead of unsetting them will mean they still show up when looping over class variables.*
+  *Risky rule: changing variables to ``null`` instead of unsetting them will mean they still show up when looping over class variables. With PHP 7.4, this rule might introduce ``null`` assignments to property whose type declaration does not allow it.*
 
 * **no_unused_imports** [@Symfony, @PhpCsFixer]
 

+ 2 - 1
src/Fixer/ClassNotation/ClassAttributesSeparationFixer.php

@@ -21,6 +21,7 @@ use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
 use PhpCsFixer\FixerDefinition\CodeSample;
 use PhpCsFixer\FixerDefinition\FixerDefinition;
 use PhpCsFixer\Preg;
+use PhpCsFixer\Tokenizer\CT;
 use PhpCsFixer\Tokenizer\Token;
 use PhpCsFixer\Tokenizer\Tokens;
 use PhpCsFixer\Tokenizer\TokensAnalyzer;
@@ -228,7 +229,7 @@ class Sample
      */
     private function fixSpaceAboveClassElement(Tokens $tokens, $classStartIndex, $elementIndex)
     {
-        static $methodAttr = [T_PRIVATE, T_PROTECTED, T_PUBLIC, T_ABSTRACT, T_FINAL, T_STATIC];
+        static $methodAttr = [T_PRIVATE, T_PROTECTED, T_PUBLIC, T_ABSTRACT, T_FINAL, T_STATIC, T_STRING, T_NS_SEPARATOR, T_VAR, CT::T_NULLABLE_TYPE];
 
         $nonWhiteAbove = null;
 

+ 1 - 1
src/Fixer/ClassNotation/NoNullPropertyInitializationFixer.php

@@ -28,7 +28,7 @@ final class NoNullPropertyInitializationFixer extends AbstractFixer
     public function getDefinition()
     {
         return new FixerDefinition(
-            'Properties MUST not be explicitly initialized with `null`.',
+            'Properties MUST not be explicitly initialized with `null` except when they have a type declaration (PHP 7.4).',
             [
                 new CodeSample(
                     '<?php

+ 22 - 13
src/Fixer/ClassNotation/SingleClassElementPerStatementFixer.php

@@ -87,7 +87,7 @@ final class Example
                 continue; // not in configuration
             }
 
-            $this->fixElement($tokens, $index);
+            $this->fixElement($tokens, $element['type'], $index);
         }
     }
 
@@ -108,9 +108,10 @@ final class Example
     }
 
     /**
-     * @param int $index
+     * @param string $type
+     * @param int    $index
      */
-    private function fixElement(Tokens $tokens, $index)
+    private function fixElement(Tokens $tokens, $type, $index)
     {
         $tokensAnalyzer = new TokensAnalyzer($tokens);
         $repeatIndex = $index;
@@ -142,16 +143,18 @@ final class Example
         $start = $tokens->getPrevTokenOfKind($index, [';', '{', '}']);
         $this->expandElement(
             $tokens,
+            $type,
             $tokens->getNextMeaningfulToken($start),
             $tokens->getNextTokenOfKind($index, [';'])
         );
     }
 
     /**
-     * @param int $startIndex
-     * @param int $endIndex
+     * @param string $type
+     * @param int    $startIndex
+     * @param int    $endIndex
      */
-    private function expandElement(Tokens $tokens, $startIndex, $endIndex)
+    private function expandElement(Tokens $tokens, $type, $startIndex, $endIndex)
     {
         $divisionContent = null;
         if ($tokens[$startIndex - 1]->isWhitespace()) {
@@ -191,31 +194,37 @@ final class Example
             }
 
             // collect modifiers
-            $sequence = $this->getModifiersSequences($tokens, $startIndex, $endIndex);
+            $sequence = $this->getModifiersSequences($tokens, $type, $startIndex, $endIndex);
             $tokens->insertAt($i + 2, $sequence);
         }
     }
 
     /**
-     * @param int $startIndex
-     * @param int $endIndex
+     * @param string $type
+     * @param int    $startIndex
+     * @param int    $endIndex
      *
      * @return Token[]
      */
-    private function getModifiersSequences(Tokens $tokens, $startIndex, $endIndex)
+    private function getModifiersSequences(Tokens $tokens, $type, $startIndex, $endIndex)
     {
+        if ('property' === $type) {
+            $tokenKinds = [T_PUBLIC, T_PROTECTED, T_PRIVATE, T_STATIC, T_VAR, T_STRING, T_NS_SEPARATOR, CT::T_NULLABLE_TYPE];
+        } else {
+            $tokenKinds = [T_PUBLIC, T_PROTECTED, T_PRIVATE, T_CONST];
+        }
+
         $sequence = [];
         for ($i = $startIndex; $i < $endIndex - 1; ++$i) {
-            if ($tokens[$i]->isWhitespace() || $tokens[$i]->isComment()) {
+            if ($tokens[$i]->isComment()) {
                 continue;
             }
 
-            if (!$tokens[$i]->isGivenKind([T_PUBLIC, T_PROTECTED, T_PRIVATE, T_STATIC, T_CONST, T_VAR])) {
+            if (!$tokens[$i]->isWhitespace() && !$tokens[$i]->isGivenKind($tokenKinds)) {
                 break;
             }
 
             $sequence[] = clone $tokens[$i];
-            $sequence[] = new Token([T_WHITESPACE, ' ']);
         }
 
         return $sequence;

+ 15 - 1
src/Fixer/ClassNotation/VisibilityRequiredFixer.php

@@ -22,6 +22,7 @@ use PhpCsFixer\FixerDefinition\CodeSample;
 use PhpCsFixer\FixerDefinition\FixerDefinition;
 use PhpCsFixer\FixerDefinition\VersionSpecification;
 use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
+use PhpCsFixer\Tokenizer\CT;
 use PhpCsFixer\Tokenizer\Token;
 use PhpCsFixer\Tokenizer\Tokens;
 use PhpCsFixer\Tokenizer\TokensAnalyzer;
@@ -115,18 +116,31 @@ class Sample
             $abstractFinalIndex = null;
             $visibilityIndex = null;
             $staticIndex = null;
+            $typeIndex = null;
             $prevIndex = $tokens->getPrevMeaningfulToken($index);
-            while ($tokens[$prevIndex]->isGivenKind([T_ABSTRACT, T_FINAL, T_PRIVATE, T_PROTECTED, T_PUBLIC, T_STATIC, T_VAR])) {
+
+            $expectedKinds = [T_ABSTRACT, T_FINAL, T_PRIVATE, T_PROTECTED, T_PUBLIC, T_STATIC, T_VAR];
+            if ('property' === $element['type']) {
+                $expectedKinds = array_merge($expectedKinds, [T_STRING, T_NS_SEPARATOR, CT::T_NULLABLE_TYPE]);
+            }
+
+            while ($tokens[$prevIndex]->isGivenKind($expectedKinds)) {
                 if ($tokens[$prevIndex]->isGivenKind([T_ABSTRACT, T_FINAL])) {
                     $abstractFinalIndex = $prevIndex;
                 } elseif ($tokens[$prevIndex]->isGivenKind(T_STATIC)) {
                     $staticIndex = $prevIndex;
+                } elseif ($tokens[$prevIndex]->isGivenKind([T_STRING, T_NS_SEPARATOR, CT::T_NULLABLE_TYPE])) {
+                    $typeIndex = $prevIndex;
                 } else {
                     $visibilityIndex = $prevIndex;
                 }
                 $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex);
             }
 
+            if (null !== $typeIndex) {
+                $index = $typeIndex;
+            }
+
             if ($tokens[$prevIndex]->equals(',')) {
                 continue;
             }

+ 2 - 1
src/Fixer/LanguageConstruct/NoUnsetOnPropertyFixer.php

@@ -34,7 +34,8 @@ final class NoUnsetOnPropertyFixer extends AbstractFixer
             [new CodeSample("<?php\nunset(\$this->a);\n")],
             null,
             'Changing variables to `null` instead of unsetting them will mean they still show up '.
-            'when looping over class variables.'
+            'when looping over class variables. With PHP 7.4, this rule might introduce `null` assignments to '.
+            'property whose type declaration does not allow it.'
         );
     }
 

+ 1 - 1
src/Tokenizer/Transformer/NullableTypeTransformer.php

@@ -63,7 +63,7 @@ final class NullableTypeTransformer extends AbstractTransformer
         $prevIndex = $tokens->getPrevMeaningfulToken($index);
         $prevToken = $tokens[$prevIndex];
 
-        if ($prevToken->equalsAny(['(', ',', [CT::T_TYPE_COLON]])) {
+        if ($prevToken->equalsAny(['(', ',', [CT::T_TYPE_COLON], [T_PRIVATE], [T_PROTECTED], [T_PUBLIC], [T_VAR]])) {
             $tokens[$index] = new Token([CT::T_NULLABLE_TYPE, '?']);
         }
     }

+ 46 - 0
tests/Fixer/ClassNotation/ClassAttributesSeparationFixerTest.php

@@ -676,6 +676,17 @@ public function B(); // allowed comment
                 public function C(); // allowed comment
             }',
         ];
+        $cases[] = [
+            '<?php class Foo {
+                var $a;
+
+                var $b;
+            }',
+            '<?php class Foo {
+                var $a;
+                var $b;
+            }',
+        ];
 
         return $cases;
     }
@@ -1070,4 +1081,39 @@ private $d = 123;
             ],
         ];
     }
+
+    /**
+     * @param string      $expected
+     * @param null|string $input
+     *
+     * @dataProvider provideFix74Cases
+     * @requires PHP 7.4
+     */
+    public function testFix74($expected, $input = null)
+    {
+        $this->doTest($expected, $input);
+    }
+
+    public function provideFix74Cases()
+    {
+        yield [
+            '<?php
+            class Foo {
+                private ?int $foo;
+
+                protected string $bar;
+
+                public iterable $baz;
+
+                var ? Foo\Bar $qux;
+            }',
+            '<?php
+            class Foo {
+                private ?int $foo;
+                protected string $bar;
+                public iterable $baz;
+                var ? Foo\Bar $qux;
+            }',
+        ];
+    }
 }

+ 22 - 0
tests/Fixer/ClassNotation/NoNullPropertyInitializationFixerTest.php

@@ -199,4 +199,26 @@ null;#13
             ],
         ];
     }
+
+    /**
+     * @param string      $expected
+     * @param null|string $input
+     *
+     * @dataProvider provideFix74Cases
+     * @requires PHP 7.4
+     */
+    public function testFix74($expected, $input = null)
+    {
+        $this->doTest($expected, $input);
+    }
+
+    public function provideFix74Cases()
+    {
+        yield [
+            '<?php class Foo { protected ?int $bar = null; }',
+        ];
+        yield [
+            '<?php class Foo { protected ? string $bar = null; }',
+        ];
+    }
 }

+ 55 - 0
tests/Fixer/ClassNotation/OrderedClassElementsFixerTest.php

@@ -824,6 +824,61 @@ EOT
         ];
     }
 
+    /**
+     * @param string      $expected
+     * @param null|string $input
+     *
+     * @dataProvider provideFix74Cases
+     * @requires PHP 7.4
+     */
+    public function testFix74($expected, $input = null, array $configuration = null)
+    {
+        if (null !== $configuration) {
+            $this->fixer->configure($configuration);
+        }
+
+        $this->doTest($expected, $input);
+    }
+
+    public function provideFix74Cases()
+    {
+        yield [
+            '<?php
+            class Foo {
+                public iterable $baz;
+                var ? Foo\Bar $qux;
+                protected string $bar;
+                private ?int $foo;
+            }',
+            '<?php
+            class Foo {
+                private ?int $foo;
+                protected string $bar;
+                public iterable $baz;
+                var ? Foo\Bar $qux;
+            }',
+        ];
+        yield [
+            '<?php
+            class Foo {
+                public string $bar;
+                public iterable $baz;
+                public ?int $foo;
+                public ? Foo\Bar $qux;
+            }',
+            '<?php
+            class Foo {
+                public iterable $baz;
+                public ? Foo\Bar $qux;
+                public string $bar;
+                public ?int $foo;
+            }',
+            [
+                'sortAlgorithm' => 'alpha',
+            ],
+        ];
+    }
+
     public function testWrongConfig()
     {
         $this->expectException(\PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException::class);

Некоторые файлы не были показаны из-за большого количества измененных файлов