Skip to content

Commit

Permalink
bug: Fix parsing of edge cases phpdoc types (#6977)
Browse files Browse the repository at this point in the history
Co-authored-by: Greg Korba <greg@codito.dev>
  • Loading branch information
mvorisek and Wirone committed May 22, 2023
1 parent a3ed743 commit 60d6c21
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 31 deletions.
44 changes: 30 additions & 14 deletions src/DocBlock/TypeExpression.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,20 @@ final class TypeExpression
public const REGEX_TYPES = '
(?<types> # several types separated by `|` or `&`
(?<type> # single type
(?<nullable>\??)
(?<nullable>\??\h*)
(?:
(?<object_like_array>
(?<object_like_array_start>(array|object)\h*\{\h*)
(?<object_like_array_start>(?:array|list|object)\h*\{\h*)
(?<object_like_array_inners>
(?<object_like_array_inner>
(?<object_like_array_inner_key>(?:(?&constant)|(?&name))\h*\??\h*:\h*)?
(?<object_like_array_inner_value>(?&types))
)
(?:\h*,\h*(?&object_like_array_inner))*
(?:
\h*,\h*
(?&object_like_array_inner)
)*
(?:\h*,\h*)?
)?
\h*\}
)
Expand All @@ -54,6 +58,7 @@ final class TypeExpression
\h*,\h*
(?&types)
)*
(?:\h*,\h*)?
)?
\h*\)
(?:
Expand All @@ -78,24 +83,32 @@ final class TypeExpression
)
|
(?<class_constant> # class constants with optional wildcard, e.g.: `Foo::*`, `Foo::CONST_A`, `FOO::CONST_*`
(?&name)::(\*|\w+\*?)
(?&name)::\*?(?:(?&identifier)\*?)*
)
|
(?<array> # array expression, e.g.: `string[]`, `string[][]`
(?&name)(\[\])+
)
|
(?<constant> # single constant value (case insensitive), e.g.: 1, `\'a\'`
(?<constant> # single constant value (case insensitive), e.g.: 1, -1.8E+6, `\'a\'`
(?i)
null | true | false
| -?(?:\d+(?:\.\d*)?|\.\d+) # all sorts of numbers with or without minus, e.g.: 1, 1.1, 1., .1, -1
| \'(?:[^\'\\\\]|\\\\.)*?\' | "(?:[^"\\\\]|\\\\.)*?"
# all sorts of numbers: with or without sign, supports literal separator and several numeric systems,
# e.g.: 1, +1.1, 1., .1, -1, 123E+8, 123_456_789, 0x7Fb4, 0b0110, 0o777
| [+-]?(?:
(?:0b[01]++(?:_[01]++)*+)
| (?:0o[0-7]++(?:_[0-7]++)*+)
| (?:0x[\da-f]++(?:_[\da-f]++)*+)
| (?:(?<constant_digits>\d++(?:_\d++)*+)|(?=\.\d))
(?:\.(?&constant_digits)|(?<=\d)\.)?+
(?:e[+-]?(?&constant_digits))?+
)
| \'(?:[^\'\\\\]|\\\\.)*+\'
| "(?:[^"\\\\]|\\\\.)*+"
| [@$]?(?:this | self | static)
(?-i)
)
|
(?<name> # single type, e.g.: `null`, `int`, `\Foo\Bar`
[\\\\\w-]++
(?<name> # full name, e.g.: `int`, `\DateTime`, `\Foo\Bar`
\\\\?+
(?<identifier>(?!(?<!\*)\d)[^\x00-\x2f\x3a-\x40\x5b-\x5e\x60\x7b-\x7f]++)
(?:[\\\\\-](?&identifier))*+
)
|
(?<parenthesized> # parenthesized type, e.g.: `(int)`, `(int|\stdClass)`
Expand All @@ -109,7 +122,7 @@ final class TypeExpression
|
(?<conditional> # conditional type, e.g.: `$foo is \Throwable ? false : $foo`
(?<conditional_cond_left>
(?:\$\w++)
(?:\$(?&identifier))
|
(?<conditional_cond_left_types>(?&types))
)
Expand All @@ -126,6 +139,9 @@ final class TypeExpression
\h*\)
)
)
(?<array> # array, e.g.: `string[]`, `array<int, string>[][]`
(\h*\[\h*\])*
)
)
(?:
\h*(?<glue>[|&])\h*
Expand Down
114 changes: 106 additions & 8 deletions tests/DocBlock/TypeExpressionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ final class TypeExpressionTest extends TestCase
/**
* @param string[] $expectedTypes
*
* @dataProvider provideGetConstTypesCases
* @dataProvider provideGetTypesCases
*/
public function testGetTypes(string $typesExpression, array $expectedTypes): void
Expand All @@ -52,10 +53,22 @@ public static function provideGetTypesCases(): iterable
{
yield ['int', ['int']];

yield ['Foo[][]', ['Foo[][]']];
yield ['Foo5', ['Foo5']];

yield ['🚀_kůň', ['🚀_kůň']];

yield ['positive-int', ['positive-int']];

yield ['?int', ['?int']];

yield ['? int', ['? int']];

yield ['int[]', ['int[]']];

yield ['Foo[][]', ['Foo[][]']];

yield ['Foo [ ] []', ['Foo [ ] []']];

yield ['int[]|null', ['int[]', 'null']];

yield ['int[]|null|?int|array', ['int[]', 'null', '?int', 'array']];
Expand All @@ -78,6 +91,10 @@ public static function provideGetTypesCases(): iterable

yield ['gen<int, gener<string, null|bool>>', ['gen<int, gener<string, null|bool>>']];

yield ['gen<int>[][]', ['gen<int>[][]']];

yield ['non-empty-array<int>', ['non-empty-array<int>']];

yield ['null|gen<int, gener<string, bool>>|int|string[]', ['null', 'gen<int, gener<string, bool>>', 'int', 'string[]']];

yield ['null|gen<int, gener<string, bool>>|int|array<int, string>|string[]', ['null', 'gen<int, gener<string, bool>>', 'int', 'array<int, string>', 'string[]']];
Expand All @@ -102,6 +119,10 @@ public static function provideGetTypesCases(): iterable

yield ['Foo::A*', ['Foo::A*']];

yield ['Foo::*0*_Bar', ['Foo::*0*_Bar']];

yield ['?Foo::*[]', ['?Foo::*[]']];

yield ['array<Foo::A*>|null', ['array<Foo::A*>', 'null']];

yield ['null|true|false|1|-1|1.5|-1.5|.5|1.|\'a\'|"b"', ['null', 'true', 'false', '1', '-1', '1.5', '-1.5', '.5', '1.', "'a'", '"b"']];
Expand All @@ -128,12 +149,18 @@ public static function provideGetTypesCases(): iterable

yield ['array{bool, int}', ['array{bool, int}']];

yield ['array{bool,}', ['array{bool,}']];

yield ['list{int, bool}', ['list{int, bool}']];

yield ['object{ bool, foo2: int }', ['object{ bool, foo2: int }']];

yield ['callable(string)', ['callable(string)']];

yield ['callable(string): bool', ['callable(string): bool']];

yield ['callable(string,): bool', ['callable(string,): bool']];

yield ['callable(array<int, string>, array<int, Foo>): bool', ['callable(array<int, string>, array<int, Foo>): bool']];

yield ['array<int, callable(string): bool>', ['array<int, callable(string): bool>']];
Expand Down Expand Up @@ -174,17 +201,58 @@ public static function provideGetTypesCases(): iterable

yield ['($foo is int ? false : true)', ['($foo is int ? false : true)']];

yield ['\'\'', ['\'\'']];

yield ['\'foo\'', ['\'foo\'']];

yield ['\'\\\\\'', ['\'\\\\\'']];

yield ['\'\\\'\'', ['\'\\\'\'']];
yield ['($foo🚀3 is int ? false : true)', ['($foo🚀3 is int ? false : true)']];

yield ['\'a\\\'s"\\\\\n\r\t\'|"b\\"s\'\\\\\n\r\t"', ['\'a\\\'s"\\\\\n\r\t\'', '"b\\"s\'\\\\\n\r\t"']];
}

public static function provideGetConstTypesCases(): iterable
{
foreach ([
'null',
'true',
'FALSE',

'123',
'+123',
'-123',
'0b0110101',
'0o777',
'0x7Fb4',
'-0O777',
'-0X7Fb4',
'123_456',
'0b01_01_01',
'-0X7_Fb_4',
'18_446_744_073_709_551_616', // 64-bit unsigned long + 1, larger than PHP_INT_MAX

'123.4',
'.123',
'123.',
'123e4',
'123E4',
'12.3e4',
'+123.5',
'-123.',
'-123.4',
'-.123',
'-123.',
'-123e-4',
'-12.3e-4',
'-1_2.3_4e5_6',
'123E+80',
'8.2023437675747321', // greater precision than 64-bit double
'-0.0',

'\'\'',
'\'foo\'',
'\'\\\\\'',
'\'\\\'\'',
] as $type) {
yield [$type, [$type]];
}
}

/**
* @dataProvider provideGetTypesGlueCases
*/
Expand Down Expand Up @@ -407,6 +475,21 @@ public static function provideSortTypesCases(): iterable
'array{bool|int}',
];

