ClassDefinitionFixerTest.php 31 KB

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