PhpdocToPropertyTypeFixerTest.php 18 KB

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