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

Issue:
ClassAttributesSeparationFixer - Option to add blank lines only before items with PHPDocs
I'd like to have a new option for `class_attributes_separation` rule that makes it add blank lines before items with PHPDocs only, e.g.:

```diff
 class Foo
 {
     private $foo;
     private $bar;
 }
 
 class Bar
 {
     /**
      * @var int
      */
     private $foo;
+
     /**
      * @var int
      */
     private $bar;
 }
```
instead of:
```diff
 class Foo
 {
     private $foo;
+
     private $bar;
 }
 
 class Bar
 {
     /**
      * @var int
      */
     private $foo;
+
     /**
      * @var int
      */
     private $bar;
 }
```

Maybe the format of this option should mimic the format of option `elements` as one might not want to enable the new behavior for all kinds of element. I would make it default to constants and properties.

WDYT?
ClassAttributesSeparationFixer - ['const' => 'none'] removing unexpected whitespaces if including docblocks
## Bug report

PHP version:
```
PHP 8.0.5 (cli) (built: Apr 28 2021 00:30:18) ( ZTS Visual C++ 2019 x64 )
Copyright (c) The PHP Group
Zend Engine v4.0.5, Copyright (c) Zend Technologies
    with Zend OPcache v8.0.5, Copyright (c), by Zend Technologies
    with Xdebug v3.0.4, Copyright (c) 2002-2021, by Derick Rethans
```

PHP-CS-Fixer version:
```
PHP CS Fixer 3.0.0 Constitution by Fabien Potencier and Dariusz Ruminski
```

Command used:
```
vendor/bin/php-cs-fixer fix -v
```

Relevant configuration:
```
'class_attributes_separation' => [
    'elements' => [
        'const' => 'none',
        'method' => 'one',
        'property' => 'one',
    ],
],
```

### Code snippet that reproduces the problem

case 1:
```diff
 class Foo
 {
     public const BAR = 1;
-
     public const BAZ = 2;

     public static function getX()
     {
         return 'X';
     }
 }
```
Change here is totally expected.

case 2:
```diff
 class Foo
 {
     /** @var int */
     public const BAR = 1;
-
     /** @var int */
     public const BAZ = 2;
-
     /**
      * Defaults
      *
      * @var array<int, string>
      */
     protected array $defaults = [];

     public static function getX()
     {
         return 'X';
     }
 }
```

For case 2, if the const has a doc block, it still strips the whitespaces around it. Then, when the last const is followed by a property with a docblock, the whitespace below the const is also removed.

Pull request:
ClassAttributesSeparationFixer - Introduce `only_if_meta` spacing option
Closes #4014 .

This PR introduces a new spacing option `only_if_meta` to `class_attributes_separation` fixer. The behavior of this option is technically the same with `none` except that when there is a `T_DOC_COMMENT` or `T_ATTRIBUTE` (or both) above the class element the option will now behave as `one`.

**Examples:**

`['elements' => ['const' => 'only_if_meta']]`
```php
// Before
class Sample
{
    /** @var int */
    const FOO = 1;
    /** @var int */
    const BAR = 2;
    #[Foo\Baz]
    const BAZ = 3;
    const OTHER = 4;
}

// After
class Sample
{
    /** @var int */
    const FOO = 1;

    /** @var int */
    const BAR = 2;

    #[Foo\Baz]
    const BAZ = 3;
    const OTHER = 4;
}
```

`['elements' => ['property' => 'only_if_meta']]`
```php
// Before
class Sample
{
    public $a;
    public $b;
    /** @var string */
    public $c;
}

// After
class Sample
{
    public $a;
    public $b;

    /** @var string */
    public $c;
}
```

Patch:
diff --git a/doc/rules/class_notation/class_attributes_separation.rst b/doc/rules/class_notation/class_attributes_separation.rst
index fe7087dd0..32ad8be71 100644
--- a/doc/rules/class_notation/class_attributes_separation.rst
+++ b/doc/rules/class_notation/class_attributes_separation.rst
@@ -11,7 +11,8 @@ Configuration
 ``elements``
 ~~~~~~~~~~~~
 
