AttributeAnalyzerTest.php 13 KB

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