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

Issue:
`no_break_comment` adds comment before `default` of `match` expression and adds no comment if `match`without `default`is used
## Bug report

<!--
  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

<!--
  Please describe the problem.
-->

When using the `no_break_comment` rule, a `// no break` comment gets added before a default case within a [`match`](https://www.php.net/manual/de/control-structures.match.php) expression even if a break for the `case` is placed (after the match).

Additionally, if a `match` expression is used within a `switch-case` expression without using the default pattern, no `// no break` comment is placed even though a break is missing and the case is falling though to the next case (in this case the default case)

### Runtime version

<!--
   Provide PHP CS Fixer version and PHP runtime by sharing the result of this command:
   $ php-cs-fixer -V
-->

```
PHP CS Fixer 3.16.0 BoY42 by Fabien Potencier and Dariusz Ruminski.
PHP runtime: 8.2.2
```

### Used command

<!--
   Provide the command used to run PHP CS Fixer (run with `-vvv`).
-->

```bash
$ ./vendor/bin/php-cs-fixer fix -vvv --diff
PHP CS Fixer 3.16.0 BoY42 by Fabien Potencier and Dariusz Ruminski.
PHP runtime: 8.2.2
Loaded config default from "/var/www/html/.php-cs-fixer.php".
Using cache file ".php-cs-fixer.cache".
F                                                                   1 / 1 (100%)
Legend: .-no changes, F-fixed, S-skipped (cached or empty file), I-invalid file syntax (file ignored), E-error
   1) test.php (no_break_comment)
      ---------- begin diff ----------
--- /var/www/html/test.php
+++ /var/www/html/test.php
@@ -3,6 +3,7 @@
 switch ('wrong placement of no_break_comment') {
     case 'a':
         $x = match (true) {
+        // no break
             default => 'a'
         };
     default:
@@ -12,6 +13,7 @@
 switch ('wrong placement of no_break_comment if case has break statement') {
     case 'a':
         $x = match (true) {
+        // no break
             default => 'a'
         };
         break;

      ----------- end diff -----------


Fixed 1 of 1 files in 0.052 seconds, 12.000 MB memory used
```
### Configuration file

<!--
   Provide content of configuration file you used (.php-cs-fixer[.dist].php).
-->
```php
<?php

$finder = PhpCsFixer\Finder::create()
    ->name('*.php')
    ->in(__DIR__);

$config = new PhpCsFixer\Config();
return $config->setRules([
        'no_break_comment' => true,
    ])
    ->setFinder($finder);
```

### 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.
-->

```php
<?php

switch ('wrong placement of no_break_comment') {
    case 'a':
        $x = match (true) {
            default => 'a'
        };
    default:
        $x = 'b';
}

switch ('wrong placement of no_break_comment if case has break statement') {
    case 'a':
        $x = match (true) {
            default => 'a'
        };
        break;
    default:
        $x = 'b';
}

switch ('no placement of no_break_comment') {
    case 'a':
        $x = match (true) {
            false => 'a'
        };
        $y = 'a';
    default:
        $x = 'b';
}
```



Discussion:
Hi @irobin591, thanks for the report. I believe it's really edge case, why would you want to put `match` inside `switch` 🤔? Anyway, fixer probably does not recognise cases' boundaries properly, `NoBreakCommentFixerTest` does not contain scenarios where `switch` and `match` are mixed together while `NoBreakCommentFixer` has some logic related to `default` and `=>` used in `match`.. It requires deeper verification and fix. But most probably will need to wait.
We already cover edge cases [such as](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/v3.16.0/tests/Fixer/ControlStructure/NoBreakCommentFixerTest.php#L762-L764):
```php
    case 1: {
        throw new \Exception();
    }
```
so we should this one too.



Patch:
diff --git a/src/Fixer/ControlStructure/NoBreakCommentFixer.php b/src/Fixer/ControlStructure/NoBreakCommentFixer.php
index dfcb208804f..b101fabd785 100644
--- a/src/Fixer/ControlStructure/NoBreakCommentFixer.php
+++ b/src/Fixer/ControlStructure/NoBreakCommentFixer.php
@@ -141,7 +141,7 @@ private function fixCase(Tokens $tokens, int $casePosition): void
         $commentPosition = null;
 
         for ($i = $casePosition + 1, $max = \count($tokens); $i < $max; ++$i) {
-            if ($tokens[$i]->isGivenKind([T_SWITCH, T_IF, T_ELSE, T_ELSEIF, T_FOR, T_FOREACH, T_WHILE, T_DO, T_FUNCTION, T_CLASS])) {
+            if ($tokens[$i]->isGivenKind([...self::getParenthesisedStructureKinds(), T_ELSE, T_DO, T_CLASS])) {
                 $empty = false;
                 $i = $this->getStructureEnd($tokens, $i);
 
@@ -312,7 +312,7 @@ private function getStructureEnd(Tokens $tokens, int $position): int
     {
         $initialToken = $tokens[$position];
 
-        if ($initialToken->isGivenKind([T_FOR, T_FOREACH, T_WHILE, T_IF, T_ELSEIF, T_SWITCH, T_FUNCTION])) {
+        if ($initialToken->isGivenKind(self::getParenthesisedStructureKinds())) {
             $position = $tokens->findBlockEnd(
                 Tokens::BLOCK_TYPE_PARENTHESIS_BRACE,
                 $tokens->getNextTokenOfKind($position, ['('])
@@ -347,4 +347,21 @@ private function getStructureEnd(Tokens $tokens, int $position): int
 
         return $position;
     }
+
+    /**
+     * @return array<int>
+     */
+    private static function getParenthesisedStructureKinds(): array
+    {
+        static $structureKinds = null;
+
+        if (null === $structureKinds) {
+            $structureKinds = [T_FOR, T_FOREACH, T_WHILE, T_IF, T_ELSEIF, T_SWITCH, T_FUNCTION];
+            if (\defined('T_MATCH')) { // @TODO: drop condition when PHP 8.0+ is required
+                $structureKinds[] = T_MATCH;
+            }
+        }
+
+        return $structureKinds;
+    }
 }


Test patch:
diff --git a/tests/Fixer/ControlStructure/NoBreakCommentFixerTest.php b/tests/Fixer/ControlStructure/NoBreakCommentFixerTest.php
index a65bd1dfc3c..999ec08f0c5 100644
--- a/tests/Fixer/ControlStructure/NoBreakCommentFixerTest.php
+++ b/tests/Fixer/ControlStructure/NoBreakCommentFixerTest.php
@@ -1282,6 +1282,38 @@ public static function provideFix80Cases(): iterable
                 };
             ',
         ];
+
+        yield 'switch with break and nested match' => [
+            '<?php switch ($value) {
+                case 1:
+                    $x = match (true) {
+                        default => 2
+                    };
+                    break;
+                default:
+                    $x = 3;
+            }',
+        ];
+
+        yield 'switch without break and nested match' => [
+            '<?php switch ($value) {
+                case 1:
+                    $x = match (true) {
+                        default => 2
+                    };
+                    // no break
+                default:
+                    $x = 3;
+            }',
+            '<?php switch ($value) {
+                case 1:
+                    $x = match (true) {
+                        default => 2
+                    };
+                default:
+                    $x = 3;
+            }',
+        ];
     }
 
     /**