-Dictionary of ``const|method|property|trait_import`` => ``none|one`` values.
+Dictionary of ``const|method|property|trait_import`` =>
+``none|one|only_if_meta`` values.
 
 Allowed types: ``array``
 
@@ -80,6 +81,80 @@ With configuration: ``['elements' => ['const' => 'one']]``.
         const B = 3600;
     }
 
+Example #4
+~~~~~~~~~~
+
+With configuration: ``['elements' => ['const' => 'only_if_meta']]``.
+
+.. code-block:: diff
+
+   --- Original
+   +++ New
+    <?php
+    class Sample
+    {
+        /** @var int */
+        const SECOND = 1;
+   +
+        /** @var int */
+        const MINUTE = 60;
+   -
+        const HOUR = 3600;
+   -
+        const DAY = 86400;
+    }
+
+Example #5
+~~~~~~~~~~
+
+With configuration: ``['elements' => ['property' => 'only_if_meta']]``.
+
+.. code-block:: diff
+
+   --- Original
+   +++ New
+    <?php
+    class Sample
+    {
+        public $a;
+   +
+        #[SetUp]
+        public $b;
+   +
+        /** @var string */
+        public $c;
+        public $d;
+   -
+        public $e;
+    }
+
+Example #6
+~~~~~~~~~~
+
+With configuration: ``['elements' => ['property' => 'only_if_meta']]``.
+
+.. code-block:: diff
+
+   --- Original
+   +++ New
+    <?php
+    class Sample
+    {
+        public $a;
+   +
+        #[SetUp]
+        public $b;
+   +
+        /** @var string */
+        public $c;
+   +
+        /** @internal */
+        #[Assert\String()]
+        public $d;
+   -
+        public $e;
+    }
+
 Rule sets
 ---------
 
diff --git a/src/Fixer/ClassNotation/ClassAttributesSeparationFixer.php b/src/Fixer/ClassNotation/ClassAttributesSeparationFixer.php
index 51b416286..636b486e8 100644
--- a/src/Fixer/ClassNotation/ClassAttributesSeparationFixer.php
+++ b/src/Fixer/ClassNotation/ClassAttributesSeparationFixer.php
@@ -23,6 +23,8 @@
 use PhpCsFixer\FixerDefinition\CodeSample;
 use PhpCsFixer\FixerDefinition\FixerDefinition;
 use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
+use PhpCsFixer\FixerDefinition\VersionSpecification;
+use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
 use PhpCsFixer\Preg;
 use PhpCsFixer\Tokenizer\CT;
 use PhpCsFixer\Tokenizer\Token;
