AttributeAnalyzerTest.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  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\Analyzer;
  13. use PhpCsFixer\Tests\TestCase;
  14. use PhpCsFixer\Tokenizer\Analyzer\Analysis\AttributeAnalysis;
  15. use PhpCsFixer\Tokenizer\Analyzer\AttributeAnalyzer;
  16. use PhpCsFixer\Tokenizer\CT;
  17. use PhpCsFixer\Tokenizer\Tokens;
  18. /**
  19. * @internal
  20. *
  21. * @covers \PhpCsFixer\Tokenizer\Analyzer\AttributeAnalyzer
  22. */
  23. final class AttributeAnalyzerTest extends TestCase
  24. {
  25. /**
  26. * Check it's not crashing for PHP lower than 8.0 as other tests run for PHP 8.0 only.
  27. */
  28. public function testNotAnAttribute(): void
  29. {
  30. $tokens = Tokens::fromCode('<?php class Foo { private $bar; }');
  31. foreach ($tokens as $index => $token) {
  32. self::assertFalse(AttributeAnalyzer::isAttribute($tokens, $index));
  33. }
  34. }
  35. /**
  36. * @requires PHP 8.0
  37. *
  38. * @dataProvider provideIsAttributeCases
  39. */
  40. public function testIsAttribute(bool $isInAttribute, string $code): void
  41. {
  42. $tokens = Tokens::fromCode($code);
  43. foreach ($tokens as $index => $token) {
  44. if ($token->equals([T_STRING, 'Foo'])) {
  45. if (isset($testedIndex)) {
  46. self::fail('Test is run against index of "Foo", multiple occurrences found.');
  47. }
  48. $testedIndex = $index;
  49. }
  50. }
  51. if (!isset($testedIndex)) {
  52. self::fail('Test is run against index of "Foo", but it was not found in the code.');
  53. }
  54. self::assertSame($isInAttribute, AttributeAnalyzer::isAttribute($tokens, $testedIndex));
  55. }
  56. /**
  57. * Test case requires to having "Foo" as it will be searched for to test its index.
  58. */
  59. public static function provideIsAttributeCases(): iterable
  60. {
  61. yield [false, '<?php Foo; #[Attr] class Bar {}'];
  62. yield [false, '<?php Foo(); #[Attr] class Bar {};'];
  63. yield [false, '<?php \Foo(); #[Attr] class Bar {}'];
  64. yield [false, '<?php #[Attr] class Foo {};'];
  65. yield [false, '<?php #[Attr] class Bar {}; Foo;'];
  66. yield [false, '<?php #[Attr] class Bar {}; \Foo();'];
  67. yield [false, '<?php #[Attr] class Bar { const Foo = 42; };'];
  68. yield [false, '<?php #[Attr] class Bar { function Foo() {} };'];
  69. yield [false, '<?php #[Attr(Foo)] class Bar {}'];
  70. yield [false, '<?php #[Attr(Foo::Bar)] class Baz {}'];
  71. yield [false, '<?php #[Attr(Bar::Foo)] class Baz {}'];
  72. yield [false, '<?php #[Attr(1, 2, Foo)] class Bar {}'];
  73. yield [false, '<?php #[Attr(Foo, 1, 2)] class Bar {}'];
  74. yield [false, '<?php #[Attr(1, 2, Foo, 3, 4)] class Bar {}'];
  75. yield [false, '<?php #[Attr(2 * (3 + 7), 2, Foo)] class Bar {}'];
  76. yield [false, '<?php #[Attr(2 * (3 + 7), 2, Foo, 5)] class Bar {}'];
  77. yield [false, '<?php #[Attr(2 * (3 + 7), 2, Foo, (16 + 4) / 5 )] class Bar {}'];
  78. yield [false, '<?php #[Attr] function Foo() {};'];
  79. yield [false, '<?php #[Attr("Foo()")] class Foo {}'];
  80. yield [true, '<?php #[Foo] class Bar {}'];
  81. yield [true, '<?php #[Foo, Bar] class Baz {}'];
  82. yield [true, '<?php #[Bar, Foo] class Baz {}'];
  83. yield [true, '<?php #[Bar, Foo, Baz] class Qux {}'];
  84. yield [true, '<?php #[Foo(), Bar()] class Baz {}'];
  85. yield [true, '<?php #[Bar(), Foo()] class Baz {}'];
  86. yield [true, '<?php #[Bar(), Foo(), Baz()] class Qux {}'];
  87. yield [true, '<?php #[Bar(), Foo, Baz()] class Qux {}'];
  88. yield [true, '<?php #[Bar, Foo, Baz] class Qux {}'];
  89. yield [true, '<?php #[\Foo] class Bar {}'];
  90. yield [true, '<?php #[\Bar, \Foo] class Baz {}'];
  91. yield [true, '<?php #[Attr1(2 * (3 + 7)), Foo, Attr2((16 + 4) / 5 )] class Bar {}'];
  92. yield [true, '<?php #[Foo("(")] class Bar {}'];
  93. }
  94. /**
  95. * @requires PHP 8.0
  96. *
  97. * @dataProvider provideGetAttributeDeclarationsCases
  98. *
  99. * @param list<AttributeAnalysis> $expectedAnalyses
  100. */
  101. public function testGetAttributeDeclarations(string $code, int $startIndex, array $expectedAnalyses): void
  102. {
  103. $tokens = Tokens::fromCode($code);
  104. $actualAnalyses = AttributeAnalyzer::collect($tokens, $startIndex);
  105. foreach ($expectedAnalyses as $expectedAnalysis) {
  106. self::assertSame(T_ATTRIBUTE, $tokens[$expectedAnalysis->getOpeningBracketIndex()]->getId());
  107. self::assertSame(CT::T_ATTRIBUTE_CLOSE, $tokens[$expectedAnalysis->getClosingBracketIndex()]->getId());
  108. }
  109. self::assertSame(
  110. serialize($expectedAnalyses),
  111. serialize($actualAnalyses),
  112. );
  113. }
  114. /**
  115. * @return iterable<array{0: string, 1: int, 2: list<AttributeAnalysis>}>
  116. */
  117. public static function provideGetAttributeDeclarationsCases(): iterable
  118. {
  119. yield 'multiple #[] in a multiline group' => [
  120. '<?php
  121. /**
  122. * Start docblock
  123. */
  124. #[AB\Baz(prop: \'baz\')]
  125. #[A\B\Quux(prop1: [1, 2, 4], prop2: true, prop3: \'foo bar\')]
  126. #[\A\B\Qux()]
  127. #[BarAlias(3)]
  128. /**
  129. * Corge docblock
  130. */
  131. #[Corge]
  132. #[Foo(4, \'baz qux\')]
  133. /**
  134. * End docblock
  135. */
  136. function foo() {}
  137. ',
  138. 4,
  139. [
  140. new AttributeAnalysis(4, 15, 4, 14, [[
  141. 'start' => 5,
  142. 'end' => 13,
  143. 'name' => 'AB\\Baz',
  144. ]]),
  145. new AttributeAnalysis(16, 49, 16, 48, [[
  146. 'start' => 17,
  147. 'end' => 47,
  148. 'name' => 'A\\B\\Quux',
  149. ]]),
  150. new AttributeAnalysis(50, 60, 50, 59, [[
  151. 'start' => 51,
  152. 'end' => 58,
  153. 'name' => '\\A\\B\\Qux',
  154. ]]),
  155. new AttributeAnalysis(61, 67, 61, 66, [[
  156. 'start' => 62,
  157. 'end' => 65,
  158. 'name' => 'BarAlias',
  159. ]]),
  160. new AttributeAnalysis(68, 73, 70, 72, [[
  161. 'start' => 71,
  162. 'end' => 71,
  163. 'name' => 'Corge',
  164. ]]),
  165. new AttributeAnalysis(74, 83, 74, 82, [[
  166. 'start' => 75,
  167. 'end' => 81,
  168. 'name' => 'Foo',
  169. ]]),
  170. ],
  171. ];
  172. yield 'multiple #[] in a single line group' => [
  173. '<?php
  174. /** Start docblock */#[AB\Baz(prop: \'baz\')] #[A\B\Quux(prop1: [1, 2, 4], prop2: true, prop3: \'foo bar\')] #[\A\B\Qux()] #[BarAlias(3)] /** Corge docblock */#[Corge] #[Foo(4, \'baz qux\')]/** End docblock */
  175. function foo() {}
  176. ',
  177. 3,
  178. [
  179. new AttributeAnalysis(3, 14, 3, 13, [[
  180. 'start' => 4,
  181. 'end' => 12,
  182. 'name' => 'AB\\Baz',
  183. ]]),
  184. new AttributeAnalysis(15, 48, 15, 47, [[
  185. 'start' => 16,
  186. 'end' => 46,
  187. 'name' => 'A\\B\\Quux',
  188. ]]),
  189. new AttributeAnalysis(49, 59, 49, 58, [[
  190. 'start' => 50,
  191. 'end' => 57,
  192. 'name' => '\\A\\B\\Qux',
  193. ]]),
  194. new AttributeAnalysis(60, 66, 60, 65, [[
  195. 'start' => 61,
  196. 'end' => 64,
  197. 'name' => 'BarAlias',
  198. ]]),
  199. new AttributeAnalysis(67, 71, 68, 70, [[
  200. 'start' => 69,
  201. 'end' => 69,
  202. 'name' => 'Corge',
  203. ]]),
  204. new AttributeAnalysis(72, 80, 72, 80, [[
  205. 'start' => 73,
  206. 'end' => 79,
  207. 'name' => 'Foo',
  208. ]]),
  209. ],
  210. ];
  211. yield 'comma-separated attributes in a multiline #[]' => [
  212. '<?php
  213. #[
  214. /*
  215. * AB\Baz comment
  216. */
  217. AB\Baz(prop: \'baz\'),
  218. A\B\Quux(prop1: [1, 2, 4], prop2: true, prop3: \'foo bar\'),
  219. \A\B\Qux(),
  220. BarAlias(3),
  221. /*
  222. * Corge comment
  223. */
  224. Corge,
  225. /**
  226. * Foo docblock
  227. */
  228. Foo(4, \'baz qux\'),
  229. ]
  230. function foo() {}
  231. ',
  232. 2,
  233. [
  234. new AttributeAnalysis(2, 83, 2, 82, [[
  235. 'start' => 3,
  236. 'end' => 14,
  237. 'name' => 'AB\\Baz',
  238. ], [
  239. 'start' => 16,
  240. 'end' => 47,
  241. 'name' => 'A\\B\\Quux',
  242. ], [
  243. 'start' => 49,
  244. 'end' => 57,
  245. 'name' => '\\A\\B\\Qux',
  246. ], [
  247. 'start' => 59,
  248. 'end' => 63,
  249. 'name' => 'BarAlias',
  250. ], [
  251. 'start' => 65,
  252. 'end' => 68,
  253. 'name' => 'Corge',
  254. ], [
  255. 'start' => 70,
  256. 'end' => 79,
  257. 'name' => 'Foo',
  258. ]]),
  259. ],
  260. ];
  261. yield 'comma-separated attributes in a single line #[]' => [
  262. '<?php
  263. #[/* AB\Baz comment */AB\Baz(prop: \'baz\'), A\B\Quux(prop1: [1, 2, 4], prop2: true, prop3: \'foo bar\'), \A\B\Qux(), BarAlias(3), /* Corge comment */Corge, /** Foo docblock */Foo(4, \'baz qux\')]
  264. function foo() {}
  265. ',
  266. 2,
  267. [
  268. new AttributeAnalysis(2, 77, 2, 76, [[
  269. 'start' => 3,
  270. 'end' => 12,
  271. 'name' => 'AB\\Baz',
  272. ], [
  273. 'start' => 14,
  274. 'end' => 45,
  275. 'name' => 'A\\B\\Quux',
  276. ], [
  277. 'start' => 47,
  278. 'end' => 55,
  279. 'name' => '\\A\\B\\Qux',
  280. ], [
  281. 'start' => 57,
  282. 'end' => 61,
  283. 'name' => 'BarAlias',
  284. ], [
  285. 'start' => 63,
  286. 'end' => 65,
  287. 'name' => 'Corge',
  288. ], [
  289. 'start' => 67,
  290. 'end' => 75,
  291. 'name' => 'Foo',
  292. ]]),
  293. ],
  294. ];
  295. }
  296. /**
  297. * @requires PHP 8.1
  298. *
  299. * @dataProvider provideGetAttributeDeclarations81Cases
  300. *
  301. * @param list<AttributeAnalysis> $expectedAnalyses
  302. */
  303. public function testGetAttributeDeclarations81(string $code, int $startIndex, array $expectedAnalyses): void
  304. {
  305. $tokens = Tokens::fromCode($code);
  306. $actualAnalyses = AttributeAnalyzer::collect($tokens, $startIndex);
  307. foreach ($expectedAnalyses as $expectedAnalysis) {
  308. self::assertSame(T_ATTRIBUTE, $tokens[$expectedAnalysis->getOpeningBracketIndex()]->getId());
  309. self::assertSame(CT::T_ATTRIBUTE_CLOSE, $tokens[$expectedAnalysis->getClosingBracketIndex()]->getId());
  310. }
  311. self::assertSame(
  312. serialize($expectedAnalyses),
  313. serialize($actualAnalyses),
  314. );
  315. }
  316. /**
  317. * @return iterable<array{0: string, 1: int, 2: list<AttributeAnalysis>}>
  318. */
  319. public static function provideGetAttributeDeclarations81Cases(): iterable
  320. {
  321. yield 'multiple #[] in a group, including `new` in arguments' => [
  322. '<?php
  323. #[AB\Baz(prop: \'baz\')]
  324. #[\A\B\Qux(prop: new P\R())]
  325. #[Corge]
  326. function foo() {}
  327. ',
  328. 2,
  329. [
  330. new AttributeAnalysis(2, 13, 2, 12, [[
  331. 'start' => 3,
  332. 'end' => 11,
  333. 'name' => 'AB\\Baz',
  334. ]]),
  335. new AttributeAnalysis(14, 34, 14, 33, [[
  336. 'start' => 15,
  337. 'end' => 32,
  338. 'name' => '\\A\\B\\Qux',
  339. ]]),
  340. new AttributeAnalysis(35, 38, 35, 37, [[
  341. 'start' => 36,
  342. 'end' => 36,
  343. 'name' => 'Corge',
  344. ]]),
  345. ],
  346. ];
  347. yield 'comma-separated attributes in single #[] group, including `new` in arguments' => [
  348. '<?php
  349. #[
  350. AB\Baz(prop: \'baz\'),
  351. \A\B\Qux(prop: new P\R()),
  352. Corge,
  353. ]
  354. function foo() {}
  355. ',
  356. 2,
  357. [
  358. new AttributeAnalysis(2, 39, 2, 38, [[
  359. 'start' => 3,
  360. 'end' => 12,
  361. 'name' => 'AB\\Baz',
  362. ], [
  363. 'start' => 14,
  364. 'end' => 32,
  365. 'name' => '\\A\\B\\Qux',
  366. ], [
  367. 'start' => 34,
  368. 'end' => 35,
  369. 'name' => 'Corge',
  370. ]]),
  371. ],
  372. ];
  373. }
  374. }