TypeExpressionTest.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4. * This file is part of PHP CS Fixer.
  5. *
  6. * (c) Fabien Potencier <fabien@symfony.com>
  7. * Dariusz Rumiński <dariusz.ruminski@gmail.com>
  8. *
  9. * This source file is subject to the MIT license that is bundled
  10. * with this source code in the file LICENSE.
  11. */
  12. namespace PhpCsFixer\Tests\DocBlock;
  13. use PhpCsFixer\DocBlock\TypeExpression;
  14. use PhpCsFixer\Tests\TestCase;
  15. use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis;
  16. use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceUseAnalysis;
  17. /**
  18. * @covers \PhpCsFixer\DocBlock\TypeExpression
  19. *
  20. * @internal
  21. */
  22. final class TypeExpressionTest extends TestCase
  23. {
  24. /**
  25. * @param string[] $expectedTypes
  26. *
  27. * @dataProvider provideGetTypesCases
  28. */
  29. public function testGetTypes(string $typesExpression, array $expectedTypes): void
  30. {
  31. $expression = new TypeExpression($typesExpression, null, []);
  32. static::assertSame($expectedTypes, $expression->getTypes());
  33. }
  34. public static function provideGetTypesCases(): iterable
  35. {
  36. yield ['int', ['int']];
  37. yield ['Foo[][]', ['Foo[][]']];
  38. yield ['int[]', ['int[]']];
  39. yield ['int[]|null', ['int[]', 'null']];
  40. yield ['int[]|null|?int|array', ['int[]', 'null', '?int', 'array']];
  41. yield ['null|Foo\Bar|\Baz\Bax|int[]', ['null', 'Foo\Bar', '\Baz\Bax', 'int[]']];
  42. yield ['gen<int>', ['gen<int>']];
  43. yield ['int|gen<int>', ['int', 'gen<int>']];
  44. yield ['\int|\gen<\int, \bool>', ['\int', '\gen<\int, \bool>']];
  45. yield ['gen<int, int>', ['gen<int, int>']];
  46. yield ['gen<int, bool|string>', ['gen<int, bool|string>']];
  47. yield ['gen<int, string[]>', ['gen<int, string[]>']];
  48. yield ['gen<int, gener<string, bool>>', ['gen<int, gener<string, bool>>']];
  49. yield ['gen<int, gener<string, null|bool>>', ['gen<int, gener<string, null|bool>>']];
  50. yield ['null|gen<int, gener<string, bool>>|int|string[]', ['null', 'gen<int, gener<string, bool>>', 'int', 'string[]']];
  51. yield ['null|gen<int, gener<string, bool>>|int|array<int, string>|string[]', ['null', 'gen<int, gener<string, bool>>', 'int', 'array<int, string>', 'string[]']];
  52. yield ['this', ['this']];
  53. yield ['@this', ['@this']];
  54. yield ['$SELF|int', ['$SELF', 'int']];
  55. yield ['array<string|int, string>', ['array<string|int, string>']];
  56. yield ['Collection<Foo<Bar>, Foo<Baz>>', ['Collection<Foo<Bar>, Foo<Baz>>']];
  57. yield ['int | string', ['int', 'string']];
  58. yield ['Foo::*', ['Foo::*']];
  59. yield ['Foo::A', ['Foo::A']];
  60. yield ['Foo::A|Foo::B', ['Foo::A', 'Foo::B']];
  61. yield ['Foo::A*', ['Foo::A*']];
  62. yield ['array<Foo::A*>|null', ['array<Foo::A*>', 'null']];
  63. 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"']];
  64. yield ['int | "a" | A<B<C, D>, E<F::*|G[]>>', ['int', '"a"', 'A<B<C, D>, E<F::*|G[]>>']];
  65. yield ['class-string<Foo>', ['class-string<Foo>']];
  66. yield ['A&B', ['A', 'B']];
  67. yield ['A & B', ['A', 'B']];
  68. yield ['array{1: bool, 2: bool}', ['array{1: bool, 2: bool}']];
  69. yield ['array{a: int|string, b?: bool}', ['array{a: int|string, b?: bool}']];
  70. yield ['array{\'a\': "a", "b"?: \'b\'}', ['array{\'a\': "a", "b"?: \'b\'}']];
  71. yield ['array { a : int | string , b ? : A<B, C> }', ['array { a : int | string , b ? : A<B, C> }']];
  72. yield ['callable(string)', ['callable(string)']];
  73. yield ['callable(string): bool', ['callable(string): bool']];
  74. yield ['callable(array<int, string>, array<int, Foo>): bool', ['callable(array<int, string>, array<int, Foo>): bool']];
  75. yield ['array<int, callable(string): bool>', ['array<int, callable(string): bool>']];
  76. yield ['callable(string): callable(int)', ['callable(string): callable(int)']];
  77. yield ['callable(string) : callable(int) : bool', ['callable(string) : callable(int) : bool']];
  78. yield ['TheCollection<callable(Foo, Bar,Baz): Foo[]>|string[]|null', ['TheCollection<callable(Foo, Bar,Baz): Foo[]>', 'string[]', 'null']];
  79. yield ['Closure()', ['Closure()']];
  80. yield ['Closure(string)', ['Closure(string)']];
  81. yield ['\\Closure', ['\\Closure']];
  82. yield ['\\Closure()', ['\\Closure()']];
  83. yield ['\\Closure(string)', ['\\Closure(string)']];
  84. yield ['\\Closure(string, bool)', ['\\Closure(string, bool)']];
  85. yield ['\\Closure(string|int, bool)', ['\\Closure(string|int, bool)']];
  86. yield ['\\Closure(string):bool', ['\\Closure(string):bool']];
  87. yield ['\\Closure(string): bool', ['\\Closure(string): bool']];
  88. yield ['\\Closure(string|int, bool): bool', ['\\Closure(string|int, bool): bool']];
  89. yield ['array < int , callable ( string ) : bool >', ['array < int , callable ( string ) : bool >']];
  90. }
  91. /**
  92. * @dataProvider provideGetTypesGlueCases
  93. */
  94. public function testGetTypesGlue(string $expectedTypesGlue, string $typesExpression): void
  95. {
  96. $expression = new TypeExpression($typesExpression, null, []);
  97. static::assertSame($expectedTypesGlue, $expression->getTypesGlue());
  98. }
  99. public static function provideGetTypesGlueCases(): iterable
  100. {
  101. yield ['|', 'string']; // for backward behaviour
  102. yield ['|', 'bool|string'];
  103. yield ['&', 'Foo&Bar'];
  104. }
  105. /**
  106. * @param NamespaceUseAnalysis[] $namespaceUses
  107. *
  108. * @dataProvider provideCommonTypeCases
  109. */
  110. public function testGetCommonType(string $typesExpression, ?string $expectedCommonType, NamespaceAnalysis $namespace = null, array $namespaceUses = []): void
  111. {
  112. $expression = new TypeExpression($typesExpression, $namespace, $namespaceUses);
  113. static::assertSame($expectedCommonType, $expression->getCommonType());
  114. }
  115. public static function provideCommonTypeCases(): iterable
  116. {
  117. $globalNamespace = new NamespaceAnalysis('', '', 0, 999, 0, 999);
  118. $appNamespace = new NamespaceAnalysis('App', 'App', 0, 999, 0, 999);
  119. $useTraversable = new NamespaceUseAnalysis('Traversable', 'Traversable', false, 0, 0, NamespaceUseAnalysis::TYPE_CLASS);
  120. $useObjectAsTraversable = new NamespaceUseAnalysis('Foo', 'Traversable', false, 0, 0, NamespaceUseAnalysis::TYPE_CLASS);
  121. yield ['true', 'bool'];
  122. yield ['false', 'bool'];
  123. yield ['bool', 'bool'];
  124. yield ['int', 'int'];
  125. yield ['float', 'float'];
  126. yield ['string', 'string'];
  127. yield ['array', 'array'];
  128. yield ['object', 'object'];
  129. yield ['self', 'self'];
  130. yield ['static', 'static'];
  131. yield ['bool[]', 'array'];
  132. yield ['int[]', 'array'];
  133. yield ['float[]', 'array'];
  134. yield ['string[]', 'array'];
  135. yield ['array[]', 'array'];
  136. yield ['bool[][]', 'array'];
  137. yield ['int[][]', 'array'];
  138. yield ['float[][]', 'array'];
  139. yield ['string[][]', 'array'];
  140. yield ['array[][]', 'array'];
  141. yield ['array|iterable', 'iterable'];
  142. yield ['iterable|array', 'iterable'];
  143. yield ['array|Traversable', 'iterable'];
  144. yield ['array|\Traversable', 'iterable'];
  145. yield ['array|Traversable', 'iterable', $globalNamespace];
  146. yield ['iterable|Traversable', 'iterable'];
  147. yield ['array<string>', 'array'];
  148. yield ['array<int, string>', 'array'];
  149. yield ['iterable<string>', 'iterable'];
  150. yield ['iterable<int, string>', 'iterable'];
  151. yield ['\Traversable<string>', '\Traversable'];
  152. yield ['Traversable<int, string>', 'Traversable'];
  153. yield ['Collection<string>', 'Collection'];
  154. yield ['Collection<int, string>', 'Collection'];
  155. yield ['array<int, string>|iterable<int, string>', 'iterable'];
  156. yield ['int[]|string[]', 'array'];
  157. yield ['int|null', 'int'];
  158. yield ['null|int', 'int'];
  159. yield ['void', 'void'];
  160. yield ['never', 'never'];
  161. yield ['array|Traversable', 'iterable', null, [$useTraversable]];
  162. yield ['array|Traversable', 'iterable', $globalNamespace, [$useTraversable]];
  163. yield ['array|Traversable', 'iterable', $appNamespace, [$useTraversable]];
  164. yield ['self|static', 'self'];
  165. yield ['array|Traversable', null, null, [$useObjectAsTraversable]];
  166. yield ['array|Traversable', null, $globalNamespace, [$useObjectAsTraversable]];
  167. yield ['array|Traversable', null, $appNamespace, [$useObjectAsTraversable]];
  168. yield ['bool|int', null];
  169. yield ['string|bool', null];
  170. yield ['array<int, string>|Collection<int, string>', null];
  171. }
  172. /**
  173. * @dataProvider provideAllowsNullCases
  174. */
  175. public function testAllowsNull(string $typesExpression, bool $expectNullAllowed): void
  176. {
  177. $expression = new TypeExpression($typesExpression, null, []);
  178. static::assertSame($expectNullAllowed, $expression->allowsNull());
  179. }
  180. public static function provideAllowsNullCases(): iterable
  181. {
  182. yield ['null', true];
  183. yield ['mixed', true];
  184. yield ['null|mixed', true];
  185. yield ['int|bool|null', true];
  186. yield ['int|bool|mixed', true];
  187. yield ['int', false];
  188. yield ['bool', false];
  189. yield ['string', false];
  190. }
  191. /**
  192. * @dataProvider provideSortTypesCases
  193. */
  194. public function testSortTypes(string $typesExpression, string $expectResult): void
  195. {
  196. $expression = new TypeExpression($typesExpression, null, []);
  197. $expression->sortTypes(static function (TypeExpression $a, TypeExpression $b): int {
  198. return strcasecmp($a->toString(), $b->toString());
  199. });
  200. static::assertSame($expectResult, $expression->toString());
  201. }
  202. public static function provideSortTypesCases(): iterable
  203. {
  204. yield 'not a union type' => [
  205. 'int',
  206. 'int',
  207. ];
  208. yield 'simple' => [
  209. 'int|bool',
  210. 'bool|int',
  211. ];
  212. yield 'simple in generic' => [
  213. 'array<int|bool>',
  214. 'array<bool|int>',
  215. ];
  216. yield 'generic with multiple types' => [
  217. 'array<int|bool, string|float>',
  218. 'array<bool|int, float|string>',
  219. ];
  220. yield 'simple in array shape with int key' => [
  221. 'array{0: int|bool}',
  222. 'array{0: bool|int}',
  223. ];
  224. yield 'simple in array shape with string key' => [
  225. 'array{"foo": int|bool}',
  226. 'array{"foo": bool|int}',
  227. ];
  228. yield 'simple in array shape with multiple keys' => [
  229. 'array{0: int|bool, "foo": int|bool}',
  230. 'array{0: bool|int, "foo": bool|int}',
  231. ];
  232. yield 'simple in callable argument' => [
  233. 'callable(int|bool)',
  234. 'callable(bool|int)',
  235. ];
  236. yield 'callable with multiple arguments' => [
  237. 'callable(int|bool, null|array)',
  238. 'callable(bool|int, array|null)',
  239. ];
  240. yield 'simple in callable return type' => [
  241. 'callable(): string|float',
  242. 'callable(): float|string',
  243. ];
  244. yield 'simple in closure argument' => [
  245. 'Closure(int|bool)',
  246. 'Closure(bool|int)',
  247. ];
  248. yield 'closure with multiple arguments' => [
  249. 'Closure(int|bool, null|array)',
  250. 'Closure(bool|int, array|null)',
  251. ];
  252. yield 'simple in closure return type' => [
  253. 'Closure(): string|float',
  254. 'Closure(): float|string',
  255. ];
  256. yield 'with multiple nesting levels' => [
  257. 'array{0: Foo<int|bool>|Bar<callable(string|float|array<int|bool>): Foo|Bar>}',
  258. 'array{0: Bar<callable(array<bool|int>|float|string): Bar|Foo>|Foo<bool|int>}',
  259. ];
  260. yield 'nullable generic' => [
  261. '?array<Foo|Bar>',
  262. '?array<Bar|Foo>',
  263. ];
  264. yield 'nullable callable' => [
  265. '?callable(Foo|Bar): Foo|Bar',
  266. '?callable(Bar|Foo): Bar|Foo',
  267. ];
  268. yield 'nullable array shape' => [
  269. '?array{0: Foo|Bar}',
  270. '?array{0: Bar|Foo}',
  271. ];
  272. yield 'simple types alternation' => [
  273. 'array<Foo&Bar>',
  274. 'array<Bar&Foo>',
  275. ];
  276. yield 'nesty stuff' => [
  277. 'array<Level11&array<Level2|array<Level31&Level32>>>',
  278. 'array<array<array<Level31&Level32>|Level2>&Level11>',
  279. ];
  280. }
  281. }