@@ -39,17 +41,14 @@
  */
 final class ClassAttributesSeparationFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
 {
-    /**
-     * @internal
-     */
-    public const SPACING_NONE = 'none';
+    private const SPACING_NONE = 'none';
 
-    /**
-     * @internal
-     */
-    public const SPACING_ONE = 'one';
+    private const SPACING_ONE = 'one';
+
+    private const SPACING_ONLY_IF_META = 'only_if_meta';
+
+    private const SUPPORTED_SPACINGS = [self::SPACING_NONE, self::SPACING_ONE, self::SPACING_ONLY_IF_META];
 
-    private const SUPPORTED_SPACINGS = [self::SPACING_NONE, self::SPACING_ONE];
     private const SUPPORTED_TYPES = ['const', 'method', 'property', 'trait_import'];
 
     /**
@@ -57,6 +56,11 @@ final class ClassAttributesSeparationFixer extends AbstractFixer implements Conf
      */
     private $classElementTypes = [];
 
+    /**
+     * @var array<int, int>
+     */
+    private $possibleMetadataKinds = [];
+
     /**
      * {@inheritdoc}
      */
@@ -69,6 +73,12 @@ public function configure(array $configuration): void
         foreach ($this->configuration['elements'] as $elementType => $spacing) {
             $this->classElementTypes[$elementType] = $spacing;
         }
+
+        $this->possibleMetadataKinds = [T_DOC_COMMENT];
+        if (\defined('T_ATTRIBUTE')) {
+            // @todo remove check when PHP 8.0+ is required
+            $this->possibleMetadataKinds[] = T_ATTRIBUTE;
+        }
     }
 
     /**
@@ -115,6 +125,58 @@ class Sample
 ',
                     ['elements' => ['const' => self::SPACING_ONE]]
                 ),
+                new CodeSample(
+                    '<?php
+class Sample
+{
+    /** @var int */
+    const SECOND = 1;
+    /** @var int */
+    const MINUTE = 60;
+
+    const HOUR = 3600;
+
+    const DAY = 86400;
+}
+',
+                    ['elements' => ['const' => self::SPACING_ONLY_IF_META]]
+                ),
+                new VersionSpecificCodeSample(
+                    '<?php
+class Sample
+{
+    public $a;
+    #[SetUp]
+    public $b;
+    /** @var string */
+    public $c;
+    public $d;
+
+    public $e;
+}
+',
+                    new VersionSpecification(80000),
+                    ['elements' => ['property' => self::SPACING_ONLY_IF_META]]
+                ),
+                new VersionSpecificCodeSample(
+                    '<?php
+class Sample
+{
+    public $a;
+    #[SetUp]
+    public $b;
+    /** @var string */
+    public $c;
+    /** @internal */
+    #[Assert\String()]
+    public $d;
+
+    public $e;
+}
+',
+                    new VersionSpecification(80000),
+                    ['elements' => ['property' => self::SPACING_ONLY_IF_META]]
+                ),
             ]
         );
     }
