PhpdocAddMissingParamAnnotationFixerTest.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  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\Phpdoc;
  13. use PhpCsFixer\ConfigurationException\InvalidConfigurationException;
  14. use PhpCsFixer\Tests\Test\AbstractFixerTestCase;
  15. use PhpCsFixer\WhitespacesFixerConfig;
  16. /**
  17. * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
  18. *
  19. * @internal
  20. *
  21. * @covers \PhpCsFixer\Fixer\Phpdoc\PhpdocAddMissingParamAnnotationFixer
  22. */
  23. final class PhpdocAddMissingParamAnnotationFixerTest extends AbstractFixerTestCase
  24. {
  25. public function testConfigureRejectsUnknownConfigurationKey(): void
  26. {
  27. $key = 'foo';
  28. $this->expectException(InvalidConfigurationException::class);
  29. $this->expectExceptionMessage(sprintf(
  30. '[phpdoc_add_missing_param_annotation] Invalid configuration: The option "%s" does not exist.',
  31. $key
  32. ));
  33. $this->fixer->configure([
  34. $key => 'bar',
  35. ]);
  36. }
  37. /**
  38. * @dataProvider provideConfigureRejectsInvalidConfigurationValueCases
  39. *
  40. * @param mixed $value
  41. */
  42. public function testConfigureRejectsInvalidConfigurationValue($value, string $expectedMessage): void
  43. {
  44. $this->expectException(InvalidConfigurationException::class);
  45. $this->expectExceptionMessageMatches($expectedMessage);
  46. $this->fixer->configure([
  47. 'only_untyped' => $value,
  48. ]);
  49. }
  50. /**
  51. * @return iterable<string, array>
  52. */
  53. public static function provideConfigureRejectsInvalidConfigurationValueCases(): iterable
  54. {
  55. yield 'null' => [
  56. null,
  57. '#expected to be of type "bool", but is of type "(null|NULL)"\.$#',
  58. ];
  59. yield 'int' => [
  60. 1,
  61. '#expected to be of type "bool", but is of type "(int|integer)"\.$#',
  62. ];
  63. yield 'array' => [
  64. [],
  65. '#expected to be of type "bool", but is of type "array"\.$#',
  66. ];
  67. yield 'float' => [
  68. 0.1,
  69. '#expected to be of type "bool", but is of type "(float|double)"\.$#',
  70. ];
  71. yield 'object' => [
  72. new \stdClass(),
  73. '#expected to be of type "bool", but is of type "stdClass"\.$#',
  74. ];
  75. }
  76. /**
  77. * @param null|array<string, mixed> $config
  78. *
  79. * @dataProvider provideFixCases
  80. */
  81. public function testFix(string $expected, ?string $input = null, ?array $config = null): void
  82. {
  83. $this->fixer->configure($config ?? ['only_untyped' => false]);
  84. $this->doTest($expected, $input);
  85. }
  86. public static function provideFixCases(): iterable
  87. {
  88. yield [
  89. '<?php
  90. /**
  91. *
  92. */',
  93. ];
  94. yield [
  95. '<?php
  96. /**
  97. * @param int $foo
  98. * @param mixed $bar
  99. */
  100. function f1($foo, $bar) {}',
  101. '<?php
  102. /**
  103. * @param int $foo
  104. */
  105. function f1($foo, $bar) {}',
  106. ];
  107. yield [
  108. '<?php
  109. /**
  110. * @param int $bar
  111. * @param mixed $foo
  112. */
  113. function f2($foo, $bar) {}',
  114. '<?php
  115. /**
  116. * @param int $bar
  117. */
  118. function f2($foo, $bar) {}',
  119. ];
  120. yield [
  121. '<?php
  122. /**
  123. * @return void
  124. * @param mixed $foo
  125. * @param mixed $bar
  126. */
  127. function f3($foo, $bar) {}',
  128. '<?php
  129. /**
  130. * @return void
  131. */
  132. function f3($foo, $bar) {}',
  133. ];
  134. yield [
  135. '<?php
  136. abstract class Foo {
  137. /**
  138. * @param int $bar
  139. * @param mixed $foo
  140. */
  141. abstract public function f4a($foo, $bar);
  142. }',
  143. '<?php
  144. abstract class Foo {
  145. /**
  146. * @param int $bar
  147. */
  148. abstract public function f4a($foo, $bar);
  149. }',
  150. ];
  151. yield [
  152. '<?php
  153. class Foo {
  154. /**
  155. * @param int $bar
  156. * @param mixed $foo
  157. */
  158. static final public function f4b($foo, $bar) {}
  159. }',
  160. '<?php
  161. class Foo {
  162. /**
  163. * @param int $bar
  164. */
  165. static final public function f4b($foo, $bar) {}
  166. }',
  167. ];
  168. yield [
  169. '<?php
  170. class Foo {
  171. /**
  172. * @var int
  173. */
  174. private $foo;
  175. }',
  176. ];
  177. yield [
  178. '<?php
  179. /**
  180. * @param $bar No type !!
  181. * @param mixed $foo
  182. */
  183. function f5($foo, $bar) {}',
  184. '<?php
  185. /**
  186. * @param $bar No type !!
  187. */
  188. function f5($foo, $bar) {}',
  189. ];
  190. yield [
  191. '<?php
  192. /**
  193. * @param int
  194. * @param int $bar
  195. * @param Foo\Bar $foo
  196. */
  197. function f6(Foo\Bar $foo, $bar) {}',
  198. '<?php
  199. /**
  200. * @param int
  201. * @param int $bar
  202. */
  203. function f6(Foo\Bar $foo, $bar) {}',
  204. ];
  205. yield [
  206. '<?php
  207. /**
  208. * @param int $bar
  209. * @param null|string $foo
  210. */
  211. function f7(string $foo = nuLl, $bar) {}',
  212. '<?php
  213. /**
  214. * @param int $bar
  215. */
  216. function f7(string $foo = nuLl, $bar) {}',
  217. ];
  218. yield [
  219. '<?php
  220. /**
  221. * @param int $bar
  222. * @param mixed $baz
  223. *
  224. * @return void
  225. */
  226. function f9(string $foo, $bar, $baz) {}',
  227. '<?php
  228. /**
  229. * @param int $bar
  230. *
  231. * @return void
  232. */
  233. function f9(string $foo, $bar, $baz) {}',
  234. ['only_untyped' => true],
  235. ];
  236. yield [
  237. '<?php
  238. /**
  239. * @param bool|bool[] $caseSensitive Line 1
  240. * Line 2
  241. */
  242. function f11($caseSensitive) {}
  243. ',
  244. ];
  245. yield [
  246. '<?php
  247. /** @return string */
  248. function hello($string)
  249. {
  250. return $string;
  251. }',
  252. ];
  253. yield [
  254. '<?php
  255. /** @return string
  256. * @param mixed $string
  257. */
  258. function hello($string)
  259. {
  260. return $string;
  261. }',
  262. '<?php
  263. /** @return string
  264. */
  265. function hello($string)
  266. {
  267. return $string;
  268. }',
  269. ];
  270. yield [
  271. '<?php
  272. /**
  273. * @param mixed $string
  274. * @return string */
  275. function hello($string)
  276. {
  277. return $string;
  278. }',
  279. '<?php
  280. /**
  281. * @return string */
  282. function hello($string)
  283. {
  284. return $string;
  285. }',
  286. ];
  287. yield [
  288. '<?php
  289. /**
  290. * @param int $bar
  291. * @param string $foo
  292. */
  293. function f8(string $foo = "null", $bar) {}',
  294. '<?php
  295. /**
  296. * @param int $bar
  297. */
  298. function f8(string $foo = "null", $bar) {}',
  299. ];
  300. yield [
  301. '<?php
  302. /**
  303. * @{inheritdoc}
  304. */
  305. function f10(string $foo = "null", $bar) {}',
  306. ];
  307. yield [
  308. '<?php
  309. /**
  310. * @inheritDoc
  311. */
  312. function f10(string $foo = "null", $bar) {}',
  313. ];
  314. yield [
  315. '<?php
  316. /**
  317. * @param int $bar
  318. * @param ?array $foo
  319. */
  320. function p1(?array $foo = null, $bar) {}',
  321. '<?php
  322. /**
  323. * @param int $bar
  324. */
  325. function p1(?array $foo = null, $bar) {}',
  326. ['only_untyped' => false],
  327. ];
  328. yield [
  329. '<?php
  330. /**
  331. * Foo
  332. * @param mixed $bar
  333. */
  334. function p1(?int $foo = 0, $bar) {}',
  335. '<?php
  336. /**
  337. * Foo
  338. */
  339. function p1(?int $foo = 0, $bar) {}',
  340. ['only_untyped' => true],
  341. ];
  342. yield [
  343. '<?php
  344. /**
  345. * Foo
  346. * @return int
  347. */
  348. function p1(?int $foo = 0) {}',
  349. null,
  350. ['only_untyped' => true],
  351. ];
  352. }
  353. /**
  354. * @param null|array<string, mixed> $config
  355. *
  356. * @dataProvider provideMessyWhitespacesCases
  357. */
  358. public function testMessyWhitespaces(string $expected, ?string $input = null, ?array $config = null): void
  359. {
  360. $this->fixer->setWhitespacesConfig(new WhitespacesFixerConfig("\t", "\r\n"));
  361. $this->fixer->configure($config ?? ['only_untyped' => false]);
  362. $this->doTest($expected, $input);
  363. }
  364. public static function provideMessyWhitespacesCases(): iterable
  365. {
  366. yield [
  367. "<?php\r\n\t/**\r\n\t * @param int \$bar\r\n\t * @param null|string \$foo\r\n\t */\r\n\tfunction f7(string \$foo = nuLl, \$bar) {}",
  368. "<?php\r\n\t/**\r\n\t * @param int \$bar\r\n\t */\r\n\tfunction f7(string \$foo = nuLl, \$bar) {}",
  369. ];
  370. }
  371. /**
  372. * @dataProvider provideByReferenceCases
  373. */
  374. public function testByReference(string $expected, string $input): void
  375. {
  376. $this->fixer->configure(['only_untyped' => false]);
  377. $this->doTest($expected, $input);
  378. }
  379. public static function provideByReferenceCases(): iterable
  380. {
  381. yield [
  382. '<?php
  383. /**
  384. * something
  385. * @param mixed $numbers
  386. */
  387. function add(&$numbers) {}
  388. ',
  389. '<?php
  390. /**
  391. * something
  392. */
  393. function add(&$numbers) {}
  394. ',
  395. ];
  396. yield [
  397. '<?php
  398. /**
  399. * something
  400. * @param null|array $numbers
  401. */
  402. function add(array &$numbers = null) {}
  403. ',
  404. '<?php
  405. /**
  406. * something
  407. */
  408. function add(array &$numbers = null) {}
  409. ',
  410. ];
  411. }
  412. /**
  413. * @dataProvider provideVariableNumberOfArgumentsCases
  414. */
  415. public function testVariableNumberOfArguments(string $expected, string $input): void
  416. {
  417. $this->fixer->configure(['only_untyped' => false]);
  418. $this->doTest($expected, $input);
  419. }
  420. public static function provideVariableNumberOfArgumentsCases(): iterable
  421. {
  422. yield [
  423. '<?php
  424. /**
  425. * something
  426. * @param array $numbers
  427. */
  428. function sum(...$numbers) {}
  429. ',
  430. '<?php
  431. /**
  432. * something
  433. */
  434. function sum(...$numbers) {}
  435. ',
  436. ];
  437. yield [
  438. '<?php
  439. /**
  440. * @param int $a
  441. * @param array $numbers
  442. */
  443. function sum($a, ...$numbers) {}
  444. ',
  445. '<?php
  446. /**
  447. * @param int $a
  448. */
  449. function sum($a, ...$numbers) {}
  450. ',
  451. ];
  452. yield [
  453. '<?php
  454. /**
  455. * @param \Date[] $numbers
  456. */
  457. function sum(\Date ...$numbers) {}
  458. ',
  459. '<?php
  460. /**
  461. */
  462. function sum(\Date ...$numbers) {}
  463. ',
  464. ];
  465. }
  466. /**
  467. * @dataProvider provideFix80Cases
  468. *
  469. * @requires PHP 8.0
  470. */
  471. public function testFix80(string $expected, ?string $input = null): void
  472. {
  473. $this->fixer->configure(['only_untyped' => false]);
  474. $this->doTest($expected, $input);
  475. }
  476. public static function provideFix80Cases(): iterable
  477. {
  478. yield [
  479. '<?php class Foo {
  480. /**
  481. * @param Bar $x
  482. * @param ?Bar $y
  483. * @param null|Bar $z
  484. */
  485. public function __construct(
  486. public Bar $x,
  487. protected ?Bar $y,
  488. private null|Bar $z,
  489. ) {}
  490. }',
  491. '<?php class Foo {
  492. /**
  493. */
  494. public function __construct(
  495. public Bar $x,
  496. protected ?Bar $y,
  497. private null|Bar $z,
  498. ) {}
  499. }',
  500. ];
  501. }
  502. /**
  503. * @dataProvider provideFix81Cases
  504. *
  505. * @requires PHP 8.1
  506. */
  507. public function testFix81(string $expected, ?string $input = null): void
  508. {
  509. $this->fixer->configure(['only_untyped' => false]);
  510. $this->doTest($expected, $input);
  511. }
  512. public static function provideFix81Cases(): iterable
  513. {
  514. yield [
  515. '<?php class Foo {
  516. /**
  517. * @param Bar $bar
  518. * @param Baz $baz
  519. */
  520. public function __construct(
  521. public readonly Bar $bar,
  522. readonly public Baz $baz,
  523. ) {}
  524. }',
  525. '<?php class Foo {
  526. /**
  527. */
  528. public function __construct(
  529. public readonly Bar $bar,
  530. readonly public Baz $baz,
  531. ) {}
  532. }',
  533. ];
  534. }
  535. }