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

minor #5277 PHP8 - Constructor Property Promotion support (SpacePossum)

This PR was merged into the 2.16 branch.

Discussion
----------

PHP8 - Constructor Property Promotion support

Commits
-------

f838ff36b PHP8 - Constructor Property Promotion support
SpacePossum 4 лет назад
Родитель
Сommit
7e481ac675

+ 3 - 0
src/Tokenizer/CT.php

@@ -44,6 +44,9 @@ final class CT
     const T_TYPE_COLON = 10025;
     const T_USE_LAMBDA = 10026;
     const T_USE_TRAIT = 10027;
+    const T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC = 10028;
+    const T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED = 10029;
+    const T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE = 10030;
 
     private function __construct()
     {

+ 4 - 1
src/Tokenizer/Token.php

@@ -337,10 +337,13 @@ class Token
                 CT::T_ARRAY_TYPEHINT => CT::T_ARRAY_TYPEHINT,
                 CT::T_CLASS_CONSTANT => CT::T_CLASS_CONSTANT,
                 CT::T_CONST_IMPORT => CT::T_CONST_IMPORT,
+                CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE => CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE,
+                CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED => CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED,
+                CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC => CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC,
                 CT::T_FUNCTION_IMPORT => CT::T_FUNCTION_IMPORT,
                 CT::T_NAMESPACE_OPERATOR => CT::T_NAMESPACE_OPERATOR,
-                CT::T_USE_TRAIT => CT::T_USE_TRAIT,
                 CT::T_USE_LAMBDA => CT::T_USE_LAMBDA,
+                CT::T_USE_TRAIT => CT::T_USE_TRAIT,
             ];
         }
 

+ 78 - 0
src/Tokenizer/Transformer/ConstructorPromotionTransformer.php

@@ -0,0 +1,78 @@
+<?php
+
+/*
+ * 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\Tokenizer\Transformer;
+
+use PhpCsFixer\Tokenizer\AbstractTransformer;
+use PhpCsFixer\Tokenizer\CT;
+use PhpCsFixer\Tokenizer\Token;
+use PhpCsFixer\Tokenizer\Tokens;
+
+/**
+ * Transforms for Constructor Property Promotion.
+ *
+ * Transform T_PUBLIC, T_PROTECTED and T_PRIVATE of Constructor Property Promotion into custom tokens.
+ *
+ * @internal
+ */
+final class ConstructorPromotionTransformer extends AbstractTransformer
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function getRequiredPhpVersionId()
+    {
+        return 80000;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function process(Tokens $tokens, Token $token, $index)
+    {
+        if (!$tokens[$index]->isGivenKind(T_FUNCTION)) {
+            return;
+        }
+
+        $index = $tokens->getNextMeaningfulToken($index);
+
+        if (!$tokens[$index]->isGivenKind(T_STRING) || '__construct' !== strtolower($tokens[$index]->getContent())) {
+            return;
+        }
+
+        /** @var int $openIndex */
+        $openIndex = $tokens->getNextMeaningfulToken($index); // we are @ '(' now
+        $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex);
+
+        for ($index = $openIndex; $index < $closeIndex; ++$index) {
+            if ($tokens[$index]->isGivenKind(T_PUBLIC)) {
+                $tokens[$index] = new Token([CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, $tokens[$index]->getContent()]);
+            } elseif ($tokens[$index]->isGivenKind(T_PROTECTED)) {
+                $tokens[$index] = new Token([CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, $tokens[$index]->getContent()]);
+            } elseif ($tokens[$index]->isGivenKind(T_PRIVATE)) {
+                $tokens[$index] = new Token([CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE, $tokens[$index]->getContent()]);
+            }
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getDeprecatedCustomTokens()
+    {
+        return [
+            CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC,
+            CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED,
+            CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE,
+        ];
+    }
+}

+ 21 - 0
tests/Fixer/Casing/LowercaseKeywordsFixerTest.php

@@ -97,5 +97,26 @@ echo MATCH (1) {
     2 => 7,
 };',
         ];
+
+        yield [
+            '<?php
+class Point {
+    public function __construct(
+        public float $x = 0.0,
+        protected float $y = 0.0,
+        private float $z = 0.0,
+    ) {}
+}
+',
+            '<?php
+class Point {
+    public function __construct(
+        PUBLIC float $x = 0.0,
+        Protected float $y = 0.0,
+        privatE float $z = 0.0,
+    ) {}
+}
+',
+        ];
     }
 }

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

@@ -242,4 +242,20 @@ null;#13
             '<?php class Foo { public $bar = \/* oh hai! */null; }',
         ];
     }