@@ -186,7 +248,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
     protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
     {
         return new FixerConfigurationResolver([
-            (new FixerOptionBuilder('elements', 'Dictionary of `const|method|property|trait_import` => `none|one` values.'))
+            (new FixerOptionBuilder('elements', sprintf('Dictionary of `%s` => `%s` values.', implode('|', self::SUPPORTED_TYPES), implode('|', self::SUPPORTED_SPACINGS))))
                 ->setAllowedTypes(['array'])
                 ->setAllowedValues([static function (array $option) {
                     foreach ($option as $type => $spacing) {
@@ -252,7 +314,9 @@ private function fixSpaceBelowClassElement(Tokens $tokens, int $classEndIndex, i
             return;
         }
 
-        $this->correctLineBreaks($tokens, $elementEndIndex, $nextNotWhite, $nextNotWhite === $classEndIndex || self::SPACING_NONE === $spacing ? 1 : 2);
+        $reqLineCount = $this->determineRequiredLineCount($tokens, $nextNotWhite, $classEndIndex, $spacing);
+
+        $this->correctLineBreaks($tokens, $elementEndIndex, $nextNotWhite, $reqLineCount);
     }
 
     /**
@@ -264,8 +328,9 @@ private function fixSpaceBelowClassElement(Tokens $tokens, int $classEndIndex, i
     private function fixSpaceBelowClassMethod(Tokens $tokens, int $classEndIndex, int $elementEndIndex, string $spacing): void
     {
         $nextNotWhite = $tokens->getNextNonWhitespace($elementEndIndex);
+        $reqLineCount = $this->determineRequiredLineCount($tokens, $nextNotWhite, $classEndIndex, $spacing);
 
-        $this->correctLineBreaks($tokens, $elementEndIndex, $nextNotWhite, $nextNotWhite === $classEndIndex || self::SPACING_NONE === $spacing ? 1 : 2);
+        $this->correctLineBreaks($tokens, $elementEndIndex, $nextNotWhite, $reqLineCount);
     }
 
     /**
@@ -305,7 +370,7 @@ private function fixSpaceAboveClassElement(Tokens $tokens, int $classStartIndex,
                 return;
             }
 
-            // $tokens[$nonWhiteAbove+1] is always a white space token here
+            // $tokens[$nonWhiteAbove + 1] is always a white space token here
             if (substr_count($tokens[$nonWhiteAbove + 1]->getContent(), "\n") > 1) {
                 // more than one line break, always bring it back to 2 line breaks between the element start and what is above it
                 $this->correctLineBreaks($tokens, $nonWhiteAbove, $firstElementAttributeIndex, 2);
@@ -339,7 +404,14 @@ private function fixSpaceAboveClassElement(Tokens $tokens, int $classStartIndex,
 
             // there should be one blank line between the PHPDoc and whatever is above (with the exception when it is directly after a class opening)
             $nonWhiteAbovePHPDoc = $tokens->getPrevNonWhitespace($nonWhiteAbove);
-            $this->correctLineBreaks($tokens, $nonWhiteAbovePHPDoc, $nonWhiteAbove, $nonWhiteAbovePHPDoc === $classStartIndex ? 1 : 2);
+            $reqLineCount = $nonWhiteAbovePHPDoc === $classStartIndex ? 1 : 2;
+
+            if ($tokens[$nonWhiteAbovePHPDoc]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) {
+                // PHPDocs can have attributes adjacent to them, so adjust our comparison points
+                list($nonWhiteAbove, $nonWhiteAbovePHPDoc, $reqLineCount) = $this->getAttributePhpdocSequenceOffset($tokens, $nonWhiteAbovePHPDoc, $classStartIndex);
+            }
+
+            $this->correctLineBreaks($tokens, $nonWhiteAbovePHPDoc, $nonWhiteAbove, $reqLineCount);
 
             return;
         }
@@ -349,16 +421,53 @@ private function fixSpaceAboveClassElement(Tokens $tokens, int $classStartIndex,
             // there should be one linebreak between the element and the attribute above it
             $this->correctLineBreaks($tokens, $nonWhiteAbove, $firstElementAttributeIndex, 1);
 
-            // make sure there is blank line above the comment (with the exception when it is directly after a class opening)
-            $nonWhiteAbove = $this->findAttributeBlockStart($tokens, $nonWhiteAbove);
-            $nonWhiteAboveComment = $tokens->getPrevNonWhitespace($nonWhiteAbove);
+            // make sure there is blank line above the attribute comment
+            // with the exception when it is directly after a class opening or has an adjacent PHPDoc
+            list($nonWhiteAbove, $nonWhiteAboveComment, $reqLineCount) = $this->getAttributePhpdocSequenceOffset($tokens, $nonWhiteAbove, $classStartIndex);
 
-            $this->correctLineBreaks($tokens, $nonWhiteAboveComment, $nonWhiteAbove, $nonWhiteAboveComment === $classStartIndex ? 1 : 2);
+            $this->correctLineBreaks($tokens, $nonWhiteAboveComment, $nonWhiteAbove, $reqLineCount);
 
             return;
         }
 
-        $this->correctLineBreaks($tokens, $nonWhiteAbove, $firstElementAttributeIndex, $nonWhiteAbove === $classStartIndex || self::SPACING_NONE === $spacing ? 1 : 2);
+        $reqLineCount = $this->determineRequiredLineCount($tokens, $nonWhiteAbove, $classStartIndex, $spacing);
+
+        $this->correctLineBreaks($tokens, $nonWhiteAbove, $firstElementAttributeIndex, $reqLineCount);
+    }
+
+    /**
+     * @return array<int, int>
+     */
+    private function getAttributePhpdocSequenceOffset(Tokens $tokens, int $attributeCloseIndex, int $classStartIndex): array
+    {
+        $attributeStartIndex = $this->findAttributeBlockStart($tokens, $attributeCloseIndex);
+        $nonWhiteAboveAttribute = $tokens->getNonWhitespaceSibling($attributeStartIndex, -1);
+        $reqLineCount = $nonWhiteAboveAttribute === $classStartIndex || $tokens[$nonWhiteAboveAttribute]->isGivenKind(T_DOC_COMMENT) ? 1 : 2;
+
+        return [$attributeStartIndex, $nonWhiteAboveAttribute, $reqLineCount];
+    }
+
+    private function determineRequiredLineCount(Tokens $tokens, int $notWhiteIndex, int $classStartOrEndIndex, string $spacing): int
+    {
+        // if the two indices are equal, this means the index is either at the
+        // start or end of the class and no additional line breaks are needed
+        if ($notWhiteIndex === $classStartOrEndIndex) {
+            return 1;
+        }
+
+        if (self::SPACING_NONE === $spacing) {
+            return 1;
+        }
+
+        if (self::SPACING_ONE === $spacing) {
+            return 2;
+        }
+
+        if (self::SPACING_ONLY_IF_META === $spacing && $tokens[$notWhiteIndex]->isGivenKind($this->possibleMetadataKinds)) {
+            return 2;
+        }
+
+        return 1;
     }
 
     private function correctLineBreaks(Tokens $tokens, int $startIndex, int $endIndex, int $reqLineCount = 2): void


Test patch:
diff --git a/tests/Fixer/ClassNotation/ClassAttributesSeparationFixerTest.php b/tests/Fixer/ClassNotation/ClassAttributesSeparationFixerTest.php
index 1e8f0270e..8d846e03f 100644
--- a/tests/Fixer/ClassNotation/ClassAttributesSeparationFixerTest.php
+++ b/tests/Fixer/ClassNotation/ClassAttributesSeparationFixerTest.php
@@ -15,7 +15,6 @@
 namespace PhpCsFixer\Tests\Fixer\ClassNotation;
 
 use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException;
-use PhpCsFixer\Fixer\ClassNotation\ClassAttributesSeparationFixer;
 use PhpCsFixer\Tests\Test\AbstractFixerTestCase;
 use PhpCsFixer\Tokenizer\Tokens;
 use PhpCsFixer\WhitespacesFixerConfig;
@@ -972,7 +971,7 @@ class A
                         function A() {}
                      }
                 ',
-                ['elements' => ['property' => ClassAttributesSeparationFixer::SPACING_ONE]],
+                ['elements' => ['property' => 'one']],
             ],
             [
                 '<?php
@@ -994,7 +993,7 @@ class A
                         function A() {}
                     }
                 ',
-                ['elements' => ['property' => ClassAttributesSeparationFixer::SPACING_NONE]],
+                ['elements' => ['property' => 'none']],
             ],
             [
                 '<?php
@@ -1016,7 +1015,7 @@ class A
                         const B = 2;
                     }
                 ',
-                ['elements' => ['const' => ClassAttributesSeparationFixer::SPACING_ONE]],
+                ['elements' => ['const' => 'one']],
             ],
             [
                 '<?php
@@ -1037,7 +1036,7 @@ class A
                         const B = 2;
                     }
                 ',
-                ['elements' => ['const' => ClassAttributesSeparationFixer::SPACING_NONE]],
+                ['elements' => ['const' => 'none']],
             ],
             [
                 '<?php
@@ -1055,7 +1054,7 @@ function D() {}
                         function B4() {}
                     }
                 ',
-                ['elements' => ['method' => ClassAttributesSeparationFixer::SPACING_ONE]],
+                ['elements' => ['method' => 'one']],
             ],
             [
                 '<?php
@@ -1073,7 +1072,7 @@ function A() {}
                         function B() {}
                     }
                 ',
