BraceTransformerTest.php 15 KB

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