CurlyBraceTransformerTest.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  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\CurlyBraceTransformer
  22. */
  23. final class CurlyBraceTransformerTest 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 'array index curly brace open/close' => [
  98. '<?php
  99. echo $arr{$index};
  100. echo $arr[$index];
  101. if (1) {}
  102. ',
  103. [
  104. 5 => CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN,
  105. 7 => CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE,
  106. ],
  107. ];
  108. yield 'array index curly brace open/close, after square index' => [
  109. '<?php $b = [1]{0};
  110. ',
  111. [
  112. 8 => CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN,
  113. 10 => CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE,
  114. ],
  115. ];
  116. yield 'array index curly brace open/close, nested' => [
  117. '<?php
  118. echo $nestedArray{$index}{$index2}[$index3]{$index4};
  119. ',
  120. [
  121. 5 => CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN,
  122. 7 => CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE,
  123. 8 => CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN,
  124. 10 => CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE,
  125. 14 => CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN,
  126. 16 => CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE,
  127. ],
  128. ];
  129. yield 'array index curly brace open/close, repeated' => [
  130. '<?php
  131. echo $array{0}->foo;
  132. echo $collection->items{1}->property;
  133. ',
  134. [
  135. 5 => CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN,
  136. 7 => CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE,
  137. 17 => CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN,
  138. 19 => CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE,
  139. ],
  140. ];
  141. yield 'array index curly brace open/close, minimal' => [
  142. '<?php
  143. echo [1]{0};
  144. echo array(1){0};
  145. ',
  146. [
  147. 7 => CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN,
  148. 9 => CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE,
  149. 18 => CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN,
  150. 20 => CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE,
  151. ],
  152. ];
  153. yield 'mixed' => [
  154. '<?php echo "This is {$great}";
  155. $a = "a{$b->c()}d";
  156. echo "I\'d like an {${beers::$ale}}\n";
  157. ',
  158. [
  159. 5 => T_CURLY_OPEN,
  160. 7 => CT::T_CURLY_CLOSE,
  161. 17 => T_CURLY_OPEN,
  162. 23 => CT::T_CURLY_CLOSE,
  163. 32 => T_CURLY_OPEN,
  164. 34 => CT::T_DYNAMIC_VAR_BRACE_OPEN,
  165. 38 => CT::T_DYNAMIC_VAR_BRACE_CLOSE,
  166. 39 => CT::T_CURLY_CLOSE,
  167. ],
  168. ];
  169. yield 'do not touch' => [
  170. '<?php if (1) {} class Foo{ } function bar(){ }',
  171. ];
  172. yield 'dynamic property with string with variable' => [
  173. '<?php $object->{"set_{$name}"}(42);',
  174. [
  175. 3 => CT::T_DYNAMIC_PROP_BRACE_OPEN,
  176. 6 => T_CURLY_OPEN,
  177. 8 => CT::T_CURLY_CLOSE,
  178. 10 => CT::T_DYNAMIC_PROP_BRACE_CLOSE,
  179. ],
  180. ];
  181. yield 'group import' => [
  182. '<?php use some\a\{ClassA, ClassB, ClassC as C};',
  183. [
  184. 7 => CT::T_GROUP_IMPORT_BRACE_OPEN,
  185. 19 => CT::T_GROUP_IMPORT_BRACE_CLOSE,
  186. ],
  187. ];
  188. yield 'nested curly open + close' => [
  189. '<?php echo "{$foo->{"{$bar}"}}";',
  190. [
  191. 4 => T_CURLY_OPEN,
  192. 7 => CT::T_DYNAMIC_PROP_BRACE_OPEN,
  193. 9 => T_CURLY_OPEN,
  194. 11 => CT::T_CURLY_CLOSE,
  195. 13 => CT::T_DYNAMIC_PROP_BRACE_CLOSE,
  196. 14 => CT::T_CURLY_CLOSE,
  197. ],
  198. ];
  199. }
  200. /**
  201. * @param array<int, int> $expectedTokens
  202. *
  203. * @dataProvider provideProcess80Cases
  204. *
  205. * @requires PHP 8.0
  206. */
  207. public function testProcess80(string $source, array $expectedTokens = []): void
  208. {
  209. $this->doTest(
  210. $source,
  211. $expectedTokens,
  212. [
  213. T_CURLY_OPEN,
  214. CT::T_CURLY_CLOSE,
  215. T_DOLLAR_OPEN_CURLY_BRACES,
  216. CT::T_DOLLAR_CLOSE_CURLY_BRACES,
  217. CT::T_DYNAMIC_PROP_BRACE_OPEN,
  218. CT::T_DYNAMIC_PROP_BRACE_CLOSE,
  219. CT::T_DYNAMIC_VAR_BRACE_OPEN,
  220. CT::T_DYNAMIC_VAR_BRACE_CLOSE,
  221. CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN,
  222. CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE,
  223. CT::T_GROUP_IMPORT_BRACE_OPEN,
  224. CT::T_GROUP_IMPORT_BRACE_CLOSE,
  225. ]
  226. );
  227. }
  228. public static function provideProcess80Cases(): iterable
  229. {
  230. yield 'dynamic nullable property brace open/close' => [
  231. '<?php $foo?->{$bar};',
  232. [
  233. 3 => CT::T_DYNAMIC_PROP_BRACE_OPEN,
  234. 5 => CT::T_DYNAMIC_PROP_BRACE_CLOSE,
  235. ],
  236. ];
  237. }
  238. /**
  239. * @dataProvider provideNotDynamicClassConstantFetchCases
  240. */
  241. public function testNotDynamicClassConstantFetch(string $source): void
  242. {
  243. Tokens::clearCache();
  244. $tokens = Tokens::fromCode($source);
  245. self::assertFalse(
  246. $tokens->isAnyTokenKindsFound(
  247. [
  248. CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN,
  249. CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE,
  250. ]
  251. )
  252. );
  253. }
  254. public static function provideNotDynamicClassConstantFetchCases(): iterable
  255. {
  256. yield 'negatives' => [
  257. '<?php
  258. namespace B {$b = Z::B;};
  259. echo $c::{$static_method}();
  260. echo Foo::{$static_method}();
  261. echo Foo::${static_property};
  262. echo Foo::${$static_property};
  263. echo Foo::class;
  264. echo $foo::$bar;
  265. echo $foo::bar();
  266. echo foo()::A();
  267. {$z = A::C;}
  268. ',
  269. ];
  270. }
  271. /**
  272. * @param array<int, int> $expectedTokens
  273. *
  274. * @dataProvider provideDynamicClassConstantFetchCases
  275. *
  276. * @requires PHP 8.3
  277. */
  278. public function testDynamicClassConstantFetch(array $expectedTokens, string $source): void
  279. {
  280. $this->doTest(
  281. $source,
  282. $expectedTokens,
  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. public static function provideDynamicClassConstantFetchCases(): iterable
  290. {
  291. yield 'simple' => [
  292. [
  293. 5 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN,
  294. 7 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE,
  295. ],
  296. '<?php echo Foo::{$bar};',
  297. ];
  298. yield 'static method var, string' => [
  299. [
  300. 10 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN,
  301. 12 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE,
  302. ],
  303. "<?php echo Foo::{\$static_method}(){'XYZ'};",
  304. ];
  305. yield 'long way of writing `Bar::class`' => [
  306. [
  307. 5 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN,
  308. 7 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE,
  309. ],
  310. "<?php echo Bar::{'class'};",
  311. ];
  312. yield 'variable variable wrapped, close tag' => [
  313. [
  314. 5 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN,
  315. 10 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE,
  316. ],
  317. '<?php echo Foo::{${$var}}?>',
  318. ];
  319. yield 'variable variable, comment' => [
  320. [
  321. 5 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN,
  322. 8 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE,
  323. ],
  324. '<?php echo Foo::{$$var}/* */;?>',
  325. ];
  326. yield 'static, self' => [
  327. [
  328. 37 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN,
  329. 39 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE,
  330. 46 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN,
  331. 48 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE,
  332. 55 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN,
  333. 57 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE,
  334. ],
  335. '<?php
  336. class Foo
  337. {
  338. private const X = 1;
  339. public function Bar($var): void
  340. {
  341. echo self::{$var};
  342. echo static::{$var};
  343. echo static::{"X"};
  344. }
  345. }
  346. ',
  347. ];
  348. yield 'chained' => [
  349. [
  350. 5 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN,
  351. 7 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE,
  352. 9 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN,
  353. 11 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE,
  354. ],
  355. "<?php echo Foo::{'BAR'}::{'BLA'}::{static_method}(1,2) ?>",
  356. ];
  357. yield 'mixed chain' => [
  358. [
  359. 17 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN,
  360. 19 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE,
  361. 21 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN,
  362. 23 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE,
  363. 25 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN,
  364. 27 => CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE,
  365. ],
  366. '<?php echo Foo::{\'static_method\'}()::{$$a}(){"const"}::{some_const}::{$other_const}::{$last_static_method}();',
  367. ];
  368. }
  369. }