PhpdocToPropertyTypeFixerTest.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  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; }',
  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; }',
  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; }',
  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; }',
  157. '<?php class Foo { /** @var null|array */ private $foo; }',
  158. ];
  159. yield 'generics' => [
  160. '<?php class Foo { /** @var array<int, bool> */ private array $foo; }',
  161. '<?php class Foo { /** @var array<int, bool> */ private $foo; }',
  162. ];
  163. yield 'array of types' => [
  164. '<?php class Foo { /** @var Foo[] */ private array $foo; }',
  165. '<?php class Foo { /** @var Foo[] */ private $foo; }',
  166. ];
  167. yield 'array of array of types' => [
  168. '<?php class Foo { /** @var Foo[][] */ private array $foo; }',
  169. '<?php class Foo { /** @var Foo[][] */ private $foo; }',
  170. ];
  171. yield 'nullable array of types' => [
  172. '<?php class Foo { /** @var null|Foo[] */ private ?array $foo; }',
  173. '<?php class Foo { /** @var null|Foo[] */ private $foo; }',
  174. ];
  175. yield 'comments' => [
  176. '<?php
  177. class Foo
  178. {
  179. // comment 0
  180. /** @var Foo */ # comment 1
  181. public/**/Foo $foo/**/;# comment 2
  182. }
  183. ',
  184. '<?php
  185. class Foo
  186. {
  187. // comment 0
  188. /** @var Foo */ # comment 1
  189. public/**/$foo/**/;# comment 2
  190. }
  191. ',
  192. ];
  193. yield 'array and traversable' => [
  194. '<?php class Foo { /** @var array|Traversable */ private iterable $foo; }',
  195. '<?php class Foo { /** @var array|Traversable */ private $foo; }',
  196. ];
  197. yield 'array and traversable with leading slash' => [
  198. '<?php class Foo { /** @var array|\Traversable */ private iterable $foo; }',
  199. '<?php class Foo { /** @var array|\Traversable */ private $foo; }',
  200. ];
  201. yield 'array and traversable in a namespace' => [
  202. '<?php
  203. namespace App;
  204. class Foo {
  205. /** @var array|Traversable */
  206. private $foo;
  207. }
  208. ',
  209. ];
  210. yield 'array and traversable with leading slash in a namespace' => [
  211. '<?php
  212. namespace App;
  213. class Foo {
  214. /** @var array|\Traversable */
  215. private iterable $foo;
  216. }
  217. ',
  218. '<?php
  219. namespace App;
  220. class Foo {
  221. /** @var array|\Traversable */
  222. private $foo;
  223. }
  224. ',
  225. ];
  226. yield 'array and imported traversable in a namespace' => [
  227. '<?php
  228. namespace App;
  229. use Traversable;
  230. class Foo {
  231. /** @var array|Traversable */
  232. private iterable $foo;
  233. }
  234. ',
  235. '<?php
  236. namespace App;
  237. use Traversable;
  238. class Foo {
  239. /** @var array|Traversable */
  240. private $foo;
  241. }
  242. ',
  243. ];
  244. yield 'array and object aliased as traversable in a namespace' => [
  245. '<?php
  246. namespace App;
  247. use Bar as Traversable;
  248. class Foo {
  249. /** @var array|Traversable */
  250. private $foo;
  251. }
  252. ',
  253. null,
  254. ];
  255. yield 'array of object and traversable' => [
  256. '<?php class Foo { /** @var Foo[]|Traversable */ private iterable $foo; }',
  257. '<?php class Foo { /** @var Foo[]|Traversable */ private $foo; }',
  258. ];
  259. yield 'array of object and iterable' => [
  260. '<?php class Foo { /** @var Foo[]|iterable */ private iterable $foo; }',
  261. '<?php class Foo { /** @var Foo[]|iterable */ private $foo; }',
  262. ];
  263. yield 'array of string and array of int' => [
  264. '<?php class Foo { /** @var string[]|int[] */ private array $foo; }',
  265. '<?php class Foo { /** @var string[]|int[] */ private $foo; }',
  266. ];
  267. yield 'trait' => [
  268. '<?php trait Foo { /** @var int */ private int $foo; }',
  269. '<?php trait Foo { /** @var int */ private $foo; }',
  270. ];
  271. yield 'static property' => [
  272. '<?php class Foo { /** @var int */ private static int $foo; }',
  273. '<?php class Foo { /** @var int */ private static $foo; }',
  274. ];
  275. yield 'static property reverse order' => [
  276. '<?php class Foo { /** @var int */ static private int $foo; }',
  277. '<?php class Foo { /** @var int */ static private $foo; }',
  278. ];
  279. yield 'var' => [
  280. '<?php class Foo { /** @var int */ var int $foo; }',
  281. '<?php class Foo { /** @var int */ var $foo; }',
  282. ];
  283. yield 'with default value' => [
  284. '<?php class Foo { /** @var int */ public int $foo = 1; }',
  285. '<?php class Foo { /** @var int */ public $foo = 1; }',
  286. ];
  287. yield 'multiple properties of the same type' => [
  288. '<?php class Foo {
  289. /**
  290. * @var int $foo
  291. * @var int $bar
  292. */
  293. public int $foo, $bar;
  294. }',
  295. '<?php class Foo {
  296. /**
  297. * @var int $foo
  298. * @var int $bar
  299. */
  300. public $foo, $bar;
  301. }',
  302. ];
  303. yield 'multiple properties of different types' => [
  304. '<?php class Foo {
  305. /**
  306. * @var int $foo
  307. * @var string $bar
  308. */
  309. public $foo, $bar;
  310. }',
  311. ];
  312. yield 'single property with different annotations' => [
  313. '<?php class Foo {
  314. /**
  315. * @var int $foo
  316. * @var string $foo
  317. */
  318. public $foo;
  319. }',
  320. ];
  321. yield 'multiple properties with missing annotation' => [
  322. '<?php class Foo {
  323. /**
  324. * @var int $foo
  325. */
  326. public $foo, $bar;
  327. }',
  328. ];
  329. yield 'multiple properties with annotation without name' => [
  330. '<?php class Foo {
  331. /**
  332. * @var int
  333. * @var int $bar
  334. */
  335. public $foo, $bar;
  336. }',
  337. ];
  338. yield 'multiple properties with annotation without name reverse order' => [
  339. '<?php class Foo {
  340. /**
  341. * @var int $foo
  342. * @var int
  343. */
  344. public $foo, $bar;
  345. }',
  346. ];
  347. yield 'multiple properties with extra annotations' => [
  348. '<?php class Foo {
  349. /**
  350. * @var string
  351. * @var int $foo
  352. * @var int $bar
  353. * @var int
  354. */
  355. public int $foo, $bar;
  356. }',
  357. '<?php class Foo {
  358. /**
  359. * @var string
  360. * @var int $foo
  361. * @var int $bar
  362. * @var int
  363. */
  364. public $foo, $bar;
  365. }',
  366. ];
  367. yield 'abstract method' => [
  368. '<?php abstract class Foo {
  369. /** @var Bar */ private Bar $foo;
  370. public abstract function getFoo();
  371. }',
  372. '<?php abstract class Foo {
  373. /** @var Bar */ private $foo;
  374. public abstract function getFoo();
  375. }',
  376. ];
  377. yield 'great number of properties' => [
  378. '<?php class Foo {
  379. /** @var string */
  380. private string $foo1;
  381. /** @var string */
  382. private string $foo2;
  383. /** @var int */
  384. private int $foo3;
  385. /** @var string */
  386. private string $foo4;
  387. /** @var string */
  388. private string $foo5;
  389. /** @var string */
  390. private string $foo6;
  391. /** @var string */
  392. private string $foo7;
  393. /** @var int */
  394. private int $foo8;
  395. /** @var string */
  396. private string $foo9;
  397. /** @var int|null */
  398. private ?int $foo10;
  399. }',
  400. '<?php class Foo {
  401. /** @var string */
  402. private $foo1;
  403. /** @var string */
  404. private $foo2;
  405. /** @var int */
  406. private $foo3;
  407. /** @var string */
  408. private $foo4;
  409. /** @var string */
  410. private $foo5;
  411. /** @var string */
  412. private $foo6;
  413. /** @var string */
  414. private $foo7;
  415. /** @var int */
  416. private $foo8;
  417. /** @var string */
  418. private $foo9;
  419. /** @var int|null */
  420. private $foo10;
  421. }',
  422. ];
  423. yield 'anonymous class' => [
  424. '<?php new class { /** @var int */ private int $foo; };',
  425. '<?php new class { /** @var int */ private $foo; };',
  426. ];
  427. yield 'intersection types' => [
  428. '<?php class Foo { /** @var Bar&Baz */ private $x; }',
  429. ];
  430. yield 'very long class name before ampersand' => [
  431. '<?php class Foo { /** @var Baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaar&Baz */ private $x; }',
  432. ];
  433. yield 'very long class name after ampersand' => [
  434. '<?php class Foo { /** @var Bar&Baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaz */ private $x; }',
  435. ];
  436. }
  437. /**
  438. * @dataProvider provideFixPre80Cases
  439. *
  440. * @requires PHP <8.0
  441. */
  442. public function testFixPre80(string $expected, ?string $input = null): void
  443. {
  444. $this->doTest($expected, $input);
  445. }
  446. /**
  447. * @return iterable<string, array{string}>
  448. */
  449. public static function provideFixPre80Cases(): iterable
  450. {
  451. yield 'skip mixed type' => [
  452. '<?php class Foo { /** @var mixed */ private $foo; }',
  453. ];
  454. yield 'skip union types' => [
  455. '<?php class Foo { /** @var Foo|Bar */ private $foo; }',
  456. ];
  457. yield 'skip union nullable types' => [
  458. '<?php class Foo { /** @var null|Foo|Bar */ private $foo; }',
  459. ];
  460. }
  461. /**
  462. * @dataProvider provideFix80Cases
  463. *
  464. * @requires PHP 8.0
  465. */
  466. public function testFix80(string $expected, ?string $input = null): void
  467. {
  468. $this->doTest($expected, $input);
  469. }
  470. /**
  471. * @return iterable<string, array{string, string}>
  472. */
  473. public static function provideFix80Cases(): iterable
  474. {
  475. yield 'fix mixed type' => [
  476. '<?php class Foo { /** @var mixed */ private mixed $foo; }',
  477. '<?php class Foo { /** @var mixed */ private $foo; }',
  478. ];
  479. yield 'union types' => [
  480. '<?php class Foo { /** @var Foo|Bar */ private Foo|Bar $foo; }',
  481. '<?php class Foo { /** @var Foo|Bar */ private $foo; }',
  482. ];
  483. yield 'union types including nullable' => [
  484. '<?php class Foo { /** @var null|Foo|Bar */ private Foo|Bar|null $foo; }',
  485. '<?php class Foo { /** @var null|Foo|Bar */ private $foo; }',
  486. ];
  487. yield 'union types including generics' => [
  488. '<?php class Foo { /** @var string|array<int, bool> */ private string|array $foo; }',
  489. '<?php class Foo { /** @var string|array<int, bool> */ private $foo; }',
  490. ];
  491. }
  492. /**
  493. * @dataProvider provideFix81Cases
  494. *
  495. * @requires PHP 8.1
  496. */
  497. public function testFix81(string $expected): void
  498. {
  499. $this->doTest($expected);
  500. }
  501. /**
  502. * @return iterable<string, array{string}>
  503. */
  504. public static function provideFix81Cases(): iterable
  505. {
  506. yield 'readonly properties are always typed, make sure the fixer does not crash' => [
  507. '<?php class Foo { /** @var int */ private readonly string $foo; }',
  508. ];
  509. }
  510. }