Repository: FriendsOfPHP/PHP-CS-Fixer (php)

Issue:
`TernaryOperatorSpacesFixer` incompatible with nullable constructor property promotion
## Bug report

After upgrading to PHP8 I have problems with CS Fixer. `TernaryOperatorSpacesFixer` inserts space after `?` that marks nullable arguments in constructors with property promotion:

```diff
--- Original
+++ New
@@ -7,7 +7,7 @@
 class Foo
 {
     public function __construct(
-        private ?string $foo = null,
+        private ? string $foo = null,
     ) {
         is_null(true ? '...' : null);
     }
```

### Code snippet that reproduces the problem

```php
<?php

class Foo
{
    public function __construct(
        private ?string $foo = null,
    ) {
        is_null(true ? '...' : null);
    }
}
```

**NOTE**: Ternary operator used anywhere in the class triggers this invalid change. Without actual ternary in the code, constructor is not touched. Without property promotion it works properly too.

Pull request description:
NullableTypeTransformer - constructor property promotion support
Fixes #5401

Patch:
diff --git a/src/Tokenizer/Transformer/NullableTypeTransformer.php b/src/Tokenizer/Transformer/NullableTypeTransformer.php
index d11558c2c..c444d47aa 100644
--- a/src/Tokenizer/Transformer/NullableTypeTransformer.php
+++ b/src/Tokenizer/Transformer/NullableTypeTransformer.php
@@ -55,7 +55,19 @@ public function process(Tokens $tokens, Token $token, $index)
         $prevIndex = $tokens->getPrevMeaningfulToken($index);
         $prevToken = $tokens[$prevIndex];
 
-        if ($prevToken->equalsAny(['(', ',', [CT::T_TYPE_COLON], [T_PRIVATE], [T_PROTECTED], [T_PUBLIC], [T_VAR], [T_STATIC]])) {
+        if ($prevToken->equalsAny([
+            '(',
+            ',',
+            [CT::T_TYPE_COLON],
+            [CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC],
+            [CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED],
+            [CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE],
+            [T_PRIVATE],
+            [T_PROTECTED],
+            [T_PUBLIC],
+            [T_VAR],
+            [T_STATIC],
+        ])) {
             $tokens[$index] = new Token([CT::T_NULLABLE_TYPE, '?']);
         }
     }


Test patch:
diff --git a/tests/Fixer/Operator/TernaryOperatorSpacesFixerTest.php b/tests/Fixer/Operator/TernaryOperatorSpacesFixerTest.php
index 4940f8d90..940bd9600 100644
--- a/tests/Fixer/Operator/TernaryOperatorSpacesFixerTest.php
+++ b/tests/Fixer/Operator/TernaryOperatorSpacesFixerTest.php
@@ -197,4 +197,36 @@ function A(){}
             ],
         ];
     }
+
+    /**
+     * @param string      $expected
+     * @param null|string $input
+     *
+     * @dataProvider provideFix80Cases
+     * @requires PHP 8.0
+     */
+    public function testFix80($expected, $input = null)
+    {
+        $this->doTest($expected, $input);
+    }
+
+    public function provideFix80Cases()
+    {
+        return [
+            'nullable types in constructor property promotion' => [
+                '<?php
+
+class Foo
+{
+    public function __construct(
+        private ?string $foo = null,
+        protected ?string $bar = null,
+        public ?string $xyz = null,
+    ) {
+        /* ternary operator to make the file a candidate for fixing */ true ? 1 : 0;
+    }
+}',
+            ],
+        ];
+    }
 }
diff --git a/tests/Tokenizer/Transformer/NullableTypeTransformerTest.php b/tests/Tokenizer/Transformer/NullableTypeTransformerTest.php
index d4e8a91da..098252139 100644
--- a/tests/Tokenizer/Transformer/NullableTypeTransformerTest.php
+++ b/tests/Tokenizer/Transformer/NullableTypeTransformerTest.php
@@ -135,4 +135,44 @@ public function provideProcess74Cases()
             ],
         ];
     }
+
+    /**
+     * @param string $source
+     *
+     * @dataProvider provideProcess80Cases
+     * @requires PHP 8.0
+     */
+    public function testProcess80($source, array $expectedTokens = [])
+    {
+        $this->doTest(
+            $source,
+            $expectedTokens,
+            [
+                CT::T_NULLABLE_TYPE,
+            ]
+        );
+    }
+
+    public function provideProcess80Cases()
+    {
+        return [
+            [
+                '<?php
+                    class Foo
+                    {
+                        public function __construct(
+                            private ?string $foo = null,
+                            protected ?string $bar = null,
+                            public ?string $xyz = null,
+                        ) {
+                        }
+                    }',
+                [
+                    17 => CT::T_NULLABLE_TYPE,
+                    29 => CT::T_NULLABLE_TYPE,
+                    41 => CT::T_NULLABLE_TYPE,
+                ],
+            ],
+        ];
+    }
 }