PhpdocToParamTypeFixerTest.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  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\Fixer\FunctionNotation;
  13. use PhpCsFixer\Tests\Test\AbstractFixerTestCase;
  14. /**
  15. * @author Jan Gantzert <jan@familie-gantzert.de>
  16. *
  17. * @internal
  18. *
  19. * @covers \PhpCsFixer\Fixer\FunctionNotation\PhpdocToParamTypeFixer
  20. */
  21. final class PhpdocToParamTypeFixerTest extends AbstractFixerTestCase
  22. {
  23. /**
  24. * @param array<string, mixed> $config
  25. *
  26. * @dataProvider provideFixCases
  27. */
  28. public function testFix(string $expected, ?string $input = null, ?int $versionSpecificFix = null, array $config = []): void
  29. {
  30. if (
  31. null !== $input
  32. && (null !== $versionSpecificFix && \PHP_VERSION_ID < $versionSpecificFix)
  33. ) {
  34. $expected = $input;
  35. $input = null;
  36. }
  37. $this->fixer->configure($config);
  38. $this->doTest($expected, $input);
  39. }
  40. public static function provideFixCases(): iterable
  41. {
  42. yield 'typehint already defined' => [
  43. '<?php /** @param int $foo */ function foo(int $foo) {}',
  44. ];
  45. yield 'typehint already defined with wrong phpdoc typehint' => [
  46. '<?php /** @param string $foo */ function foo(int $foo) {}',
  47. ];
  48. yield 'no phpdoc param' => [
  49. '<?php function my_foo() {}',
  50. ];
  51. yield 'invalid - phpdoc param without variable' => [
  52. '<?php /** @param */ function my_foo($bar) {}',
  53. ];
  54. yield 'invalid - phpdoc param with non existing class' => [
  55. '<?php /** @param \9 */ function my_foo($bar) {}',
  56. ];
  57. yield 'invalid - phpdoc param with false class hint' => [
  58. '<?php /** @param $foo \\Foo\\\\Bar */ function my_foo($foo) {}',
  59. ];
  60. yield 'invalid - phpdoc param with false param order' => [
  61. '<?php /** @param $foo string */ function my_foo($foo) {}',
  62. ];
  63. yield 'invalid - phpdoc param with hint for next method' => [
  64. '<?php
  65. /**
  66. * @param string $bar
  67. */
  68. function my_foo() {}
  69. function my_foo2($bar) {}
  70. ',
  71. ];
  72. yield 'invalid - phpdoc param with keyword' => [
  73. '<?php
  74. /** @param Break $foo */ function foo_break($foo) {}
  75. /** @param __CLASS__ $foo */ function foo_class($foo) {}
  76. /** @param I\Want\To\Break\\\\Free $foo */ function foo_queen($foo) {}
  77. ',
  78. ];
  79. yield 'non-root class with single int param' => [
  80. '<?php /** @param int $bar */ function my_foo(int $bar) {}',
  81. '<?php /** @param int $bar */ function my_foo($bar) {}',
  82. ];
  83. yield 'non-root class with single float param' => [
  84. '<?php /** @param float $bar */ function my_foo(float $bar) {}',
  85. '<?php /** @param float $bar */ function my_foo($bar) {}',
  86. ];
  87. yield 'non-root class with multiple string params' => [
  88. '<?php
  89. /**
  90. * @param string $bar
  91. * @param string $baz
  92. */
  93. function my_foo(string $bar, string $baz) {}',
  94. '<?php
  95. /**
  96. * @param string $bar
  97. * @param string $baz
  98. */
  99. function my_foo($bar, $baz) {}',
  100. ];
  101. yield 'non-root class with not sorted multiple string params' => [
  102. '<?php
  103. /**
  104. * @param int $foo
  105. * @param string $bar
  106. */
  107. function my_foo(string $bar, int $foo) {}',
  108. '<?php
  109. /**
  110. * @param int $foo
  111. * @param string $bar
  112. */
  113. function my_foo($bar, $foo) {}',
  114. ];
  115. yield 'non-root class with not sorted multiple params and different types' => [
  116. '<?php
  117. /**
  118. * @param int $foo
  119. * @param string $bar
  120. * @param Baz $hey
  121. * @param float $tab
  122. * @param bool $baz
  123. */
  124. function my_foo(string $bar, int $foo, bool $baz, float $tab, Baz $hey) {}',
  125. '<?php
  126. /**
  127. * @param int $foo
  128. * @param string $bar
  129. * @param Baz $hey
  130. * @param float $tab
  131. * @param bool $baz
  132. */
  133. function my_foo($bar, $foo, $baz, $tab, $hey) {}',
  134. ];
  135. yield 'non-root class with massive string params' => [
  136. '<?php
  137. /**
  138. * @param string $bar
  139. * @param string $baz
  140. * @param string $tab
  141. * @param string $foo
  142. */
  143. function my_foo(string $bar, string $baz, string $tab, string $foo) {}',
  144. '<?php
  145. /**
  146. * @param string $bar
  147. * @param string $baz
  148. * @param string $tab
  149. * @param string $foo
  150. */
  151. function my_foo($bar, $baz, $tab, $foo) {}',
  152. ];
  153. yield 'non-root class with different types of params' => [
  154. '<?php
  155. /**
  156. * @param string $bar
  157. * @param int $baz
  158. * @param float $tab
  159. */
  160. function my_foo(string $bar, int $baz, float $tab) {}',
  161. '<?php
  162. /**
  163. * @param string $bar
  164. * @param int $baz
  165. * @param float $tab
  166. */
  167. function my_foo($bar, $baz, $tab) {}',
  168. ];
  169. yield 'non-root class with mixed type of param' => [
  170. '<?php
  171. /**
  172. * @param mixed $bar
  173. */
  174. function my_foo($bar) {}',
  175. ];
  176. yield 'non-root namespaced class' => [
  177. '<?php /** @param My\Bar $foo */ function my_foo(My\Bar $foo) {}',
  178. '<?php /** @param My\Bar $foo */ function my_foo($foo) {}',
  179. ];
  180. yield 'root class' => [
  181. '<?php /** @param \My\Bar $foo */ function my_foo(\My\Bar $foo) {}',
  182. '<?php /** @param \My\Bar $foo */ function my_foo($foo) {}',
  183. ];
  184. yield 'interface' => [
  185. '<?php interface Foo { /** @param Bar $bar */ function my_foo(Bar $bar); }',
  186. '<?php interface Foo { /** @param Bar $bar */ function my_foo($bar); }',
  187. ];
  188. yield 'iterable return on ^7.1' => [
  189. '<?php /** @param iterable $counter */ function my_foo(iterable $counter) {}',
  190. '<?php /** @param iterable $counter */ function my_foo($counter) {}',
  191. ];
  192. yield 'array native type' => [
  193. '<?php /** @param array $foo */ function my_foo(array $foo) {}',
  194. '<?php /** @param array $foo */ function my_foo($foo) {}',
  195. ];
  196. yield 'callable type' => [
  197. '<?php /** @param callable $foo */ function my_foo(callable $foo) {}',
  198. '<?php /** @param callable $foo */ function my_foo($foo) {}',
  199. ];
  200. yield 'self accessor' => [
  201. '<?php
  202. class Foo {
  203. /** @param self $foo */ function my_foo(self $foo) {}
  204. }
  205. ',
  206. '<?php
  207. class Foo {
  208. /** @param self $foo */ function my_foo($foo) {}
  209. }
  210. ',
  211. ];
  212. yield 'report static as self' => [
  213. '<?php
  214. class Foo {
  215. /** @param static $foo */ function my_foo(self $foo) {}
  216. }
  217. ',
  218. '<?php
  219. class Foo {
  220. /** @param static $foo */ function my_foo($foo) {}
  221. }
  222. ',
  223. ];
  224. yield 'skip resource special type' => [
  225. '<?php /** @param $bar resource */ function my_foo($bar) {}',
  226. ];
  227. yield 'skip mixed special type' => [
  228. '<?php /** @param $bar mixed */ function my_foo($bar) {}',
  229. ];
  230. yield 'null alone cannot be a param type' => [
  231. '<?php /** @param $bar null */ function my_foo($bar) {}',
  232. ];
  233. yield 'skip mixed types' => [
  234. '<?php /** @param Foo|Bar $bar */ function my_foo($bar) {}',
  235. ];
  236. yield 'skip mixed types including array' => [
  237. '<?php /** @param array|Foo $expected */ function testResolveIntersectionOfPaths($expected) {}',
  238. ];
  239. yield 'skip primitive or array types' => [
  240. '<?php /** @param string|string[] $expected */ function testResolveIntersectionOfPaths($expected) {}',
  241. ];
  242. yield 'array of types' => [
  243. '<?php /** @param Foo[] $foo */ function my_foo(array $foo) {}',
  244. '<?php /** @param Foo[] $foo */ function my_foo($foo) {}',
  245. ];
  246. yield 'nullable array of types' => [
  247. '<?php /** @param null|Foo[] $foo */ function my_foo(?array $foo) {}',
  248. '<?php /** @param null|Foo[] $foo */ function my_foo($foo) {}',
  249. ];
  250. yield 'nullable and mixed types of arrays' => [
  251. '<?php /** @param null|Foo[]|Bar[] $foo */ function my_foo(?array $foo) {}',
  252. '<?php /** @param null|Foo[]|Bar[] $foo */ function my_foo($foo) {}',
  253. ];
  254. yield 'nullable and array and array of types' => [
  255. '<?php /** @param null|Foo[]|array $foo */ function my_foo(?array $foo) {}',
  256. '<?php /** @param null|Foo[]|array $foo */ function my_foo($foo) {}',
  257. ];
  258. yield 'nullable array of array of types' => [
  259. '<?php /** @param null|Foo[][] $foo */ function my_foo(?array $foo) {}',
  260. '<?php /** @param null|Foo[][] $foo */ function my_foo($foo) {}',
  261. ];
  262. yield 'nullable and string param' => [
  263. '<?php /** @param null|string $foo */ function my_foo(?string $foo) {}',
  264. '<?php /** @param null|string $foo */ function my_foo($foo) {}',
  265. ];
  266. yield 'nullable and int param' => [
  267. '<?php /** @param null|int $foo */ function my_foo(?int $foo) {}',
  268. '<?php /** @param null|int $foo */ function my_foo($foo) {}',
  269. ];
  270. yield 'nullable and float param' => [
  271. '<?php /** @param null|float $foo */ function my_foo(?float $foo) {}',
  272. '<?php /** @param null|float $foo */ function my_foo($foo) {}',
  273. ];
  274. yield 'nullable and bool param' => [
  275. '<?php /** @param null|bool $foo */ function my_foo(?bool $foo) {}',
  276. '<?php /** @param null|bool $foo */ function my_foo($foo) {}',
  277. ];
  278. yield 'nullable and callable param' => [
  279. '<?php /** @param null|callable $foo */ function my_foo(?callable $foo) {}',
  280. '<?php /** @param null|callable $foo */ function my_foo($foo) {}',
  281. ];
  282. yield 'nullable and iterable param' => [
  283. '<?php /** @param null|iterable $foo */ function my_foo(?iterable $foo) {}',
  284. '<?php /** @param null|iterable $foo */ function my_foo($foo) {}',
  285. ];
  286. yield 'nullable and class name param' => [
  287. '<?php /** @param null|Foo $foo */ function my_foo(?Foo $foo) {}',
  288. '<?php /** @param null|Foo $foo */ function my_foo($foo) {}',
  289. ];
  290. yield 'nullable with ? notation in phpDoc' => [
  291. '<?php /** @param ?Foo $foo */ function my_foo(?Foo $foo) {}',
  292. '<?php /** @param ?Foo $foo */ function my_foo($foo) {}',
  293. ];
  294. yield 'array and iterable param' => [
  295. '<?php /** @param Foo[]|iterable $foo */ function my_foo(iterable $foo) {}',
  296. '<?php /** @param Foo[]|iterable $foo */ function my_foo($foo) {}',
  297. ];
  298. yield 'object param' => [
  299. '<?php /** @param object $foo */ function my_foo(object $foo) {}',
  300. '<?php /** @param object $foo */ function my_foo($foo) {}',
  301. 7_02_00,
  302. ];
  303. yield 'nullable and object param' => [
  304. '<?php /** @param null|object $foo */ function my_foo(?object $foo) {}',
  305. '<?php /** @param null|object $foo */ function my_foo($foo) {}',
  306. 7_02_00,
  307. ];
  308. yield 'generics with single type' => [
  309. '<?php /** @param array<foo> $foo */ function my_foo(array $foo) {}',
  310. '<?php /** @param array<foo> $foo */ function my_foo($foo) {}',
  311. ];
  312. yield 'generics with multiple types' => [
  313. '<?php /** @param array<int, string> $foo */ function my_foo(array $foo) {}',
  314. '<?php /** @param array<int, string> $foo */ function my_foo($foo) {}',
  315. ];
  316. yield 'stop searching last token' => [
  317. '<?php class Foo { /** @param Bar $bar */ public function foo($tab) { } }',
  318. ];
  319. yield 'param by reference' => [
  320. '<?php /** @param array $data */ function foo(array &$data) {}',
  321. '<?php /** @param array $data */ function foo(&$data) {}',
  322. ];
  323. yield 'optional param by reference' => [
  324. '<?php /** @param null|string[] $matches */ function matchAll(?array &$matches) {}',
  325. '<?php /** @param null|string[] $matches */ function matchAll(&$matches) {}',
  326. ];
  327. yield 'void as type in phpdoc' => [
  328. '<?php /** @param void $bar */ function foo($bar) {}',
  329. ];
  330. yield 'array and traversable' => [
  331. '<?php /** @param array|Traversable $foo */ function my_foo(iterable $foo) {}',
  332. '<?php /** @param array|Traversable $foo */ function my_foo($foo) {}',
  333. ];
  334. yield 'array and traversable with leading slash' => [
  335. '<?php /** @param array|\Traversable $foo */ function my_foo(iterable $foo) {}',
  336. '<?php /** @param array|\Traversable $foo */ function my_foo($foo) {}',
  337. ];
  338. yield 'array and traversable in a namespace' => [
  339. '<?php
  340. namespace App;
  341. /** @param array|Traversable $foo */
  342. function my_foo($foo) {}
  343. ',
  344. ];
  345. yield 'array and traversable with leading slash in a namespace' => [
  346. '<?php
  347. namespace App;
  348. /** @param array|\Traversable $foo */
  349. function my_foo(iterable $foo) {}
  350. ',
  351. '<?php
  352. namespace App;
  353. /** @param array|\Traversable $foo */
  354. function my_foo($foo) {}
  355. ',
  356. ];
  357. yield 'array and imported traversable in a namespace' => [
  358. '<?php
  359. namespace App;
  360. use Traversable;
  361. /** @param array|Traversable $foo */
  362. function my_foo(iterable $foo) {}
  363. ',
  364. '<?php
  365. namespace App;
  366. use Traversable;
  367. /** @param array|Traversable $foo */
  368. function my_foo($foo) {}
  369. ',
  370. ];
  371. yield 'array and object aliased as traversable in a namespace' => [
  372. '<?php
  373. namespace App;
  374. use Foo as Traversable;
  375. /** @param array|Traversable $foo */
  376. function my_foo($foo) {}
  377. ',
  378. ];
  379. yield 'array of object and traversable' => [
  380. '<?php /** @param Foo[]|Traversable $foo */ function my_foo(iterable $foo) {}',
  381. '<?php /** @param Foo[]|Traversable $foo */ function my_foo($foo) {}',
  382. 7_01_00,
  383. ];
  384. yield 'array of object and iterable' => [
  385. '<?php /** @param Foo[]|iterable $foo */ function my_foo(iterable $foo) {}',
  386. '<?php /** @param Foo[]|iterable $foo */ function my_foo($foo) {}',
  387. ];
  388. yield 'array of string and array of int' => [
  389. '<?php /** @param string[]|int[] $foo */ function my_foo(array $foo) {}',
  390. '<?php /** @param string[]|int[] $foo */ function my_foo($foo) {}',
  391. ];
  392. yield 'do not fix scalar types when configured as such' => [
  393. '<?php /** @param int $foo */ function my_foo($foo) {}',
  394. null,
  395. null,
  396. ['scalar_types' => false],
  397. ];
  398. yield 'do not fix function call' => [
  399. '<?php
  400. /** @param string $foo */
  401. function bar($notFoo) {
  402. return baz($foo);
  403. }
  404. ',
  405. ];
  406. yield 'do not fix function call when no parameter' => [
  407. '<?php
  408. /** @param string $foo */
  409. function bar() {
  410. return baz($foo);
  411. }
  412. ',
  413. ];
  414. yield 'intersection types' => [
  415. '<?php
  416. /** @param Bar&Baz $x */
  417. function bar($x) {}
  418. ',
  419. ];
  420. yield 'very long class name before ampersand' => [
  421. '<?php
  422. /** @param Baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaar&Baz $x */
  423. function bar($x) {}
  424. ',
  425. ];
  426. yield 'very long class name after ampersand' => [
  427. '<?php
  428. /** @param Bar&Baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaz $x */
  429. function bar($x) {}
  430. ',
  431. ];
  432. }
  433. }