-                ['elements' => ['method' => ClassAttributesSeparationFixer::SPACING_NONE]],
+                ['elements' => ['method' => 'none']],
             ],
             [
                 '<?php
@@ -1096,7 +1095,7 @@ final function f1() {}
                         final function f2() {}
                      }
                 ',
-                ['elements' => ['property' => ClassAttributesSeparationFixer::SPACING_NONE, 'method' => ClassAttributesSeparationFixer::SPACING_ONE]],
+                ['elements' => ['property' => 'none', 'method' => 'one']],
             ],
             [
                 '<?php
@@ -1119,7 +1118,7 @@ function f1() {}
                         function f2() {}
                      }
                 ',
-                ['elements' => ['const' => ClassAttributesSeparationFixer::SPACING_NONE, 'method' => ClassAttributesSeparationFixer::SPACING_ONE]],
+                ['elements' => ['const' => 'none', 'method' => 'one']],
             ],
             [
                 '<?php
@@ -1142,7 +1141,135 @@ public function f1() {}
                         public function f2() {}
                      }
                 ',
-                ['elements' => ['const' => ClassAttributesSeparationFixer::SPACING_NONE, 'method' => ClassAttributesSeparationFixer::SPACING_ONE]],
+                ['elements' => ['const' => 'none', 'method' => 'one']],
+            ],
+            [
+                '<?php
+                    class A
+                    {
+                        const FOO = 1;
+                        const BAR = 2;
+
+                        /** @var int */
+                        const BAZ = 3;
+
+                        /** @var int */
+                        const NEW = 4;
+                    }
+                ',
+                '<?php
+                    class A
+                    {
+                        const FOO = 1;
+
+                        const BAR = 2;
+
+                        /** @var int */
+                        const BAZ = 3;
+                        /** @var int */
+                        const NEW = 4;
+                    }
+                ',
+                ['elements' => ['const' => 'only_if_meta']],
+            ],
+            [
+                '<?php
+                    class B
+                    {
+                        public $foo;
+
+                        /** @var string */
+                        public $bar;
+                        public $baz;
+                    }
+                ',
+                '<?php
+                    class B
+                    {
+                        public $foo;
+                        /** @var string */
+                        public $bar;
+
+                        public $baz;
+                    }
+                ',
+                ['elements' => ['property' => 'only_if_meta']],
+            ],
+            [
+                '<?php
+                    class C
+                    {
+                        public function f1() {}
+                        public function f2() {}
+                        public function f3() {}
+
+                        /** @return string */
+                        public function f4() {}
+                    }
+                ',
+                '<?php
+                    class C
+                    {
+                        public function f1() {}
+
+                        public function f2() {}
+
+                        public function f3() {}
+                        /** @return string */
+                        public function f4() {}
+                    }
+                ',
+                ['elements' => ['method' => 'only_if_meta']],
+            ],
+            [
+                '<?php
+                class Sample
+                {
+                    /** @var int */
+                    const ART = 1;
+                    const SCIENCE = 2;
+
+                    /** @var string */
+                    public $a;
+
+                    /** @var int */
+                    public $b;
+                    public $c;
+
+                    /**
+                     * @param string $a
+                     * @param int $b
+                     * @param int $c
+                     */
+                    public function __construct($a, $b, $c) {}
+                    public function __destruct() {}
+                }
+                ',
+                '<?php
+                class Sample
+                {
+                    /** @var int */
+                    const ART = 1;
+
+                    const SCIENCE = 2;
+                    /** @var string */
+                    public $a;
+                    /** @var int */
+                    public $b;
+
+                    public $c;
+
+                    /**
+                     * @param string $a
+                     * @param int $b
+                     * @param int $c
+                     */
+                    public function __construct($a, $b, $c) {}
+
+                    public function __destruct() {}
+                }
+                ',
+                ['elements' => ['const' => 'only_if_meta', 'property' => 'only_if_meta', 'method' => 'only_if_meta']],
             ],
         ];
     }
