TypeExpressionTest.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  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. yield ['(int)', ['(int)']];
  91. yield ['(int|\\Exception)', ['(int|\\Exception)']];
  92. yield ['($foo is int ? false : true)', ['($foo is int ? false : true)']];
  93. }
  94. /**
  95. * @dataProvider provideGetTypesGlueCases
  96. */
  97. public function testGetTypesGlue(string $expectedTypesGlue, string $typesExpression): void
  98. {
  99. $expression = new TypeExpression($typesExpression, null, []);
  100. static::assertSame($expectedTypesGlue, $expression->getTypesGlue());
  101. }
  102. public static function provideGetTypesGlueCases(): iterable
  103. {
  104. yield ['|', 'string']; // for backward behaviour
  105. yield ['|', 'bool|string'];
  106. yield ['&', 'Foo&Bar'];
  107. }
  108. /**
  109. * @param NamespaceUseAnalysis[] $namespaceUses
  110. *
  111. * @dataProvider provideCommonTypeCases
  112. */
  113. public function testGetCommonType(string $typesExpression, ?string $expectedCommonType, NamespaceAnalysis $namespace = null, array $namespaceUses = []): void
  114. {
  115. $expression = new TypeExpression($typesExpression, $namespace, $namespaceUses);
  116. static::assertSame($expectedCommonType, $expression->getCommonType());
  117. }
  118. public static function provideCommonTypeCases(): iterable
  119. {
  120. $globalNamespace = new NamespaceAnalysis('', '', 0, 999, 0, 999);
  121. $appNamespace = new NamespaceAnalysis('App', 'App', 0, 999, 0, 999);
  122. $useTraversable = new NamespaceUseAnalysis('Traversable', 'Traversable', false, 0, 0, NamespaceUseAnalysis::TYPE_CLASS);
  123. $useObjectAsTraversable = new NamespaceUseAnalysis('Foo', 'Traversable', false, 0, 0, NamespaceUseAnalysis::TYPE_CLASS);
  124. yield ['true', 'bool'];
  125. yield ['false', 'bool'];
  126. yield ['bool', 'bool'];
  127. yield ['int', 'int'];
  128. yield ['float', 'float'];
  129. yield ['string', 'string'];
  130. yield ['array', 'array'];
  131. yield ['object', 'object'];
  132. yield ['self', 'self'];
  133. yield ['static', 'static'];
  134. yield ['bool[]', 'array'];
  135. yield ['int[]', 'array'];
  136. yield ['float[]', 'array'];
  137. yield ['string[]', 'array'];
  138. yield ['array[]', 'array'];
  139. yield ['bool[][]', 'array'];
  140. yield ['int[][]', 'array'];
  141. yield ['float[][]', 'array'];
  142. yield ['string[][]', 'array'];
  143. yield ['array[][]', 'array'];
  144. yield ['array|iterable', 'iterable'];
  145. yield ['iterable|array', 'iterable'];
  146. yield ['array|Traversable', 'iterable'];
  147. yield ['array|\Traversable', 'iterable'];
  148. yield ['array|Traversable', 'iterable', $globalNamespace];
  149. yield ['iterable|Traversable', 'iterable'];
  150. yield ['array<string>', 'array'];
  151. yield ['array<int, string>', 'array'];
  152. yield ['iterable<string>', 'iterable'];
  153. yield ['iterable<int, string>', 'iterable'];
  154. yield ['\Traversable<string>', '\Traversable'];
  155. yield ['Traversable<int, string>', 'Traversable'];
  156. yield ['Collection<string>', 'Collection'];
  157. yield ['Collection<int, string>', 'Collection'];
  158. yield ['array<int, string>|iterable<int, string>', 'iterable'];
  159. yield ['int[]|string[]', 'array'];
  160. yield ['int|null', 'int'];
  161. yield ['null|int', 'int'];
  162. yield ['void', 'void'];
  163. yield ['never', 'never'];
  164. yield ['array|Traversable', 'iterable', null, [$useTraversable]];
  165. yield ['array|Traversable', 'iterable', $globalNamespace, [$useTraversable]];
  166. yield ['array|Traversable', 'iterable', $appNamespace, [$useTraversable]];
  167. yield ['self|static', 'self'];
  168. yield ['array|Traversable', null, null, [$useObjectAsTraversable]];
  169. yield ['array|Traversable', null, $globalNamespace, [$useObjectAsTraversable]];
  170. yield ['array|Traversable', null, $appNamespace, [$useObjectAsTraversable]];
  171. yield ['bool|int', null];
  172. yield ['string|bool', null];
  173. yield ['array<int, string>|Collection<int, string>', null];
  174. }
  175. /**
  176. * @dataProvider provideAllowsNullCases
  177. */
  178. public function testAllowsNull(string $typesExpression, bool $expectNullAllowed): void
  179. {
  180. $expression = new TypeExpression($typesExpression, null, []);
  181. static::assertSame($expectNullAllowed, $expression->allowsNull());
  182. }
  183. public static function provideAllowsNullCases(): iterable
  184. {
  185. yield ['null', true];
  186. yield ['mixed', true];
  187. yield ['null|mixed', true];
  188. yield ['int|bool|null', true];
  189. yield ['int|bool|mixed', true];
  190. yield ['int', false];
  191. yield ['bool', false];
  192. yield ['string', false];
  193. }
  194. /**
  195. * @dataProvider provideSortTypesCases
  196. */
  197. public function testSortTypes(string $typesExpression, string $expectResult): void
  198. {
  199. $expression = new TypeExpression($typesExpression, null, []);
  200. $expression->sortTypes(static function (TypeExpression $a, TypeExpression $b): int {
  201. return strcasecmp($a->toString(), $b->toString());
  202. });
  203. static::assertSame($expectResult, $expression->toString());
  204. }
  205. public static function provideSortTypesCases(): iterable
  206. {
  207. yield 'not a union type' => [
  208. 'int',
  209. 'int',
  210. ];
  211. yield 'simple' => [
  212. 'int|bool',
  213. 'bool|int',
  214. ];
  215. yield 'simple in generic' => [
  216. 'array<int|bool>',
  217. 'array<bool|int>',
  218. ];
  219. yield 'generic with multiple types' => [
  220. 'array<int|bool, string|float>',
  221. 'array<bool|int, float|string>',
  222. ];
  223. yield 'simple in array shape with int key' => [
  224. 'array{0: int|bool}',
  225. 'array{0: bool|int}',
  226. ];
  227. yield 'simple in array shape with string key' => [
  228. 'array{"foo": int|bool}',
  229. 'array{"foo": bool|int}',
  230. ];
  231. yield 'simple in array shape with multiple keys' => [
  232. 'array{0: int|bool, "foo": int|bool}',
  233. 'array{0: bool|int, "foo": bool|int}',
  234. ];
  235. yield 'simple in callable argument' => [
  236. 'callable(int|bool)',
  237. 'callable(bool|int)',
  238. ];
  239. yield 'callable with multiple arguments' => [
  240. 'callable(int|bool, null|array)',
  241. 'callable(bool|int, array|null)',
  242. ];
  243. yield 'simple in callable return type' => [
  244. 'callable(): string|float',
  245. 'callable(): float|string',
  246. ];
  247. yield 'simple in closure argument' => [
  248. 'Closure(int|bool)',
  249. 'Closure(bool|int)',
  250. ];
  251. yield 'closure with multiple arguments' => [
  252. 'Closure(int|bool, null|array)',
  253. 'Closure(bool|int, array|null)',
  254. ];
  255. yield 'simple in closure return type' => [
  256. 'Closure(): string|float',
  257. 'Closure(): float|string',
  258. ];
  259. yield 'with multiple nesting levels' => [
  260. 'array{0: Foo<int|bool>|Bar<callable(string|float|array<int|bool>): Foo|Bar>}',
  261. 'array{0: Bar<callable(array<bool|int>|float|string): Bar|Foo>|Foo<bool|int>}',
  262. ];
  263. yield 'nullable generic' => [
  264. '?array<Foo|Bar>',
  265. '?array<Bar|Foo>',
  266. ];
  267. yield 'nullable callable' => [
  268. '?callable(Foo|Bar): Foo|Bar',
  269. '?callable(Bar|Foo): Bar|Foo',
  270. ];
  271. yield 'nullable array shape' => [
  272. '?array{0: Foo|Bar}',
  273. '?array{0: Bar|Foo}',
  274. ];
  275. yield 'simple types alternation' => [
  276. 'array<Foo&Bar>',
  277. 'array<Bar&Foo>',
  278. ];
  279. yield 'nesty stuff' => [
  280. 'array<Level11&array<Level2|array<Level31&Level32>>>',
  281. 'array<array<array<Level31&Level32>|Level2>&Level11>',
  282. ];
  283. yield 'parenthesized' => [
  284. '(Foo|Bar)',
  285. '(Bar|Foo)',
  286. ];
  287. yield 'parenthesized intersect' => [
  288. '(Foo&Bar)',
  289. '(Bar&Foo)',
  290. ];
  291. yield 'parenthesized in closure return type' => [
  292. 'Closure(): (string|float)',
  293. 'Closure(): (float|string)',
  294. ];
  295. yield 'conditional with variable' => [
  296. '($x is (CFoo|(CBaz&CBar)) ? (TFoo|(TBaz&TBar)) : (FFoo|(FBaz&FBar)))',
  297. '($x is ((CBar&CBaz)|CFoo) ? ((TBar&TBaz)|TFoo) : ((FBar&FBaz)|FFoo))',
  298. ];
  299. yield 'conditional with type' => [
  300. '((Foo|Bar) is x ? y : z)',
  301. '((Bar|Foo) is x ? y : z)',
  302. ];
  303. yield 'conditional in conditional' => [
  304. '((Foo|Bar) is x ? ($x is (CFoo|CBar) ? (TFoo|TBar) : (FFoo|FBar)) : z)',
  305. '((Bar|Foo) is x ? ($x is (CBar|CFoo) ? (TBar|TFoo) : (FBar|FFoo)) : z)',
  306. ];
  307. }
  308. }