PhpdocToPropertyTypeFixerTest.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618
  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. * @internal
  16. *
  17. * @group phpdoc
  18. *
  19. * @covers \PhpCsFixer\Fixer\FunctionNotation\PhpdocToPropertyTypeFixer
  20. *
  21. * @extends AbstractFixerTestCase<\PhpCsFixer\Fixer\FunctionNotation\PhpdocToPropertyTypeFixer>
  22. *
  23. * @phpstan-import-type _AutogeneratedInputConfiguration from \PhpCsFixer\Fixer\FunctionNotation\PhpdocToPropertyTypeFixer
  24. */
  25. final class PhpdocToPropertyTypeFixerTest extends AbstractFixerTestCase
  26. {
  27. /**
  28. * @param _AutogeneratedInputConfiguration $config
  29. *
  30. * @dataProvider provideFixCases
  31. */
  32. public function testFix(string $expected, ?string $input = null, array $config = []): void
  33. {
  34. $this->fixer->configure($config);
  35. $this->doTest($expected, $input);
  36. }
  37. public static function provideFixCases(): iterable
  38. {
  39. yield 'no phpdoc return' => [
  40. '<?php class Foo { private $foo; }',
  41. ];
  42. yield 'invalid return' => [
  43. '<?php class Foo { /** @var */ private $foo; }',
  44. ];
  45. yield 'invalid class 1' => [
  46. '<?php class Foo { /** @var \9 */ private $foo; }',
  47. ];
  48. yield 'invalid class 2' => [
  49. '<?php class Foo { /** @var \Foo\\\Bar */ private $foo; }',
  50. ];
  51. yield 'multiple returns' => [
  52. '<?php
  53. class Foo {
  54. /**
  55. * @var Bar
  56. * @var Baz
  57. */
  58. private $foo;
  59. }
  60. ',
  61. ];
  62. yield 'non-root class' => [
  63. '<?php class Foo { /** @var Bar */ private Bar $foo; }',
  64. '<?php class Foo { /** @var Bar */ private $foo; }',
  65. ];
  66. yield 'non-root namespaced class' => [
  67. '<?php class Foo { /** @var My\Bar */ private My\Bar $foo; }',
  68. '<?php class Foo { /** @var My\Bar */ private $foo; }',
  69. ];
  70. yield 'root class' => [
  71. '<?php class Foo { /** @var \My\Bar */ private \My\Bar $foo; }',
  72. '<?php class Foo { /** @var \My\Bar */ private $foo; }',
  73. ];
  74. yield 'void' => [
  75. '<?php class Foo { /** @var void */ private $foo; }',
  76. ];
  77. yield 'never' => [
  78. '<?php class Foo { /** @var never */ private $foo; }',
  79. ];
  80. yield 'iterable' => [
  81. '<?php class Foo { /** @var iterable */ private iterable $foo; }',
  82. '<?php class Foo { /** @var iterable */ private $foo; }',
  83. ];
  84. yield 'object' => [
  85. '<?php class Foo { /** @var object */ private object $foo; }',
  86. '<?php class Foo { /** @var object */ private $foo; }',
  87. ];
  88. yield 'fix scalar types by default, int' => [
  89. '<?php class Foo { /** @var int */ private int $foo; }',
  90. '<?php class Foo { /** @var int */ private $foo; }',
  91. ];
  92. yield 'fix scalar types by default, float' => [
  93. '<?php class Foo { /** @var float */ private float $foo; }',
  94. '<?php class Foo { /** @var float */ private $foo; }',
  95. ];
  96. yield 'fix scalar types by default, string' => [
  97. '<?php class Foo { /** @var string */ private string $foo; }',
  98. '<?php class Foo { /** @var string */ private $foo; }',
  99. ];
  100. yield 'fix scalar types by default, bool' => [
  101. '<?php class Foo { /** @var bool */ private bool $foo; }',
  102. '<?php class Foo { /** @var bool */ private $foo; }',
  103. ];
  104. yield 'fix scalar types by default, false' => [
  105. '<?php class Foo { /** @var false */ private bool $foo; }',
  106. '<?php class Foo { /** @var false */ private $foo; }',
  107. ];
  108. yield 'fix scalar types by default, true' => [
  109. '<?php class Foo { /** @var true */ private bool $foo; }',
  110. '<?php class Foo { /** @var true */ private $foo; }',
  111. ];
  112. yield 'do not fix scalar types when configured as such' => [
  113. '<?php class Foo { /** @var int */ private $foo; }',
  114. null,
  115. ['scalar_types' => false],
  116. ];
  117. yield 'do not fix union types when configured as such' => [
  118. '<?php class Foo { /** @var int|string */ private $foo; }',
  119. null,
  120. ['union_types' => false],
  121. ];
  122. yield 'array native type' => [
  123. '<?php class Foo { /** @var array */ private array $foo; }',
  124. '<?php class Foo { /** @var array */ private $foo; }',
  125. ];
  126. yield 'callable type' => [
  127. '<?php class Foo { /** @var callable */ private $foo; }',
  128. ];
  129. yield 'self accessor' => [
  130. '<?php class Foo { /** @var self */ private self $foo; }',
  131. '<?php class Foo { /** @var self */ private $foo; }',
  132. ];
  133. yield 'report static as self' => [
  134. '<?php class Foo { /** @var static */ private self $foo; }',
  135. '<?php class Foo { /** @var static */ private $foo; }',
  136. ];
  137. yield 'skip resource special type' => [
  138. '<?php class Foo { /** @var resource */ private $foo; }',
  139. ];
  140. yield 'null alone cannot be a property type' => [
  141. '<?php class Foo { /** @var null */ private $foo; }',
  142. ];
  143. yield 'nullable type' => [
  144. '<?php class Foo { /** @var null|Bar */ private ?Bar $foo = null; }',
  145. '<?php class Foo { /** @var null|Bar */ private $foo; }',
  146. ];
  147. yield 'nullable type with ? notation in phpDoc' => [
  148. '<?php class Foo { /** @var ?Bar */ private ?Bar $foo = null; }',
  149. '<?php class Foo { /** @var ?Bar */ private $foo; }',
  150. ];
  151. yield 'nullable type reverse order' => [
  152. '<?php class Foo { /** @var Bar|null */ private ?Bar $foo = null; }',
  153. '<?php class Foo { /** @var Bar|null */ private $foo; }',
  154. ];
  155. yield 'nullable native type' => [
  156. '<?php class Foo { /** @var null|array */ private ?array $foo = null; }',
  157. '<?php class Foo { /** @var null|array */ private $foo; }',
  158. ];
  159. yield 'nullable type with short notation' => [
  160. '<?php class Foo { /** @var ?array */ private ?array $foo = null; }',
  161. '<?php class Foo { /** @var ?array */ private $foo; }',
  162. ];
  163. yield 'nullable type with existing default value' => [
  164. '<?php class Foo { /** @var ?array */ private ?array $foo = []; }',
  165. '<?php class Foo { /** @var ?array */ private $foo = []; }',
  166. ];
  167. yield 'generics' => [
  168. '<?php class Foo { /** @var array<int, bool> */ private array $foo; }',
  169. '<?php class Foo { /** @var array<int, bool> */ private $foo; }',
  170. ];
  171. yield 'array of types' => [
  172. '<?php class Foo { /** @var Foo[] */ private array $foo; }',
  173. '<?php class Foo { /** @var Foo[] */ private $foo; }',
  174. ];
  175. yield 'array of array of types' => [
  176. '<?php class Foo { /** @var Foo[][] */ private array $foo; }',
  177. '<?php class Foo { /** @var Foo[][] */ private $foo; }',
  178. ];
  179. yield 'nullable array of types' => [
  180. '<?php class Foo { /** @var null|Foo[] */ private ?array $foo = null; }',
  181. '<?php class Foo { /** @var null|Foo[] */ private $foo; }',
  182. ];
  183. yield 'comments' => [
  184. '<?php
  185. class Foo
  186. {
  187. // comment 0
  188. /** @var Foo */ # comment 1
  189. public/**/Foo $foo/**/;# comment 2
  190. }
  191. ',
  192. '<?php
  193. class Foo
  194. {
  195. // comment 0
  196. /** @var Foo */ # comment 1
  197. public/**/$foo/**/;# comment 2
  198. }
  199. ',
  200. ];
  201. yield 'array and traversable' => [
  202. '<?php class Foo { /** @var array|Traversable */ private iterable $foo; }',
  203. '<?php class Foo { /** @var array|Traversable */ private $foo; }',
  204. ];
  205. yield 'array and traversable with leading slash' => [
  206. '<?php class Foo { /** @var array|\Traversable */ private iterable $foo; }',
  207. '<?php class Foo { /** @var array|\Traversable */ private $foo; }',
  208. ];
  209. yield 'array and traversable in a namespace' => [
  210. '<?php
  211. namespace App;
  212. class Foo {
  213. /** @var array|Traversable */
  214. private $foo;
  215. }
  216. ',
  217. ];
  218. yield 'array and traversable with leading slash in a namespace' => [
  219. '<?php
  220. namespace App;
  221. class Foo {
  222. /** @var array|\Traversable */
  223. private iterable $foo;
  224. }
  225. ',
  226. '<?php
  227. namespace App;
  228. class Foo {
  229. /** @var array|\Traversable */
  230. private $foo;
  231. }
  232. ',
  233. ];
  234. yield 'array and imported traversable in a namespace' => [
  235. '<?php
  236. namespace App;
  237. use Traversable;
  238. class Foo {
  239. /** @var array|Traversable */
  240. private iterable $foo;
  241. }
  242. ',
  243. '<?php
  244. namespace App;
  245. use Traversable;
  246. class Foo {
  247. /** @var array|Traversable */
  248. private $foo;
  249. }
  250. ',
  251. ];
  252. yield 'array and object aliased as traversable in a namespace' => [
  253. '<?php
  254. namespace App;
  255. use Bar as Traversable;
  256. class Foo {
  257. /** @var array|Traversable */
  258. private $foo;
  259. }
  260. ',
  261. null,
  262. ];
  263. yield 'array of object and traversable' => [
  264. '<?php class Foo { /** @var Foo[]|Traversable */ private iterable $foo; }',
  265. '<?php class Foo { /** @var Foo[]|Traversable */ private $foo; }',
  266. ];
  267. yield 'array of object and iterable' => [
  268. '<?php class Foo { /** @var Foo[]|iterable */ private iterable $foo; }',
  269. '<?php class Foo { /** @var Foo[]|iterable */ private $foo; }',
  270. ];
  271. yield 'array of string and array of int' => [
  272. '<?php class Foo { /** @var string[]|int[] */ private array $foo; }',
  273. '<?php class Foo { /** @var string[]|int[] */ private $foo; }',
  274. ];
  275. yield 'trait' => [
  276. '<?php trait Foo { /** @var int */ private int $foo; }',
  277. '<?php trait Foo { /** @var int */ private $foo; }',
  278. ];
  279. yield 'static property' => [
  280. '<?php class Foo { /** @var int */ private static int $foo; }',
  281. '<?php class Foo { /** @var int */ private static $foo; }',
  282. ];
  283. yield 'static property reverse order' => [
  284. '<?php class Foo { /** @var int */ static private int $foo; }',
  285. '<?php class Foo { /** @var int */ static private $foo; }',
  286. ];
  287. yield 'var' => [
  288. '<?php class Foo { /** @var int */ var int $foo; }',
  289. '<?php class Foo { /** @var int */ var $foo; }',
  290. ];
  291. yield 'with default value' => [
  292. '<?php class Foo { /** @var int */ public int $foo = 1; }',
  293. '<?php class Foo { /** @var int */ public $foo = 1; }',
  294. ];
  295. yield 'multiple properties of the same type' => [
  296. '<?php class Foo {
  297. /**
  298. * @var int $foo
  299. * @var int $bar
  300. */
  301. public int $foo, $bar;
  302. }',
  303. '<?php class Foo {
  304. /**
  305. * @var int $foo
  306. * @var int $bar
  307. */
  308. public $foo, $bar;
  309. }',
  310. ];
  311. yield 'multiple properties of different types' => [
  312. '<?php class Foo {
  313. /**
  314. * @var int $foo
  315. * @var string $bar
  316. */
  317. public $foo, $bar;
  318. }',
  319. ];
  320. yield 'single property with different annotations' => [
  321. '<?php class Foo {
  322. /**
  323. * @var int $foo
  324. * @var string $foo
  325. */
  326. public $foo;
  327. }',
  328. ];
  329. yield 'multiple properties with missing annotation' => [
  330. '<?php class Foo {
  331. /**
  332. * @var int $foo
  333. */
  334. public $foo, $bar;
  335. }',
  336. ];
  337. yield 'multiple properties with annotation without name' => [
  338. '<?php class Foo {
  339. /**
  340. * @var int
  341. * @var int $bar
  342. */
  343. public $foo, $bar;
  344. }',
  345. ];
  346. yield 'multiple properties with annotation without name reverse order' => [
  347. '<?php class Foo {
  348. /**
  349. * @var int $foo
  350. * @var int
  351. */
  352. public $foo, $bar;
  353. }',
  354. ];
  355. yield 'multiple properties with extra annotations' => [
  356. '<?php class Foo {
  357. /**
  358. * @var string
  359. * @var int $foo
  360. * @var int $bar
  361. * @var int
  362. */
  363. public int $foo, $bar;
  364. }',
  365. '<?php class Foo {
  366. /**
  367. * @var string
  368. * @var int $foo
  369. * @var int $bar
  370. * @var int
  371. */
  372. public $foo, $bar;
  373. }',
  374. ];
  375. yield 'abstract method' => [
  376. '<?php abstract class Foo {
  377. /** @var Bar */ private Bar $foo;
  378. public abstract function getFoo();
  379. }',
  380. '<?php abstract class Foo {
  381. /** @var Bar */ private $foo;
  382. public abstract function getFoo();
  383. }',
  384. ];
  385. yield 'great number of properties' => [
  386. '<?php class Foo {
  387. /** @var string */
  388. private string $foo1;
  389. /** @var string */
  390. private string $foo2;
  391. /** @var int */
  392. private int $foo3;
  393. /** @var string */
  394. private string $foo4;
  395. /** @var string */
  396. private string $foo5;
  397. /** @var string */
  398. private string $foo6;
  399. /** @var string */
  400. private string $foo7;
  401. /** @var int */
  402. private int $foo8;
  403. /** @var string */
  404. private string $foo9;
  405. /** @var int|null */
  406. private ?int $foo10 = null;
  407. }',
  408. '<?php class Foo {
  409. /** @var string */
  410. private $foo1;
  411. /** @var string */
  412. private $foo2;
  413. /** @var int */
  414. private $foo3;
  415. /** @var string */
  416. private $foo4;
  417. /** @var string */
  418. private $foo5;
  419. /** @var string */
  420. private $foo6;
  421. /** @var string */
  422. private $foo7;
  423. /** @var int */
  424. private $foo8;
  425. /** @var string */
  426. private $foo9;
  427. /** @var int|null */
  428. private $foo10;
  429. }',
  430. ];
  431. yield 'anonymous class' => [
  432. '<?php new class { /** @var int */ private int $foo; };',
  433. '<?php new class { /** @var int */ private $foo; };',
  434. ];
  435. yield 'intersection types' => [
  436. '<?php class Foo { /** @var Bar&Baz */ private $x; }',
  437. ];
  438. yield 'very long class name before ampersand' => [
  439. '<?php class Foo { /** @var Baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaar&Baz */ private $x; }',
  440. ];
  441. yield 'very long class name after ampersand' => [
  442. '<?php class Foo { /** @var Bar&Baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaz */ private $x; }',
  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' => [
  460. '<?php class Foo { /** @var mixed */ private $foo; }',
  461. ];
  462. yield 'skip union types' => [
  463. '<?php class Foo { /** @var Foo|Bar */ private $foo; }',
  464. ];
  465. yield 'skip union nullable types' => [
  466. '<?php class Foo { /** @var null|Foo|Bar */ private $foo; }',
  467. ];
  468. }
  469. /**
  470. * @dataProvider provideFix80Cases
  471. *
  472. * @requires PHP 8.0
  473. */
  474. public function testFix80(string $expected, ?string $input = null): void
  475. {
  476. $this->doTest($expected, $input);
  477. }
  478. /**
  479. * @return iterable<string, array{string, string}>
  480. */
  481. public static function provideFix80Cases(): iterable
  482. {
  483. yield 'fix mixed type' => [
  484. '<?php class Foo { /** @var mixed */ private mixed $foo = null; }',
  485. '<?php class Foo { /** @var mixed */ private $foo; }',
  486. ];
  487. yield 'union types' => [
  488. '<?php class Foo { /** @var Foo|Bar */ private Foo|Bar $foo; }',
  489. '<?php class Foo { /** @var Foo|Bar */ private $foo; }',
  490. ];
  491. yield 'union types including nullable' => [
  492. '<?php class Foo { /** @var null|Foo|Bar */ private Foo|Bar|null $foo; }',
  493. '<?php class Foo { /** @var null|Foo|Bar */ private $foo; }',
  494. ];
  495. yield 'union types including generics' => [
  496. '<?php class Foo { /** @var string|array<int, bool> */ private string|array $foo; }',
  497. '<?php class Foo { /** @var string|array<int, bool> */ private $foo; }',
  498. ];
  499. }
  500. /**
  501. * @dataProvider provideFix81Cases
  502. *
  503. * @requires PHP 8.1
  504. */
  505. public function testFix81(string $expected): void
  506. {
  507. $this->doTest($expected);
  508. }
  509. /**
  510. * @return iterable<string, array{string}>
  511. */
  512. public static function provideFix81Cases(): iterable
  513. {
  514. yield 'readonly properties are always typed, make sure the fixer does not crash' => [
  515. '<?php class Foo { /** @var int */ private readonly string $foo; }',
  516. ];
  517. }
  518. }