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:
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,
+                ],
+            ],
+        ];
+    }
 }