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

Issue:
NoSuperfluousPhpdocTagsFixer does not remove tag when PHP attribute exists
## Bug report
The following does not remove `@var int` from `$firstField`. 

```php
<?php

#[\Attribute(\Attribute::TARGET_PROPERTY)]
class MyAttribute
{
}

class MyClass
{
    /**
     * @var int
     */
    #[MyAttribute]
    private int $firstField;

    /**
     * @var int
     */
    private int $secondField;
}
```


Discussion:

Patch:
diff --git a/src/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixer.php b/src/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixer.php
index 38965f70877..70a4838615d 100644
--- a/src/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixer.php
+++ b/src/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixer.php
@@ -118,13 +118,14 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
                 continue;
             }
 
-            $content = $initialContent = $token->getContent();
             $documentedElement = $this->findDocumentedElement($tokens, $index);
 
             if (null === $documentedElement) {
                 continue;
             }
 
+            $content = $initialContent = $token->getContent();
+
             if (true === $this->configuration['remove_inheritdoc']) {
                 $content = $this->removeSuperfluousInheritDoc($content);
             }
@@ -201,6 +202,14 @@ private function findDocumentedElement(Tokens $tokens, int $docCommentIndex): ?a
 
         $index = $tokens->getNextMeaningfulToken($docCommentIndex);
 
