Browse Source

feature: Parse parenthesized & conditional phpdoc type (#6796)

Michael Voříšek 1 year ago
parent
commit
10084fdf1b

+ 71 - 0
src/DocBlock/TypeExpression.php

@@ -96,6 +96,34 @@ final class TypeExpression
                 (?<name> # single type, e.g.: `null`, `int`, `\Foo\Bar`
                     [\\\\\w-]++
                 )
+                |
+                (?<parenthesized> # parenthesized type, e.g.: `(int)`, `(int|\stdClass)`
+                    (?<parenthesized_start>
+                        \(\h*
+                    )
+                    (?:
+                        (?<parenthesized_types>
+                            (?&types)
+                        )
+                        |
+                        (?<conditional> # conditional type, e.g.: `$foo is \Throwable ? false : $foo`
+                            (?<conditional_cond_left>
+                                (?:\$\w++)
+                                |
+                                (?<conditional_cond_left_types>(?&types))
+                            )
+                            (?<conditional_cond_middle>
+                                \h+(?i)is(?:\h+not)?(?-i)\h+
+                            )
+                            (?<conditional_cond_right_types>(?&types))
+                            (?<conditional_true_start>\h*\?\h*)
+                            (?<conditional_true_types>(?&types))
+                            (?<conditional_false_start>\h*:\h*)
+                            (?<conditional_false_types>(?&types))
+                        )
+                    )
+                    \h*\)
+                )
             )
         )
         (?:
@@ -319,6 +347,49 @@ final class TypeExpression
                 $index + \strlen($matches['object_like_array_start']),
                 $matches['object_like_array_keys']
             );
+
+            return;
+        }
+
+        if ('' !== ($matches['parenthesized'] ?? '')) {
+            $index += \strlen($matches['parenthesized_start']);
+
+            if ('' !== ($matches['conditional'] ?? '')) {
+                if ('' !== ($matches['conditional_cond_left_types'] ?? '')) {
+                    $this->innerTypeExpressions[] = [
+                        'start_index' => $index,
+                        'expression' => $this->inner($matches['conditional_cond_left_types']),
+                    ];
+                }
+
+                $index += \strlen($matches['conditional_cond_left']) + \strlen($matches['conditional_cond_middle']);
+
+                $this->innerTypeExpressions[] = [
+                    'start_index' => $index,
+                    'expression' => $this->inner($matches['conditional_cond_right_types']),
+                ];
+
+                $index += \strlen($matches['conditional_cond_right_types']) + \strlen($matches['conditional_true_start']);
+
+                $this->innerTypeExpressions[] = [
+                    'start_index' => $index,
+                    'expression' => $this->inner($matches['conditional_true_types']),
+                ];
+
+                $index += \strlen($matches['conditional_true_types']) + \strlen($matches['conditional_false_start']);
+
+                $this->innerTypeExpressions[] = [
+                    'start_index' => $index,
+                    'expression' => $this->inner($matches['conditional_false_types']),
+                ];
+            } else {
+                $this->innerTypeExpressions[] = [
+                    'start_index' => $index,
+                    'expression' => $this->inner($matches['parenthesized_types']),
+                ];
+            }
+
+            return;
         }
     }
 

+ 1 - 1
src/FixerConfiguration/AliasedFixerOptionBuilder.php