@@ -1223,7 +1350,7 @@ public function pass($a, $b) {
     public function testFix71(string $expected, string $input): void
     {
         $this->fixer->configure([
-            'elements' => ['method' => ClassAttributesSeparationFixer::SPACING_ONE, 'const' => ClassAttributesSeparationFixer::SPACING_ONE],
+            'elements' => ['method' => 'one', 'const' => 'one'],
         ]);
         $this->doTest($expected, $input);
     }
@@ -1265,8 +1392,9 @@ public function H1(){}
      * @dataProvider provideFix74Cases
      * @requires PHP 7.4
      */
-    public function testFix74(string $expected, ?string $input = null): void
+    public function testFix74(string $expected, ?string $input, array $config): void
     {
+        $this->fixer->configure($config);
         $this->doTest($expected, $input);
     }
 
@@ -1290,6 +1418,7 @@ class Foo {
                 public iterable $baz;
                 var ? Foo\Bar $qux;
             }',
+            ['elements' => ['property' => 'one']],
         ];
 
         yield [
@@ -1304,6 +1433,45 @@ class Foo {
                 private array $foo;
                 private array $bar;
             }',
+            ['elements' => ['property' => 'one']],
+        ];
+
+        yield [
+            '<?php
+            class Entity
+            {
+                /**
+                 * @ORM\Column(name="one", type="text")
+                 */
+                private string $one;
+
+                /**
+                 * @ORM\Column(name="two", type="text")
+                 */
+                private string $two;
+                private string $three;
+                private string $four;
+                private string $five;
+            }',
+            '<?php
+            class Entity
+            {
+                /**
+                 * @ORM\Column(name="one", type="text")
+                 */
+                private string $one;
+                /**
+                 * @ORM\Column(name="two", type="text")
+                 */
+                private string $two;
+
+                private string $three;
+
+                private string $four;
+
+                private string $five;
+            }',
+            ['elements' => ['property' => 'only_if_meta']],
         ];
     }
 
