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

Issue:
`no_superfluous_phpdoc_tags` removes actual docs when these are after phpDoc tag
## Bug report

My guess is, php-cs-fixer detects the `@return void` as useless, but then decides to delete the whole comment instead of just the single tag from the phpdoc

<!--
  Before reporting a bug, make sure PHP CS Fixer is up-to-date.

  Check that the issue is NOT regarded to an applied rule by using the `-vvv` option (will list applied rules) on a minimal code snippet.
-->

### Description

a phpdoc is removed, which should be preserved
<!--
  Please describe the problem.
-->

### Runtime version

<!--
  Provide PHP CS Fixer version and PHP runtime by sharing result of this command:
  $ php-cs-fixer -V
-->

```
$ php php-cs-fixer -V
PHP CS Fixer 3.46.1-DEV Three Keys by Fabien Potencier, Dariusz Ruminski and contributors.
PHP runtime: 8.2.12
```

using https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/commit/3566e7c926ada331cd07cf6436947d0d89a1d216

### Used command

`php php-cs-fixer fix --dry-run test.php --diff -vvv`
<!--
  Provide the command used to run PHP CS Fixer (run with `-vvv`).
-->

### Configuration file

default config from PHP-CS-Fixer repo

<!--
  Provide content of configuration file you used (.php-cs-fixer[.dist].php).
-->

### Code snippet that reproduces the problem

<!--
  Provide a minimal code snippet before running PHP CS Fixer. If relevant,
  also provide the expected output, the actual output and/or the error that
  occurred, if any.

  Please use markdown syntax for each code snippet, e.g.:


-->

```php
<?php

class X {
    /**
     * @return void
     *
     * Validate price information values
     */
    private function validatePriceinformationConfigset(): void
    {

    }
}
```

expected result

```php
<?php

class X {
    /**
     * Validate price information values
     */
    private function validatePriceinformationConfigset(): void
    {

    }
}
```

actual result

```php
<?php

class X {
    private function validatePriceinformationConfigset(): void
    {

    }
}
```


Pull request:
fix: `no_superfluous_phpdoc_tags` must honor multiline docs
fix #7692

Patch:
diff --git a/src/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixer.php b/src/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixer.php
index e08e1af58..bfb550c5b 100644
--- a/src/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixer.php
+++ b/src/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixer.php
@@ -514,11 +514,11 @@ private function annotationIsSuperfluous(
         array $symbolShortNames
     ): bool {
         if ('param' === $annotation->getTag()->getName()) {
-            $regex = '{\*\h*@param(?:\h+'.TypeExpression::REGEX_TYPES.')?(?!\S)(?:\h+(?:\&\h*)?(?:\.{3}\h*)?\$\S+)?(?:\h+(?<description>(?!\*+\/)\S+))?}s';
+            $regex = '{\*\h*@param(?:\h+'.TypeExpression::REGEX_TYPES.')?(?!\S)(?:\h+(?:\&\h*)?(?:\.{3}\h*)?\$\S+)?(?:\s+(?<description>(?!\*+\/)\S+))?}s';
         } elseif ('var' === $annotation->getTag()->getName()) {
-            $regex = '{\*\h*@var(?:\h+'.TypeExpression::REGEX_TYPES.')?(?!\S)(?:\h+\$\S+)?(?:\h+(?<description>(?!\*\/)\S+))?}s';
+            $regex = '{\*\h*@var(?:\h+'.TypeExpression::REGEX_TYPES.')?(?!\S)(?:\h+\$\S+)?(?:\s+(?<description>(?!\*\/)\S+))?}s';
         } else {
-            $regex = '{\*\h*@return(?:\h+'.TypeExpression::REGEX_TYPES.')?(?!\S)(?:\h+(?<description>(?!\*\/)\S+))?}s';
+            $regex = '{\*\h*@return(?:\h+'.TypeExpression::REGEX_TYPES.')?(?!\S)(?:\s+(?<description>(?!\*\/)\S+))?}s';
         }
 
         if (!Preg::match($regex, $annotation->getContent(), $matches)) {


Test patch:
diff --git a/tests/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixerTest.php b/tests/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixerTest.php
index 6425e5762..b2b1b5b39 100644
--- a/tests/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixerTest.php
+++ b/tests/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixerTest.php
@@ -2528,6 +2528,44 @@ static function ($foo): int { return 1; };',
 /** @return int */
 static fn ($foo): int => 1;',
         ];
+
+        yield 'multiline @param must be kept even if there is no description on the phpdoc tag line' => [
+            <<<'EOD'
+                <?php
+                /**
+                 * @param string $arg
+                 *                    - foo
+                 *                    - foo2
+                 */
+                function foo(string $arg) {}
+                EOD,
+        ];
+
+        yield 'multiline @return must be kept even if there is no description on the phpdoc tag line' => [
+            <<<'EOD'
+                <?php
+                /**
+                 * @return string
+                 *                - foo
+                 *                - foo2
+                 */
+                function foo(string $arg): string {}
+                EOD,
+        ];
+
+        yield 'multiline @var must be kept even if there is no description on the phpdoc tag line' => [
+            <<<'EOD'
+                <?php
+                class Cl {
+                    /**
+                     * @var string
+                     *             - foo
+                     *             - foo2
+                     */
+                    public string $prop;
+                }
+                EOD,
+        ];
     }
 
     /**