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

Issue:
blank_line_before_statement: prevent adding line before enum case
## Feature request
Given:
```php
enum Foo
{
    case One;
    case Two;
}
```

`blank_line_before_statement`, with `case` configured will transform to:

```php
enum Foo
{
    case One;
    
    case Two;
}
```

This is undesirable formatting.

An option could be introduced to somehow exclude these specific enum`cases`.



Discussion:
Yes, please.



Patch:
diff --git a/src/Fixer/Whitespace/BlankLineBeforeStatementFixer.php b/src/Fixer/Whitespace/BlankLineBeforeStatementFixer.php
index 562baeb77ac..c9bd8eeec6d 100644
--- a/src/Fixer/Whitespace/BlankLineBeforeStatementFixer.php
+++ b/src/Fixer/Whitespace/BlankLineBeforeStatementFixer.php
@@ -266,6 +266,10 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
                 continue;
             }
 
+            if ($token->isGivenKind(T_CASE) && $analyzer->isEnumCase($index)) {
+                continue;
+            }
+
             $insertBlankLineIndex = $this->getInsertBlankLineIndex($tokens, $index);
             $prevNonWhitespace = $tokens->getPrevNonWhitespace($insertBlankLineIndex);
 
diff --git a/src/Tokenizer/TokensAnalyzer.php b/src/Tokenizer/TokensAnalyzer.php
index c67f10b1d6c..dcd7f137fed 100644
--- a/src/Tokenizer/TokensAnalyzer.php
+++ b/src/Tokenizer/TokensAnalyzer.php
@@ -633,6 +633,31 @@ public function isWhilePartOfDoWhile(int $index): bool
         return $tokens[$beforeStartIndex]->isGivenKind(T_DO);
     }
 
+    /**
+     * @throws \LogicException when provided index does not point to token containing T_CASE
+     */
+    public function isEnumCase(int $caseIndex): bool
+    {
+        $tokens = $this->tokens;
+        $token = $tokens[$caseIndex];
+
+        if (!$token->isGivenKind(T_CASE)) {
+            throw new \LogicException(sprintf(
+                'No T_CASE given at index %d, got %s instead.',
+                $caseIndex,
+                $token->getName() ?? $token->getContent()
+            ));
+        }
+
+        if (!\defined('T_ENUM') || !$tokens->isTokenKindFound(T_ENUM)) {
+            return false;
+        }
+
+        $prevIndex = $tokens->getPrevTokenOfKind($caseIndex, [[T_ENUM], [T_SWITCH]]);
+
+        return null !== $prevIndex && $tokens[$prevIndex]->isGivenKind(T_ENUM);
+    }
+
     public function isSuperGlobal(int $index): bool
     {
         static $superNames = [


Test patch:
diff --git a/tests/Fixer/Whitespace/BlankLineBeforeStatementFixerTest.php b/tests/Fixer/Whitespace/BlankLineBeforeStatementFixerTest.php
index 158e7a1eb6b..7825a247e40 100644
--- a/tests/Fixer/Whitespace/BlankLineBeforeStatementFixerTest.php
+++ b/tests/Fixer/Whitespace/BlankLineBeforeStatementFixerTest.php
@@ -1515,9 +1515,7 @@ public static function provideFix81Cases(): iterable
             '<?php
 enum Suit {
     case Hearts;
-
     case Diamonds;
-
     case Clubs;
 
 
@@ -1526,7 +1524,6 @@ enum Suit {
 
 enum UserStatus: string {
     case Pending = "P";
-
     case Active = "A";
 
     public function label(): string {
diff --git a/tests/Tokenizer/TokensAnalyzerTest.php b/tests/Tokenizer/TokensAnalyzerTest.php
index 2a440edea2e..c74e8b47164 100644
--- a/tests/Tokenizer/TokensAnalyzerTest.php
+++ b/tests/Tokenizer/TokensAnalyzerTest.php
@@ -2317,6 +2317,145 @@ public function testIsWhilePartOfDoWhile(): void
         }
     }
 
+    /**
+     * @dataProvider provideIsEnumCaseCases
+     *
+     * @param array<int, bool> $expected
+     *
+     * @requires PHP 8.1
+     */
+    public function testIsEnumCase(string $source, array $expected): void
+    {
+        $tokens = Tokens::fromCode($source);
+        $tokensAnalyzer = new TokensAnalyzer($tokens);
+
+        foreach ($tokens as $index => $token) {
+            if (!$token->isGivenKind(T_CASE)) {
+                try {
+                    $tokensAnalyzer->isEnumCase($index);
+                    self::fail('TokensAnalyzer::isEnumCase() did not throw LogicException.');
+                } catch (\Throwable $e) {
+                    self::assertInstanceOf(\LogicException::class, $e);
+                    self::assertMatchesRegularExpression('/^No T_CASE given at index \d+, got \S+ instead\.$/', $e->getMessage());
+                }
+
+                continue;
+            }
+
+            self::assertSame($expected[$index], $tokensAnalyzer->isEnumCase($index));
+        }
+    }
+
+    public static function provideIsEnumCaseCases(): iterable
+    {
+        yield 'switch only' => [
+            '<?php
+function bar(string $a): string
+{
+    switch ($a) {
+        case \'one\':
+            return $a;
+        case \'two\':
+        default:
+            return strtoupper($a);
+    }
+}
+',
+            [
+                23 => false,
+                33 => false,
+            ],
+        ];
+
+        yield 'pure enum' => [
+            '<?php
+enum Foo
+{
+    case One;
+    case Two;
+}
+',
+            [
+                7 => true,
+                12 => true,
+            ],
+        ];
+
+        yield 'pure enum with switch' => [
+            '<?php
+enum Foo
+{
+    case One;
+    case Two;
+
+    public static function getLowerName(self $instance): string
+    {
+        switch ($instance->name) {
+            case \'One\':
+            case \'Two\':
+                return strtolower($instance->name);
+        }
+    }
+}
+',
+            [
+                7 => true,
+                12 => true,
+                45 => false,
+                50 => false,
+            ],
+        ];
+
+        yield 'backed enum' => [
+            '<?php
+enum Suit: string
+{
+    case Hearts = \'hearts\';
+    case Spades = \'spades\';
+    case Clubs = \'clubs\';
+    case Diamonds = \'diamonds\';
+}
+',
+            [
+                10 => true,
+                19 => true,
+                28 => true,
+                37 => true,
+            ],
+        ];
+
+        yield 'backed enum with switch' => [
+            '<?php
+enum Suit: string
+{
+    case Hearts = \'hearts\';
+    case Spades = \'spades\';
+    case Clubs = \'clubs\';
+    case Diamonds = \'diamonds\';
+
+    public static function getUppercasedValue(self $instance): string
+    {
+        switch ($instance->value) {
+            case \'hearts\':
+            case \'spades\':
+                return strtoupper($instance->value);
+            default:
+                return $instance->value;
+        }
+    }
+}
+',
+            [
+                10 => true,
+                19 => true,
+                28 => true,
+                37 => true,
+                74 => false,
+                79 => false,
+            ],
+        ];
+    }
+
     /**
      * @param array<int, list<int>>|list<int> $expected
      *