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

Issue:
group_import removes import type when mixing multiple types
## Bug report

The `group_import` rule removes the import type when mixing imports of different types.

### Code snippet that reproduces the problem

```php
<?php 
use Foo\Bar;
use function Foo\baz;
```

This will be fixed to
```php
<?php
use Foo\{Bar, baz};
```

while it should be fixed to
```php
<?php
use Foo\{Bar, function baz};
```

Pull request description:
GroupImportFixer - fix removal of import type when mixing multiple types
Fixes #5346 

Patch:
diff --git a/src/Fixer/Import/GroupImportFixer.php b/src/Fixer/Import/GroupImportFixer.php
index 1c7620d7f..4e3ba3641 100644
--- a/src/Fixer/Import/GroupImportFixer.php
+++ b/src/Fixer/Import/GroupImportFixer.php
@@ -79,22 +79,22 @@ private function getSameNamespaces(Tokens $tokens)
             return [];
         }
 
-        $allNamespaces = array_map(
+        $allNamespaceAndType = array_map(
             function (NamespaceUseAnalysis $useDeclaration) {
-                return $this->getNamespaceNameWithSlash($useDeclaration);
+                return $this->getNamespaceNameWithSlash($useDeclaration).$useDeclaration->getType();
             },
             $useDeclarations
         );
 
-        $sameNamespaces = array_filter(array_count_values($allNamespaces), function ($count) {
+        $sameNamespaces = array_filter(array_count_values($allNamespaceAndType), function ($count) {
             return $count > 1;
         });
         $sameNamespaces = array_keys($sameNamespaces);
 
         $sameNamespaceAnalysis = array_filter($useDeclarations, function ($useDeclaration) use ($sameNamespaces) {
-            $namespaceName = $this->getNamespaceNameWithSlash($useDeclaration);
+            $namespaceNameAndType = $this->getNamespaceNameWithSlash($useDeclaration).$useDeclaration->getType();
 
-            return \in_array($namespaceName, $sameNamespaces, true);
+            return \in_array($namespaceNameAndType, $sameNamespaces, true);
         });
 
         usort($sameNamespaceAnalysis, function (NamespaceUseAnalysis $a, NamespaceUseAnalysis $b) {
@@ -143,7 +143,7 @@ private function removeSingleUseStatements(array $statements, Tokens $tokens)
      */
     private function addGroupUseStatements(array $statements, Tokens $tokens)
     {
-        $currentNamespace = '';
+        $currentUseDeclaration = null;
         $insertIndex = \array_slice($statements, -1)[0]->getEndIndex();
 
         while ($tokens[$insertIndex]->isGivenKind([T_COMMENT, T_DOC_COMMENT])) {
@@ -151,15 +151,18 @@ private function addGroupUseStatements(array $statements, Tokens $tokens)
         }
 
         foreach ($statements as $index => $useDeclaration) {
-            $namespace = $this->getNamespaceNameWithSlash($useDeclaration);
-
-            if ($currentNamespace !== $namespace) {
+            if ($this->areDeclarationsDifferent($currentUseDeclaration, $useDeclaration)) {
                 if ($index > 1) {
                     ++$insertIndex;
                 }
 
-                $currentNamespace = $namespace;
-                $insertIndex += $this->createNewGroup($tokens, $insertIndex, $useDeclaration, $currentNamespace);
+                $currentUseDeclaration = $useDeclaration;
+                $insertIndex += $this->createNewGroup(
+                    $tokens,
+                    $insertIndex,
+                    $useDeclaration,
+                    $this->getNamespaceNameWithSlash($currentUseDeclaration)
+                );
             } else {
                 $newTokens = [
                     new Token(','),
@@ -176,7 +179,7 @@ private function addGroupUseStatements(array $statements, Tokens $tokens)
 
                 $newTokens[] = new Token([T_STRING, $useDeclaration->getShortName()]);
 
-                if (!isset($statements[$index + 1]) || $this->getNamespaceNameWithSlash($statements[$index + 1]) !== $currentNamespace) {
+                if (!isset($statements[$index + 1]) || $this->areDeclarationsDifferent($currentUseDeclaration, $statements[$index + 1])) {
                     $newTokens[] = new Token([CT::T_GROUP_IMPORT_BRACE_CLOSE, '}']);
                     $newTokens[] = new Token(';');
                     $newTokens[] = new Token([T_WHITESPACE, "\n"]);
@@ -278,4 +281,24 @@ private function createNewGroup(Tokens $tokens, $insertIndex, NamespaceUseAnalys
 
         return $insertedTokens;
     }
+
+    /**
+     * Check if namespace use analyses are different.
+     *
+     * @param null|NamespaceUseAnalysis $analysis1
+     * @param null|NamespaceUseAnalysis $analysis2
+     *
+     * @return bool
+     */
+    private function areDeclarationsDifferent($analysis1, $analysis2)
+    {
+        if (null === $analysis1 || null === $analysis2) {
+            return true;
+        }
+
+        $namespaceName1 = $this->getNamespaceNameWithSlash($analysis1);
+        $namespaceName2 = $this->getNamespaceNameWithSlash($analysis2);
+
+        return $namespaceName1 !== $namespaceName2 || $analysis1->getType() !== $analysis2->getType();
+    }
 }
diff --git a/src/Tokenizer/Analyzer/Analysis/NamespaceUseAnalysis.php b/src/Tokenizer/Analyzer/Analysis/NamespaceUseAnalysis.php
index 687aeff09..0621292cc 100644
--- a/src/Tokenizer/Analyzer/Analysis/NamespaceUseAnalysis.php
+++ b/src/Tokenizer/Analyzer/Analysis/NamespaceUseAnalysis.php
@@ -121,6 +121,14 @@ public function getEndIndex()
         return $this->endIndex;
     }
 
+    /**
+     * @return int
+     */
+    public function getType()
+    {
+        return $this->type;
+    }
+
     /**
      * @return bool
      */


Test patch:
diff --git a/tests/Fixer/Import/GroupImportFixerTest.php b/tests/Fixer/Import/GroupImportFixerTest.php
index b38815d61..48ebd0583 100644
--- a/tests/Fixer/Import/GroupImportFixerTest.php
+++ b/tests/Fixer/Import/GroupImportFixerTest.php
@@ -290,6 +290,19 @@ public function provideFixCases()
 use Framework\Bar\Class5;
 use Framework\Baz\Class6;
 use Framework\Foo\Class7;
+',
+            ],
+            [
+                '<?php
+
+use function Foo\baz;
+use Foo\{Bar, Baz};
+',
+                '<?php
+
+use Foo\Bar;
+use function Foo\baz;
+use Foo\Baz;
 ',
             ],
         ];
diff --git a/tests/Tokenizer/Analyzer/Analysis/NamespaceUseAnalysisTest.php b/tests/Tokenizer/Analyzer/Analysis/NamespaceUseAnalysisTest.php
index 0f4726bad..445a2bb38 100644
--- a/tests/Tokenizer/Analyzer/Analysis/NamespaceUseAnalysisTest.php
+++ b/tests/Tokenizer/Analyzer/Analysis/NamespaceUseAnalysisTest.php
@@ -73,13 +73,16 @@ public function testTypeCheck()
         static::assertTrue($class->isClass());
         static::assertFalse($class->isFunction());
         static::assertFalse($class->isConstant());
+        static::assertSame(NamespaceUseAnalysis::TYPE_CLASS, $class->getType());
 
         static::assertFalse($function->isClass());
         static::assertTrue($function->isFunction());
         static::assertFalse($function->isConstant());
+        static::assertSame(NamespaceUseAnalysis::TYPE_FUNCTION, $function->getType());
 
         static::assertFalse($constant->isClass());
         static::assertFalse($constant->isFunction());
         static::assertTrue($constant->isConstant());
+        static::assertSame(NamespaceUseAnalysis::TYPE_CONSTANT, $constant->getType());
     }
 }