PhpdocToParamTypeFixerTest.php 20 KB

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