BraceTransformerTest.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  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\Tokenizer\Transformer;
  13. use PhpCsFixer\Tests\Test\AbstractTransformerTestCase;
  14. use PhpCsFixer\Tokenizer\CT;
  15. use PhpCsFixer\Tokenizer\Tokens;
  16. /**
  17. * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
  18. *
  19. * @internal
  20. *
  21. * @covers \PhpCsFixer\Tokenizer\Transformer\BraceTransformer
  22. *
  23. * @phpstan-import-type _TransformerTestExpectedTokens from AbstractTransformerTestCase
  24. */
  25. final class BraceTransformerTest extends AbstractTransformerTestCase
  26. {
  27. /**
  28. * @param _TransformerTestExpectedTokens $expectedTokens
  29. *
  30. * @dataProvider provideProcessCases
  31. */
  32. public function testProcess(string $source, array $expectedTokens = []): void
  33. {
  34. $this->doTest(
  35. $source,
  36. $expectedTokens,
  37. [
  38. T_CURLY_OPEN,
  39. CT::T_CURLY_CLOSE,
  40. T_DOLLAR_OPEN_CURLY_BRACES,
  41. CT::T_DOLLAR_CLOSE_CURLY_BRACES,
  42. CT::T_DYNAMIC_PROP_BRACE_OPEN,
  43. CT::T_DYNAMIC_PROP_BRACE_CLOSE,
  44. CT::T_DYNAMIC_VAR_BRACE_OPEN,
  45. CT::T_DYNAMIC_VAR_BRACE_CLOSE,
  46. CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN,
  47. CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE,
  48. CT::T_GROUP_IMPORT_BRACE_OPEN,
  49. CT::T_GROUP_IMPORT_BRACE_CLOSE,
  50. ]
  51. );
  52. }
  53. public static function provideProcessCases(): iterable
  54. {
  55. yield 'curly open/close I' => [
  56. '<?php echo "This is {$great}";',
  57. [
  58. 5 => T_CURLY_OPEN,
  59. 7 => CT::T_CURLY_CLOSE,
  60. ],
  61. ];
  62. yield 'curly open/close II' => [
  63. '<?php $a = "a{$b->c()}d";',
  64. [
  65. 7 => T_CURLY_OPEN,
  66. 13 => CT::T_CURLY_CLOSE,
  67. ],
  68. ];
  69. yield 'dynamic var brace open/close' => [
  70. '<?php echo "I\'d like an {${beers::$ale}}\n";',
  71. [
  72. 5 => T_CURLY_OPEN,
  73. 7 => CT::T_DYNAMIC_VAR_BRACE_OPEN,
  74. 11 => CT::T_DYNAMIC_VAR_BRACE_CLOSE,
  75. 12 => CT::T_CURLY_CLOSE,
  76. ],
  77. ];
  78. yield 'dollar curly brace open/close' => [
  79. '<?php echo "This is ${great}";',
  80. [
  81. 5 => T_DOLLAR_OPEN_CURLY_BRACES,
  82. 7 => CT::T_DOLLAR_CLOSE_CURLY_BRACES,
  83. ],
  84. ];
  85. yield 'dynamic property brace open/close' => [
  86. '<?php $foo->{$bar};',
  87. [
  88. 3 => CT::T_DYNAMIC_PROP_BRACE_OPEN,
  89. 5 => CT::T_DYNAMIC_PROP_BRACE_CLOSE,
  90. ],
  91. ];
  92. yield 'dynamic variable brace open/close' => [
  93. '<?php ${$bar};',
  94. [
  95. 2 => CT::T_DYNAMIC_VAR_BRACE_OPEN,
  96. 4 => CT::T_DYNAMIC_VAR_BRACE_CLOSE,
  97. ],
  98. ];
  99. yield 'mixed' => [
  100. '<?php echo "This is {$great}";
  101. $a = "a{$b->c()}d";
  102. echo "I\'d like an {${beers::$ale}}\n";
  103. ',
  104. [
  105. 5 => T_CURLY_OPEN,
  106. 7 => CT::T_CURLY_CLOSE,
  107. 17 => T_CURLY_OPEN,
  108. 23 => CT::T_CURLY_CLOSE,
  109. 32 => T_CURLY_OPEN,
  110. 34 => CT::T_DYNAMIC_VAR_BRACE_OPEN,
  111. 38 => CT::T_DYNAMIC_VAR_BRACE_CLOSE,
  112. 39 => CT::T_CURLY_CLOSE,
  113. ],
  114. ];
  115. yield 'do not touch' => [
  116. '<?php if (1) {} class Foo{ } function bar(){ }',
  117. ];
  118. yield 'dynamic property with string with variable' => [
  119. '<?php $object->{"set_{$name}"}(42);',
  120. [
  121. 3 => CT::T_DYNAMIC_PROP_BRACE_OPEN,
  122. 6 => T_CURLY_OPEN,
  123. 8 => CT::T_CURLY_CLOSE,
  124. 10 => CT::T_DYNAMIC_PROP_BRACE_CLOSE,
  125. ],
  126. ];
  127. yield 'group import' => [
  128. '<?php use some\a\{ClassA, ClassB, ClassC as C};',
  129. [
  130. 7 => CT::T_GROUP_IMPORT_BRACE_OPEN,
  131. 19 => CT::T_GROUP_IMPORT_BRACE_CLOSE,
  132. ],
  133. ];
  134. yield 'nested curly open + close' => [
  135. '<?php echo "{$foo->{"{$bar}"}}";',
  136. [
  137. 4 => T_CURLY_OPEN,
  138. 7 => CT::T_DYNAMIC_PROP_BRACE_OPEN,
  139. 9 => T_CURLY_OPEN,
  140. 11 => CT::T_CURLY_CLOSE,
  141. 13 => CT::T_DYNAMIC_PROP_BRACE_CLOSE,
  142. 14 => CT::T_CURLY_CLOSE,
  143. ],
  144. ];
  145. }
  146. /**
  147. * @param _TransformerTestExpectedTokens $expectedTokens
  148. *
  149. * @dataProvider provideProcess80Cases
  150. *
  151. * @requires PHP 8.0
  152. */
  153. public function testProcess80(string $source, array $expectedTokens = []): void
  154. {
  155. $this->doTest(
  156. $source,
  157. $expectedTokens,
  158. [
  159. T_CURLY_OPEN,
  160. CT::T_CURLY_CLOSE,
  161. T_DOLLAR_OPEN_CURLY_BRACES,
  162. CT::T_DOLLAR_CLOSE_CURLY_BRACES,
  163. CT::T_DYNAMIC_PROP_BRACE_OPEN,
  164. CT::T_DYNAMIC_PROP_BRACE_CLOSE,
  165. CT::T_DYNAMIC_VAR_BRACE_OPEN,
  166. CT::T_DYNAMIC_VAR_BRACE_CLOSE,
  167. CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN,
  168. CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE,
  169. CT::T_GROUP_IMPORT_BRACE_OPEN,
  170. CT::T_GROUP_IMPORT_BRACE_CLOSE,
  171. ]
  172. );
  173. }
  174. public static function provideProcess80Cases(): iterable
  175. {
  176. yield 'dynamic nullable property brace open/close' => [
  177. '<?php $foo?->{$bar};',
  178. [
  179. 3 => CT::T_DYNAMIC_PROP_BRACE_OPEN,
  180. 5 => CT::T_DYNAMIC_PROP_BRACE_CLOSE,
  181. ],
  182. ];
  183. }
  184. /**
  185. * @param _TransformerTestExpectedTokens $expectedTokens
  186. *
  187. * @dataProvider providePre84ProcessCases
  188. *
  189. * @requires PHP <8.4
  190. */
  191. public function testPre84Process(string $source, array $expectedTokens = []): void
  192. {
  193. $this->doTest(
  194. $source,
  195. $expectedTokens,
  196. [
  197. T_CURLY_OPEN,
  198. CT::T_CURLY_CLOSE,
  199. T_DOLLAR_OPEN_CURLY_BRACES,
  200. CT::T_DOLLAR_CLOSE_CURLY_BRACES,
  201. CT::T_DYNAMIC_PROP_BRACE_OPEN,
  202. CT::T_DYNAMIC_PROP_BRACE_CLOSE,
  203. CT::T_DYNAMIC_VAR_BRACE_OPEN,
  204. CT::T_DYNAMIC_VAR_BRACE_CLOSE,
  205. CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN,
  206. CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE,
  207. CT::T_GROUP_IMPORT_BRACE_OPEN,
  208. CT::T_GROUP_IMPORT_BRACE_CLOSE,
  209. ]
  210. );
  211. }
  212. /**
  213. * @return iterable<array{string, array<int, int>}>
  214. */
  215. public static function providePre84ProcessCases(): iterable
  216. {
  217. yield 'array index curly brace open/close' => [
  218. '<?php
  219. echo $arr{$index};
  220. echo $arr[$index];
  221. if (1) {}
  222. ',
  223. [
  224. 5 => CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN,
  225. 7 => CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE,
  226. ],
  227. ];
  228. yield 'array index curly brace open/close, after square index' => [
  229. '<?php $b = [1]{0};
  230. ',
  231. [
  232. 8 => CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN,
  233. 10 => CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE,
  234. ],
  235. ];
  236. yield 'array index curly brace open/close, nested' => [
  237. '<?php
  238. echo $nestedArray{$index}{$index2}[$index3]{$index4};
  239. ',
  240. [
  241. 5 => CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN,
  242. 7 => CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE,
  243. 8 => CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN,
  244. 10 => CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE,
  245. 14 => CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN,
  246. 16 => CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE,
  247. ],
  248. ];
  249. yield 'array index curly brace open/close, repeated' => [
  250. '<?php
  251. echo $array{0}->foo;
  252. echo $collection->items{1}->property;
  253. ',
  254. [
  255. 5 => CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN,
  256. 7 => CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE,
  257. 17 => CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN,
  258. 19 => CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE,
  259. ],
  260. ];
  261. yield 'array index curly brace open/close, minimal' => [
  262. '<?php
  263. echo [1]{0};
  264. echo array(1){0};
  265. ',
  266. [
  267. 7 => CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN,
  268. 9 => CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE,
  269. 18 => CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN,
  270. 20 => CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE,
  271. ],
  272. ];
  273. }
  274. /**
  275. * @dataProvider provideNotDynamicClassConstantFetchCases
  276. */
  277. public function testNotDynamicClassConstantFetch(string $source): void
  278. {
  279. Tokens::clearCache();
  280. $tokens = Tokens::fromCode($source);
  281. self::assertFalse(
  282. $tokens->isAnyTokenKindsFound(
  283. [
  284. CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN,
  285. CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE,
  286. ]
  287. )
  288. );
  289. }
  290. /**
  291. * @return iterable<string, array{string}>
  292. */
  293. public static function provideNotDynamicClassConstantFetchCases(): iterable
  294. {
  295. yield 'negatives' => [
  296. '<?php
  297. namespace B {$b = Z::B;};
  298. echo $c::{$static_method}();
  299. echo Foo::{$static_method}();
  300. echo Foo::${static_property};
  301. echo Foo::${$static_property};
  302. echo Foo::class;
  303. echo $foo::$bar;
  304. echo $foo::bar();
  305. echo foo()::A();
  306. {$z = A::C;}
  307. ',
  308. ];
  309. }
  310. /**
  311. * @param _TransformerTestExpectedTokens $expectedTokens
  312. *
  313. * @dataProvider provideDynamicClassConstantFetchCases
  314. *
  315. * @requires PHP 8.3
  316. */
  317. public function testDynamicClassConstantFetch(array $expectedTokens, string $source): void
  318. {
  319. $this->doTest(
  320. $source,
  321. $expectedTokens,
  322. [
  323. CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN,
  324. CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE,
  325. ],
  326. );
  327. }
  328. public static function provideDynamicClassConstantFetchCases(): iterable
  329. {
  330. yield 'simple' => [
  331. [
  332. 5 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN,
  333. 7 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE,
  334. ],
  335. '<?php echo Foo::{$bar};',
  336. ];
  337. yield 'long way of writing `Bar::class`' => [
  338. [
  339. 5 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN,
  340. 7 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE,
  341. ],
  342. "<?php echo Bar::{'class'};",
  343. ];
  344. yield 'variable variable wrapped, close tag' => [
  345. [
  346. 5 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN,
  347. 10 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE,
  348. ],
  349. '<?php echo Foo::{${$var}}?>',
  350. ];
  351. yield 'variable variable, comment' => [
  352. [
  353. 5 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN,
  354. 8 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE,
  355. ],
  356. '<?php echo Foo::{$$var}/* */;?>',
  357. ];
  358. yield 'static, self' => [
  359. [
  360. 37 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN,
  361. 39 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE,
  362. 46 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN,
  363. 48 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE,
  364. 55 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN,
  365. 57 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE,
  366. ],
  367. '<?php
  368. class Foo
  369. {
  370. private const X = 1;
  371. public function Bar($var): void
  372. {
  373. echo self::{$var};
  374. echo static::{$var};
  375. echo static::{"X"};
  376. }
  377. }
  378. ',
  379. ];
  380. yield 'chained' => [
  381. [
  382. 5 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN,
  383. 7 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE,
  384. 9 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN,
  385. 11 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE,
  386. ],
  387. "<?php echo Foo::{'BAR'}::{'BLA'}::{static_method}(1,2) ?>",
  388. ];
  389. yield 'mixed chain' => [
  390. [
  391. 21 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN,
  392. 23 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE,
  393. 25 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN,
  394. 27 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE,
  395. ],
  396. '<?php echo Foo::{\'static_method\'}()::{$$a}()["const"]::{some_const}::{$other_const}::{$last_static_method}();',
  397. ];
  398. }
  399. /**
  400. * @param _TransformerTestExpectedTokens $expectedTokens
  401. *
  402. * @dataProvider provideDynamicClassConstantFetchPhp83Cases
  403. *
  404. * @requires PHP ~8.3.0
  405. */
  406. public function testDynamicClassConstantFetchPhp83(array $expectedTokens, string $source): void
  407. {
  408. $this->doTest(
  409. $source,
  410. $expectedTokens,
  411. [
  412. CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN,
  413. CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE,
  414. ],
  415. );
  416. }
  417. /**
  418. * @return iterable<array{array<int, int>, string}>
  419. */
  420. public static function provideDynamicClassConstantFetchPhp83Cases(): iterable
  421. {
  422. yield 'static method var, string' => [
  423. [
  424. 10 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN,
  425. 12 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE,
  426. ],
  427. "<?php echo Foo::{\$static_method}(){'XYZ'};",
  428. ];
  429. yield 'mixed chain' => [
  430. [
  431. 17 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN,
  432. 19 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE,
  433. 21 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN,
  434. 23 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE,
  435. 25 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN,
  436. 27 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE,
  437. ],
  438. '<?php echo Foo::{\'static_method\'}()::{$$a}(){"const"}::{some_const}::{$other_const}::{$last_static_method}();',
  439. ];
  440. }
  441. }