TokensTest.php 30 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090
  1. <?php
  2. /*
  3. * This file is part of the PHP CS utility.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * This source file is subject to the MIT license that is bundled
  8. * with this source code in the file LICENSE.
  9. */
  10. namespace Symfony\CS\Tests\Tokenizer;
  11. use Symfony\CS\Tokenizer\Token;
  12. use Symfony\CS\Tokenizer\Tokens;
  13. /**
  14. * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
  15. * @author Max Voloshin <voloshin.dp@gmail.com>
  16. * @author Gregor Harlan <gharlan@web.de>
  17. */
  18. class TokensTest extends \PHPUnit_Framework_TestCase
  19. {
  20. /**
  21. * @param Token[]|null $expected
  22. * @param Token[]|null $input
  23. */
  24. private function assertEqualsTokensArray(array $expected = null, array $input = null)
  25. {
  26. if (null === $expected) {
  27. $this->assertNull($input);
  28. return;
  29. }
  30. $this->assertSame(array_keys($expected), array_keys($input), 'Both arrays need to have same keys.');
  31. foreach ($expected as $index => $expectedToken) {
  32. $this->assertTrue(
  33. $expectedToken->equals($input[$index]),
  34. sprintf('The token at index %d should be %s, got %s', $index, $expectedToken->toJson(), $input[$index]->toJson())
  35. );
  36. }
  37. }
  38. public function testGetClassyElements()
  39. {
  40. $source = <<<'PHP'
  41. <?php
  42. class Foo
  43. {
  44. public $prop0;
  45. protected $prop1;
  46. private $prop2 = 1;
  47. var $prop3 = array(1,2,3);
  48. public function bar4()
  49. {
  50. $a = 5;
  51. return " ({$a})";
  52. }
  53. public function bar5($data)
  54. {
  55. $message = $data;
  56. $example = function ($arg) use ($message) {
  57. echo $arg . ' ' . $message;
  58. };
  59. $example('hello');
  60. }
  61. }
  62. PHP;
  63. $tokens = Tokens::fromCode($source);
  64. $elements = array_values($tokens->getClassyElements());
  65. $this->assertCount(6, $elements);
  66. $this->assertSame('property', $elements[0]['type']);
  67. $this->assertSame('property', $elements[1]['type']);
  68. $this->assertSame('property', $elements[2]['type']);
  69. $this->assertSame('property', $elements[3]['type']);
  70. $this->assertSame('method', $elements[4]['type']);
  71. $this->assertSame('method', $elements[5]['type']);
  72. }
  73. public function testReadFromCacheAfterClearing()
  74. {
  75. $code = '<?php echo 1;';
  76. $tokens = Tokens::fromCode($code);
  77. $countBefore = $tokens->count();
  78. for ($i = 0; $i < $countBefore; ++$i) {
  79. $tokens[$i]->clear();
  80. }
  81. $tokens = Tokens::fromCode($code);
  82. $this->assertSame($countBefore, $tokens->count());
  83. }
  84. /**
  85. * @dataProvider provideIsLambdaCases
  86. */
  87. public function testIsLambda($source, array $expected)
  88. {
  89. $tokens = Tokens::fromCode($source);
  90. foreach ($expected as $index => $expectedValue) {
  91. $this->assertSame($expectedValue, $tokens->isLambda($index));
  92. }
  93. }
  94. public function provideIsLambdaCases()
  95. {
  96. return array(
  97. array(
  98. '<?php function foo () {}',
  99. array(1 => false),
  100. ),
  101. array(
  102. '<?php function /** foo */ foo () {}',
  103. array(1 => false),
  104. ),
  105. array(
  106. '<?php $foo = function () {}',
  107. array(5 => true),
  108. ),
  109. array(
  110. '<?php $foo = function /** foo */ () {}',
  111. array(5 => true),
  112. ),
  113. array(
  114. '<?php
  115. preg_replace_callback(
  116. "/(^|[a-z])/",
  117. function (array $matches) {
  118. return "a";
  119. },
  120. $string
  121. );',
  122. array(7 => true),
  123. ),
  124. array(
  125. '<?php $foo = function &() {}',
  126. array(5 => true),
  127. ),
  128. );
  129. }
  130. /**
  131. * @dataProvider provideIsShortArrayCases
  132. */
  133. public function testIsShortArray($source, array $expected)
  134. {
  135. $tokens = Tokens::fromCode($source);
  136. foreach ($expected as $index => $expectedValue) {
  137. $this->assertSame($expectedValue, $tokens->isShortArray($index));
  138. }
  139. }
  140. public function provideIsShortArrayCases()
  141. {
  142. return array(
  143. array(
  144. '<?php [];',
  145. array(1 => true),
  146. ),
  147. array(
  148. '<?php [1, "foo"];',
  149. array(1 => true),
  150. ),
  151. array(
  152. '<?php [[]];',
  153. array(1 => true, 2 => true),
  154. ),
  155. array(
  156. '<?php ["foo", ["bar", "baz"]];',
  157. array(1 => true, 5 => true),
  158. ),
  159. array(
  160. '<?php (array) [1, 2];',
  161. array(3 => true),
  162. ),
  163. array(
  164. '<?php [1,2][$x];',
  165. array(1 => true, 6 => false),
  166. ),
  167. array(
  168. '<?php array();',
  169. array(1 => false),
  170. ),
  171. array(
  172. '<?php $x[] = 1;',
  173. array(2 => false),
  174. ),
  175. array(
  176. '<?php $x[1];',
  177. array(2 => false),
  178. ),
  179. array(
  180. '<?php $x [ 1 ];',
  181. array(3 => false),
  182. ),
  183. array(
  184. '<?php ${"x"}[1];',
  185. array(5 => false),
  186. ),
  187. array(
  188. '<?php FOO[1];',
  189. array(2 => false),
  190. ),
  191. array(
  192. '<?php array("foo")[1];',
  193. array(5 => false),
  194. ),
  195. array(
  196. '<?php foo()[1];',
  197. array(4 => false),
  198. ),
  199. array(
  200. '<?php "foo"[1];',
  201. array(2 => false),
  202. ),
  203. array(
  204. '<?php "foo$bar"[1];',
  205. array(5 => false),
  206. ),
  207. );
  208. }
  209. /**
  210. * @dataProvider provideIsUnarySuccessorOperator
  211. */
  212. public function testIsUnarySuccessorOperator($source, array $expected)
  213. {
  214. $tokens = Tokens::fromCode($source);
  215. foreach ($expected as $index => $expectedValue) {
  216. $this->assertSame($expectedValue, $tokens->isUnarySuccessorOperator($index));
  217. if ($expectedValue) {
  218. $this->assertFalse($tokens->isUnaryPredecessorOperator($index));
  219. $this->assertFalse($tokens->isBinaryOperator($index));
  220. }
  221. }
  222. }
  223. public function provideIsUnarySuccessorOperator()
  224. {
  225. return array(
  226. array(
  227. '<?php $a++;',
  228. array(2 => true),
  229. ),
  230. array(
  231. '<?php $a--',
  232. array(2 => true),
  233. ),
  234. array(
  235. '<?php $a ++;',
  236. array(3 => true),
  237. ),
  238. array(
  239. '<?php $a++ + 1;',
  240. array(2 => true, 4 => false),
  241. ),
  242. array(
  243. '<?php ${"a"}++',
  244. array(5 => true),
  245. ),
  246. array(
  247. '<?php $foo->bar++',
  248. array(4 => true),
  249. ),
  250. array(
  251. '<?php $foo->{"bar"}++',
  252. array(6 => true),
  253. ),
  254. array(
  255. '<?php $a["foo"]++',
  256. array(5 => true),
  257. ),
  258. );
  259. }
  260. /**
  261. * @dataProvider provideIsUnaryPredecessorOperator
  262. */
  263. public function testIsUnaryPredecessorOperator($source, array $expected)
  264. {
  265. $tokens = Tokens::fromCode($source);
  266. foreach ($expected as $index => $expectedValue) {
  267. $this->assertSame($expectedValue, $tokens->isUnaryPredecessorOperator($index));
  268. if ($expectedValue) {
  269. $this->assertFalse($tokens->isUnarySuccessorOperator($index));
  270. $this->assertFalse($tokens->isBinaryOperator($index));
  271. }
  272. }
  273. }
  274. public function provideIsUnaryPredecessorOperator()
  275. {
  276. return array(
  277. array(
  278. '<?php ++$a;',
  279. array(1 => true),
  280. ),
  281. array(
  282. '<?php --$a',
  283. array(1 => true),
  284. ),
  285. array(
  286. '<?php -- $a;',
  287. array(1 => true),
  288. ),
  289. array(
  290. '<?php $a + ++$b;',
  291. array(3 => false, 5 => true),
  292. ),
  293. array(
  294. '<?php !!$a;',
  295. array(1 => true, 2 => true),
  296. ),
  297. array(
  298. '<?php $a = &$b;',
  299. array(5 => true),
  300. ),
  301. array(
  302. '<?php function &foo() {}',
  303. array(3 => true),
  304. ),
  305. array(
  306. '<?php @foo();',
  307. array(1 => true),
  308. ),
  309. array(
  310. '<?php foo(+ $a, -$b);',
  311. array(3 => true, 8 => true),
  312. ),
  313. array(
  314. '<?php function foo(&$a, array &$b, Bar &$c) {}',
  315. array(5 => true, 11 => true, 17 => true),
  316. ),
  317. );
  318. }
  319. /**
  320. * @dataProvider provideIsUnaryPredecessorOperator56
  321. * @requires PHP 5.6
  322. */
  323. public function testIsUnaryPredecessorOperator56($source, array $expected)
  324. {
  325. $tokens = Tokens::fromCode($source);
  326. foreach ($expected as $index => $expectedValue) {
  327. $this->assertSame($expectedValue, $tokens->isUnaryPredecessorOperator($index));
  328. if ($expectedValue) {
  329. $this->assertFalse($tokens->isUnarySuccessorOperator($index));
  330. $this->assertFalse($tokens->isBinaryOperator($index));
  331. }
  332. }
  333. }
  334. public function provideIsUnaryPredecessorOperator56()
  335. {
  336. return array(
  337. array(
  338. '<?php function foo($a, ...$b);',
  339. array(8 => true),
  340. ),
  341. array(
  342. '<?php function foo(&...$b);',
  343. array(5 => true, 6 => true),
  344. ),
  345. array(
  346. '<?php function foo(array ...$b);',
  347. array(7 => true),
  348. ),
  349. array(
  350. '<?php foo(...$a);',
  351. array(3 => true),
  352. ),
  353. array(
  354. '<?php foo($a, ...$b);',
  355. array(6 => true),
  356. ),
  357. );
  358. }
  359. /**
  360. * @dataProvider provideIsBinaryOperator
  361. */
  362. public function testIsBinaryOperator($source, array $expected)
  363. {
  364. $tokens = Tokens::fromCode($source);
  365. foreach ($expected as $index => $expectedValue) {
  366. $this->assertSame($expectedValue, $tokens->isBinaryOperator($index));
  367. if ($expectedValue) {
  368. $this->assertFalse($tokens->isUnarySuccessorOperator($index));
  369. $this->assertFalse($tokens->isUnaryPredecessorOperator($index));
  370. }
  371. }
  372. }
  373. public function provideIsBinaryOperator()
  374. {
  375. $cases = array(
  376. array(
  377. '<?php $a + $b;',
  378. array(3 => true),
  379. ),
  380. array(
  381. '<?php 1 + $b;',
  382. array(3 => true),
  383. ),
  384. array(
  385. '<?php 0.2 + $b;',
  386. array(3 => true),
  387. ),
  388. array(
  389. '<?php $a[1] + $b;',
  390. array(6 => true),
  391. ),
  392. array(
  393. '<?php FOO + $b;',
  394. array(3 => true),
  395. ),
  396. array(
  397. '<?php foo() + $b;',
  398. array(5 => true),
  399. ),
  400. array(
  401. '<?php ${"foo"} + $b;',
  402. array(6 => true),
  403. ),
  404. array(
  405. '<?php $a+$b;',
  406. array(2 => true),
  407. ),
  408. array(
  409. '<?php $a /* foo */ + /* bar */ $b;',
  410. array(5 => true),
  411. ),
  412. array(
  413. '<?php $a =
  414. $b;',
  415. array(3 => true),
  416. ),
  417. array(
  418. '<?php $a
  419. = $b;',
  420. array(3 => true),
  421. ),
  422. array(
  423. '<?php $a = array("b" => "c", );',
  424. array(3 => true, 9 => true, 12 => false),
  425. ),
  426. array(
  427. '<?php $a * -$b;',
  428. array(3 => true, 5 => false),
  429. ),
  430. array(
  431. '<?php $a = -2 / +5;',
  432. array(3 => true, 5 => false, 8 => true, 10 => false),
  433. ),
  434. array(
  435. '<?php $a = &$b;',
  436. array(3 => true, 5 => false),
  437. ),
  438. array(
  439. '<?php $a++ + $b;',
  440. array(2 => false, 4 => true),
  441. ),
  442. array(
  443. '<?php $a = FOO & $bar;',
  444. array(7 => true),
  445. ),
  446. array(
  447. '<?php __LINE__ - 1;',
  448. array(3 => true),
  449. ),
  450. array(
  451. '<?php `echo 1` + 1;',
  452. array(5 => true),
  453. ),
  454. );
  455. $operators = array(
  456. '+', '-', '*', '/', '%', '<', '>', '|', '^', '&=', '&&', '||', '.=', '/=', '==', '>=', '===', '!=',
  457. '<>', '!==', '<=', 'and', 'or', 'xor', '-=', '%=', '*=', '|=', '+=', '<<', '<<=', '>>', '>>=', '^',
  458. );
  459. foreach ($operators as $operator) {
  460. $cases[] = array(
  461. '<?php $a '.$operator.' $b;',
  462. array(3 => true),
  463. );
  464. }
  465. return $cases;
  466. }
  467. /**
  468. * @dataProvider provideIsBinaryOperator56
  469. * @requires PHP 5.6
  470. */
  471. public function testIsBinaryOperator56($source, array $expected)
  472. {
  473. $tokens = Tokens::fromCode($source);
  474. foreach ($expected as $index => $expectedValue) {
  475. $this->assertSame($expectedValue, $tokens->isBinaryOperator($index));
  476. if ($expectedValue) {
  477. $this->assertFalse($tokens->isUnarySuccessorOperator($index));
  478. $this->assertFalse($tokens->isUnaryPredecessorOperator($index));
  479. }
  480. }
  481. }
  482. public function provideIsBinaryOperator56()
  483. {
  484. return array(
  485. array(
  486. '<?php $a ** $b;',
  487. array(3 => true),
  488. ),
  489. array(
  490. '<?php $a **= $b;',
  491. array(3 => true),
  492. ),
  493. );
  494. }
  495. /**
  496. * @dataProvider provideIsBinaryOperator70
  497. * @requires PHP 7.0
  498. */
  499. public function testIsBinaryOperator70($source, array $expected)
  500. {
  501. $tokens = Tokens::fromCode($source);
  502. foreach ($expected as $index => $expectedValue) {
  503. $this->assertSame($expectedValue, $tokens->isBinaryOperator($index));
  504. if ($expectedValue) {
  505. $this->assertFalse($tokens->isUnarySuccessorOperator($index));
  506. $this->assertFalse($tokens->isUnaryPredecessorOperator($index));
  507. }
  508. }
  509. }
  510. public function provideIsBinaryOperator70()
  511. {
  512. return array(
  513. array(
  514. '<?php $a <=> $b;',
  515. array(3 => true),
  516. ),
  517. array(
  518. '<?php $a ?? $b;',
  519. array(3 => true),
  520. ),
  521. );
  522. }
  523. /**
  524. * @dataProvider provideFindSequence
  525. */
  526. public function testFindSequence($source, $expected, array $params)
  527. {
  528. $tokens = Tokens::fromCode($source);
  529. $this->assertEqualsTokensArray($expected, call_user_func_array(array($tokens, 'findSequence'), $params));
  530. }
  531. public function provideFindSequence()
  532. {
  533. return array(
  534. array(
  535. '<?php $x = 1;',
  536. null,
  537. array(array(
  538. array(T_OPEN_TAG),
  539. array(T_VARIABLE, '$y'),
  540. )),
  541. ),
  542. array(
  543. '<?php $x = 1;',
  544. array(
  545. 0 => new Token(array(T_OPEN_TAG, '<?php ', 1)),
  546. 1 => new Token(array(T_VARIABLE, '$x', 1)),
  547. ),
  548. array(array(
  549. array(T_OPEN_TAG),
  550. array(T_VARIABLE, '$x'),
  551. )),
  552. ),
  553. array(
  554. '<?php $x = 1;',
  555. array(
  556. 3 => new Token('='),
  557. 5 => new Token(array(T_LNUMBER, '1', 1)),
  558. 6 => new Token(';'),
  559. ),
  560. array(array(
  561. '=',
  562. array(T_LNUMBER, '1'),
  563. ';',
  564. )),
  565. ),
  566. array(
  567. '<?php $x = 1;',
  568. array(
  569. 0 => new Token(array(T_OPEN_TAG, '<?php ', 1)),
  570. 1 => new Token(array(T_VARIABLE, '$x', 1)),
  571. ),
  572. array(array(
  573. array(T_OPEN_TAG),
  574. array(T_VARIABLE, '$x'),
  575. ), 0),
  576. ),
  577. array(
  578. '<?php $x = 1;',
  579. null,
  580. array(array(
  581. array(T_OPEN_TAG),
  582. array(T_VARIABLE, '$x'),
  583. ), 1),
  584. ),
  585. array(
  586. '<?php $x = 1;',
  587. array(
  588. 3 => new Token('='),
  589. 5 => new Token(array(T_LNUMBER, '1', 1)),
  590. 6 => new Token(';'),
  591. ),
  592. array(array(
  593. '=',
  594. array(T_LNUMBER, '1'),
  595. ';',
  596. ), 3, 6),
  597. ),
  598. array(
  599. '<?php $x = 1;',
  600. null,
  601. array(array(
  602. '=',
  603. array(T_LNUMBER, '1'),
  604. ';',
  605. ), 4, 6),
  606. ),
  607. array(
  608. '<?php $x = 1;',
  609. null,
  610. array(array(
  611. '=',
  612. array(T_LNUMBER, '1'),
  613. ';',
  614. ), 3, 5),
  615. ),
  616. array(
  617. '<?php $x = 1;',
  618. array(
  619. 0 => new Token(array(T_OPEN_TAG, '<?php ', 1)),
  620. 1 => new Token(array(T_VARIABLE, '$x', 1)),
  621. ),
  622. array(array(
  623. array(T_OPEN_TAG),
  624. array(T_VARIABLE, '$x'),
  625. ), 0, 1, true),
  626. ),
  627. array(
  628. '<?php $x = 1;',
  629. null,
  630. array(array(
  631. array(T_OPEN_TAG),
  632. array(T_VARIABLE, '$X'),
  633. ), 0, 1, true),
  634. ),
  635. array(
  636. '<?php $x = 1;',
  637. null,
  638. array(array(
  639. array(T_OPEN_TAG),
  640. array(T_VARIABLE, '$X'),
  641. ), 0, 1, array(true, true)),
  642. ),
  643. array(
  644. '<?php $x = 1;',
  645. array(
  646. 0 => new Token(array(T_OPEN_TAG, '<?php ', 1)),
  647. 1 => new Token(array(T_VARIABLE, '$x', 1)),
  648. ),
  649. array(array(
  650. array(T_OPEN_TAG),
  651. array(T_VARIABLE, '$X'),
  652. ), 0, 1, false),
  653. ),
  654. array(
  655. '<?php $x = 1;',
  656. array(
  657. 0 => new Token(array(T_OPEN_TAG, '<?php ', 1)),
  658. 1 => new Token(array(T_VARIABLE, '$x', 1)),
  659. ),
  660. array(array(
  661. array(T_OPEN_TAG),
  662. array(T_VARIABLE, '$X'),
  663. ), 0, 1, array(true, false)),
  664. ),
  665. array(
  666. '<?php $x = 1;',
  667. array(
  668. 0 => new Token(array(T_OPEN_TAG, '<?php ', 1)),
  669. 1 => new Token(array(T_VARIABLE, '$x', 1)),
  670. ),
  671. array(array(
  672. array(T_OPEN_TAG),
  673. array(T_VARIABLE, '$X'),
  674. ), 0, 1, array(1 => false)),
  675. ),
  676. array(
  677. '<?php $x = 1;',
  678. null,
  679. array(array(
  680. array(T_OPEN_TAG),
  681. array(T_VARIABLE, '$X'),
  682. ), 0, 1, array(2 => false)),
  683. ),
  684. );
  685. }
  686. /**
  687. * @expectedException \InvalidArgumentException
  688. * @dataProvider provideFindSequenceExceptions
  689. */
  690. public function testFindSequenceException($message, $sequence)
  691. {
  692. $tokens = Tokens::fromCode('<?php $x = 1;');
  693. try {
  694. $tokens->findSequence($sequence);
  695. } catch (\InvalidArgumentException $e) {
  696. $this->assertSame($message, $e->getMessage());
  697. throw $e;
  698. }
  699. }
  700. public function provideFindSequenceExceptions()
  701. {
  702. $emptyToken = new Token('!');
  703. $emptyToken->clear();
  704. return array(
  705. array('Invalid sequence', array()),
  706. array('Non-meaningful token at position: 0', array(
  707. array(T_WHITESPACE, ' '),
  708. )),
  709. array('Non-meaningful token at position: 1', array(
  710. '{', array(T_COMMENT, '// Foo'), '}',
  711. )),
  712. array('Non-meaningful token at position: 2', array(
  713. '{', '!', $emptyToken, '}',
  714. )),
  715. );
  716. }
  717. public function testClearRange()
  718. {
  719. $source = <<<'PHP'
  720. <?php
  721. class FooBar
  722. {
  723. public function foo()
  724. {
  725. return 'bar';
  726. }
  727. public function bar()
  728. {
  729. return 'foo';
  730. }
  731. }
  732. PHP;
  733. $tokens = Tokens::fromCode($source);
  734. $publicIndexes = array_keys($tokens->findGivenKind(T_PUBLIC));
  735. $fooIndex = $publicIndexes[0];
  736. $barIndex = $publicIndexes[1];
  737. $tokens->clearRange($fooIndex, $barIndex - 1);
  738. $newPublicIndexes = array_keys($tokens->findGivenKind(T_PUBLIC));
  739. $this->assertSame($barIndex, reset($newPublicIndexes));
  740. for ($i = $fooIndex; $i < $barIndex; ++$i) {
  741. $this->assertTrue($tokens[$i]->isWhitespace());
  742. }
  743. }
  744. /**
  745. * @dataProvider provideMonolithicPhpDetection
  746. *
  747. * @param string $source
  748. * @param bool $monolitic
  749. */
  750. public function testMonolithicPhpDetection($source, $monolitic)
  751. {
  752. $tokens = Tokens::fromCode($source);
  753. $this->assertSame($monolitic, $tokens->isMonolithicPhp());
  754. }
  755. public function provideMonolithicPhpDetection()
  756. {
  757. return array(
  758. array("<?php\n", true),
  759. array("<?php\n?>", true),
  760. array('', false),
  761. array(' ', false),
  762. array("#!/usr/bin/env php\n<?php\n", false),
  763. array(" <?php\n", false),
  764. array("<?php\n?> ", false),
  765. array("<?php\n?><?php\n", false),
  766. );
  767. }
  768. /**
  769. * @dataProvider provideShortOpenTagMonolithicPhpDetection
  770. *
  771. * @param string $source
  772. * @param bool $monolitic
  773. */
  774. public function testShortOpenTagMonolithicPhpDetection($source, $monolitic)
  775. {
  776. /*
  777. * short_open_tag setting is ignored by HHVM
  778. * @see https://github.com/facebook/hhvm/issues/4758
  779. */
  780. if (!ini_get('short_open_tag') && !defined('HHVM_VERSION')) {
  781. // Short open tag is parsed as T_INLINE_HTML
  782. $monolitic = false;
  783. }
  784. $tokens = Tokens::fromCode($source);
  785. $this->assertSame($monolitic, $tokens->isMonolithicPhp());
  786. }
  787. public function provideShortOpenTagMonolithicPhpDetection()
  788. {
  789. return array(
  790. array("<?\n", true),
  791. array("<?\n?>", true),
  792. array(" <?\n", false),
  793. array("<?\n?> ", false),
  794. array("<?\n?><?\n", false),
  795. array("<?\n?><?php\n", false),
  796. array("<?\n?><?=' ';\n", false),
  797. array("<?php\n?><?\n", false),
  798. array("<?=' '\n?><?\n", false),
  799. );
  800. }
  801. /**
  802. * @dataProvider provideShortOpenTagEchoMonolithicPhpDetection
  803. *
  804. * @param string $source
  805. * @param bool $monolitic
  806. */
  807. public function testShortOpenTagEchoMonolithicPhpDetection($source, $monolitic)
  808. {
  809. /*
  810. * short_open_tag setting is ignored by HHVM
  811. * @see https://github.com/facebook/hhvm/issues/4758
  812. */
  813. if (!ini_get('short_open_tag') && 50400 > PHP_VERSION_ID && !defined('HHVM_VERSION')) {
  814. // Short open tag echo is parsed as T_INLINE_HTML
  815. $monolitic = false;
  816. }
  817. $tokens = Tokens::fromCode($source);
  818. $this->assertSame($monolitic, $tokens->isMonolithicPhp());
  819. }
  820. public function provideShortOpenTagEchoMonolithicPhpDetection()
  821. {
  822. return array(
  823. array("<?=' ';\n", true),
  824. array("<?=' '?>", true),
  825. array(" <?=' ';\n", false),
  826. array("<?=' '?> ", false),
  827. array("<?php\n?><?=' ';\n", false),
  828. array("<?=' '\n?><?php\n", false),
  829. array("<?=' '\n?><?=' ';\n", false),
  830. );
  831. }
  832. /**
  833. * @dataProvider provideIsArray
  834. * @requires PHP 5.4
  835. */
  836. public function testIsArray($source, $tokenIndex, $isMultilineArray = false, $isShortArray = false)
  837. {
  838. $tokens = Tokens::fromCode($source);
  839. $this->assertTrue($tokens->isArray($tokenIndex), 'Expected to be an array.');
  840. $this->assertSame($isMultilineArray, $tokens->isArrayMultiLine($tokenIndex), sprintf('Expected %sto be a multiline array', $isMultilineArray ? '' : 'not '));
  841. $this->assertSame($isShortArray, $tokens->isShortArray($tokenIndex), sprintf('Expected %sto be a short array', $isShortArray ? '' : 'not '));
  842. }
  843. public function provideIsArray()
  844. {
  845. $cases = array(
  846. array(
  847. '<?php
  848. array("a" => 1);
  849. ',
  850. 2,
  851. ),
  852. array(
  853. // short array PHP 5.4 single line
  854. '<?php
  855. ["a" => 2];
  856. ',
  857. 2, false, true,
  858. ),
  859. array(
  860. '<?php
  861. array(
  862. "a" => 3
  863. );
  864. ',
  865. 2, true,
  866. ),
  867. array(
  868. // short array PHP 5.4 multi line
  869. '<?php
  870. [
  871. "a" => 4
  872. ];
  873. ',
  874. 2, true, true,
  875. ),
  876. array(
  877. '<?php
  878. array(
  879. "a" => array(5, 6, 7),
  880. 8 => new \Exception("Ellow")
  881. );
  882. ',
  883. 2, true,
  884. ),
  885. array(
  886. // mix short array syntax
  887. '<?php
  888. array(
  889. "a" => [9, 10, 11],
  890. 12 => new \Exception("Ellow")
  891. );
  892. ',
  893. 2, true,
  894. ),
  895. // Windows/Max EOL testing
  896. array(
  897. "<?php\r\narray('a' => 13);\r\n",
  898. 1,
  899. ),
  900. array(
  901. "<?php\r\n array(\r\n 'a' => 14,\r\n 'b' => 15\r\n );\r\n",
  902. 2, true,
  903. ),
  904. );
  905. return $cases;
  906. }
  907. /**
  908. * @dataProvider provideArrayExceptions
  909. */
  910. public function testIsNotArray($source, $tokenIndex)
  911. {
  912. $tokens = Tokens::fromCode($source);
  913. $this->assertFalse($tokens->isArray($tokenIndex));
  914. }
  915. /**
  916. * @dataProvider provideArrayExceptions
  917. */
  918. public function testIsNotShortArray($source, $tokenIndex)
  919. {
  920. $tokens = Tokens::fromCode($source);
  921. $this->assertFalse($tokens->isShortArray($tokenIndex));
  922. }
  923. /**
  924. * @expectedException \InvalidArgumentException
  925. * @dataProvider provideArrayExceptions
  926. */
  927. public function testIsMultiLineArrayException($source, $tokenIndex)
  928. {
  929. $tokens = Tokens::fromCode($source);
  930. $tokens->isArrayMultiLine($tokenIndex);
  931. }
  932. public function provideArrayExceptions()
  933. {
  934. $cases = array(
  935. array('<?php $a;', 1),
  936. array("<?php\n \$a = (0+1); // [0,1]", 4),
  937. array('<?php $text = "foo $bbb[0] bar";', 8),
  938. array('<?php $text = "foo ${aaa[123]} bar";', 9),
  939. );
  940. return $cases;
  941. }
  942. public function testFindGivenKind()
  943. {
  944. $source = <<<'PHP'
  945. <?php
  946. class FooBar
  947. {
  948. public function foo()
  949. {
  950. return 'bar';
  951. }
  952. public function bar()
  953. {
  954. return 'foo';
  955. }
  956. }
  957. PHP;
  958. $tokens = Tokens::fromCode($source);
  959. /** @var Token[] $found */
  960. $found = $tokens->findGivenKind(T_CLASS);
  961. $this->assertTrue(is_array($found));
  962. $this->assertCount(1, $found);
  963. $this->assertArrayHasKey(1, $found);
  964. $this->assertSame(T_CLASS, $found[1]->getId());
  965. /** @var array $found */
  966. $found = $tokens->findGivenKind(array(T_CLASS, T_FUNCTION));
  967. $this->assertCount(2, $found);
  968. $this->assertArrayHasKey(T_CLASS, $found);
  969. $this->assertTrue(is_array($found[T_CLASS]));
  970. $this->assertCount(1, $found[T_CLASS]);
  971. $this->assertArrayHasKey(1, $found[T_CLASS]);
  972. $this->assertSame(T_CLASS, $found[T_CLASS][1]->getId());
  973. $this->assertArrayHasKey(T_FUNCTION, $found);
  974. $this->assertTrue(is_array($found[T_FUNCTION]));
  975. $this->assertCount(2, $found[T_FUNCTION]);
  976. $this->assertArrayHasKey(9, $found[T_FUNCTION]);
  977. $this->assertSame(T_FUNCTION, $found[T_FUNCTION][9]->getId());
  978. $this->assertArrayHasKey(26, $found[T_FUNCTION]);
  979. $this->assertSame(T_FUNCTION, $found[T_FUNCTION][26]->getId());
  980. // test offset and limits of the search
  981. $found = $tokens->findGivenKind(array(T_CLASS, T_FUNCTION), 10);
  982. $this->assertCount(0, $found[T_CLASS]);
  983. $this->assertCount(1, $found[T_FUNCTION]);
  984. $this->assertArrayHasKey(26, $found[T_FUNCTION]);
  985. $found = $tokens->findGivenKind(array(T_CLASS, T_FUNCTION), 2, 10);
  986. $this->assertCount(0, $found[T_CLASS]);
  987. $this->assertCount(1, $found[T_FUNCTION]);
  988. $this->assertArrayHasKey(9, $found[T_FUNCTION]);
  989. }
  990. public function testIsMethodNameIsMagic()
  991. {
  992. $this->assertTrue(Tokens::isMethodNameIsMagic('__construct'));
  993. $this->assertFalse(Tokens::isMethodNameIsMagic('testIsMethodNameIsMagic'));
  994. }
  995. }