@@ -52,7 +52,7 @@ final class AliasedFixerOptionBuilder
     }
 
     /**
-     * @param list<(callable(mixed): bool)|null|scalar> $allowedValues
+     * @param list<null|(callable(mixed): bool)|scalar> $allowedValues
      */
     public function setAllowedValues(array $allowedValues): self
     {

+ 4 - 4
src/FixerConfiguration/FixerOption.php

@@ -33,7 +33,7 @@ final class FixerOption implements FixerOptionInterface
     private $allowedTypes;
 
     /**
-     * @var null|list<(callable(mixed): bool)|null|scalar>
+     * @var null|list<null|(callable(mixed): bool)|scalar>
      */
     private $allowedValues;
 
@@ -43,9 +43,9 @@ final class FixerOption implements FixerOptionInterface
     private $normalizer;
 
     /**
-     * @param mixed             $default
-     * @param null|list<string> $allowedTypes
-     * @param null|list<(callable(mixed): bool)|null|scalar> $allowedValues
+     * @param mixed                                          $default
+     * @param null|list<string>                              $allowedTypes
+     * @param null|list<null|(callable(mixed): bool)|scalar> $allowedValues
      */
     public function __construct(
         string $name,

+ 2 - 2
src/FixerConfiguration/FixerOptionBuilder.php

@@ -33,7 +33,7 @@ final class FixerOptionBuilder
     private $allowedTypes;
 
     /**
-     * @var null|list<(callable(mixed): bool)|null|scalar>
+     * @var null|list<null|(callable(mixed): bool)|scalar>
      */
     private $allowedValues;
 
@@ -79,7 +79,7 @@ final class FixerOptionBuilder
     }
 
     /**
-     * @param list<(callable(mixed): bool)|null|scalar> $allowedValues
+     * @param list<null|(callable(mixed): bool)|scalar> $allowedValues
      *
      * @return $this
      */

+ 1 - 1
src/FixerConfiguration/FixerOptionInterface.php

@@ -35,7 +35,7 @@ interface FixerOptionInterface
     public function getAllowedTypes(): ?array;
 
     /**
-     * @return null|list<(callable(mixed): bool)|null|scalar>
+     * @return null|list<null|(callable(mixed): bool)|scalar>
      */
     public function getAllowedValues(): ?array;
 

+ 36 - 0
tests/DocBlock/TypeExpressionTest.php

@@ -146,6 +146,12 @@ final class TypeExpressionTest extends TestCase
         yield ['\\Closure(string|int, bool): bool', ['\\Closure(string|int, bool): bool']];
 
         yield ['array  <  int   , callable  (  string  )  :   bool  >', ['array  <  int   , callable  (  string  )  :   bool  >']];
+
+        yield ['(int)', ['(int)']];
+
+        yield ['(int|\\Exception)', ['(int|\\Exception)']];
+
+        yield ['($foo is int ? false : true)', ['($foo is int ? false : true)']];
     }
 
     /**
@@ -424,5 +430,35 @@ final class TypeExpressionTest extends TestCase
             'array<Level11&array<Level2|array<Level31&Level32>>>',
             'array<array<array<Level31&Level32>|Level2>&Level11>',
         ];
+
+        yield 'parenthesized' => [
+            '(Foo|Bar)',
+            '(Bar|Foo)',
+        ];
+
+        yield 'parenthesized intersect' => [
+            '(Foo&Bar)',
+            '(Bar&Foo)',
+        ];
+
+        yield 'parenthesized in closure return type' => [
+            'Closure(): (string|float)',
+            'Closure(): (float|string)',
+        ];
+
+        yield 'conditional with variable' => [
+            '($x is (CFoo|(CBaz&CBar)) ? (TFoo|(TBaz&TBar)) : (FFoo|(FBaz&FBar)))',
+            '($x is ((CBar&CBaz)|CFoo) ? ((TBar&TBaz)|TFoo) : ((FBar&FBaz)|FFoo))',
+        ];
+
+        yield 'conditional with type' => [
+            '((Foo|Bar) is x ? y : z)',
+            '((Bar|Foo) is x ? y : z)',
+        ];
+
+        yield 'conditional in conditional' => [
+            '((Foo|Bar) is x ? ($x is (CFoo|CBar) ? (TFoo|TBar) : (FFoo|FBar)) : z)',
+            '((Bar|Foo) is x ? ($x is (CBar|CFoo) ? (TBar|TFoo) : (FBar|FFoo)) : z)',
+        ];
     }
 }

+ 18 - 0
tests/Fixer/Phpdoc/PhpdocAlignFixerTest.php

@@ -1495,6 +1495,24 @@ class Foo {}
         ');
     }
 
+    public function testTypesParenthesized(): void
+    {
+        $this->doTest(
+            '<?php
+            /**
+             * @param list<string>                                   $allowedTypes
+             * @param null|list<\Closure(mixed): (bool|null|scalar)> $allowedValues
+             */
+            ',
+            '<?php
+            /**
+             * @param list<string> $allowedTypes
+             * @param null|list<\Closure(mixed): (bool|null|scalar)> $allowedValues
+             */
+            '
+        );
+    }
+
     /**
      * @dataProvider provideCallableTypesWithUglyCodeCases
      */

+ 1 - 1
tests/FixerConfiguration/AliasedFixerOptionTest.php

@@ -134,7 +134,7 @@ final class AliasedFixerOptionTest extends TestCase
     }
 
     /**
-     * @param list<(callable(mixed): bool)|null|scalar>|null $allowedValues
+     * @param null|list<null|(callable(mixed): bool)|scalar> $allowedValues
      *
      * @dataProvider provideGetAllowedValuesCases
      */