NativeTypeDeclarationCasingFixerTest.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675
  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\Casing;
  13. use PhpCsFixer\Tests\Test\AbstractFixerTestCase;
  14. /**
  15. * @internal
  16. *
  17. * @covers \PhpCsFixer\Fixer\Casing\NativeTypeDeclarationCasingFixer
  18. *
  19. * @extends AbstractFixerTestCase<\PhpCsFixer\Fixer\Casing\NativeTypeDeclarationCasingFixer>
  20. */
  21. final class NativeTypeDeclarationCasingFixerTest extends AbstractFixerTestCase
  22. {
  23. /**
  24. * @requires PHP <8.0
  25. */
  26. public function testFixPre80(): void
  27. {
  28. $this->doTest('<?php
  29. class D {
  30. private MIXED $m;
  31. };
  32. ');
  33. }
  34. /**
  35. * @dataProvider provideFixCases
  36. */
  37. public function testFix(string $expected, ?string $input = null): void
  38. {
  39. $this->doTest($expected, $input);
  40. }
  41. public static function provideFixCases(): iterable
  42. {
  43. yield [
  44. '<?php
  45. function A(int $a): void {}
  46. class Foo
  47. {
  48. private bool $c = false;
  49. private bool $d = false;
  50. public function B(int $a): bool { return $this->c || $this->d; }
  51. }
  52. function C(float $a): array { return [$a];}
  53. function D(array $a): array { return [$a];}
  54. ',
  55. '<?php
  56. function A(INT $a): VOID {}
  57. class Foo
  58. {
  59. private BOOL $c = false;
  60. private BOOL $d = false;
  61. public function B(INT $a): BOOL { return $this->c || $this->d; }
  62. }
  63. function C(FLOAT $a): ARRAY { return [$a];}
  64. function D(ARRAY $a): ARRAY { return [$a];}
  65. ',
  66. ];
  67. yield [
  68. '<?php
  69. class Foo
  70. {
  71. private function Bar(array $bar) {
  72. return false;
  73. }
  74. }
  75. ',
  76. '<?php
  77. class Foo
  78. {
  79. private function Bar(ARRAY $bar) {
  80. return false;
  81. }
  82. }
  83. ',
  84. ];
  85. yield [
  86. '<?php
  87. interface Foo
  88. {
  89. public function Bar(array $bar);
  90. }
  91. ',
  92. '<?php
  93. interface Foo
  94. {
  95. public function Bar(ArrAY $bar);
  96. }
  97. ',
  98. ];
  99. yield [
  100. '<?php
  101. function Foo(/**/array/**/$bar) {
  102. return false;
  103. }
  104. ',
  105. '<?php
  106. function Foo(/**/ARRAY/**/$bar) {
  107. return false;
  108. }
  109. ',
  110. ];
  111. yield [
  112. '<?php
  113. class Bar { function Foo(array $a, callable $b, self $c) {} }
  114. ',
  115. '<?php
  116. class Bar { function Foo(ARRAY $a, CALLABLE $b, Self $c) {} }
  117. ',
  118. ];
  119. yield [
  120. '<?php
  121. function Foo(INTEGER $a) {}
  122. ',
  123. ];
  124. yield [
  125. '<?php function Foo(
  126. String\A $x,
  127. B\String\C $y
  128. ) {}',
  129. ];
  130. yield [
  131. '<?php final class Foo1 { final public function Foo(bool $A, float $B, int $C, string $D): int {} }',
  132. '<?php final class Foo1 { final public function Foo(BOOL $A, FLOAT $B, INT $C, STRING $D): INT {} }',
  133. ];
  134. yield [
  135. '<?php function Foo(bool $A, float $B, int $C, string $D): int {}',
  136. '<?php function Foo(BOOL $A, FLOAT $B, INT $C, STRING $D): INT {}',
  137. ];
  138. yield [
  139. '<?php function Foo(): Foo\A { return new Foo(); }',
  140. ];
  141. yield [
  142. '<?php trait XYZ { function Foo(iterable $A): void {} }',
  143. '<?php trait XYZ { function Foo(ITERABLE $A): VOID {} }',
  144. ];
  145. yield [
  146. '<?php function Foo(iterable $A): void {}',
  147. '<?php function Foo(ITERABLE $A): VOID {}',
  148. ];
  149. yield [
  150. '<?php function Foo(?int $A): void {}',
  151. '<?php function Foo(?INT $A): VOID {}',
  152. ];
  153. yield [
  154. '<?php function Foo(string $A): ?/* */int {}',
  155. '<?php function Foo(STRING $A): ?/* */INT {}',
  156. ];
  157. yield [
  158. '<?php function Foo(object $A): void {}',
  159. '<?php function Foo(OBJECT $A): VOID {}',
  160. ];
  161. yield [
  162. '<?php return function (callable $c) {};',
  163. '<?php return function (CALLABLE $c) {};',
  164. ];
  165. yield [
  166. '<?php return fn (callable $c): int => 1;',
  167. '<?php return fn (CALLABLE $c): INT => 1;',
  168. ];
  169. yield [
  170. '<?php
  171. class Foo
  172. {
  173. const A = 1;
  174. const B = [];
  175. const INT = "A"; // class constant; INT is the name of the const, not the type
  176. const FLOAT=1.2;
  177. }
  178. const INT = "A"; // outside class; INT is the name of the const, not the type
  179. ',
  180. ];
  181. yield 'class properties single type' => [
  182. '<?php
  183. class D{}
  184. $a = new class extends D {
  185. private array $ax;
  186. private bool $bx = false;
  187. private float $cx = 3.14;
  188. private int $dx = 667;
  189. private iterable $ex = [];
  190. private object $f;
  191. private parent $g;
  192. private self $h;
  193. private static $i;
  194. private ?string $j;
  195. private $INT = 1;
  196. private FOO $bar;
  197. private A\INT\B $z;
  198. };
  199. ',
  200. '<?php
  201. class D{}
  202. $a = new class extends D {
  203. private ARRAY $ax;
  204. private BOOL $bx = false;
  205. private FLOAT $cx = 3.14;
  206. private INT $dx = 667;
  207. private ITERABLE $ex = [];
  208. private OBJECT $f;
  209. private PARENT $g;
  210. private Self $h;
  211. private STatic $i;
  212. private ?STRIng $j;
  213. private $INT = 1;
  214. private FOO $bar;
  215. private A\INT\B $z;
  216. };
  217. ',
  218. ];
  219. yield 'var keyword' => [
  220. '<?php class Foo {
  221. var $bar;
  222. }',
  223. ];
  224. yield 'static property without type' => [
  225. '<?php class Foo { static $bar; }',
  226. '<?php class Foo { STATIC $bar; }',
  227. ];
  228. yield 'dynamic property' => [
  229. '<?php class Foo {
  230. public function doFoo() {
  231. $this->Object->doBar();
  232. }
  233. }',
  234. ];
  235. yield 'constants' => [
  236. <<<'PHP'
  237. <?php
  238. function f() {}
  239. if (True === $x) {
  240. } elseif (True == $y) {
  241. } elseif ($z === False) {}
  242. PHP,
  243. ];
  244. }
  245. /**
  246. * @dataProvider provideFix80Cases
  247. *
  248. * @requires PHP 8.0
  249. */
  250. public function testFix80(string $expected, string $input): void
  251. {
  252. $this->doTest($expected, $input);
  253. }
  254. /**
  255. * @return iterable<int|string, array{string, string}>
  256. */
  257. public static function provideFix80Cases(): iterable
  258. {
  259. yield 'class properties single type' => [
  260. '<?php
  261. class D {
  262. private mixed $m;
  263. };
  264. ',
  265. '<?php
  266. class D {
  267. private MIXED $m;
  268. };
  269. ',
  270. ];
  271. yield [
  272. '<?php class T { public function Foo(object $A): static {}}',
  273. '<?php class T { public function Foo(object $A): StatiC {}}',
  274. ];
  275. yield [
  276. '<?php class T { public function Foo(object $A): ?static {}}',
  277. '<?php class T { public function Foo(object $A): ?StatiC {}}',
  278. ];
  279. yield [
  280. '<?php class T { public function Foo(mixed $A): mixed {}}',
  281. '<?php class T { public function Foo(Mixed $A): MIXED {}}',
  282. ];
  283. yield 'mixed in arrow function' => [
  284. '<?php return fn (mixed $c): mixed => 1;',
  285. '<?php return fn (MiXeD $c): MIXED => 1;',
  286. ];
  287. yield [
  288. '<?php function foo(int|bool $x) {}',
  289. '<?php function foo(INT|BOOL $x) {}',
  290. ];
  291. yield [
  292. '<?php function foo(int | bool $x) {}',
  293. '<?php function foo(INT | BOOL $x) {}',
  294. ];
  295. yield [
  296. '<?php function foo(): int|bool {}',
  297. '<?php function foo(): INT|BOOL {}',
  298. ];
  299. yield 'return type string|false' => [
  300. '<?php function foo(): string|false {}',
  301. '<?php function foo(): string|FALSE {}',
  302. ];
  303. yield 'return type string|null' => [
  304. '<?php function foo(): string|null {}',
  305. '<?php function foo(): string|NULL {}',
  306. ];
  307. yield 'union types in arrow function' => [
  308. '<?php return fn (string|null $c): int|null => 1;',
  309. '<?php return fn (string|NULL $c): INT|NULL => 1;',
  310. ];
  311. yield 'union Types' => [
  312. '<?php $a = new class {
  313. private null|int|bool $a4 = false;
  314. };',
  315. '<?php $a = new class {
  316. private NULL|INT|BOOL $a4 = false;
  317. };',
  318. ];
  319. yield 'promoted properties' => [
  320. <<<'PHP'
  321. <?php class Foo extends Bar {
  322. public function __construct(
  323. public int $i,
  324. protected parent $p,
  325. private string $s
  326. ) {}
  327. }
  328. PHP,
  329. <<<'PHP'
  330. <?php class Foo extends Bar {
  331. public function __construct(
  332. public INT $i,
  333. protected PARENT $p,
  334. private STRING $s
  335. ) {}
  336. }
  337. PHP,
  338. ];
  339. }
  340. /**
  341. * @dataProvider provideFix81Cases
  342. *
  343. * @requires PHP 8.1
  344. */
  345. public function testFix81(string $expected, string $input): void
  346. {
  347. $this->doTest($expected, $input);
  348. }
  349. /**
  350. * @return iterable<string, array{string, string}>
  351. */
  352. public static function provideFix81Cases(): iterable
  353. {
  354. yield 'return type `never`' => [
  355. '<?php class T { public function Foo(object $A): never {die;}}',
  356. '<?php class T { public function Foo(object $A): NEVER {die;}}',
  357. ];
  358. yield 'class readonly property' => [
  359. '<?php class Z {
  360. private readonly array $ax;
  361. };',
  362. '<?php class Z {
  363. private readonly ARRAY $ax;
  364. };',
  365. ];
  366. }
  367. /**
  368. * @dataProvider provideFix82Cases
  369. *
  370. * @requires PHP 8.2
  371. */
  372. public function testFix82(string $expected, string $input): void
  373. {
  374. $this->doTest($expected, $input);
  375. }
  376. /**
  377. * @return iterable<string, array{string, string}>
  378. */
  379. public static function provideFix82Cases(): iterable
  380. {
  381. yield 'disjunctive normal form types in arrow function' => [
  382. '<?php return fn ((A&B)|C|null $c): (X&Y)|Z|null => 1;',
  383. '<?php return fn ((A&B)|C|Null $c): (X&Y)|Z|NULL => 1;',
  384. ];
  385. yield 'iterable: disjunctive normal form types in arrow function' => [
  386. '<?php return fn (iterable|C|null $c): (X&Y)|Z|null => 1;',
  387. '<?php return fn (ITERABLE|C|Null $c): (X&Y)|Z|NULL => 1;',
  388. ];
  389. foreach (['true', 'false', 'null'] as $type) {
  390. yield \sprintf('standalone type `%s` in class method', $type) => [
  391. \sprintf('<?php class T { public function Foo(%s $A): %1$s {return $A;}}', $type),
  392. \sprintf('<?php class T { public function Foo(%s $A): %1$s {return $A;}}', strtoupper($type)),
  393. ];
  394. yield \sprintf('standalone type `%s` in function', $type) => [
  395. \sprintf('<?php function Foo(%s $A): %1$s {return $A;}', $type),
  396. \sprintf('<?php function Foo(%s $A): %1$s {return $A;}', strtoupper($type)),
  397. ];
  398. yield \sprintf('standalone type `%s` in closure', $type) => [
  399. \sprintf('<?php array_filter([], function (%s $A): %1$s {return $A;});', $type),
  400. \sprintf('<?php array_filter([], function (%s $A): %1$s {return $A;});', strtoupper($type)),
  401. ];
  402. yield \sprintf('standalone type `%s` in arrow function', $type) => [
  403. \sprintf('<?php array_filter([], fn (%s $A): %1$s => $A);', $type),
  404. \sprintf('<?php array_filter([], fn (%s $A): %1$s => $A);', strtoupper($type)),
  405. ];
  406. }
  407. yield 'intersection Types' => [
  408. '<?php $a = new class {
  409. private (A&B)|int|D $d5;
  410. private (A\STRING\B&B\INT\C)|int|(A&B) $e6;
  411. };',
  412. '<?php $a = new class {
  413. private (A&B)|INT|D $d5;
  414. private (A\STRING\B&B\INT\C)|int|(A&B) $e6;
  415. };',
  416. ];
  417. }
  418. /**
  419. * @dataProvider provideFix83Cases
  420. *
  421. * @requires PHP 8.3
  422. */
  423. public function testFix83(string $expected, ?string $input = null): void
  424. {
  425. $this->doTest($expected, $input);
  426. }
  427. /**
  428. * @return iterable<string, array{0: string, 1?: string}>
  429. */
  430. public static function provideFix83Cases(): iterable
  431. {
  432. yield 'simple case' => [
  433. '<?php
  434. class Foo
  435. {
  436. const int FOO = 6;
  437. }
  438. ',
  439. '<?php
  440. class Foo
  441. {
  442. const INT FOO = 6;
  443. }
  444. ',
  445. ];
  446. yield 'single types' => [
  447. '<?php
  448. class Foo
  449. {
  450. const int SOME_INT = 3;
  451. const array SOME_ARRAY = [7];
  452. const float SOME_FLOAT = 1.23;
  453. const iterable SOME_ITERABLE = [1, 2];
  454. const mixed SOME_MIXED = 1;
  455. const null SOME_NULL = NULL;
  456. const string SOME_STRING = "X";
  457. }
  458. ',
  459. '<?php
  460. class Foo
  461. {
  462. const INT SOME_INT = 3;
  463. const ARRAY SOME_ARRAY = [7];
  464. const Float SOME_FLOAT = 1.23;
  465. const ITERABLE SOME_ITERABLE = [1, 2];
  466. const MIXED SOME_MIXED = 1;
  467. const NULL SOME_NULL = NULL;
  468. const STRING SOME_STRING = "X";
  469. }
  470. ',
  471. ];
  472. yield 'nullables `self`, `parent` and `object`' => [
  473. '<?php
  474. class Foo extends FooParent
  475. {
  476. const ?object SOME_OBJECT = NULL;
  477. const ?parent SOME_PARENT = NULL;
  478. const ?self SOME_SELF = NULL;
  479. const ?int/* x */A/* y */= 3;
  480. const ?BAR B = null;
  481. const ?BAR C = null;
  482. const ?\BAR D = null;
  483. const ?\INT\B E = null;
  484. public const ?INT\A/* x */X=C;
  485. }
  486. ',
  487. '<?php
  488. class Foo extends FooParent
  489. {
  490. const ?OBJECT SOME_OBJECT = NULL;
  491. const ?PARENT SOME_PARENT = NULL;
  492. const ?Self SOME_SELF = NULL;
  493. const ?INT/* x */A/* y */= 3;
  494. const ?BAR B = null;
  495. const ?BAR C = null;
  496. const ?\BAR D = null;
  497. const ?\INT\B E = null;
  498. public const ?INT\A/* x */X=C;
  499. }
  500. ',
  501. ];
  502. yield 'simple `|`' => [
  503. '<?php class Foo1 {
  504. const D|float BAR = 1.0;
  505. }',
  506. '<?php class Foo1 {
  507. const D|Float BAR = 1.0;
  508. }',
  509. ];
  510. yield 'multiple `|`' => [
  511. '<?php class Foo2 {
  512. const int|array|null|A\B|\C\D|float BAR_1 = NULL;
  513. }',
  514. '<?php class Foo2 {
  515. const INT|ARRAY|NULL|A\B|\C\D|FLOAT BAR_1 = NULL;
  516. }',
  517. ];
  518. yield 'handle mix of `|` and `(X&Y)`' => [
  519. '<?php
  520. class Foo extends FooParent
  521. {
  522. private const Z|A\S\T\R|int|float|iterable SOMETHING = 123;
  523. protected const \A\B|(Foo & Stringable )|null|\X D = null;
  524. public const Foo&Stringable G = V::A;
  525. }
  526. ',
  527. '<?php
  528. class Foo extends FooParent
  529. {
  530. private const Z|A\S\T\R|INT|FLOAT|ITERABLE SOMETHING = 123;
  531. protected const \A\B|(Foo & Stringable )|NULL|\X D = null;
  532. public const Foo&Stringable G = V::A;
  533. }
  534. ',
  535. ];
  536. yield 'interface, nullable int' => [
  537. '<?php interface SomeInterface {
  538. const ?int FOO = 1;
  539. }',
  540. '<?php interface SomeInterface {
  541. const ?INT FOO = 1;
  542. }',
  543. ];
  544. yield 'trait, string' => [
  545. '<?php trait T {
  546. const string TEST = E::TEST;
  547. }',
  548. '<?php trait T {
  549. const STRING TEST = E::TEST;
  550. }',
  551. ];
  552. yield 'enum, int' => [
  553. '<?php enum E: string {
  554. case Hearts = "H";
  555. const int TEST = 789;
  556. const self A = self::Hearts;
  557. const static B = self::Hearts;
  558. }',
  559. '<?php enum E: STRING {
  560. case Hearts = "H";
  561. const INT TEST = 789;
  562. const SELF A = self::Hearts;
  563. const STATIC B = self::Hearts;
  564. }',
  565. ];
  566. yield 'enum with "Mixed" case' => [
  567. <<<'PHP'
  568. <?php
  569. enum Foo
  570. {
  571. case Mixed;
  572. public function bar()
  573. {
  574. self::Mixed;
  575. }
  576. }
  577. PHP,
  578. ];
  579. yield 'do not fix' => [
  580. '<?php class Foo {
  581. PUBLIC CONST FOO&STRINGABLE G = A::B;
  582. }
  583. CONST A = 1;',
  584. ];
  585. yield 'fix "false" in type' => [
  586. '<?php class Foo { private false|int $bar; private false $baz; }',
  587. '<?php class Foo { private FALSE|INT $bar; private FALSE $baz; }',
  588. ];
  589. }
  590. }