ClassDefinitionFixerTest.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116
  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\ClassNotation;
  13. use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException;
  14. use PhpCsFixer\Fixer\ClassNotation\ClassDefinitionFixer;
  15. use PhpCsFixer\Tests\Test\AbstractFixerTestCase;
  16. use PhpCsFixer\Tokenizer\Tokens;
  17. use PhpCsFixer\WhitespacesFixerConfig;
  18. /**
  19. * @internal
  20. *
  21. * @covers \PhpCsFixer\Fixer\ClassNotation\ClassDefinitionFixer
  22. */
  23. final class ClassDefinitionFixerTest extends AbstractFixerTestCase
  24. {
  25. public function testConfigure(): void
  26. {
  27. $defaultConfig = [
  28. 'inline_constructor_arguments' => true,
  29. 'multi_line_extends_each_single_line' => false,
  30. 'single_item_single_line' => false,
  31. 'single_line' => false,
  32. 'space_before_parenthesis' => false,
  33. ];
  34. $fixer = new ClassDefinitionFixer();
  35. $fixer->configure($defaultConfig);
  36. self::assertConfigurationSame($defaultConfig, $fixer);
  37. $fixer->configure([]);
  38. self::assertConfigurationSame($defaultConfig, $fixer);
  39. }
  40. /**
  41. * @param array<mixed> $config
  42. *
  43. * @dataProvider provideInvalidConfigurationCases
  44. */
  45. public function testInvalidConfiguration(array $config, string $exceptionExpression): void
  46. {
  47. $this->expectException(InvalidFixerConfigurationException::class);
  48. $this->expectExceptionMessageMatches($exceptionExpression);
  49. $this->fixer->configure($config);
  50. }
  51. /**
  52. * @return iterable<array{array<mixed>, string}>
  53. */
  54. public static function provideInvalidConfigurationCases(): iterable
  55. {
  56. yield 'invalid configuration key' => [
  57. ['a' => false],
  58. '/^\[class_definition\] Invalid configuration: The option "a" does not exist\. Defined options are: "inline_constructor_arguments", "multi_line_extends_each_single_line", "single_item_single_line", "single_line", "space_before_parenthesis"\.$/',
  59. ];
  60. yield 'invalid configuration value' => [
  61. ['single_line' => 'z'],
  62. '/^\[class_definition\] Invalid configuration: The option "single_line" with value "z" is expected to be of type "bool", but is of type "string"\.$/',
  63. ];
  64. }
  65. /**
  66. * @param array<string, bool> $configuration
  67. *
  68. * @dataProvider provideFixCases
  69. */
  70. public function testFix(string $expected, string $input, array $configuration = []): void
  71. {
  72. $this->fixer->configure($configuration);
  73. $this->doTest($expected, $input);
  74. }
  75. public static function provideFixCases(): iterable
  76. {
  77. yield [
  78. '<?php $a = new class(0) extends SomeClass implements SomeInterface, D {};',
  79. "<?php \$a = new class(0) extends\nSomeClass\timplements SomeInterface, D {};",
  80. ];
  81. yield [
  82. '<?php $a = new class(1) extends SomeClass implements SomeInterface, D {};',
  83. "<?php \$a = new class(1) extends\nSomeClass\timplements SomeInterface, D {};",
  84. ['single_line' => true],
  85. ];
  86. yield [
  87. "<?php \$a = new class('1a') implements\nA\n{};",
  88. "<?php \$a = new class('1a') implements\nA{};",
  89. ];
  90. yield [
  91. "<?php \$a = new class('1a') implements A {};",
  92. "<?php \$a = new class('1a') implements\nA{};",
  93. ['single_item_single_line' => true],
  94. ];
  95. yield [
  96. '<?php $a = new class {};',
  97. '<?php $a = new class{};',
  98. ];
  99. yield [
  100. '<?php $a = new class {};',
  101. "<?php \$a = new class\n{};",
  102. ];
  103. yield [
  104. '<?php $a = new class() {};',
  105. "<?php \$a = new\n class ( ){};",
  106. ];
  107. yield [
  108. '<?php $a = new class( ) {};',
  109. "<?php \$a = new\n class ( ){};",
  110. ['inline_constructor_arguments' => false],
  111. ];
  112. yield [
  113. '<?php $a = new class implements Foo {};',
  114. "<?php \$a = new\n class implements Foo {};",
  115. ['inline_constructor_arguments' => false],
  116. ];
  117. yield [
  118. '<?php $a = new class( $this->foo() , bar ( $a) ) {};',
  119. "<?php \$a = new\n class ( \$this->foo() , bar ( \$a) ){};",
  120. ['inline_constructor_arguments' => false],
  121. ];
  122. yield [
  123. '<?php $a = new class(10, 1, /**/ 2) {};',
  124. '<?php $a = new class( 10, 1,/**/2 ){};',
  125. ];
  126. yield [
  127. '<?php $a = new class( 10, 1,/**/2 ) {};',
  128. '<?php $a = new class( 10, 1,/**/2 ){};',
  129. ['inline_constructor_arguments' => false],
  130. ];
  131. yield [
  132. '<?php $a = new class(2) {};',
  133. '<?php $a = new class(2){};',
  134. ];
  135. yield [
  136. '<?php $a = new class($this->prop) {};',
  137. '<?php $a = new class( $this->prop ){};',
  138. ];
  139. yield [
  140. '<?php $a = new class( $this->prop ) {};',
  141. '<?php $a = new class( $this->prop ){};',
  142. ['inline_constructor_arguments' => false],
  143. ];
  144. yield [
  145. "<?php \$a = new class(\n\t\$a,\n\t\$b,\n\t\$c,\n\t\$d) implements A, B {};",
  146. "<?php \$a = new class(\n\t\$a,\n\t\$b,\n\t\$c,\n\t\$d) implements A, \t B{};",
  147. ['inline_constructor_arguments' => false],
  148. ];
  149. yield [
  150. "<?php \$a = new class(\n\t\$a,\n\t\$b,\n\t\$c,\n\t\$d) implements A, B {};",
  151. "<?php \$a = new class (\n\t\$a,\n\t\$b,\n\t\$c,\n\t\$d) implements A, \t B{};",
  152. ['inline_constructor_arguments' => false],
  153. ];
  154. yield [
  155. '<?php $a = new class($this->prop, $v[3], 4) {};',
  156. '<?php $a = new class( $this->prop,$v[3], 4) {};',
  157. ];
  158. yield 'PSR-12 Extends/Implements Parenthesis on the next line.' => [
  159. '<?php
  160. $instance = new class extends \Foo implements
  161. \ArrayAccess,
  162. \Countable,
  163. \Serializable
  164. {};',
  165. '<?php
  166. $instance = new class extends \Foo implements
  167. \ArrayAccess,\Countable,\Serializable{};',
  168. ];
  169. yield 'PSR-12 Implements Parenthesis on the next line.' => [
  170. '<?php
  171. $instance = new class implements
  172. \ArrayAccess,
  173. \Countable,
  174. \Serializable
  175. {};',
  176. '<?php
  177. $instance = new class implements
  178. \ArrayAccess,\Countable,\Serializable{};',
  179. ];
  180. yield 'PSR-12 Extends Parenthesis on the next line.' => [
  181. '<?php
  182. $instance = new class extends
  183. ArrayAccess
  184. {};',
  185. '<?php
  186. $instance = new class
  187. extends
  188. ArrayAccess
  189. {};',
  190. ];
  191. yield [
  192. "<?php \$a = new #
  193. class #
  194. ( #
  195. '1a', #
  196. 1 #
  197. ) #
  198. implements#
  199. A, #
  200. B,
  201. C #
  202. {#
  203. #
  204. }#
  205. ;",
  206. "<?php \$a = new#
  207. class#
  208. (#
  209. '1a',#
  210. 1 #
  211. )#
  212. implements#
  213. A, #
  214. B,C#
  215. {#
  216. #
  217. }#
  218. ;",
  219. ];
  220. yield [
  221. "<?php \$a = new #
  222. class #
  223. ( #
  224. '1a', #
  225. 1 #
  226. ) #
  227. implements #
  228. A #
  229. {#
  230. #
  231. }#
  232. ;",
  233. "<?php \$a = new#
  234. class#
  235. (#
  236. '1a',#
  237. 1 #
  238. )#
  239. implements#
  240. A#
  241. {#
  242. #
  243. }#
  244. ;",
  245. ['single_item_single_line' => true],
  246. ];
  247. yield [
  248. '<?php $a = new class() #
  249. {};',
  250. '<?php $a = new class()#
  251. {};',
  252. ];
  253. yield 'space_before_parenthesis 1' => [
  254. '<?php $z = new class () {};',
  255. '<?php $z = new class() {};',
  256. ['space_before_parenthesis' => true],
  257. ];
  258. yield 'space_before_parenthesis 2' => [
  259. '<?php $z = new class () {};',
  260. '<?php $z = new class () {};',
  261. ['space_before_parenthesis' => true],
  262. ];
  263. yield 'space_before_parenthesis and inline_constructor_arguments' => [
  264. '<?php $z = new class ( static::foo($this->bar()) ,baz() ) {};',
  265. '<?php $z = new class ( static::foo($this->bar()) ,baz() ) {};',
  266. ['space_before_parenthesis' => true, 'inline_constructor_arguments' => false],
  267. ];
  268. yield from self::provideClassyCases('class');
  269. yield from self::provideClassyExtendingCases('class');
  270. yield from self::provideClassyImplementsCases();
  271. yield [
  272. "<?php class configA implements B, C\n{}",
  273. "<?php class configA implements\nB, C{}",
  274. ['single_line' => true],
  275. ];
  276. yield [
  277. "<?php class configA1 extends B\n{}",
  278. "<?php class configA1\n extends\nB{}",
  279. ['single_line' => true],
  280. ];
  281. yield [
  282. "<?php class configA1a extends B\n{}",
  283. "<?php class configA1a\n extends\nB{}",
  284. ['single_line' => false, 'single_item_single_line' => true],
  285. ];
  286. yield [
  287. "<?php class configA2 extends D implements B, C\n{}",
  288. "<?php class configA2 extends D implements\nB,\nC{}",
  289. ['single_line' => true],
  290. ];
  291. yield [
  292. "<?php class configA3 extends D implements B, C\n{}",
  293. "<?php class configA3\n extends\nD\n\t implements\nB,\nC{}",
  294. ['single_line' => true],
  295. ];
  296. yield [
  297. "<?php class configA4 extends D implements B, #\nC\n{}",
  298. "<?php class configA4\n extends\nD\n\t implements\nB,#\nC{}",
  299. ['single_line' => true],
  300. ];
  301. yield [
  302. "<?php class configA5 implements A\n{}",
  303. "<?php class configA5 implements\nA{}",
  304. ['single_line' => false, 'single_item_single_line' => true],
  305. ];
  306. yield [
  307. "<?php interface TestWithMultiExtendsMultiLine extends\n A,\nAb,\n C,\n D\n{}",
  308. "<?php interface TestWithMultiExtendsMultiLine extends A,\nAb,C,D\n{}",
  309. [
  310. 'single_line' => false,
  311. 'single_item_single_line' => false,
  312. 'multi_line_extends_each_single_line' => true,
  313. ],
  314. ];
  315. yield from self::provideClassyCases('interface');
  316. yield from self::provideClassyExtendingCases('interface');
  317. yield [
  318. '<?php
  319. interface Test extends
  320. /*a*/ /*b*/TestInterface1 , \A\B\C , /* test */
  321. TestInterface2 , // test
  322. '.'
  323. // Note: PSR does not have a rule for multiple extends
  324. TestInterface3, /**/ TestInterface4 ,
  325. TestInterface5 , '.'
  326. /**/TestInterface65
  327. {}
  328. ',
  329. '<?php
  330. interface Test
  331. extends
  332. /*a*/ /*b*/TestInterface1 , \A\B\C , /* test */
  333. TestInterface2 , // test
  334. '.'
  335. // Note: PSR does not have a rule for multiple extends
  336. TestInterface3, /**/ TestInterface4 ,
  337. TestInterface5 , '.'
  338. /**/TestInterface65 {}
  339. ',
  340. ];
  341. yield from self::provideClassyCases('trait');
  342. yield [
  343. '<?php
  344. $a = new class implements
  345. \RFb,
  346. \Fcc,
  347. \GFddZz
  348. {
  349. };',
  350. '<?php
  351. $a = new class implements
  352. \RFb,
  353. \Fcc, \GFddZz
  354. {
  355. };',
  356. ];
  357. yield [
  358. '<?php
  359. $a = new class implements
  360. \RFb,
  361. \Fcc,
  362. \GFddZz
  363. {
  364. }?>',
  365. '<?php
  366. $a = new class implements
  367. \RFb,
  368. \Fcc, \GFddZz
  369. {
  370. }?>',
  371. ];
  372. yield [
  373. '<?php new class(1, 2, 3, ) {};',
  374. '<?php new class(1, 2, 3,) {};',
  375. ];
  376. yield [
  377. '<?php new class(1, 2, 3, ) {};',
  378. '<?php new class(
  379. 1,
  380. 2,
  381. 3,
  382. ) {};',
  383. ];
  384. }
  385. /**
  386. * @param array<string, mixed> $expected
  387. *
  388. * @dataProvider provideClassyDefinitionInfoCases
  389. */
  390. public function testClassyDefinitionInfo(string $source, array $expected): void
  391. {
  392. Tokens::clearCache();
  393. $tokens = Tokens::fromCode($source);
  394. $method = new \ReflectionMethod($this->fixer, 'getClassyDefinitionInfo');
  395. $method->setAccessible(true);
  396. $result = $method->invoke($this->fixer, $tokens, $expected['classy']);
  397. ksort($expected);
  398. ksort($result);
  399. self::assertSame($expected, $result);
  400. }
  401. public static function provideClassyDefinitionInfoCases(): iterable
  402. {
  403. yield [
  404. '<?php class A{}',
  405. [
  406. 'start' => 1,
  407. 'classy' => 1,
  408. 'open' => 4,
  409. 'extends' => false,
  410. 'implements' => false,
  411. 'anonymousClass' => false,
  412. 'final' => false,
  413. 'abstract' => false,
  414. 'readonly' => false,
  415. ],
  416. ];
  417. yield [
  418. '<?php final class A{}',
  419. [
  420. 'start' => 1,
  421. 'classy' => 3,
  422. 'open' => 6,
  423. 'extends' => false,
  424. 'implements' => false,
  425. 'anonymousClass' => false,
  426. 'final' => 1,
  427. 'abstract' => false,
  428. 'readonly' => false,
  429. ],
  430. ];
  431. yield [
  432. '<?php abstract /**/ class A{}',
  433. [
  434. 'start' => 1,
  435. 'classy' => 5,
  436. 'open' => 8,
  437. 'extends' => false,
  438. 'implements' => false,
  439. 'anonymousClass' => false,
  440. 'final' => false,
  441. 'abstract' => 1,
  442. 'readonly' => false,
  443. ],
  444. ];
  445. yield [
  446. '<?php class A extends B {}',
  447. [
  448. 'start' => 1,
  449. 'classy' => 1,
  450. 'open' => 9,
  451. 'extends' => [
  452. 'start' => 5,
  453. 'numberOfExtends' => 1,
  454. 'multiLine' => false,
  455. ],
  456. 'implements' => false,
  457. 'anonymousClass' => false,
  458. 'final' => false,
  459. 'abstract' => false,
  460. 'readonly' => false,
  461. ],
  462. ];
  463. yield [
  464. '<?php interface A extends B,C,D {}',
  465. [
  466. 'start' => 1,
  467. 'classy' => 1,
  468. 'open' => 13,
  469. 'extends' => [
  470. 'start' => 5,
  471. 'numberOfExtends' => 3,
  472. 'multiLine' => false,
  473. ],
  474. 'implements' => false,
  475. 'anonymousClass' => false,
  476. 'final' => false,
  477. 'abstract' => false,
  478. 'readonly' => false,
  479. ],
  480. ];
  481. }
  482. /**
  483. * @param array<string, mixed> $expected
  484. *
  485. * @dataProvider provideClassyInheritanceInfoCases
  486. */
  487. public function testClassyInheritanceInfo(string $source, string $label, array $expected): void
  488. {
  489. $this->doTestClassyInheritanceInfo($source, $label, $expected);
  490. }
  491. public static function provideClassyInheritanceInfoCases(): iterable
  492. {
  493. yield '1' => [
  494. '<?php
  495. class X11 implements Z , T,R
  496. {
  497. }',
  498. 'numberOfImplements',
  499. ['start' => 5, 'numberOfImplements' => 3, 'multiLine' => false],
  500. ];
  501. yield '2' => [
  502. '<?php
  503. class X10 implements Z , T,R //
  504. {
  505. }',
  506. 'numberOfImplements',
  507. ['start' => 5, 'numberOfImplements' => 3, 'multiLine' => false],
  508. ];
  509. yield '3' => [
  510. '<?php class A implements B {}',
  511. 'numberOfImplements',
  512. ['start' => 5, 'numberOfImplements' => 1, 'multiLine' => false],
  513. ];
  514. yield '4' => [
  515. "<?php class A implements B,\n I{}",
  516. 'numberOfImplements',
  517. ['start' => 5, 'numberOfImplements' => 2, 'multiLine' => true],
  518. ];
  519. yield '5' => [
  520. "<?php class A implements Z\\C\\B,C,D {\n\n\n}",
  521. 'numberOfImplements',
  522. ['start' => 5, 'numberOfImplements' => 3, 'multiLine' => false],
  523. ];
  524. if (\PHP_VERSION_ID < 8_00_00) {
  525. $multiLine = true;
  526. $code = '<?php
  527. namespace A {
  528. interface X {}
  529. }
  530. namespace {
  531. class B{}
  532. class A extends //
  533. B implements /* */ \A
  534. \C, Z{
  535. public function test()
  536. {
  537. echo 1;
  538. }
  539. }
  540. $a = new A();
  541. $a->test();
  542. }';
  543. } else {
  544. $multiLine = false;
  545. $code = '<?php
  546. namespace A {
  547. interface X {}
  548. }
  549. namespace {
  550. class B{}
  551. class A extends //
  552. B implements /* */ \A\C, Z{
  553. public function test()
  554. {
  555. echo 1;
  556. }
  557. }
  558. $a = new A();
  559. $a->test();
  560. }';
  561. }
  562. yield [
  563. $code,
  564. 'numberOfImplements',
  565. ['start' => 36, 'numberOfImplements' => 2, 'multiLine' => $multiLine],
  566. ];
  567. yield [
  568. "<?php \$a = new class(3) extends\nSomeClass\timplements SomeInterface, D {};",
  569. 'numberOfExtends',
  570. ['start' => 12, 'numberOfExtends' => 1, 'multiLine' => true],
  571. ];
  572. yield [
  573. "<?php \$a = new class(4) extends\nSomeClass\timplements SomeInterface, D\n\n{};",
  574. 'numberOfImplements',
  575. ['start' => 16, 'numberOfImplements' => 2, 'multiLine' => false],
  576. ];
  577. yield [
  578. "<?php \$a = new class(5) extends SomeClass\nimplements SomeInterface, D {};",
  579. 'numberOfExtends',
  580. ['start' => 12, 'numberOfExtends' => 1, 'multiLine' => true],
  581. ];
  582. }
  583. /**
  584. * @dataProvider provideWithWhitespacesConfigCases
  585. */
  586. public function testWithWhitespacesConfig(string $expected, ?string $input = null): void
  587. {
  588. $this->fixer->setWhitespacesConfig(new WhitespacesFixerConfig("\t", "\r\n"));
  589. $this->doTest($expected, $input);
  590. }
  591. public static function provideWithWhitespacesConfigCases(): iterable
  592. {
  593. yield [
  594. "<?php\r\nclass Aaa implements\r\n\tBbb,\r\n\tCcc,\r\n\tDdd\r\n\t{\r\n\t}",
  595. "<?php\r\nclass Aaa implements\r\n\tBbb, Ccc,\r\n\tDdd\r\n\t{\r\n\t}",
  596. ];
  597. }
  598. /**
  599. * @dataProvider provideFix80Cases
  600. *
  601. * @requires PHP 8.0
  602. */
  603. public function testFix80(string $expected, ?string $input = null): void
  604. {
  605. $this->doTest($expected, $input);
  606. }
  607. public static function provideFix80Cases(): iterable
  608. {
  609. yield 'anonymous class, single attribute' => [
  610. '<?php $a = new #[FOO] class(2) {};',
  611. '<?php $a = new #[FOO] class(2){};',
  612. ];
  613. yield 'anonymous class, multiple attributes' => [
  614. '<?php $a = new #[FOO] #[BAR] class {};',
  615. '<?php $a = new #[FOO] #[BAR] class {};',
  616. ];
  617. }
  618. /**
  619. * @dataProvider provideFix81Cases
  620. *
  621. * @requires PHP 8.1
  622. */
  623. public function testFix81(string $expected, ?string $input = null): void
  624. {
  625. $this->doTest($expected, $input);
  626. }
  627. public static function provideFix81Cases(): iterable
  628. {
  629. yield [
  630. "<?php enum SomeEnum implements SomeInterface, D\n{};",
  631. "<?php enum SomeEnum \timplements SomeInterface, D {};",
  632. ];
  633. yield [
  634. "<?php enum SomeEnum : int\n{}",
  635. '<?php enum SomeEnum : int {}',
  636. ];
  637. yield [
  638. "<?php enum SomeEnum\n{}",
  639. "<?php enum\tSomeEnum{}",
  640. ];
  641. }
  642. /**
  643. * @dataProvider provideFix82Cases
  644. *
  645. * @requires PHP 8.2
  646. */
  647. public function testFix82(string $expected, string $input): void
  648. {
  649. $this->doTest($expected, $input);
  650. }
  651. public static function provideFix82Cases(): iterable
  652. {
  653. yield 'final readonly works' => [
  654. '<?php final readonly class a
  655. {}',
  656. '<?php final readonly class a
  657. {}',
  658. ];
  659. yield 'final - readonly modifiers get sorted' => [
  660. '<?php final readonly class a
  661. {}',
  662. '<?php readonly final class a
  663. {}',
  664. ];
  665. yield 'abstract - readonly modifiers get sorted' => [
  666. '<?php abstract readonly class a
  667. {}',
  668. '<?php readonly abstract class a
  669. {}',
  670. ];
  671. }
  672. /**
  673. * @dataProvider provideFix83Cases
  674. *
  675. * @requires PHP 8.3
  676. */
  677. public function testFix83(string $expected, ?string $input = null): void
  678. {
  679. $this->doTest($expected, $input);
  680. }
  681. public static function provideFix83Cases(): iterable
  682. {
  683. yield 'anonymous class, readonly, missing spacing' => [
  684. '<?php $a = new readonly class {};',
  685. '<?php $a = new readonly class{};',
  686. ];
  687. yield 'anonymous class, readonly, to much spacing' => [
  688. '<?php $a = new readonly class {};',
  689. '<?php $a = new readonly class {};',
  690. ];
  691. yield 'anonymous class, single attribute' => [
  692. '<?php $a = new #[BAR] readonly class {};',
  693. '<?php $a = new #[BAR] readonly class{};',
  694. ];
  695. yield 'anonymous class, multiple attributes' => [
  696. '<?php $a = new #[FOO] #[BAR] readonly class {};',
  697. '<?php $a = new #[FOO] #[BAR] readonly class {};',
  698. ];
  699. }
  700. /**
  701. * @param array<string, mixed> $expected
  702. */
  703. private function doTestClassyInheritanceInfo(string $source, string $label, array $expected): void
  704. {
  705. Tokens::clearCache();
  706. $tokens = Tokens::fromCode($source);
  707. self::assertTrue($tokens[$expected['start']]->isGivenKind([T_IMPLEMENTS, T_EXTENDS]), sprintf('Token must be "implements" or "extends", got "%s".', $tokens[$expected['start']]->getContent()));
  708. $method = new \ReflectionMethod($this->fixer, 'getClassyInheritanceInfo');
  709. $method->setAccessible(true);
  710. $result = $method->invoke($this->fixer, $tokens, $expected['start'], $label);
  711. self::assertSame($expected, $result);
  712. }
  713. /**
  714. * @param array<string, mixed> $expected
  715. */
  716. private static function assertConfigurationSame(array $expected, ClassDefinitionFixer $fixer): void
  717. {
  718. $reflectionProperty = new \ReflectionProperty($fixer, 'configuration');
  719. $reflectionProperty->setAccessible(true);
  720. self::assertSame($expected, $reflectionProperty->getValue($fixer));
  721. }
  722. private static function provideClassyCases(string $classy): iterable
  723. {
  724. return [
  725. [
  726. sprintf("<?php %s A\n{}", $classy),
  727. sprintf('<?php %s A {}', $classy),
  728. ],
  729. [
  730. sprintf("<?php %s B\n{}", $classy),
  731. sprintf('<?php %s B{}', $classy),
  732. ],
  733. [
  734. sprintf("<?php %s C\n{}", $classy),
  735. sprintf("<?php %s\n\tC{}", $classy),
  736. ],
  737. [
  738. sprintf("<?php %s D //\n{}", $classy),
  739. sprintf("<?php %s D//\n{}", $classy),
  740. ],
  741. [
  742. sprintf("<?php %s /**/ E //\n{}", $classy),
  743. sprintf("<?php %s/**/E//\n{}", $classy),
  744. ],
  745. [
  746. sprintf(
  747. "<?php
  748. %s A
  749. {}
  750. %s /**/ B //
  751. /**/\n{}",
  752. $classy,
  753. $classy
  754. ),
  755. sprintf(
  756. '<?php
  757. %s
  758. A
  759. {}
  760. %s/**/B //
  761. /**/ {}',
  762. $classy,
  763. $classy
  764. ),
  765. ],
  766. [
  767. sprintf(
  768. '<?php
  769. namespace {
  770. %s IndentedNameSpacedClass
  771. {
  772. }
  773. }',
  774. $classy
  775. ),
  776. sprintf(
  777. '<?php
  778. namespace {
  779. %s IndentedNameSpacedClass {
  780. }
  781. }',
  782. $classy
  783. ),
  784. ],
  785. ];
  786. }
  787. private static function provideClassyExtendingCases(string $classy): iterable
  788. {
  789. return [
  790. [
  791. sprintf("<?php %s AE0 extends B\n{}", $classy),
  792. sprintf('<?php %s AE0 extends B {}', $classy),
  793. ],
  794. [
  795. sprintf("<?php %s /**/ AE1 /**/ extends /**/ B /**/\n{}", $classy),
  796. sprintf('<?php %s/**/AE1/**/extends/**/B/**/{}', $classy),
  797. ],
  798. [
  799. sprintf("<?php %s /*%s*/ AE2 extends\nB\n{}", $classy, $classy),
  800. sprintf("<?php %s /*%s*/ AE2 extends\nB{}", $classy, $classy),
  801. ],
  802. [
  803. sprintf('<?php
  804. %s Test124 extends
  805. \Exception
  806. {}', $classy),
  807. sprintf('<?php
  808. %s
  809. Test124
  810. extends
  811. \Exception {}', $classy),
  812. ],
  813. ];
  814. }
  815. private static function provideClassyImplementsCases(): iterable
  816. {
  817. return [
  818. [
  819. '<?php class LotOfImplements implements A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q
  820. {}',
  821. '<?php class LotOfImplements implements A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q{}',
  822. ],
  823. [
  824. "<?php class E implements B\n{}",
  825. "<?php class E \nimplements B \t{}",
  826. ],
  827. [
  828. "<?php abstract class F extends B implements C\n{}",
  829. '<?php abstract class F extends B implements C {}',
  830. ],
  831. 'multiline abstract extends implements with comments' => [
  832. "<?php abstract class G extends //
  833. B /* */ implements C\n{}",
  834. '<?php abstract class G extends //
  835. B/* */implements C{}',
  836. ],
  837. 'final extends implement' => [
  838. "<?php final class G extends //
  839. B /* */ implements C\n{}",
  840. '<?php final class G extends //
  841. B/* */implements C{}',
  842. ],
  843. 'final' => [
  844. '<?php final class G //
  845. /* */
  846. {}',
  847. '<?php final class G //
  848. /* */{}',
  849. ],
  850. [
  851. '<?php
  852. class Aaa IMPLEMENTS
  853. \RFb,
  854. \Fcc,
  855. \GFddZz
  856. {
  857. }',
  858. '<?php
  859. class Aaa IMPLEMENTS
  860. \RFb,
  861. \Fcc, \GFddZz
  862. {
  863. }',
  864. ],
  865. [
  866. '<?php
  867. class //
  868. X //
  869. extends //
  870. Y //
  871. implements //
  872. Z, //
  873. U //
  874. {} //',
  875. '<?php
  876. class //
  877. X //
  878. extends //
  879. Y //
  880. implements //
  881. Z , //
  882. U //
  883. {} //',
  884. ],
  885. [
  886. '<?php
  887. class Aaa implements
  888. PhpCsFixer\Tests\Fixer,
  889. \RFb,
  890. \Fcc1,
  891. \GFdd
  892. {
  893. }',
  894. '<?php
  895. class Aaa implements
  896. PhpCsFixer\Tests\Fixer,\RFb,
  897. \Fcc1, \GFdd
  898. {
  899. }',
  900. ],
  901. [
  902. '<?php
  903. class /**/ Test123 EXtends /**/ \RuntimeException implements
  904. TestZ
  905. {
  906. }',
  907. '<?php
  908. class/**/Test123
  909. EXtends /**/ \RuntimeException implements
  910. TestZ
  911. {
  912. }',
  913. ],
  914. [
  915. '<?php
  916. class Aaa implements Ebb, \Ccc
  917. {
  918. }',
  919. '<?php
  920. class Aaa implements Ebb, \Ccc
  921. {
  922. }',
  923. ],
  924. [
  925. '<?php
  926. class X2 IMPLEMENTS
  927. Z, //
  928. U,
  929. D
  930. {
  931. }',
  932. '<?php
  933. class X2 IMPLEMENTS
  934. Z , //
  935. U, D
  936. {
  937. }',
  938. ],
  939. [
  940. '<?php
  941. class VeryLongClassNameWithLotsOfLetters extends AnotherVeryLongClassName implements
  942. VeryLongInterfaceNameThatIDontWantOnTheSameLine
  943. {
  944. }',
  945. '<?php
  946. class VeryLongClassNameWithLotsOfLetters extends AnotherVeryLongClassName implements
  947. VeryLongInterfaceNameThatIDontWantOnTheSameLine
  948. {
  949. }',
  950. ],
  951. [
  952. '<?php
  953. class /**/ Test125 //aaa
  954. extends /*
  955. */
  956. //
  957. \Exception //
  958. {}',
  959. '<?php
  960. class/**/Test125 //aaa
  961. extends /*
  962. */
  963. //
  964. \Exception //
  965. {}',
  966. ],
  967. [
  968. '<?php
  969. class Test extends TestInterface8 implements /*a*/ /*b*/
  970. TestInterface1, /* test */
  971. TestInterface2, // test
  972. '.'
  973. // test
  974. TestInterface3, /**/
  975. TestInterface4,
  976. TestInterface5, '.'
  977. /**/TestInterface6c
  978. {
  979. }',
  980. '<?php
  981. class Test
  982. extends
  983. TestInterface8
  984. implements /*a*/ /*b*/TestInterface1 , /* test */
  985. TestInterface2 , // test
  986. '.'
  987. // test
  988. TestInterface3, /**/ TestInterface4 ,
  989. TestInterface5 , '.'
  990. /**/TestInterface6c
  991. {
  992. }',
  993. ],
  994. ];
  995. }
  996. }