+        // @TODO: drop condition when PHP 8.0+ is required
+        if (null !== $index && \defined('T_ATTRIBUTE') && $tokens[$index]->isGivenKind(T_ATTRIBUTE)) {
+            do {
+                $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ATTRIBUTE, $index);
+                $index = $tokens->getNextMeaningfulToken($index);
+            } while (null !== $index && $tokens[$index]->isGivenKind(T_ATTRIBUTE));
+        }
+
         while (true) {
             if (null === $index) {
                 break;
@@ -359,14 +368,14 @@ private function getArgumentsInfo(Tokens $tokens, int $start, int $end): array
     private function getReturnTypeInfo(Tokens $tokens, int $closingParenthesisIndex): array
     {
         $colonIndex = $tokens->getNextMeaningfulToken($closingParenthesisIndex);
-        if ($tokens[$colonIndex]->isGivenKind(CT::T_TYPE_COLON)) {
-            return $this->parseTypeHint($tokens, $tokens->getNextMeaningfulToken($colonIndex));
-        }
 
-        return [
-            'types' => [],
-            'allows_null' => true,
-        ];
+        return $tokens[$colonIndex]->isGivenKind(CT::T_TYPE_COLON)
+            ? $this->parseTypeHint($tokens, $tokens->getNextMeaningfulToken($colonIndex))
+            : [
+                'types' => [],
+                'allows_null' => true,
+            ]
+        ;
     }
 
     /**


Test patch:
diff --git a/tests/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixerTest.php b/tests/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixerTest.php
index 3540de77b5d..1f3a03eceeb 100644
--- a/tests/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixerTest.php
+++ b/tests/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixerTest.php
@@ -1867,150 +1867,200 @@ public function testFixPhp80(string $expected, ?string $input = null, array $con
         $this->doTest($expected, $input);
     }
 
-    public function provideFixPhp80Cases(): array
+    public function provideFixPhp80Cases(): iterable
     {
-        return [
-            'static return' => [
-                '<?php
+        yield 'static return' => [
+            '<?php
 class Foo {
     /**
      */
     public function foo($foo): static {}
 }',
-                '<?php
+            '<?php
 class Foo {
     /**
      * @return static
      */
     public function foo($foo): static {}
 }',
-            ],
-            'union type on parameter' => [
-                '<?php
+        ];
+
+        yield 'union type on parameter' => [
+            '<?php
 class Foo {
     /**
      */
     public function foo(int|string $foo) {}
 }',
-                '<?php
+            '<?php
 class Foo {
     /**
      * @param int|string $foo
      */
     public function foo(int|string $foo) {}
 }',
-            ],
-            'union type on return type' => [
-                '<?php
+        ];
+
+        yield 'union type on return type' => [
+            '<?php
 class Foo {
     /**
      */
     public function foo($foo): int|string {}
 }',
-                '<?php
+            '<?php
 class Foo {
     /**
      * @return int|string
      */
     public function foo($foo): int|string {}
 }',
-            ],
-            'union type on property' => [
-                '<?php
+        ];
+
+        yield 'union type on property' => [
+            '<?php
 class Foo {
     /**
      */
     public int|string $foo;
 }',
-                '<?php
+            '<?php
 class Foo {
     /**
      * @var int|string
      */
     public int|string $foo;
 }',
-            ],
-            'union type on property with spaces' => [
-                '<?php
+        ];
+
+        yield 'union type on property with spaces' => [
+            '<?php
 class Foo {
     /**
      */
     public int  |  string $foo;
 }',
-                '<?php
+            '<?php
 class Foo {
     /**
      * @var int|string
      */
     public int  |  string $foo;
 }',
-            ],
-            'union type with null' => [
-                '<?php
+        ];
+
+        yield 'union type with null' => [
+            '<?php
 /**
  */
 function foo(int|string|null $foo) {}',
-                '<?php
+            '<?php
 /**
  * @param int|string|null $foo
  */
 function foo(int|string|null $foo) {}',
-            ],
-            'union type in different order' => [
-                '<?php
+        ];
+
+        yield 'union type in different order' => [
+            '<?php
 /**
  */
 function foo(string|int $foo) {}',
-                '<?php
+            '<?php
 /**
  * @param int|string $foo
  */
 function foo(string|int $foo) {}',
-            ],
-            'more details in phpdocs' => [
-                '<?php
+        ];
+
+        yield 'more details in phpdocs' => [
+            '<?php
 /**
  * @param string|array<string> $foo
  */
 function foo(string|array $foo) {}',
-            ],
-            'missing types in phpdocs' => [
-                '<?php
+        ];
+
+        yield 'missing types in phpdocs' => [
+            '<?php
 /**
  * @param string|int $foo
  */
 function foo(string|array|int $foo) {}',
-            ],
-            'too many types in phpdocs' => [
-                '<?php
+        ];
+
+        yield 'too many types in phpdocs' => [
+            '<?php
 /**
  * @param string|array|int $foo
  */
 function foo(string|int $foo) {}',
-            ],
-            'promoted properties' => [
-                '<?php class Foo {
-                    /**
-                     */
-                    public function __construct(
-                        public string $a,
-                        protected ?string $b,
-                        private ?string $c,
-                    ) {}
-                }',
-                '<?php class Foo {
-                    /**
-                     * @param string $a
-                     * @param null|string $b
-                     * @param string|null $c
-                     */
-                    public function __construct(
-                        public string $a,
-                        protected ?string $b,
-                        private ?string $c,
-                    ) {}
-                }',
-            ],
+        ];
+
+        yield 'promoted properties' => [
+            '<?php class Foo {
+                /**
+                 */
+                public function __construct(
+                    public string $a,
+                    protected ?string $b,
+                    private ?string $c,
+                ) {}
+            }',
+            '<?php class Foo {
+                /**
+                 * @param string $a
+                 * @param null|string $b
+                 * @param string|null $c
+                 */
+                public function __construct(
+                    public string $a,
+                    protected ?string $b,
+                    private ?string $c,
+                ) {}
+            }',
+        ];
+
+        yield 'single attribute' => [
+            '<?php
+class Foo
+{
+    /**
+     */
+    #[MyAttribute]
+    private int $bar = 1;
+}',
+            '<?php
+class Foo
+{
+    /**
+     * @var int
+     */
+    #[MyAttribute]
+    private int $bar = 1;
+}',
+        ];
+
+        yield 'multiple attributes' => [
+            '<?php
+class Foo
+{
+    /**
+     */
+    #[MyAttribute]
+    #[MyAttribute2]
+    private int $bar = 1;
+}',
+            '<?php
+class Foo
+{
+    /**
+     * @var int
+     */
+    #[MyAttribute]
+    #[MyAttribute2]
+    private int $bar = 1;
+}',
         ];
     }