PhpdocToPropertyTypeFixerTest.php 17 KB

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