PhpdocVarWithoutNameFixerTest.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663
  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\Tests\Test\AbstractFixerTestCase;
  14. /**
  15. * @author Graham Campbell <hello@gjcampbell.co.uk>
  16. *
  17. * @internal
  18. *
  19. * @covers \PhpCsFixer\Fixer\Phpdoc\PhpdocVarWithoutNameFixer
  20. *
  21. * @extends AbstractFixerTestCase<\PhpCsFixer\Fixer\Phpdoc\PhpdocVarWithoutNameFixer>
  22. */
  23. final class PhpdocVarWithoutNameFixerTest extends AbstractFixerTestCase
  24. {
  25. /**
  26. * @dataProvider provideFixVarCases
  27. */
  28. public function testFixVar(string $expected, ?string $input = null): void
  29. {
  30. $this->doTest($expected, $input);
  31. }
  32. /**
  33. * @dataProvider provideFixVarCases
  34. */
  35. public function testFixType(string $expected, ?string $input = null): void
  36. {
  37. $expected = str_replace('@var', '@type', $expected);
  38. if (null !== $input) {
  39. $input = str_replace('@var', '@type', $input);
  40. }
  41. $this->doTest($expected, $input);
  42. }
  43. /**
  44. * @return iterable<int|string, array{0: string, 1?: string}>
  45. */
  46. public static function provideFixVarCases(): iterable
  47. {
  48. yield 'testFixVar' => [
  49. <<<'EOF'
  50. <?php
  51. class Foo
  52. {
  53. /**
  54. * @var string Hello!
  55. */
  56. public $foo;
  57. }
  58. EOF,
  59. <<<'EOF'
  60. <?php
  61. class Foo
  62. {
  63. /**
  64. * @var string $foo Hello!
  65. */
  66. public $foo;
  67. }
  68. EOF,
  69. ];
  70. yield 'testFixType' => [
  71. <<<'EOF'
  72. <?php
  73. class Foo
  74. {
  75. /**
  76. * @var int|null
  77. */
  78. public $bar;
  79. }
  80. EOF,
  81. <<<'EOF'
  82. <?php
  83. class Foo
  84. {
  85. /**
  86. * @var int|null $bar
  87. */
  88. public $bar;
  89. }
  90. EOF,
  91. ];
  92. yield 'testDoNothing' => [
  93. <<<'EOF'
  94. <?php
  95. class Foo
  96. {
  97. /**
  98. * @var Foo\Bar This is a variable.
  99. */
  100. public $bar;
  101. }
  102. EOF,
  103. ];
  104. yield 'testFixVarWithNestedKeys' => [
  105. <<<'EOF'
  106. <?php
  107. class Foo
  108. {
  109. /**
  110. * @var array {
  111. * @var bool $required Whether this element is required
  112. * @var string $label The display name for this element
  113. * }
  114. */
  115. public $options;
  116. }
  117. EOF,
  118. <<<'EOF'
  119. <?php
  120. class Foo
  121. {
  122. /**
  123. * @var array $options {
  124. * @var bool $required Whether this element is required
  125. * @var string $label The display name for this element
  126. * }
  127. */
  128. public $options;
  129. }
  130. EOF,
  131. ];
  132. yield 'testSingleLine' => [
  133. <<<'EOF'
  134. <?php
  135. class Foo
  136. {
  137. /** @var Foo\Bar */
  138. public $bar;
  139. }
  140. EOF,
  141. <<<'EOF'
  142. <?php
  143. class Foo
  144. {
  145. /** @var Foo\Bar $bar */
  146. public $bar;
  147. }
  148. EOF,
  149. ];
  150. yield 'testSingleLineProtected' => [
  151. <<<'EOF'
  152. <?php
  153. class Foo
  154. {
  155. /** @var Foo\Bar */
  156. protected $bar;
  157. }
  158. EOF,
  159. <<<'EOF'
  160. <?php
  161. class Foo
  162. {
  163. /** @var Foo\Bar $bar */
  164. protected $bar;
  165. }
  166. EOF,
  167. ];
  168. yield 'testSingleLinePrivate' => [
  169. <<<'EOF'
  170. <?php
  171. class Foo
  172. {
  173. /** @var Foo\Bar */
  174. private $bar;
  175. }
  176. EOF,
  177. <<<'EOF'
  178. <?php
  179. class Foo
  180. {
  181. /** @var Foo\Bar $bar */
  182. private $bar;
  183. }
  184. EOF,
  185. ];
  186. yield 'testSingleLineVar' => [
  187. <<<'EOF'
  188. <?php
  189. class Foo
  190. {
  191. /** @var Foo\Bar */
  192. var $bar;
  193. }
  194. EOF,
  195. <<<'EOF'
  196. <?php
  197. class Foo
  198. {
  199. /** @var Foo\Bar $bar */
  200. var $bar;
  201. }
  202. EOF,
  203. ];
  204. yield 'testSingleLineStatic' => [
  205. <<<'EOF'
  206. <?php
  207. class Foo
  208. {
  209. /** @var Foo\Bar */
  210. static public $bar;
  211. }
  212. EOF,
  213. <<<'EOF'
  214. <?php
  215. class Foo
  216. {
  217. /** @var Foo\Bar $bar */
  218. static public $bar;
  219. }
  220. EOF,
  221. ];
  222. yield 'testSingleLineNoSpace' => [
  223. <<<'EOF'
  224. <?php
  225. class Foo
  226. {
  227. /** @var Foo\Bar*/
  228. public $bar;
  229. }
  230. EOF,
  231. <<<'EOF'
  232. <?php
  233. class Foo
  234. {
  235. /** @var Foo\Bar $bar*/
  236. public $bar;
  237. }
  238. EOF,
  239. ];
  240. yield 'testInlineDoc' => [
  241. <<<'EOF'
  242. <?php
  243. class Foo
  244. {
  245. /**
  246. * Initializes this class with the given options.
  247. *
  248. * @param array $options {
  249. * @var bool $required Whether this element is required
  250. * @var string $label The display name for this element
  251. * }
  252. */
  253. public function init($options)
  254. {
  255. // Do something
  256. }
  257. }
  258. EOF,
  259. ];
  260. yield 'testSingleLineNoProperty' => [
  261. <<<'EOF'
  262. <?php
  263. /** @var Foo\Bar $bar */
  264. $bar;
  265. EOF,
  266. ];
  267. yield 'testMultiLineNoProperty' => [
  268. <<<'EOF'
  269. <?php
  270. /**
  271. * @var Foo\Bar $bar
  272. */
  273. $bar;
  274. EOF,
  275. ];
  276. yield 'testVeryNestedInlineDoc' => [
  277. <<<'EOF'
  278. <?php
  279. class Foo
  280. {
  281. /**
  282. * @var array {
  283. * @var array $secondLevelOne {
  284. * {@internal This should not break}
  285. * @var int $thirdLevel
  286. * }
  287. * @var array $secondLevelTwo {
  288. * @var array $thirdLevel {
  289. * @var string $fourthLevel
  290. * }
  291. * @var int $moreThirdLevel
  292. * }
  293. * @var int $secondLevelThree
  294. * }
  295. */
  296. public $nestedFoo;
  297. }
  298. EOF,
  299. <<<'EOF'
  300. <?php
  301. class Foo
  302. {
  303. /**
  304. * @var array $nestedFoo {
  305. * @var array $secondLevelOne {
  306. * {@internal This should not break}
  307. * @var int $thirdLevel
  308. * }
  309. * @var array $secondLevelTwo {
  310. * @var array $thirdLevel {
  311. * @var string $fourthLevel
  312. * }
  313. * @var int $moreThirdLevel
  314. * }
  315. * @var int $secondLevelThree
  316. * }
  317. */
  318. public $nestedFoo;
  319. }
  320. EOF,
  321. ];
  322. yield [
  323. '<?php
  324. class Foo
  325. {
  326. /**
  327. * @no_candidate string Hello!
  328. */
  329. public $foo;
  330. }
  331. ',
  332. ];
  333. yield [
  334. '<?php
  335. class Foo{}
  336. /** */',
  337. ];
  338. yield 'anonymousClass' => [
  339. <<<'EOF'
  340. <?php
  341. class Anon
  342. {
  343. public function getNewAnon()
  344. {
  345. return new class()
  346. {
  347. /**
  348. * @var string
  349. */
  350. public $stringVar;
  351. public function getNewAnon()
  352. {
  353. return new class()
  354. {
  355. /**
  356. * @var string
  357. */
  358. public $stringVar;
  359. };
  360. }
  361. };
  362. }
  363. }
  364. EOF,
  365. <<<'EOF'
  366. <?php
  367. class Anon
  368. {
  369. public function getNewAnon()
  370. {
  371. return new class()
  372. {
  373. /**
  374. * @var $stringVar string
  375. */
  376. public $stringVar;
  377. public function getNewAnon()
  378. {
  379. return new class()
  380. {
  381. /**
  382. * @var $stringVar string
  383. */
  384. public $stringVar;
  385. };
  386. }
  387. };
  388. }
  389. }
  390. EOF,
  391. ];
  392. yield [
  393. '<?php
  394. /**
  395. * Header
  396. */
  397. class A {} // for the candidate check
  398. /**
  399. * @var ClassLoader $loader
  400. */
  401. $loader = require __DIR__.\'/../vendor/autoload.php\';
  402. /**
  403. * @var \Foo\Bar $bar
  404. */
  405. $bar->doSomething(1);
  406. /**
  407. * @var $bar \Foo\Bar
  408. */
  409. $bar->doSomething(2);
  410. /**
  411. * @var User $bar
  412. */
  413. ($bar = tmp())->doSomething(3);
  414. /**
  415. * @var User $bar
  416. */
  417. list($bar) = a();
  418. ',
  419. ];
  420. yield 'const are not handled by this fixer' => [
  421. '<?php
  422. class A
  423. {
  424. /**
  425. * @var array<string, true> SKIPPED_TYPES
  426. */
  427. private const SKIPPED_TYPES = ["a" => true];
  428. }
  429. ',
  430. ];
  431. yield 'trait' => [
  432. '<?php
  433. trait StaticExample {
  434. /**
  435. * @var string Hello!
  436. */
  437. public static $static = "foo";
  438. }',
  439. '<?php
  440. trait StaticExample {
  441. /**
  442. * @var string $static Hello!
  443. */
  444. public static $static = "foo";
  445. }',
  446. ];
  447. yield 'complex type with union containing callable that has `$this` in signature' => [
  448. <<<'EOF'
  449. <?php
  450. class Foo
  451. {
  452. /**
  453. * @var array<string, string|array{ string|\Closure(mixed, string, $this): int|float }>|false Hello!
  454. */
  455. public $foo;
  456. /** @var int Hello! */
  457. public $foo2;
  458. /** @var int Hello! */
  459. public $foo3;
  460. /** @var int Hello! */
  461. public $foo4;
  462. }
  463. EOF,
  464. <<<'EOF'
  465. <?php
  466. class Foo
  467. {
  468. /**
  469. * @var array<string, string|array{ string|\Closure(mixed, string, $this): int|float }>|false $foo Hello!
  470. */
  471. public $foo;
  472. /** @var int $thi Hello! */
  473. public $foo2;
  474. /** @var int $thiss Hello! */
  475. public $foo3;
  476. /** @var int $this2 Hello! */
  477. public $foo4;
  478. }
  479. EOF,
  480. ];
  481. yield 'testFixMultibyteVariableName' => [
  482. <<<'EOF'
  483. <?php
  484. class Foo
  485. {
  486. /** @var int Hello! */
  487. public $foo;
  488. /** @var ๐Ÿš€ ๐Ÿš€ */
  489. public $foo2;
  490. }
  491. EOF,
  492. <<<'EOF'
  493. <?php
  494. class Foo
  495. {
  496. /** @var int $my๐Ÿš€ Hello! */
  497. public $foo;
  498. /** @var ๐Ÿš€ $my ๐Ÿš€ */
  499. public $foo2;
  500. }
  501. EOF,
  502. ];
  503. yield '@var with callable syntax' => [
  504. <<<'EOF'
  505. <?php
  506. class Foo
  507. {
  508. /** @var array<callable(string, Buzz): void> */
  509. protected $bar;
  510. }
  511. EOF,
  512. <<<'EOF'
  513. <?php
  514. class Foo
  515. {
  516. /** @var array<callable(string $baz, Buzz $buzz): void> */
  517. protected $bar;
  518. }
  519. EOF,
  520. ];
  521. }
  522. /**
  523. * @dataProvider provideFix81Cases
  524. *
  525. * @requires PHP 8.1
  526. */
  527. public function testFix81(string $expected, ?string $input = null): void
  528. {
  529. $this->doTest($expected, $input);
  530. }
  531. /**
  532. * @return iterable<string, array{0: string, 1?: string}>
  533. */
  534. public static function provideFix81Cases(): iterable
  535. {
  536. yield 'readonly' => [
  537. '<?php
  538. class Foo
  539. {
  540. /** @var Foo */
  541. public $bar1;
  542. /** @var Foo */
  543. public readonly int $bar2;
  544. /** @var Foo */
  545. readonly public int $bar3;
  546. /** @var Foo */
  547. readonly int $bar4;
  548. }',
  549. '<?php
  550. class Foo
  551. {
  552. /** @var Foo $bar1 */
  553. public $bar1;
  554. /** @var Foo $bar2 */
  555. public readonly int $bar2;
  556. /** @var Foo $bar3 */
  557. readonly public int $bar3;
  558. /** @var Foo $bar4 */
  559. readonly int $bar4;
  560. }',
  561. ];
  562. yield 'final public const are not handled by this fixer' => [
  563. '<?php
  564. class A
  565. {
  566. /**
  567. * @var array<string, true> SKIPPED_TYPES
  568. */
  569. final public const SKIPPED_TYPES = ["a" => true];
  570. }
  571. ',
  572. ];
  573. }
  574. }