ClassDefinitionFixerTest.php 31 KB

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