123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502 |
- <?php
- declare(strict_types=1);
- /*
- * This file is part of PHP CS Fixer.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- * Dariusz Rumiński <dariusz.ruminski@gmail.com>
- *
- * This source file is subject to the MIT license that is bundled
- * with this source code in the file LICENSE.
- */
- namespace PhpCsFixer\Tests\DocBlock;
- use PhpCsFixer\DocBlock\TypeExpression;
- use PhpCsFixer\Tests\TestCase;
- use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis;
- use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceUseAnalysis;
- /**
- * @covers \PhpCsFixer\DocBlock\TypeExpression
- *
- * @internal
- */
- final class TypeExpressionTest extends TestCase
- {
- /**
- * @param string[] $expectedTypes
- *
- * @dataProvider provideGetTypesCases
- */
- public function testGetTypes(string $typesExpression, array $expectedTypes): void
- {
- $expression = new TypeExpression($typesExpression, null, []);
- self::assertSame($expectedTypes, $expression->getTypes());
- }
- public static function provideGetTypesCases(): iterable
- {
- yield ['int', ['int']];
- yield ['Foo[][]', ['Foo[][]']];
- yield ['int[]', ['int[]']];
- yield ['int[]|null', ['int[]', 'null']];
- yield ['int[]|null|?int|array', ['int[]', 'null', '?int', 'array']];
- yield ['null|Foo\Bar|\Baz\Bax|int[]', ['null', 'Foo\Bar', '\Baz\Bax', 'int[]']];
- yield ['gen<int>', ['gen<int>']];
- yield ['int|gen<int>', ['int', 'gen<int>']];
- yield ['\int|\gen<\int, \bool>', ['\int', '\gen<\int, \bool>']];
- yield ['gen<int, int>', ['gen<int, int>']];
- yield ['gen<int, bool|string>', ['gen<int, bool|string>']];
- yield ['gen<int, string[]>', ['gen<int, string[]>']];
- yield ['gen<int, gener<string, bool>>', ['gen<int, gener<string, bool>>']];
- yield ['gen<int, gener<string, null|bool>>', ['gen<int, gener<string, null|bool>>']];
- 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[]']];
- yield ['this', ['this']];
- yield ['@this', ['@this']];
- yield ['$SELF|int', ['$SELF', 'int']];
- yield ['array<string|int, string>', ['array<string|int, string>']];
- yield ['Collection<Foo<Bar>, Foo<Baz>>', ['Collection<Foo<Bar>, Foo<Baz>>']];
- yield ['int | string', ['int', 'string']];
- yield ['Foo::*', ['Foo::*']];
- yield ['Foo::A', ['Foo::A']];
- yield ['Foo::A|Foo::B', ['Foo::A', 'Foo::B']];
- yield ['Foo::A*', ['Foo::A*']];
- 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"']];
- yield ['int | "a" | A<B<C, D>, E<F::*|G[]>>', ['int', '"a"', 'A<B<C, D>, E<F::*|G[]>>']];
- yield ['class-string<Foo>', ['class-string<Foo>']];
- yield ['A&B', ['A', 'B']];
- yield ['A & B', ['A', 'B']];
- yield ['array{}', ['array{}']];
- yield ['object{ }', ['object{ }']];
- yield ['array{1: bool, 2: bool}', ['array{1: bool, 2: bool}']];
- yield ['array{a: int|string, b?: bool}', ['array{a: int|string, b?: bool}']];
- yield ['array{\'a\': "a", "b"?: \'b\'}', ['array{\'a\': "a", "b"?: \'b\'}']];
- yield ['array { a : int | string , b ? : A<B, C> }', ['array { a : int | string , b ? : A<B, C> }']];
- yield ['array{bool, int}', ['array{bool, int}']];
- yield ['object{ bool, foo2: int }', ['object{ bool, foo2: int }']];
- yield ['callable(string)', ['callable(string)']];
- 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>']];
- yield ['callable(string): callable(int)', ['callable(string): callable(int)']];
- yield ['callable(string) : callable(int) : bool', ['callable(string) : callable(int) : bool']];
- yield ['TheCollection<callable(Foo, Bar,Baz): Foo[]>|string[]|null', ['TheCollection<callable(Foo, Bar,Baz): Foo[]>', 'string[]', 'null']];
- yield ['Closure()', ['Closure()']];
- yield ['Closure(string)', ['Closure(string)']];
- yield ['\\Closure', ['\\Closure']];
- yield ['\\Closure()', ['\\Closure()']];
- yield ['\\Closure(string)', ['\\Closure(string)']];
- yield ['\\Closure(string, bool)', ['\\Closure(string, bool)']];
- yield ['\\Closure(string|int, bool)', ['\\Closure(string|int, bool)']];
- yield ['\\Closure(string):bool', ['\\Closure(string):bool']];
- yield ['\\Closure(string): bool', ['\\Closure(string): bool']];
- 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)']];
- yield ['\'\'', ['\'\'']];
- yield ['\'foo\'', ['\'foo\'']];
- yield ['\'\\\\\'', ['\'\\\\\'']];
- yield ['\'\\\'\'', ['\'\\\'\'']];
- yield ['\'a\\\'s"\\\\\n\r\t\'|"b\\"s\'\\\\\n\r\t"', ['\'a\\\'s"\\\\\n\r\t\'', '"b\\"s\'\\\\\n\r\t"']];
- }
- /**
- * @dataProvider provideGetTypesGlueCases
- */
- public function testGetTypesGlue(string $expectedTypesGlue, string $typesExpression): void
- {
- $expression = new TypeExpression($typesExpression, null, []);
- self::assertSame($expectedTypesGlue, $expression->getTypesGlue());
- }
- public static function provideGetTypesGlueCases(): iterable
- {
- yield ['|', 'string']; // for backward behaviour
- yield ['|', 'bool|string'];
- yield ['&', 'Foo&Bar'];
- }
- /**
- * @param NamespaceUseAnalysis[] $namespaceUses
- *
- * @dataProvider provideCommonTypeCases
- */
- public function testGetCommonType(string $typesExpression, ?string $expectedCommonType, NamespaceAnalysis $namespace = null, array $namespaceUses = []): void
- {
- $expression = new TypeExpression($typesExpression, $namespace, $namespaceUses);
- self::assertSame($expectedCommonType, $expression->getCommonType());
- }
- public static function provideCommonTypeCases(): iterable
- {
- $globalNamespace = new NamespaceAnalysis('', '', 0, 999, 0, 999);
- $appNamespace = new NamespaceAnalysis('App', 'App', 0, 999, 0, 999);
- $useTraversable = new NamespaceUseAnalysis('Traversable', 'Traversable', false, 0, 0, NamespaceUseAnalysis::TYPE_CLASS);
- $useObjectAsTraversable = new NamespaceUseAnalysis('Foo', 'Traversable', false, 0, 0, NamespaceUseAnalysis::TYPE_CLASS);
- yield ['true', 'bool'];
- yield ['false', 'bool'];
- yield ['bool', 'bool'];
- yield ['int', 'int'];
- yield ['float', 'float'];
- yield ['string', 'string'];
- yield ['array', 'array'];
- yield ['object', 'object'];
- yield ['self', 'self'];
- yield ['static', 'static'];
- yield ['bool[]', 'array'];
- yield ['int[]', 'array'];
- yield ['float[]', 'array'];
- yield ['string[]', 'array'];
- yield ['array[]', 'array'];
- yield ['bool[][]', 'array'];
- yield ['int[][]', 'array'];
- yield ['float[][]', 'array'];
- yield ['string[][]', 'array'];
- yield ['array[][]', 'array'];
- yield ['array|iterable', 'iterable'];
- yield ['iterable|array', 'iterable'];
- yield ['array|Traversable', 'iterable'];
- yield ['array|\Traversable', 'iterable'];
- yield ['array|Traversable', 'iterable', $globalNamespace];
- yield ['iterable|Traversable', 'iterable'];
- yield ['array<string>', 'array'];
- yield ['array<int, string>', 'array'];
- yield ['iterable<string>', 'iterable'];
- yield ['iterable<int, string>', 'iterable'];
- yield ['\Traversable<string>', '\Traversable'];
- yield ['Traversable<int, string>', 'Traversable'];
- yield ['Collection<string>', 'Collection'];
- yield ['Collection<int, string>', 'Collection'];
- yield ['array<int, string>|iterable<int, string>', 'iterable'];
- yield ['int[]|string[]', 'array'];
- yield ['int|null', 'int'];
- yield ['null|int', 'int'];
- yield ['void', 'void'];
- yield ['never', 'never'];
- yield ['array|Traversable', 'iterable', null, [$useTraversable]];
- yield ['array|Traversable', 'iterable', $globalNamespace, [$useTraversable]];
- yield ['array|Traversable', 'iterable', $appNamespace, [$useTraversable]];
- yield ['self|static', 'self'];
- yield ['array|Traversable', null, null, [$useObjectAsTraversable]];
- yield ['array|Traversable', null, $globalNamespace, [$useObjectAsTraversable]];
- yield ['array|Traversable', null, $appNamespace, [$useObjectAsTraversable]];
- yield ['bool|int', null];
- yield ['string|bool', null];
- yield ['array<int, string>|Collection<int, string>', null];
- }
- /**
- * @dataProvider provideAllowsNullCases
- */
- public function testAllowsNull(string $typesExpression, bool $expectNullAllowed): void
- {
- $expression = new TypeExpression($typesExpression, null, []);
- self::assertSame($expectNullAllowed, $expression->allowsNull());
- }
- public static function provideAllowsNullCases(): iterable
- {
- yield ['null', true];
- yield ['mixed', true];
- yield ['null|mixed', true];
- yield ['int|bool|null', true];
- yield ['int|bool|mixed', true];
- yield ['int', false];
- yield ['bool', false];
- yield ['string', false];
- }
- /**
- * @dataProvider provideSortTypesCases
- */
- public function testSortTypes(string $typesExpression, string $expectResult): void
- {
- $expression = new TypeExpression($typesExpression, null, []);
- $expression->sortTypes(static function (TypeExpression $a, TypeExpression $b): int {
- return strcasecmp($a->toString(), $b->toString());
- });
- self::assertSame($expectResult, $expression->toString());
- }
- public static function provideSortTypesCases(): iterable
- {
- yield 'not a union type' => [
- 'int',
- 'int',
- ];
- yield 'simple' => [
- 'int|bool',
- 'bool|int',
- ];
- yield 'simple in generic' => [
- 'array<int|bool>',
- 'array<bool|int>',
- ];
- yield 'generic with multiple types' => [
- 'array<int|bool, string|float>',
- 'array<bool|int, float|string>',
- ];
- yield 'simple in array shape with int key' => [
- 'array{0: int|bool}',
- 'array{0: bool|int}',
- ];
- yield 'simple in array shape with string key' => [
- 'array{"foo": int|bool}',
- 'array{"foo": bool|int}',
- ];
- yield 'simple in array shape with multiple keys' => [
- 'array{0: int|bool, "foo": int|bool}',
- 'array{0: bool|int, "foo": bool|int}',
- ];
- yield 'simple in array shape with implicit key' => [
- 'array{int|bool}',
- 'array{bool|int}',
- ];
- 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}}',
- ];
- yield 'array shape with multiple colons - callable' => [
- 'array{array{x:int|bool}, int|bool, callable(): void}',
- 'array{array{x:bool|int}, bool|int, callable(): void}',
- ];
- yield 'simple in callable argument' => [
- 'callable(int|bool)',
- 'callable(bool|int)',
- ];
- yield 'callable with multiple arguments' => [
- 'callable(int|bool, null|array)',
- 'callable(bool|int, array|null)',
- ];
- yield 'simple in callable return type' => [
- 'callable(): string|float',
- 'callable(): float|string',
- ];
- yield 'simple in closure argument' => [
- 'Closure(int|bool)',
- 'Closure(bool|int)',
- ];
- yield 'closure with multiple arguments' => [
- 'Closure(int|bool, null|array)',
- 'Closure(bool|int, array|null)',
- ];
- yield 'simple in closure return type' => [
- 'Closure(): string|float',
- 'Closure(): float|string',
- ];
- yield 'with multiple nesting levels' => [
- 'array{0: Foo<int|bool>|Bar<callable(string|float|array<int|bool>): Foo|Bar>}',
- 'array{0: Bar<callable(array<bool|int>|float|string): Bar|Foo>|Foo<bool|int>}',
- ];
- yield 'complex type with Closure with $this' => [
- 'array<string, string|array{ string|\Closure(mixed, string, $this): (int|float) }>|false',
- 'array<string, array{ \Closure(mixed, string, $this): (float|int)|string }|string>|false',
- ];
- yield 'nullable generic' => [
- '?array<Foo|Bar>',
- '?array<Bar|Foo>',
- ];
- yield 'nullable callable' => [
- '?callable(Foo|Bar): Foo|Bar',
- '?callable(Bar|Foo): Bar|Foo',
- ];
- yield 'nullable array shape' => [
- '?array{0: Foo|Bar}',
- '?array{0: Bar|Foo}',
- ];
- yield 'simple types alternation' => [
- 'array<Foo&Bar>',
- 'array<Bar&Foo>',
- ];
- yield 'nesty stuff' => [
- '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)',
- ];
- }
- }
|