ClassDefinitionFixerTest.php 30 KB

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