PhpdocToParamTypeFixerTest.php 22 KB

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