+
+    /**
+     * @requires PHP 8.0
+     */
+    public function testFixPhp80()
+    {
+        $this->doTest('<?php
+class Point {
+    public function __construct(
+        public ?float $x = null,
+        protected ?float $y = null,
+        private ?float $z = null,
+    ) {}
+}
+');
+    }
 }

+ 7 - 0
tests/Fixer/ClassNotation/VisibilityRequiredFixerTest.php

@@ -795,26 +795,33 @@ AB# <- this is the name
         yield [
             '<?php class Foo { private int $foo; }',
         ];
+
         yield [
             '<?php class Foo { protected ?string $foo; }',
         ];
+
         yield [
             '<?php class Foo { public ? string $foo; }',
         ];
+
         yield [
             '<?php class Foo { public ? string $foo; }',
             '<?php class Foo { var ? string $foo; }',
         ];
+
         yield [
             '<?php class Foo { public static Foo\Bar $foo; }',
             '<?php class Foo { static public Foo\Bar $foo; }',
         ];
+
         yield [
             '<?php class Foo { public array $foo; }',
         ];
+
         yield [
             '<?php class Foo { public ?array $foo; }',
         ];
+
         yield [
             '<?php class Foo { public static ?array $foo; }',
             '<?php class Foo { static public ?array $foo; }',

+ 2 - 0
tests/Test/AbstractTransformerTestCase.php

@@ -107,6 +107,7 @@ abstract class AbstractTransformerTestCase extends TestCase
             return;
         }
 
+        Tokens::clearCache();
         $tokens = Tokens::fromCode('<?php ');
 
         foreach ($tokens as $index => $token) {
@@ -118,6 +119,7 @@ abstract class AbstractTransformerTestCase extends TestCase
 
     protected function doTest($source, array $expectedTokens = [], array $observedKindsOrPrototypes = [])
     {
+        Tokens::clearCache();
         $tokens = Tokens::fromCode($source);
 
         static::assertSame(

+ 125 - 0
tests/Tokenizer/Transformer/ConstructorPromotionTransformerTest.php

@@ -0,0 +1,125 @@
+<?php
+
+/*
+ * 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\Tokenizer\Transformer;
+
+use PhpCsFixer\Tests\Test\AbstractTransformerTestCase;
+use PhpCsFixer\Tokenizer\CT;
+use PhpCsFixer\Tokenizer\Tokens;
+
+/**
+ * @internal
+ *
+ * @covers \PhpCsFixer\Tokenizer\Transformer\ConstructorPromotionTransformer
+ */
+final class ConstructorPromotionTransformerTest extends AbstractTransformerTestCase
+{
+    /**
+     * @param string $source
+     *
+     * @dataProvider provideProcessCases
+     * @requires PHP 8.0
+     */
+    public function testProcess($source, array $expectedTokens)
+    {
+        $this->doTest(
+            $source,
+            $expectedTokens,
+            [
+                CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC,
+                CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED,
+                CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE,
+            ]
+        );
+    }
+
+    public function provideProcessCases()
+    {
+        yield [
+            '<?php
+class Point {
+    public function __construct(
+        public float $x = 0.0,
+        protected float $y = 0.0,
+        private float $z = 0.0,
+    ) {}
+}
+',
+            [
+                14 => CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC,
+                25 => CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED,
+                36 => CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE,
+            ],
+        ];
+
+        yield [
+            '<?php $a = new class {function/* 1 */__CONSTRUCT/* 2 */(/* 3 */public float $x,protected float $y,private float $z) {}}',
+            [
+                16 => CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC,
+                22 => CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED,
+                28 => CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE,
+            ],
+        ];
+    }
+
+    public function testNotChange()
+    {
+        $code = '<?php
+            // class Foo1 {
+            //     function __construct(
+            //         private float $z = new class {
+            //             public function __construct() {}
+            //         }
+            //     ) {}
+            // }
+
+            // class Foo2 {
+            //     function __construct(
+            //         private array $z = [new class {}],
+            //     ) {}
+            // }
+
+            // class Foo3 {
+            //     public function __construct(
+            //         public float $x = 0.0,
+            //         protected float $y = 0.0,
+            //         private float $z = 0.0,
+            //     ) {}
+            // }
+
+            function __construct(/* public */ $foo){}
+
+            class Foo4 {
+                public function construct(/* public */ $foo)
+                {}
+            }
+
+            class Foo5 {
+                public $foo1;
+                protected $foo2;
+                private $foo3;
+
+                public function __construct(/* public */ $foo){} public $foo4;
+            }
+        ';
+
+        Tokens::clearCache();
+
+        foreach (Tokens::fromCode($code) as $token) {
+            static::assertFalse($token->isGivenKind([
+                CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC,
+                CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED,
+                CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE,
+            ]));
+        }
+    }
+}