@@ -1311,8 +1479,9 @@ class Foo {
      * @dataProvider provideFixPhp80Cases
      * @requires PHP 8.0
      */
-    public function testFixPhp80(string $expected, ?string $input = null): void
+    public function testFixPhp80(string $expected, ?string $input, array $config): void
     {
+        $this->fixer->configure($config);
         $this->doTest($expected, $input);
     }
 
@@ -1355,6 +1524,7 @@ class User1
 
 
 }',
+            ['elements' => ['property' => 'one']],
         ];
 
         yield 'attributes minimal' => [
@@ -1365,6 +1535,7 @@ class User2{
 }',
             '<?php
 class User2{#[ORM\Id, ORM\Column("integer"), ORM\GeneratedValue] private $id;}',
+            ['elements' => ['property' => 'one']],
         ];
 
         yield 'attributes not blocks' => [
@@ -1387,6 +1558,7 @@ class User3
 
     #[Assert\Email(["message" => "Foo"])] private $email;
 }',
+            ['elements' => ['property' => 'one']],
         ];
 
         yield 'constructor property promotion' => [
@@ -1412,6 +1584,7 @@ public function __construct(
                     private float $z = 0.0,
                 ) {}
             }',
+            ['elements' => ['property' => 'one', 'method' => 'one']],
         ];
 
         yield 'typed properties' => [
@@ -1432,6 +1605,81 @@ class Foo {
                 private int | float | null $c;
                 private int | float | null $d;
             }',
+            ['elements' => ['property' => 'one']],
+        ];
+
+        yield 'attributes with conditional spacing' => [
+            '<?php
+class User
+{
+    private $id;
+
+    #[Assert\String()]
+    private $name;
+    private $email;
+}
+',
+            '<?php
+class User
+{
+
+    private $id;
+    #[Assert\String()]
+    private $name;
+
+    private $email;
+}
+',
+            ['elements' => ['property' => 'only_if_meta']],
+        ];
+
+        yield 'mixed attributes and phpdoc with conditional spacing' => [
+            '<?php
+class User
+{
+    private $id;
+
+    /** @var string */
+    #[Assert\Email(["message" => "Foo"])]
+    private $email;
+
+    #[Assert\String()]
+    #[ORM\Column()]
+    private $place;
+
+    #[ORM\Column()]
+    /** @var string */
+    private $hash;
+
+    /** @var string **/
+    #[ORM\Column()]
+    /** @internal */
+    private $updatedAt;
+}
+',
+            '<?php
+class User
+{
+
+    private $id;
+    /** @var string */
+    #[Assert\Email(["message" => "Foo"])]
+    private $email;
+    #[Assert\String()]
+    #[ORM\Column()]
+    private $place;
+    #[ORM\Column()]
+    /** @var string */
+    private $hash;
+
+
+    /** @var string **/
+    #[ORM\Column()]
+    /** @internal */
+    private $updatedAt;
+}
+',
+            ['elements' => ['property' => 'only_if_meta']],
         ];
     }