TokensAnalyzerTest.php 57 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190
  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;
  13. use PhpCsFixer\Tests\TestCase;
  14. use PhpCsFixer\Tokenizer\Tokens;
  15. use PhpCsFixer\Tokenizer\TokensAnalyzer;
  16. /**
  17. * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
  18. * @author Max Voloshin <voloshin.dp@gmail.com>
  19. * @author Gregor Harlan <gharlan@web.de>
  20. *
  21. * @internal
  22. *
  23. * @covers \PhpCsFixer\Tokenizer\TokensAnalyzer
  24. */
  25. final class TokensAnalyzerTest extends TestCase
  26. {
  27. /**
  28. * @dataProvider provideGetClassyElementsCases
  29. */
  30. public function testGetClassyElements(array $expectedElements, string $source): void
  31. {
  32. $tokens = Tokens::fromCode($source);
  33. array_walk(
  34. $expectedElements,
  35. static function (array &$element, $index) use ($tokens): void {
  36. $element['token'] = $tokens[$index];
  37. ksort($element);
  38. }
  39. );
  40. $tokensAnalyzer = new TokensAnalyzer($tokens);
  41. static::assertSame(
  42. $expectedElements,
  43. $tokensAnalyzer->getClassyElements()
  44. );
  45. }
  46. public function provideGetClassyElementsCases(): \Generator
  47. {
  48. yield 'trait import' => [
  49. [
  50. 10 => [
  51. 'classIndex' => 4,
  52. 'type' => 'trait_import',
  53. ],
  54. 19 => [
  55. 'classIndex' => 4,
  56. 'type' => 'trait_import',
  57. ],
  58. 24 => [
  59. 'classIndex' => 4,
  60. 'type' => 'const',
  61. ],
  62. 35 => [
  63. 'classIndex' => 4,
  64. 'type' => 'method',
  65. ],
  66. 55 => [
  67. 'classIndex' => 49,
  68. 'type' => 'trait_import',
  69. ],
  70. 64 => [
  71. 'classIndex' => 49,
  72. 'type' => 'method',
  73. ],
  74. ],
  75. '<?php
  76. /** */
  77. class Foo
  78. {
  79. use A\B;
  80. //
  81. use Foo;
  82. const A = 1;
  83. public function foo()
  84. {
  85. $a = new class()
  86. {
  87. use Z; // nested trait import
  88. public function bar()
  89. {
  90. echo 123;
  91. }
  92. };
  93. $a->bar();
  94. }
  95. }',
  96. ];
  97. yield [
  98. [
  99. 9 => [
  100. 'classIndex' => 1,
  101. 'type' => 'property',
  102. ],
  103. 14 => [
  104. 'classIndex' => 1,
  105. 'type' => 'property',
  106. ],
  107. 19 => [
  108. 'classIndex' => 1,
  109. 'type' => 'property',
  110. ],
  111. 28 => [
  112. 'classIndex' => 1,
  113. 'type' => 'property',
  114. ],
  115. 42 => [
  116. 'classIndex' => 1,
  117. 'type' => 'const',
  118. ],
  119. 53 => [
  120. 'classIndex' => 1,
  121. 'type' => 'method',
  122. ],
  123. 83 => [
  124. 'classIndex' => 1,
  125. 'type' => 'method',
  126. ],
  127. 140 => [
  128. 'classIndex' => 1,
  129. 'type' => 'method',
  130. ],
  131. 164 => [
  132. 'classIndex' => 158,
  133. 'type' => 'const',
  134. ],
  135. 173 => [
  136. 'classIndex' => 158,
  137. 'type' => 'trait_import',
  138. ],
  139. ],
  140. <<<'PHP'
  141. <?php
  142. class Foo
  143. {
  144. public $prop0;
  145. protected $prop1;
  146. private $prop2 = 1;
  147. var $prop3 = array(1,2,3);
  148. const CONSTANT = 'constant value';
  149. public function bar4()
  150. {
  151. $a = 5;
  152. return " ({$a})";
  153. }
  154. public function bar5($data)
  155. {
  156. $message = $data;
  157. $example = function ($arg) use ($message) {
  158. echo $arg . ' ' . $message;
  159. };
  160. $example('hello');
  161. }function A(){}
  162. }
  163. function test(){}
  164. class Foo2
  165. {
  166. const CONSTANT = 'constant value';
  167. use Foo\Bar; // expected in the return value
  168. }
  169. PHP
  170. ,
  171. ];
  172. }
  173. /**
  174. * @requires PHP 7.4
  175. */
  176. public function testGetClassyElementsWithNullableProperties(): void
  177. {
  178. $source = <<<'PHP'
  179. <?php
  180. class Foo
  181. {
  182. public int $prop0;
  183. protected ?array $prop1;
  184. private string $prop2 = 1;
  185. var ? Foo\Bar $prop3 = array(1,2,3);
  186. }
  187. PHP;
  188. $tokens = Tokens::fromCode($source);
  189. $tokensAnalyzer = new TokensAnalyzer($tokens);
  190. $elements = $tokensAnalyzer->getClassyElements();
  191. static::assertSame(
  192. [
  193. 11 => [
  194. 'classIndex' => 1,
  195. 'token' => $tokens[11],
  196. 'type' => 'property',
  197. ],
  198. 19 => [
  199. 'classIndex' => 1,
  200. 'token' => $tokens[19],
  201. 'type' => 'property',
  202. ],
  203. 26 => [
  204. 'classIndex' => 1,
  205. 'token' => $tokens[26],
  206. 'type' => 'property',
  207. ],
  208. 41 => [
  209. 'classIndex' => 1,
  210. 'token' => $tokens[41],
  211. 'type' => 'property',
  212. ],
  213. ],
  214. $elements
  215. );
  216. }
  217. public function testGetClassyElementsWithAnonymousClass(): void
  218. {
  219. $source = <<<'PHP'
  220. <?php
  221. class A {
  222. public $A;
  223. private function B()
  224. {
  225. return new class(){
  226. protected $level1;
  227. private function XYZ() {
  228. return new class(){private $level2 = 1;};
  229. }
  230. };
  231. }
  232. private function C() {
  233. }
  234. }
  235. function B() {} // do not count this
  236. PHP;
  237. $tokens = Tokens::fromCode($source);
  238. $tokensAnalyzer = new TokensAnalyzer($tokens);
  239. $elements = $tokensAnalyzer->getClassyElements();
  240. static::assertSame(
  241. [
  242. 9 => [
  243. 'classIndex' => 1,
  244. 'token' => $tokens[9],
  245. 'type' => 'property', // $A
  246. ],
  247. 14 => [
  248. 'classIndex' => 1,
  249. 'token' => $tokens[14],
  250. 'type' => 'method', // B
  251. ],
  252. 33 => [
  253. 'classIndex' => 26,
  254. 'token' => $tokens[33],
  255. 'type' => 'property', // $level1
  256. ],
  257. 38 => [
  258. 'classIndex' => 26,
  259. 'token' => $tokens[38],
  260. 'type' => 'method', // XYZ
  261. ],
  262. 56 => [
  263. 'classIndex' => 50,
  264. 'token' => $tokens[56],
  265. 'type' => 'property', // $level2
  266. ],
  267. 74 => [
  268. 'classIndex' => 1,
  269. 'token' => $tokens[74],
  270. 'type' => 'method', // C
  271. ],
  272. ],
  273. $elements
  274. );
  275. }
  276. public function testGetClassyElementsWithMultipleAnonymousClass(): void
  277. {
  278. $source = <<<'PHP'
  279. <?php class A0
  280. {
  281. public function AA0()
  282. {
  283. return new class
  284. {
  285. public function BB0()
  286. {
  287. }
  288. };
  289. }
  290. public function otherFunction0()
  291. {
  292. }
  293. }
  294. class A1
  295. {
  296. public function AA1()
  297. {
  298. return new class
  299. {
  300. public function BB1()
  301. {
  302. return new class
  303. {
  304. public function CC1()
  305. {
  306. return new class
  307. {
  308. public function DD1()
  309. {
  310. return new class{};
  311. }
  312. public function DD2()
  313. {
  314. return new class{};
  315. }
  316. };
  317. }
  318. };
  319. }
  320. public function BB2()
  321. {
  322. return new class
  323. {
  324. public function CC2()
  325. {
  326. return new class
  327. {
  328. public function DD2()
  329. {
  330. return new class{};
  331. }
  332. };
  333. }
  334. };
  335. }
  336. };
  337. }
  338. public function otherFunction1()
  339. {
  340. }
  341. }
  342. PHP;
  343. $tokens = Tokens::fromCode($source);
  344. $tokensAnalyzer = new TokensAnalyzer($tokens);
  345. $elements = $tokensAnalyzer->getClassyElements();
  346. static::assertSame(
  347. [
  348. 9 => [
  349. 'classIndex' => 1,
  350. 'token' => $tokens[9],
  351. 'type' => 'method',
  352. ],
  353. 27 => [
  354. 'classIndex' => 21,
  355. 'token' => $tokens[27],
  356. 'type' => 'method',
  357. ],
  358. 44 => [
  359. 'classIndex' => 1,
  360. 'token' => $tokens[44],
  361. 'type' => 'method',
  362. ],
  363. 64 => [
  364. 'classIndex' => 56,
  365. 'token' => $tokens[64],
  366. 'type' => 'method',
  367. ],
  368. 82 => [
  369. 'classIndex' => 76,
  370. 'token' => $tokens[82],
  371. 'type' => 'method',
  372. ],
  373. 100 => [
  374. 'classIndex' => 94,
  375. 'token' => $tokens[100],
  376. 'type' => 'method',
  377. ],
  378. 118 => [
  379. 'classIndex' => 112,
  380. 'token' => $tokens[118],
  381. 'type' => 'method',
  382. ],
  383. 139 => [
  384. 'classIndex' => 112,
  385. 'token' => $tokens[139],
  386. 'type' => 'method',
  387. ],
  388. 170 => [
  389. 'classIndex' => 76,
  390. 'token' => $tokens[170],
  391. 'type' => 'method',
  392. ],
  393. 188 => [
  394. 'classIndex' => 182,
  395. 'token' => $tokens[188],
  396. 'type' => 'method',
  397. ],
  398. 206 => [
  399. 'classIndex' => 200,
  400. 'token' => $tokens[206],
  401. 'type' => 'method',
  402. ],
  403. 242 => [
  404. 'classIndex' => 56,
  405. 'token' => $tokens[242],
  406. 'type' => 'method',
  407. ],
  408. ],
  409. $elements
  410. );
  411. }
  412. /**
  413. * @requires PHP 7.4
  414. */
  415. public function testGetClassyElements74(): void
  416. {
  417. $source = <<<'PHP'
  418. <?php
  419. class Foo
  420. {
  421. public int $bar = 3;
  422. protected ?string $baz;
  423. private ?string $bazNull = null;
  424. public static iterable $staticProp;
  425. public float $x, $y;
  426. var bool $flag1;
  427. var ?bool $flag2;
  428. }
  429. PHP;
  430. $tokens = Tokens::fromCode($source);
  431. $tokensAnalyzer = new TokensAnalyzer($tokens);
  432. $elements = $tokensAnalyzer->getClassyElements();
  433. $expected = [];
  434. foreach ([11, 23, 31, 44, 51, 54, 61, 69] as $index) {
  435. $expected[$index] = [
  436. 'classIndex' => 1,
  437. 'token' => $tokens[$index],
  438. 'type' => 'property',
  439. ];
  440. }
  441. static::assertSame($expected, $elements);
  442. }
  443. /**
  444. * @dataProvider provideGetClassyElements81Cases
  445. * @requires PHP 8.1
  446. */
  447. public function testGetClassyElements81(array $expected, string $source): void
  448. {
  449. $tokens = Tokens::fromCode($source);
  450. $tokensAnalyzer = new TokensAnalyzer($tokens);
  451. $elements = $tokensAnalyzer->getClassyElements();
  452. array_walk(
  453. $expected,
  454. static function (array &$element, $index) use ($tokens): void {
  455. $element['token'] = $tokens[$index];
  456. ksort($element);
  457. }
  458. );
  459. static::assertSame($expected, $elements);
  460. }
  461. public function provideGetClassyElements81Cases(): \Generator
  462. {
  463. yield [
  464. [
  465. 11 => [
  466. 'classIndex' => 1,
  467. 'type' => 'property', // $prop1
  468. ],
  469. 20 => [
  470. 'classIndex' => 1,
  471. 'type' => 'property', // $prop2
  472. ],
  473. 29 => [
  474. 'classIndex' => 1,
  475. 'type' => 'property', // $prop13
  476. ],
  477. ],
  478. '<?php
  479. class Foo
  480. {
  481. readonly string $prop1;
  482. readonly public string $prop2;
  483. public readonly string $prop3;
  484. }
  485. ',
  486. ];
  487. yield 'final const' => [
  488. [
  489. 11 => [
  490. 'classIndex' => 1,
  491. 'type' => 'const', // A
  492. ],
  493. 24 => [
  494. 'classIndex' => 1,
  495. 'type' => 'const', // B
  496. ],
  497. ],
  498. '<?php
  499. class Foo
  500. {
  501. final public const A = "1";
  502. public final const B = "2";
  503. }
  504. ',
  505. ];
  506. }
  507. /**
  508. * @dataProvider provideIsAnonymousClassCases
  509. */
  510. public function testIsAnonymousClass(array $expected, string $source): void
  511. {
  512. $tokensAnalyzer = new TokensAnalyzer(Tokens::fromCode($source));
  513. foreach ($expected as $index => $expectedValue) {
  514. static::assertSame($expectedValue, $tokensAnalyzer->isAnonymousClass($index));
  515. }
  516. }
  517. public function provideIsAnonymousClassCases(): \Generator
  518. {
  519. yield [
  520. [1 => false],
  521. '<?php class foo {}',
  522. ];
  523. yield [
  524. [7 => true],
  525. '<?php $foo = new class() {};',
  526. ];
  527. yield [
  528. [7 => true],
  529. '<?php $foo = new class() extends Foo implements Bar, Baz {};',
  530. ];
  531. yield [
  532. [1 => false, 19 => true],
  533. '<?php class Foo { function bar() { return new class() {}; } }',
  534. ];
  535. yield [
  536. [7 => true, 11 => true],
  537. '<?php $a = new class(new class($d->a) implements B{}) extends C{};',
  538. ];
  539. yield [
  540. [1 => false],
  541. '<?php interface foo {}',
  542. ];
  543. if (\PHP_VERSION_ID >= 80000) {
  544. yield [
  545. [11 => true],
  546. '<?php $object = new #[ExampleAttribute] class(){};',
  547. ];
  548. yield [
  549. [27 => true],
  550. '<?php $object = new #[A] #[B] #[C]#[D]/* */ /** */#[E]class(){};',
  551. ];
  552. }
  553. }
  554. /**
  555. * @dataProvider provideIsLambdaCases
  556. */
  557. public function testIsLambda(array $expected, string $source): void
  558. {
  559. $tokensAnalyzer = new TokensAnalyzer(Tokens::fromCode($source));
  560. foreach ($expected as $index => $isLambda) {
  561. static::assertSame($isLambda, $tokensAnalyzer->isLambda($index));
  562. }
  563. }
  564. public function provideIsLambdaCases(): array
  565. {
  566. return [
  567. [
  568. [1 => false],
  569. '<?php function foo () {};',
  570. ],
  571. [
  572. [1 => false],
  573. '<?php function /** foo */ foo () {};',
  574. ],
  575. [
  576. [5 => true],
  577. '<?php $foo = function () {};',
  578. ],
  579. [
  580. [5 => true],
  581. '<?php $foo = function /** foo */ () {};',
  582. ],
  583. [
  584. [7 => true],
  585. '<?php
  586. preg_replace_callback(
  587. "/(^|[a-z])/",
  588. function (array $matches) {
  589. return "a";
  590. },
  591. $string
  592. );',
  593. ],
  594. [
  595. [5 => true],
  596. '<?php $foo = function &() {};',
  597. ],
  598. [
  599. [6 => true],
  600. '<?php
  601. $a = function (): array {
  602. return [];
  603. };',
  604. ],
  605. [
  606. [2 => false],
  607. '<?php
  608. function foo (): array {
  609. return [];
  610. };',
  611. ],
  612. [
  613. [6 => true],
  614. '<?php
  615. $a = function (): void {
  616. return [];
  617. };',
  618. ],
  619. [
  620. [2 => false],
  621. '<?php
  622. function foo (): void {
  623. return [];
  624. };',
  625. ],
  626. [
  627. [6 => true],
  628. '<?php
  629. $a = function (): ?int {
  630. return [];
  631. };',
  632. ],
  633. [
  634. [6 => true],
  635. '<?php
  636. $a = function (): int {
  637. return [];
  638. };',
  639. ],
  640. [
  641. [2 => false],
  642. '<?php
  643. function foo (): ?int {
  644. return [];
  645. };',
  646. ],
  647. ];
  648. }
  649. /**
  650. * @dataProvider provideIsLambda74Cases
  651. * @requires PHP 7.4
  652. */
  653. public function testIsLambda74(array $expected, string $source): void
  654. {
  655. $tokensAnalyzer = new TokensAnalyzer(Tokens::fromCode($source));
  656. foreach ($expected as $index => $expectedValue) {
  657. static::assertSame($expectedValue, $tokensAnalyzer->isLambda($index));
  658. }
  659. }
  660. public function provideIsLambda74Cases(): \Generator
  661. {
  662. yield [
  663. [5 => true],
  664. '<?php $fn = fn() => [];',
  665. ];
  666. yield [
  667. [5 => true],
  668. '<?php $fn = fn () => [];',
  669. ];
  670. }
  671. /**
  672. * @dataProvider provideIsLambda80Cases
  673. * @requires PHP 8.0
  674. */
  675. public function testIsLambda80(array $expected, string $source): void
  676. {
  677. $tokensAnalyzer = new TokensAnalyzer(Tokens::fromCode($source));
  678. foreach ($expected as $index => $expectedValue) {
  679. static::assertSame($expectedValue, $tokensAnalyzer->isLambda($index));
  680. }
  681. }
  682. public function provideIsLambda80Cases(): array
  683. {
  684. return [
  685. [
  686. [6 => true],
  687. '<?php
  688. $a = function (): ?static {
  689. return [];
  690. };',
  691. ],
  692. [
  693. [6 => true],
  694. '<?php
  695. $a = function (): static {
  696. return [];
  697. };',
  698. ],
  699. [
  700. [14 => true],
  701. '<?php
  702. $c = 4; //
  703. $a = function(
  704. $a,
  705. $b,
  706. ) use (
  707. $c,
  708. ) {
  709. echo $a + $b + $c;
  710. };
  711. $a(1,2);',
  712. ],
  713. ];
  714. }
  715. public function testIsLambdaInvalid(): void
  716. {
  717. $this->expectException(\LogicException::class);
  718. $this->expectExceptionMessage('No T_FUNCTION or T_FN at given index 0, got "T_OPEN_TAG".');
  719. $tokensAnalyzer = new TokensAnalyzer(Tokens::fromCode('<?php '));
  720. $tokensAnalyzer->isLambda(0);
  721. }
  722. /**
  723. * @dataProvider provideIsConstantInvocationCases
  724. */
  725. public function testIsConstantInvocation(array $expected, string $source): void
  726. {
  727. $this->doIsConstantInvocationTest($expected, $source);
  728. }
  729. public function provideIsConstantInvocationCases(): array
  730. {
  731. return [
  732. [
  733. [3 => true],
  734. '<?php echo FOO;',
  735. ],
  736. [
  737. [4 => true],
  738. '<?php echo \FOO;',
  739. ],
  740. [
  741. [3 => false, 5 => false, 7 => true],
  742. '<?php echo Foo\Bar\BAR;',
  743. ],
  744. [
  745. [3 => true, 7 => true, 11 => true],
  746. '<?php echo FOO ? BAR : BAZ;',
  747. ],
  748. 'Bitwise & and bitwise |' => [
  749. [3 => true, 7 => true, 11 => true],
  750. '<?php echo FOO & BAR | BAZ;',
  751. ],
  752. 'Bitwise &' => [
  753. [3 => true],
  754. '<?php echo FOO & $bar;',
  755. ],
  756. [
  757. [5 => true],
  758. '<?php echo $foo[BAR];',
  759. ],
  760. [
  761. [3 => true, 5 => true],
  762. '<?php echo FOO[BAR];',
  763. ],
  764. [
  765. [1 => false, 3 => true, 6 => false, 8 => true],
  766. '<?php func(FOO, Bar\BAZ);',
  767. ],
  768. [
  769. [4 => true, 8 => true],
  770. '<?php if (FOO && BAR) {}',
  771. ],
  772. [
  773. [3 => true, 7 => false, 9 => false, 11 => true],
  774. '<?php return FOO * X\Y\BAR;',
  775. ],
  776. [
  777. [3 => false, 11 => true, 16 => true, 20 => true],
  778. '<?php function x() { yield FOO; yield FOO => BAR; }',
  779. ],
  780. [
  781. [11 => true],
  782. '<?php switch ($a) { case FOO: break; }',
  783. ],
  784. [
  785. [3 => false],
  786. '<?php namespace FOO;',
  787. ],
  788. [
  789. [3 => false],
  790. '<?php use FOO;',
  791. ],
  792. [
  793. [5 => false, 7 => false, 9 => false],
  794. '<?php use function FOO\BAR\BAZ;',
  795. ],
  796. [
  797. [3 => false, 8 => false],
  798. '<?php namespace X; const FOO = 1;',
  799. ],
  800. [
  801. [3 => false],
  802. '<?php class FOO {}',
  803. ],
  804. [
  805. [3 => false],
  806. '<?php interface FOO {}',
  807. ],
  808. [
  809. [3 => false],
  810. '<?php trait FOO {}',
  811. ],
  812. [
  813. [3 => false, 7 => false],
  814. '<?php class x extends FOO {}',
  815. ],
  816. [
  817. [3 => false, 7 => false],
  818. '<?php class x implements FOO {}',
  819. ],
  820. [
  821. [3 => false, 7 => false, 10 => false, 13 => false],
  822. '<?php class x implements FOO, BAR, BAZ {}',
  823. ],
  824. [
  825. [3 => false, 9 => false],
  826. '<?php class x { const FOO = 1; }',
  827. ],
  828. [
  829. [3 => false, 9 => false],
  830. '<?php class x { use FOO; }',
  831. ],
  832. [
  833. [3 => false, 9 => false, 12 => false, 16 => false, 18 => false, 22 => false],
  834. '<?php class x { use FOO, BAR { FOO::BAZ insteadof BAR; } }',
  835. ],
  836. [
  837. [3 => false, 6 => false, 11 => false, 17 => false],
  838. '<?php function x (FOO $foo, BAR &$bar, BAZ ...$baz) {}',
  839. ],
  840. [
  841. [1 => false],
  842. '<?php FOO();',
  843. ],
  844. [
  845. [1 => false, 3 => false],
  846. '<?php FOO::x();',
  847. ],
  848. [
  849. [1 => false, 3 => false],
  850. '<?php x::FOO();',
  851. ],
  852. [
  853. [5 => false],
  854. '<?php $foo instanceof FOO;',
  855. ],
  856. [
  857. [9 => false],
  858. '<?php try {} catch (FOO $e) {}',
  859. ],
  860. [
  861. [4 => false],
  862. '<?php "$foo[BAR]";',
  863. ],
  864. [
  865. [5 => true],
  866. '<?php "{$foo[BAR]}";',
  867. ],
  868. [
  869. [1 => false, 6 => false],
  870. '<?php FOO: goto FOO;',
  871. ],
  872. [
  873. [1 => false, 3 => true, 7 => true],
  874. '<?php foo(E_USER_DEPRECATED | E_DEPRECATED);',
  875. ],
  876. [
  877. [3 => false, 7 => false, 10 => false, 13 => false],
  878. '<?php interface Foo extends Bar, Baz, Qux {}',
  879. ],
  880. [
  881. [3 => false, 5 => false, 8 => false, 10 => false, 13 => false, 15 => false],
  882. '<?php use Foo\Bar, Foo\Baz, Foo\Qux;',
  883. ],
  884. [
  885. [3 => false, 8 => false],
  886. '<?php function x(): FOO {}',
  887. ],
  888. [
  889. [3 => false, 5 => false, 8 => false, 11 => false, 15 => false, 18 => false],
  890. '<?php use X\Y\{FOO, BAR as BAR2, BAZ};',
  891. ],
  892. [
  893. [6 => false, 16 => false, 21 => false],
  894. '<?php
  895. abstract class Baz
  896. {
  897. abstract public function test(): Foo;
  898. }
  899. ',
  900. ],
  901. [
  902. [3 => false, 6 => false],
  903. '<?php function x(?FOO $foo) {}',
  904. ],
  905. [
  906. [3 => false, 9 => false],
  907. '<?php function x(): ?FOO {}',
  908. ],
  909. [
  910. [9 => false, 11 => false, 13 => false],
  911. '<?php try {} catch (FOO|BAR|BAZ $e) {}',
  912. ],
  913. [
  914. [3 => false, 11 => false, 16 => false],
  915. '<?php interface Foo { public function bar(): Baz; }',
  916. ],
  917. [
  918. [3 => false, 11 => false, 17 => false],
  919. '<?php interface Foo { public function bar(): \Baz; }',
  920. ],
  921. [
  922. [3 => false, 11 => false, 17 => false],
  923. '<?php interface Foo { public function bar(): ?Baz; }',
  924. ],
  925. [
  926. [3 => false, 11 => false, 18 => false],
  927. '<?php interface Foo { public function bar(): ?\Baz; }',
  928. ],
  929. ];
  930. }
  931. /**
  932. * @dataProvider provideIsConstantInvocationPhp80Cases
  933. * @requires PHP 8.0
  934. */
  935. public function testIsConstantInvocationPhp80(array $expected, string $source): void
  936. {
  937. $this->doIsConstantInvocationTest($expected, $source);
  938. }
  939. public function provideIsConstantInvocationPhp80Cases(): \Generator
  940. {
  941. yield 'abstract method return alternation' => [
  942. [6 => false, 16 => false, 21 => false, 23 => false],
  943. '<?php
  944. abstract class Baz
  945. {
  946. abstract public function test(): Foo|Bar;
  947. }
  948. ',
  949. ];
  950. yield 'function return alternation' => [
  951. [3 => false, 8 => false, 10 => false],
  952. '<?php function test(): Foo|Bar {}',
  953. ];
  954. yield 'nullsafe operator' => [
  955. [3 => false, 5 => false],
  956. '<?php $a?->b?->c;',
  957. ];
  958. yield 'non-capturing catch' => [
  959. [9 => false],
  960. '<?php try {} catch (Exception) {}',
  961. ];
  962. yield 'non-capturing catch 2' => [
  963. [10 => false],
  964. '<?php try {} catch (\Exception) {}',
  965. ];
  966. yield 'non-capturing multiple catch' => [
  967. [9 => false, 13 => false],
  968. '<?php try {} catch (Foo | Bar) {}',
  969. ];
  970. yield 'attribute 1' => [
  971. [2 => false, 5 => false, 10 => false],
  972. '<?php #[Foo, Bar] function foo() {}',
  973. ];
  974. yield 'attribute 2' => [
  975. [2 => false, 7 => false, 14 => false],
  976. '<?php #[Foo(), Bar()] function foo() {}',
  977. ];
  978. yield [
  979. [2 => false, 9 => false],
  980. '<?php #[Foo()] function foo() {}',
  981. ];
  982. yield [
  983. [3 => false, 10 => false],
  984. '<?php #[\Foo()] function foo() {}',
  985. ];
  986. yield [
  987. [2 => false, 4 => false, 11 => false],
  988. '<?php #[A\Foo()] function foo() {}',
  989. ];
  990. yield [
  991. [3 => false, 5 => false, 12 => false],
  992. '<?php #[\A\Foo()] function foo() {}',
  993. ];
  994. yield 'multiple type catch with variable' => [
  995. [5 => false, 15 => false, 18 => false],
  996. '<?php try { foo(); } catch(\InvalidArgumentException|\LogicException $e) {}',
  997. ];
  998. yield 'multiple type catch without variable 1' => [
  999. [5 => false, 15 => false, 18 => false],
  1000. '<?php try { foo(); } catch(\InvalidArgumentException|\LogicException) {}',
  1001. ];
  1002. yield 'multiple type catch without variable 2' => [
  1003. [5 => false, 15 => false, 17 => false, 19 => false, 21 => false, 24 => false, 27 => false],
  1004. '<?php try { foo(); } catch(\D|Z|A\B|\InvalidArgumentException|\LogicException) {}',
  1005. ];
  1006. yield 'multiple type catch without variable 3' => [
  1007. [5 => false, 14 => false, 16 => false, 19 => false, 22 => false],
  1008. '<?php try { foo(); } catch(A\B|\InvalidArgumentException|\LogicException) {}',
  1009. ];
  1010. }
  1011. /**
  1012. * @dataProvider provideIsConstantInvocationPhp81Cases
  1013. * @requires PHP 8.1
  1014. */
  1015. public function testIsConstantInvocationPhp81(array $expected, string $source): void
  1016. {
  1017. $this->doIsConstantInvocationTest($expected, $source);
  1018. }
  1019. public function provideIsConstantInvocationPhp81Cases(): \Generator
  1020. {
  1021. yield [
  1022. [5 => false, 15 => false],
  1023. '<?php
  1024. abstract class Baz
  1025. {
  1026. final public const Y = "i";
  1027. }
  1028. ',
  1029. ];
  1030. yield [
  1031. [4 => false, 12 => false, 23 => false],
  1032. '<?php
  1033. class Test {
  1034. public function __construct(
  1035. public $prop = new Foo,
  1036. ) {}
  1037. }
  1038. ',
  1039. ];
  1040. yield 'intersection' => [
  1041. [3 => false, 9 => false, 11 => false],
  1042. '<?php function foo(): \Foo&Bar {}',
  1043. ];
  1044. yield 'abstract method return intersection' => [
  1045. [6 => false, 16 => false, 21 => false, 23 => false, 25 => false, 27 => false, 29 => false],
  1046. '<?php
  1047. abstract class Baz
  1048. {
  1049. abstract public function foo(): Foo&Bar1&Bar2&Bar3&Bar4;
  1050. }
  1051. ',
  1052. ];
  1053. }
  1054. public function testIsConstantInvocationInvalid(): void
  1055. {
  1056. $this->expectException(\LogicException::class);
  1057. $this->expectExceptionMessage('No T_STRING at given index 0, got "T_OPEN_TAG".');
  1058. $tokensAnalyzer = new TokensAnalyzer(Tokens::fromCode('<?php '));
  1059. $tokensAnalyzer->isConstantInvocation(0);
  1060. }
  1061. /**
  1062. * @requires PHP 8.0
  1063. */
  1064. public function testIsConstantInvocationForNullSafeObjectOperator(): void
  1065. {
  1066. $tokens = Tokens::fromCode('<?php $a?->b?->c;');
  1067. $tokensAnalyzer = new TokensAnalyzer($tokens);
  1068. foreach ($tokens as $index => $token) {
  1069. if (!$token->isGivenKind(T_STRING)) {
  1070. continue;
  1071. }
  1072. static::assertFalse($tokensAnalyzer->isConstantInvocation($index));
  1073. }
  1074. }
  1075. /**
  1076. * @dataProvider provideIsUnarySuccessorOperatorCases
  1077. */
  1078. public function testIsUnarySuccessorOperator(array $expected, string $source): void
  1079. {
  1080. $tokensAnalyzer = new TokensAnalyzer(Tokens::fromCode($source));
  1081. foreach ($expected as $index => $isUnary) {
  1082. static::assertSame($isUnary, $tokensAnalyzer->isUnarySuccessorOperator($index));
  1083. if ($isUnary) {
  1084. static::assertFalse($tokensAnalyzer->isUnaryPredecessorOperator($index));
  1085. static::assertFalse($tokensAnalyzer->isBinaryOperator($index));
  1086. }
  1087. }
  1088. }
  1089. public function provideIsUnarySuccessorOperatorCases(): array
  1090. {
  1091. return [
  1092. [
  1093. [2 => true],
  1094. '<?php $a++;',
  1095. ],
  1096. [
  1097. [2 => true],
  1098. '<?php $a--;',
  1099. ],
  1100. [
  1101. [3 => true],
  1102. '<?php $a ++;',
  1103. ],
  1104. [
  1105. [2 => true, 4 => false],
  1106. '<?php $a++ + 1;',
  1107. ],
  1108. [
  1109. [5 => true],
  1110. '<?php ${"a"}++;',
  1111. ],
  1112. [
  1113. [4 => true],
  1114. '<?php $foo->bar++;',
  1115. ],
  1116. [
  1117. [6 => true],
  1118. '<?php $foo->{"bar"}++;',
  1119. ],
  1120. 'array access' => [
  1121. [5 => true],
  1122. '<?php $a["foo"]++;',
  1123. ],
  1124. 'array curly access' => [
  1125. [5 => true],
  1126. '<?php $a{"foo"}++;',
  1127. ],
  1128. ];
  1129. }
  1130. /**
  1131. * @dataProvider provideIsUnaryPredecessorOperatorCases
  1132. */
  1133. public function testIsUnaryPredecessorOperator(array $expected, string $source): void
  1134. {
  1135. $tokensAnalyzer = new TokensAnalyzer(Tokens::fromCode($source));
  1136. foreach ($expected as $index => $isUnary) {
  1137. static::assertSame($isUnary, $tokensAnalyzer->isUnaryPredecessorOperator($index));
  1138. if ($isUnary) {
  1139. static::assertFalse($tokensAnalyzer->isUnarySuccessorOperator($index));
  1140. static::assertFalse($tokensAnalyzer->isBinaryOperator($index));
  1141. }
  1142. }
  1143. }
  1144. public function provideIsUnaryPredecessorOperatorCases(): array
  1145. {
  1146. return [
  1147. [
  1148. [1 => true],
  1149. '<?php ++$a;',
  1150. ],
  1151. [
  1152. [1 => true],
  1153. '<?php --$a;',
  1154. ],
  1155. [
  1156. [1 => true],
  1157. '<?php -- $a;',
  1158. ],
  1159. [
  1160. [3 => false, 5 => true],
  1161. '<?php $a + ++$b;',
  1162. ],
  1163. [
  1164. [1 => true, 2 => true],
  1165. '<?php !!$a;',
  1166. ],
  1167. [
  1168. [5 => true],
  1169. '<?php $a = &$b;',
  1170. ],
  1171. [
  1172. [3 => true],
  1173. '<?php function &foo() {}',
  1174. ],
  1175. [
  1176. [1 => true],
  1177. '<?php @foo();',
  1178. ],
  1179. [
  1180. [3 => true, 8 => true],
  1181. '<?php foo(+ $a, -$b);',
  1182. ],
  1183. [
  1184. [5 => true, 11 => true, 17 => true],
  1185. '<?php function foo(&$a, array &$b, Bar &$c) {}',
  1186. ],
  1187. [
  1188. [8 => true],
  1189. '<?php function foo($a, ...$b) {}',
  1190. ],
  1191. [
  1192. [5 => true, 6 => true],
  1193. '<?php function foo(&...$b) {}',
  1194. ],
  1195. [
  1196. [7 => true],
  1197. '<?php function foo(array ...$b) {}',
  1198. ],
  1199. [
  1200. [7 => true],
  1201. '<?php $foo = function(...$a) {};',
  1202. ],
  1203. [
  1204. [10 => true],
  1205. '<?php $foo = function($a, ...$b) {};',
  1206. ],
  1207. ];
  1208. }
  1209. /**
  1210. * @dataProvider provideIsBinaryOperatorCases
  1211. */
  1212. public function testIsBinaryOperator(array $expected, string $source): void
  1213. {
  1214. $tokensAnalyzer = new TokensAnalyzer(Tokens::fromCode($source));
  1215. foreach ($expected as $index => $isBinary) {
  1216. static::assertSame($isBinary, $tokensAnalyzer->isBinaryOperator($index));
  1217. if ($isBinary) {
  1218. static::assertFalse($tokensAnalyzer->isUnarySuccessorOperator($index));
  1219. static::assertFalse($tokensAnalyzer->isUnaryPredecessorOperator($index));
  1220. }
  1221. }
  1222. }
  1223. public function provideIsBinaryOperatorCases(): \Generator
  1224. {
  1225. yield from [
  1226. [
  1227. [8 => true],
  1228. '<?php echo $a[1] + 1;',
  1229. ],
  1230. [
  1231. [8 => true],
  1232. '<?php echo $a{1} + 1;',
  1233. ],
  1234. [
  1235. [3 => true],
  1236. '<?php $a .= $b; ?>',
  1237. ],
  1238. [
  1239. [3 => true],
  1240. '<?php $a . \'a\' ?>',
  1241. ],
  1242. [
  1243. [3 => true],
  1244. '<?php $a &+ $b;',
  1245. ],
  1246. [
  1247. [3 => true],
  1248. '<?php $a && $b;',
  1249. ],
  1250. [
  1251. [3 => true],
  1252. '<?php $a & $b;',
  1253. ],
  1254. [
  1255. [4 => true],
  1256. '<?php [] + [];',
  1257. ],
  1258. [
  1259. [3 => true],
  1260. '<?php $a + $b;',
  1261. ],
  1262. [
  1263. [3 => true],
  1264. '<?php 1 + $b;',
  1265. ],
  1266. [
  1267. [3 => true],
  1268. '<?php 0.2 + $b;',
  1269. ],
  1270. [
  1271. [6 => true],
  1272. '<?php $a[1] + $b;',
  1273. ],
  1274. [
  1275. [3 => true],
  1276. '<?php FOO + $b;',
  1277. ],
  1278. [
  1279. [5 => true],
  1280. '<?php foo() + $b;',
  1281. ],
  1282. [
  1283. [6 => true],
  1284. '<?php ${"foo"} + $b;',
  1285. ],
  1286. [
  1287. [2 => true],
  1288. '<?php $a+$b;',
  1289. ],
  1290. [
  1291. [5 => true],
  1292. '<?php $a /* foo */ + /* bar */ $b;',
  1293. ],
  1294. [
  1295. [3 => true],
  1296. '<?php $a =
  1297. $b;',
  1298. ],
  1299. [
  1300. [3 => true],
  1301. '<?php $a
  1302. = $b;',
  1303. ],
  1304. [
  1305. [3 => true, 9 => true, 12 => false],
  1306. '<?php $a = array("b" => "c", );',
  1307. ],
  1308. [
  1309. [3 => true, 5 => false],
  1310. '<?php $a * -$b;',
  1311. ],
  1312. [
  1313. [3 => true, 5 => false, 8 => true, 10 => false],
  1314. '<?php $a = -2 / +5;',
  1315. ],
  1316. [
  1317. [3 => true, 5 => false],
  1318. '<?php $a = &$b;',
  1319. ],
  1320. [
  1321. [2 => false, 4 => true],
  1322. '<?php $a++ + $b;',
  1323. ],
  1324. [
  1325. [7 => true],
  1326. '<?php $a = FOO & $bar;',
  1327. ],
  1328. [
  1329. [3 => true],
  1330. '<?php __LINE__ - 1;',
  1331. ],
  1332. [
  1333. [5 => true],
  1334. '<?php `echo 1` + 1;',
  1335. ],
  1336. [
  1337. [3 => true],
  1338. '<?php $a ** $b;',
  1339. ],
  1340. [
  1341. [3 => true],
  1342. '<?php $a **= $b;',
  1343. ],
  1344. [
  1345. [9 => false],
  1346. '<?php $a = "{$value}-{$theSwitch}";',
  1347. ],
  1348. ];
  1349. $operators = [
  1350. '+', '-', '*', '/', '%', '<', '>', '|', '^', '&=', '&&', '||', '.=', '/=', '==', '>=', '===', '!=',
  1351. '<>', '!==', '<=', 'and', 'or', 'xor', '-=', '%=', '*=', '|=', '+=', '<<', '<<=', '>>', '>>=', '^',
  1352. ];
  1353. foreach ($operators as $operator) {
  1354. yield [
  1355. [3 => true],
  1356. '<?php $a '.$operator.' $b;',
  1357. ];
  1358. }
  1359. yield [
  1360. [3 => true],
  1361. '<?php $a <=> $b;',
  1362. ];
  1363. yield [
  1364. [3 => true],
  1365. '<?php $a ?? $b;',
  1366. ];
  1367. }
  1368. /**
  1369. * @dataProvider provideIsArrayCases
  1370. */
  1371. public function testIsArray(string $source, int $tokenIndex, bool $isMultiLineArray = false): void
  1372. {
  1373. $tokens = Tokens::fromCode($source);
  1374. $tokensAnalyzer = new TokensAnalyzer($tokens);
  1375. static::assertTrue($tokensAnalyzer->isArray($tokenIndex), 'Expected to be an array.');
  1376. static::assertSame($isMultiLineArray, $tokensAnalyzer->isArrayMultiLine($tokenIndex), sprintf('Expected %sto be a multiline array', $isMultiLineArray ? '' : 'not '));
  1377. }
  1378. public function provideIsArrayCases(): array
  1379. {
  1380. return [
  1381. [
  1382. '<?php
  1383. array("a" => 1);
  1384. ',
  1385. 2,
  1386. ],
  1387. [
  1388. '<?php
  1389. ["a" => 2];
  1390. ',
  1391. 2, false,
  1392. ],
  1393. [
  1394. '<?php
  1395. array(
  1396. "a" => 3
  1397. );
  1398. ',
  1399. 2, true,
  1400. ],
  1401. [
  1402. '<?php
  1403. [
  1404. "a" => 4
  1405. ];
  1406. ',
  1407. 2, true,
  1408. ],
  1409. [
  1410. '<?php
  1411. array(
  1412. "a" => array(5, 6, 7),
  1413. 8 => new \Exception(\'Hello\')
  1414. );
  1415. ',
  1416. 2, true,
  1417. ],
  1418. [
  1419. // mix short array syntax
  1420. '<?php
  1421. array(
  1422. "a" => [9, 10, 11],
  1423. 12 => new \Exception(\'Hello\')
  1424. );
  1425. ',
  1426. 2, true,
  1427. ],
  1428. // Windows/Max EOL testing
  1429. [
  1430. "<?php\r\narray('a' => 13);\r\n",
  1431. 1,
  1432. ],
  1433. [
  1434. "<?php\r\n array(\r\n 'a' => 14,\r\n 'b' => 15\r\n );\r\n",
  1435. 2, true,
  1436. ],
  1437. ];
  1438. }
  1439. /**
  1440. * @param int[] $tokenIndexes
  1441. *
  1442. * @dataProvider provideIsArray71Cases
  1443. * @requires PHP 7.1
  1444. */
  1445. public function testIsArray71(string $source, array $tokenIndexes): void
  1446. {
  1447. $tokens = Tokens::fromCode($source);
  1448. $tokensAnalyzer = new TokensAnalyzer($tokens);
  1449. foreach ($tokens as $index => $token) {
  1450. $expect = \in_array($index, $tokenIndexes, true);
  1451. static::assertSame(
  1452. $expect,
  1453. $tokensAnalyzer->isArray($index),
  1454. sprintf('Expected %sarray, got @ %d "%s".', $expect ? '' : 'no ', $index, var_export($token, true))
  1455. );
  1456. }
  1457. }
  1458. public function provideIsArray71Cases(): array
  1459. {
  1460. return [
  1461. [
  1462. '<?php
  1463. [$a] = $z;
  1464. ["a" => $a, "b" => $b] = $array;
  1465. $c = [$d, $e] = $array[$a];
  1466. [[$a, $b], [$c, $d]] = $d;
  1467. $array = []; $d = array();
  1468. ',
  1469. [76, 84],
  1470. ],
  1471. ];
  1472. }
  1473. /**
  1474. * @dataProvider provideIsBinaryOperator71Cases
  1475. * @requires PHP 7.1
  1476. */
  1477. public function testIsBinaryOperator71(array $expected, string $source): void
  1478. {
  1479. $tokensAnalyzer = new TokensAnalyzer(Tokens::fromCode($source));
  1480. foreach ($expected as $index => $isBinary) {
  1481. static::assertSame($isBinary, $tokensAnalyzer->isBinaryOperator($index));
  1482. if ($isBinary) {
  1483. static::assertFalse($tokensAnalyzer->isUnarySuccessorOperator($index));
  1484. static::assertFalse($tokensAnalyzer->isUnaryPredecessorOperator($index));
  1485. }
  1486. }
  1487. }
  1488. public function provideIsBinaryOperator71Cases(): \Generator
  1489. {
  1490. yield [
  1491. [11 => false],
  1492. '<?php try {} catch (A | B $e) {}',
  1493. ];
  1494. }
  1495. /**
  1496. * @dataProvider provideIsBinaryOperator74Cases
  1497. * @requires PHP 7.4
  1498. */
  1499. public function testIsBinaryOperator74(array $expected, string $source): void
  1500. {
  1501. $tokensAnalyzer = new TokensAnalyzer(Tokens::fromCode($source));
  1502. foreach ($expected as $index => $isBinary) {
  1503. static::assertSame($isBinary, $tokensAnalyzer->isBinaryOperator($index));
  1504. if ($isBinary) {
  1505. static::assertFalse($tokensAnalyzer->isUnarySuccessorOperator($index));
  1506. static::assertFalse($tokensAnalyzer->isUnaryPredecessorOperator($index));
  1507. }
  1508. }
  1509. }
  1510. public function provideIsBinaryOperator74Cases(): \Generator
  1511. {
  1512. yield [
  1513. [3 => true],
  1514. '<?php $a ??= $b;',
  1515. ];
  1516. }
  1517. /**
  1518. * @dataProvider provideIsBinaryOperator80Cases
  1519. * @requires PHP 8.0
  1520. */
  1521. public function testIsBinaryOperator80(array $expected, string $source): void
  1522. {
  1523. $tokensAnalyzer = new TokensAnalyzer(Tokens::fromCode($source));
  1524. foreach ($expected as $index => $isBinary) {
  1525. static::assertSame($isBinary, $tokensAnalyzer->isBinaryOperator($index));
  1526. if ($isBinary) {
  1527. static::assertFalse($tokensAnalyzer->isUnarySuccessorOperator($index));
  1528. static::assertFalse($tokensAnalyzer->isUnaryPredecessorOperator($index));
  1529. }
  1530. }
  1531. }
  1532. public static function provideIsBinaryOperator80Cases(): iterable
  1533. {
  1534. yield [
  1535. [6 => false],
  1536. '<?php function foo(array|string $x) {}',
  1537. ];
  1538. yield [
  1539. [6 => false],
  1540. '<?php function foo(string|array $x) {}',
  1541. ];
  1542. yield [
  1543. [6 => false],
  1544. '<?php function foo(int|callable $x) {}',
  1545. ];
  1546. yield [
  1547. [6 => false],
  1548. '<?php function foo(callable|int $x) {}',
  1549. ];
  1550. }
  1551. /**
  1552. * @dataProvider provideArrayExceptionsCases
  1553. */
  1554. public function testIsNotArray(string $source, int $tokenIndex): void
  1555. {
  1556. $tokens = Tokens::fromCode($source);
  1557. $tokensAnalyzer = new TokensAnalyzer($tokens);
  1558. static::assertFalse($tokensAnalyzer->isArray($tokenIndex));
  1559. }
  1560. /**
  1561. * @dataProvider provideArrayExceptionsCases
  1562. */
  1563. public function testIsMultiLineArrayException(string $source, int $tokenIndex): void
  1564. {
  1565. $this->expectException(\InvalidArgumentException::class);
  1566. $tokens = Tokens::fromCode($source);
  1567. $tokensAnalyzer = new TokensAnalyzer($tokens);
  1568. $tokensAnalyzer->isArrayMultiLine($tokenIndex);
  1569. }
  1570. public function provideArrayExceptionsCases(): array
  1571. {
  1572. return [
  1573. ['<?php $a;', 1],
  1574. ["<?php\n \$a = (0+1); // [0,1]", 4],
  1575. ['<?php $text = "foo $bbb[0] bar";', 8],
  1576. ['<?php $text = "foo ${aaa[123]} bar";', 9],
  1577. ];
  1578. }
  1579. public function testIsBlockMultilineException(): void
  1580. {
  1581. $this->expectException(\LogicException::class);
  1582. $tokens = Tokens::fromCode('<?php foo(1, 2, 3);');
  1583. $tokensAnalyzer = new TokensAnalyzer($tokens);
  1584. $tokensAnalyzer->isBlockMultiline($tokens, 1);
  1585. }
  1586. /**
  1587. * @dataProvider provideIsBlockMultilineCases
  1588. */
  1589. public function testIsBlockMultiline(bool $isBlockMultiline, string $source, int $tokenIndex): void
  1590. {
  1591. $tokens = Tokens::fromCode($source);
  1592. $tokensAnalyzer = new TokensAnalyzer($tokens);
  1593. static::assertSame($isBlockMultiline, $tokensAnalyzer->isBlockMultiline($tokens, $tokenIndex));
  1594. }
  1595. public static function provideIsBlockMultilineCases(): \Generator
  1596. {
  1597. yield [
  1598. false,
  1599. '<?php foo(1, 2, 3);',
  1600. 2,
  1601. ];
  1602. yield [
  1603. true,
  1604. '<?php foo(1,
  1605. 2,
  1606. 3
  1607. );',
  1608. 2,
  1609. ];
  1610. yield [
  1611. false,
  1612. '<?php foo(1, "Multi
  1613. string", 2, 3);',
  1614. 2,
  1615. ];
  1616. yield [
  1617. false,
  1618. '<?php foo(1, havingNestedBlockThatIsMultilineDoesNotMakeTheMainBlockMultiline(
  1619. "a",
  1620. "b"
  1621. ), 2, 3);',
  1622. 2,
  1623. ];
  1624. }
  1625. /**
  1626. * @dataProvider provideGetFunctionPropertiesCases
  1627. */
  1628. public function testGetFunctionProperties(string $source, int $index, array $expected): void
  1629. {
  1630. $tokens = Tokens::fromCode($source);
  1631. $tokensAnalyzer = new TokensAnalyzer($tokens);
  1632. $attributes = $tokensAnalyzer->getMethodAttributes($index);
  1633. static::assertSame($expected, $attributes);
  1634. }
  1635. public function provideGetFunctionPropertiesCases(): array
  1636. {
  1637. $defaultAttributes = [
  1638. 'visibility' => null,
  1639. 'static' => false,
  1640. 'abstract' => false,
  1641. 'final' => false,
  1642. ];
  1643. $template = '
  1644. <?php
  1645. class TestClass {
  1646. %s function a() {
  1647. //
  1648. }
  1649. }
  1650. ';
  1651. $cases = [];
  1652. $attributes = $defaultAttributes;
  1653. $attributes['visibility'] = T_PRIVATE;
  1654. $cases[] = [sprintf($template, 'private'), 10, $attributes];
  1655. $attributes = $defaultAttributes;
  1656. $attributes['visibility'] = T_PUBLIC;
  1657. $cases[] = [sprintf($template, 'public'), 10, $attributes];
  1658. $attributes = $defaultAttributes;
  1659. $attributes['visibility'] = T_PROTECTED;
  1660. $cases[] = [sprintf($template, 'protected'), 10, $attributes];
  1661. $attributes = $defaultAttributes;
  1662. $attributes['visibility'] = null;
  1663. $attributes['static'] = true;
  1664. $cases[] = [sprintf($template, 'static'), 10, $attributes];
  1665. $attributes = $defaultAttributes;
  1666. $attributes['visibility'] = T_PUBLIC;
  1667. $attributes['static'] = true;
  1668. $attributes['final'] = true;
  1669. $cases[] = [sprintf($template, 'final public static'), 14, $attributes];
  1670. $attributes = $defaultAttributes;
  1671. $attributes['visibility'] = null;
  1672. $attributes['abstract'] = true;
  1673. $cases[] = [sprintf($template, 'abstract'), 10, $attributes];
  1674. $attributes = $defaultAttributes;
  1675. $attributes['visibility'] = T_PUBLIC;
  1676. $attributes['abstract'] = true;
  1677. $cases[] = [sprintf($template, 'abstract public'), 12, $attributes];
  1678. $attributes = $defaultAttributes;
  1679. $cases[] = [sprintf($template, ''), 8, $attributes];
  1680. return $cases;
  1681. }
  1682. public function testIsWhilePartOfDoWhile(): void
  1683. {
  1684. $source =
  1685. <<<'SRC'
  1686. <?php
  1687. // `not do`
  1688. while(false) {
  1689. }
  1690. while (false);
  1691. while (false)?>
  1692. <?php
  1693. if(false){
  1694. }while(false);
  1695. if(false){
  1696. }while(false)?><?php
  1697. while(false){}while(false){}
  1698. while ($i <= 10):
  1699. echo $i;
  1700. $i++;
  1701. endwhile;
  1702. ?>
  1703. <?php while(false): ?>
  1704. <?php endwhile ?>
  1705. <?php
  1706. // `do`
  1707. do{
  1708. } while(false);
  1709. do{
  1710. } while(false)?>
  1711. <?php
  1712. if (false){}do{}while(false);
  1713. // `not do`, `do`
  1714. if(false){}while(false){}do{}while(false);
  1715. SRC;
  1716. $expected = [
  1717. 3 => false,
  1718. 12 => false,
  1719. 19 => false,
  1720. 34 => false,
  1721. 47 => false,
  1722. 53 => false,
  1723. 59 => false,
  1724. 66 => false,
  1725. 91 => false,
  1726. 112 => true,
  1727. 123 => true,
  1728. 139 => true,
  1729. 153 => false,
  1730. 162 => true,
  1731. ];
  1732. $tokens = Tokens::fromCode($source);
  1733. $tokensAnalyzer = new TokensAnalyzer($tokens);
  1734. foreach ($tokens as $index => $token) {
  1735. if (!$token->isGivenKind(T_WHILE)) {
  1736. continue;
  1737. }
  1738. static::assertSame(
  1739. $expected[$index],
  1740. $tokensAnalyzer->isWhilePartOfDoWhile($index),
  1741. sprintf('Expected token at index "%d" to be detected as %sa "do-while"-loop.', $index, true === $expected[$index] ? '' : 'not ')
  1742. );
  1743. }
  1744. }
  1745. /**
  1746. * @dataProvider provideGetImportUseIndexesCases
  1747. */
  1748. public function testGetImportUseIndexes(array $expected, string $input, bool $perNamespace = false): void
  1749. {
  1750. $tokens = Tokens::fromCode($input);
  1751. $tokensAnalyzer = new TokensAnalyzer($tokens);
  1752. static::assertSame($expected, $tokensAnalyzer->getImportUseIndexes($perNamespace));
  1753. }
  1754. public function provideGetImportUseIndexesCases(): array
  1755. {
  1756. return [
  1757. [
  1758. [1, 8],
  1759. '<?php use E\F?><?php use A\B;',
  1760. ],
  1761. [
  1762. [[1], [14], [29]],
  1763. '<?php
  1764. use T\A;
  1765. namespace A { use D\C; }
  1766. namespace b { use D\C; }
  1767. ',
  1768. true,
  1769. ],
  1770. [
  1771. [[1, 8]],
  1772. '<?php use D\B; use A\C?>',
  1773. true,
  1774. ],
  1775. [
  1776. [1, 8],
  1777. '<?php use D\B; use A\C?>',
  1778. ],
  1779. [
  1780. [7, 22],
  1781. '<?php
  1782. namespace A { use D\C; }
  1783. namespace b { use D\C; }
  1784. ',
  1785. ],
  1786. [
  1787. [3, 10, 34, 45, 54, 59, 77, 95],
  1788. <<<'EOF'
  1789. use Zoo\Bar;
  1790. use Foo\Bar;
  1791. use Foo\Zar\Baz;
  1792. <?php
  1793. use Foo\Bar;
  1794. use Foo\Bar\Foo as Fooo, Foo\Bar\FooBar as FooBaz;
  1795. use Foo\Bir as FBB;
  1796. use Foo\Zar\Baz;
  1797. use SomeClass;
  1798. use Symfony\Annotation\Template, Symfony\Doctrine\Entities\Entity;
  1799. use Zoo\Bar;
  1800. $a = new someclass();
  1801. use Zoo\Tar;
  1802. class AnnotatedClass
  1803. {
  1804. }
  1805. EOF
  1806. ,
  1807. ],
  1808. [
  1809. [1, 22, 41],
  1810. '<?php
  1811. use some\a\{ClassA, ClassB, ClassC as C};
  1812. use function some\a\{fn_a, fn_b, fn_c};
  1813. use const some\a\{ConstA, ConstB, ConstC};
  1814. ',
  1815. ],
  1816. [
  1817. [[1, 22, 41]],
  1818. '<?php
  1819. use some\a\{ClassA, ClassB, ClassC as C};
  1820. use function some\a\{fn_a, fn_b, fn_c};
  1821. use const some\a\{ConstA, ConstB, ConstC};
  1822. ',
  1823. true,
  1824. ],
  1825. [
  1826. [1, 23, 43],
  1827. '<?php
  1828. use some\a\{ClassA, ClassB, ClassC as C,};
  1829. use function some\a\{fn_a, fn_b, fn_c,};
  1830. use const some\a\{ConstA, ConstB, ConstC,};
  1831. ',
  1832. ],
  1833. [
  1834. [[1, 23, 43]],
  1835. '<?php
  1836. use some\a\{ClassA, ClassB, ClassC as C,};
  1837. use function some\a\{fn_a, fn_b, fn_c,};
  1838. use const some\a\{ConstA, ConstB, ConstC,};
  1839. ',
  1840. true,
  1841. ],
  1842. ];
  1843. }
  1844. public function testGetClassyElementsWithMultipleNestedAnonymousClass(): void
  1845. {
  1846. $source = '<?php
  1847. class MyTestWithAnonymousClass extends TestCase
  1848. {
  1849. public function setUp()
  1850. {
  1851. $provider = new class(function () {}) {};
  1852. }
  1853. public function testSomethingWithMoney(
  1854. Money $amount
  1855. ) {
  1856. $a = new class(function () {
  1857. new class(function () {
  1858. new class(function () {})
  1859. {
  1860. const A=1;
  1861. };
  1862. })
  1863. {
  1864. const B=1;
  1865. public function foo() {
  1866. $c = new class() {const AA=3;};
  1867. $d = new class {const AB=3;};
  1868. }
  1869. };
  1870. })
  1871. {
  1872. const C=1;
  1873. };
  1874. }
  1875. }';
  1876. $tokens = Tokens::fromCode($source);
  1877. $tokensAnalyzer = new TokensAnalyzer($tokens);
  1878. $elements = $tokensAnalyzer->getClassyElements();
  1879. static::assertSame([
  1880. 13 => [
  1881. 'classIndex' => 1,
  1882. 'token' => $tokens[13],
  1883. 'type' => 'method', // setUp
  1884. ],
  1885. 46 => [
  1886. 'classIndex' => 1,
  1887. 'token' => $tokens[46],
  1888. 'type' => 'method', // testSomethingWithMoney
  1889. ],
  1890. 100 => [
  1891. 'classIndex' => 87,
  1892. 'token' => $tokens[100], // const A
  1893. 'type' => 'const',
  1894. ],
  1895. 115 => [
  1896. 'classIndex' => 65,
  1897. 'token' => $tokens[115], // const B
  1898. 'type' => 'const',
  1899. ],
  1900. 124 => [
  1901. 'classIndex' => 65, // $a
  1902. 'token' => $tokens[124],
  1903. 'type' => 'method', // foo
  1904. ],
  1905. 143 => [
  1906. 'classIndex' => 138,
  1907. 'token' => $tokens[143], // const AA
  1908. 'type' => 'const',
  1909. ],
  1910. 161 => [
  1911. 'classIndex' => 158,
  1912. 'token' => $tokens[161], // const AB
  1913. 'type' => 'const',
  1914. ],
  1915. ], $elements);
  1916. }
  1917. /**
  1918. * @dataProvider provideIsSuperGlobalCases
  1919. */
  1920. public function testIsSuperGlobal(bool $expected, string $source, int $index): void
  1921. {
  1922. $tokens = Tokens::fromCode($source);
  1923. $tokensAnalyzer = new TokensAnalyzer($tokens);
  1924. static::assertSame($expected, $tokensAnalyzer->isSuperGlobal($index));
  1925. }
  1926. public function provideIsSuperGlobalCases(): array
  1927. {
  1928. $superNames = [
  1929. '$_COOKIE',
  1930. '$_ENV',
  1931. '$_FILES',
  1932. '$_GET',
  1933. '$_POST',
  1934. '$_REQUEST',
  1935. '$_SERVER',
  1936. '$_SESSION',
  1937. '$GLOBALS',
  1938. ];
  1939. $cases = [];
  1940. foreach ($superNames as $superName) {
  1941. $cases[] = [
  1942. true,
  1943. sprintf('<?php echo %s[0];', $superName),
  1944. 3,
  1945. ];
  1946. }
  1947. $notGlobalCodeCases = [
  1948. '<?php echo 1; $a = static function($b) use ($a) { $a->$b(); }; // $_SERVER',
  1949. '<?php class Foo{}?> <?php $_A = 1; /* $_SESSION */',
  1950. ];
  1951. foreach ($notGlobalCodeCases as $notGlobalCodeCase) {
  1952. $tokensCount = \count(Tokens::fromCode($notGlobalCodeCase));
  1953. for ($i = 0; $i < $tokensCount; ++$i) {
  1954. $cases[] = [
  1955. false,
  1956. $notGlobalCodeCase,
  1957. $i,
  1958. ];
  1959. }
  1960. }
  1961. return $cases;
  1962. }
  1963. private function doIsConstantInvocationTest(array $expected, string $source): void
  1964. {
  1965. $tokens = Tokens::fromCode($source);
  1966. static::assertCount(
  1967. $tokens->countTokenKind(T_STRING),
  1968. $expected,
  1969. 'All T_STRING tokens must be tested'
  1970. );
  1971. $tokensAnalyzer = new TokensAnalyzer($tokens);
  1972. foreach ($expected as $index => $expectedValue) {
  1973. static::assertSame(
  1974. $expectedValue,
  1975. $tokensAnalyzer->isConstantInvocation($index),
  1976. sprintf('Token at index '.$index.' should match the expected value (%s).', $expectedValue ? 'true' : 'false')
  1977. );
  1978. }
  1979. }
  1980. }