yield 'simple in array shape with trailing comma' => [
'array{int|bool,}',
'array{bool|int,}',
];

yield 'simple in array shape with multiple types with trailing comma' => [
'array{int|bool, Foo|Bar, }',
'array{bool|int, Bar|Foo, }',
];

yield 'simple in array shape' => [
'list{int, Foo|Bar}',
'list{int, Bar|Foo}',
];

yield 'array shape with multiple colons - array shape' => [
'array{array{x:int|bool}, a:array{x:int|bool}}',
'array{array{x:bool|int}, a:array{x:bool|int}}',
Expand Down Expand Up @@ -447,6 +530,16 @@ public static function provideSortTypesCases(): iterable
'Closure(bool|int, array|null)',
];

yield 'simple in closure argument with trailing comma' => [
'Closure(int|bool,)',
'Closure(bool|int,)',
];

yield 'simple in closure argument multiple arguments with trailing comma' => [
'Closure(int|bool, null|array,)',
'Closure(bool|int, array|null,)',
];

yield 'simple in closure return type' => [
'Closure(): (string|float)',
'Closure(): (float|string)',
Expand Down Expand Up @@ -533,5 +626,10 @@ public static function provideSortTypesCases(): iterable
'((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)',
];

yield 'large numbers' => [
'18_446_744_073_709_551_616|-8.2023437675747321e-18_446_744_073_709_551_616',
'-8.2023437675747321e-18_446_744_073_709_551_616|18_446_744_073_709_551_616',
];
}
}
16 changes: 8 additions & 8 deletions tests/Fixer/ClassNotation/OrderedTypesFixerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public function testFixCases(string $expected, ?string $input = null, ?array $co
}

/**
* @return iterable<string, string[]|(string|null|array<string, string>)[]>
* @return iterable<string, (null|array<string, string>|string)[]|string[]>
*/
public static function provideFixCases(): iterable
{
Expand Down Expand Up @@ -162,7 +162,7 @@ public function testFixPhp80(string $expected, ?string $input = null, ?array $co
}

/**
* @return iterable<(string|null|array<string, string>)[]>
* @return iterable<(null|array<string, string>|string)[]>
*/
public static function providePhp80Cases(): iterable
{
Expand Down Expand Up @@ -214,7 +214,7 @@ public function testFixDefaultCases(string $expected, ?string $input = null): vo
}

/**
* @return iterable<string[]|(string|null|array<string, string>)[]>
* @return iterable<(null|array<string, string>|string)[]|string[]>
*/
public static function provideDefaultCases(): iterable
{
Expand Down Expand Up @@ -330,7 +330,7 @@ public function testFixWithAlphaAlgorithmAndNullAlwaysLast(string $expected, ?st
}

/**
* @return iterable<string[]|(string|null|string[])[]>
* @return iterable<(null|string|string[])[]|string[]>
*/
public static function provideAlphaAlgorithmAndNullAlwaysLastCases(): iterable
{
Expand Down Expand Up @@ -446,7 +446,7 @@ public function testFixWithAlphaAlgorithmOnly(string $expected, ?string $input =
}

/**
* @return iterable<string[]|(string|null|array<string, string>)[]>
* @return iterable<(null|array<string, string>|string)[]|string[]>
*/
public static function provideAlphaAlgorithmOnlyCases(): iterable
{
Expand Down Expand Up @@ -561,7 +561,7 @@ public function testFixWithSandwichedWhitespaceOrCommentInType(string $expected,
}

/**
* @return iterable<string[]|(string|null|array<string, string>)[]>
* @return iterable<(null|array<string, string>|string)[]|string[]>
*/
public static function provideSandwichedWhitespaceOrCommentInTypeCases(): iterable
{
Expand Down Expand Up @@ -603,7 +603,7 @@ public function testFixPhp81(string $expected, ?string $input = null, ?array $co
}

/**
* @return iterable<string[]|(string|null|array<string, string>)[]>
* @return iterable<(null|array<string, string>|string)[]|string[]>
*/
public static function providePhp81Cases(): iterable
{
Expand Down Expand Up @@ -660,7 +660,7 @@ public function testFixPhp82(string $expected, ?string $input = null, ?array $co
}

/**
* @return iterable<string[]|(string|null|array<string, string>)[]>
* @return iterable<(null|array<string, string>|string)[]|string[]>
*/
public static function providePhp82Cases(): iterable
{
Expand Down
2 changes: 1 addition & 1 deletion tests/Fixer/PhpUnit/PhpUnitTestAnnotationFixerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1046,7 +1046,7 @@ public function testFix80(string $expected, string $input, array $config): void
}

/**
* @return iterable<(string|array<string, string>)[]>
* @return iterable<(array<string, string>|string)[]>
*/
public static function provideFix80Cases(): iterable
{
Expand Down

0 comments on commit 60d6c21

Please sign in to comment.