PageRenderTime 29ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/Symfony/CS/Tests/Tokenizer/TokensTest.php

http://github.com/fabpot/PHP-CS-Fixer
PHP | 1438 lines | 1183 code | 118 blank | 137 comment | 16 complexity | 316304346f07db4bece7c907d5cb220f MD5 | raw file
  1. <?php
  2. /*
  3. * This file is part of PHP CS Fixer.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. * Dariusz Rumiński <dariusz.ruminski@gmail.com>
  7. *
  8. * This source file is subject to the MIT license that is bundled
  9. * with this source code in the file LICENSE.
  10. */
  11. namespace Symfony\CS\Tests\Tokenizer;
  12. use Symfony\CS\Tokenizer\Token;
  13. use Symfony\CS\Tokenizer\Tokens;
  14. /**
  15. * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
  16. * @author Max Voloshin <voloshin.dp@gmail.com>
  17. * @author Gregor Harlan <gharlan@web.de>
  18. * @author SpacePossum
  19. */
  20. class TokensTest extends \PHPUnit_Framework_TestCase
  21. {
  22. public function testGetClassyElements()
  23. {
  24. $source = <<<'PHP'
  25. <?php
  26. class Foo
  27. {
  28. public $prop0;
  29. protected $prop1;
  30. private $prop2 = 1;
  31. var $prop3 = array(1,2,3);
  32. public function bar4()
  33. {
  34. $a = 5;
  35. return " ({$a})";
  36. }
  37. public function bar5($data)
  38. {
  39. $message = $data;
  40. $example = function ($arg) use ($message) {
  41. echo $arg . ' ' . $message;
  42. };
  43. $example('hello');
  44. }
  45. }
  46. PHP;
  47. $tokens = Tokens::fromCode($source);
  48. $elements = array_values($tokens->getClassyElements());
  49. $this->assertCount(6, $elements);
  50. $this->assertSame('property', $elements[0]['type']);
  51. $this->assertSame('property', $elements[1]['type']);
  52. $this->assertSame('property', $elements[2]['type']);
  53. $this->assertSame('property', $elements[3]['type']);
  54. $this->assertSame('method', $elements[4]['type']);
  55. $this->assertSame('method', $elements[5]['type']);
  56. }
  57. public function testReadFromCacheAfterClearing()
  58. {
  59. $code = '<?php echo 1;';
  60. $tokens = Tokens::fromCode($code);
  61. $countBefore = $tokens->count();
  62. for ($i = 0; $i < $countBefore; ++$i) {
  63. $tokens[$i]->clear();
  64. }
  65. $tokens = Tokens::fromCode($code);
  66. $this->assertSame($countBefore, $tokens->count());
  67. }
  68. /**
  69. * @dataProvider provideIsAnonymousClassCases
  70. */
  71. public function testIsAnonymousClass($source, array $expected)
  72. {
  73. $tokens = Tokens::fromCode($source);
  74. foreach ($expected as $index => $expectedValue) {
  75. $this->assertSame($expectedValue, $tokens->isAnonymousClass($index));
  76. }
  77. }
  78. public function provideIsAnonymousClassCases()
  79. {
  80. return array(
  81. array(
  82. '<?php class foo {}',
  83. array(1 => false),
  84. ),
  85. array(
  86. '<?php $foo = new class() {};',
  87. array(7 => true),
  88. ),
  89. array(
  90. '<?php $foo = new class() extends Foo implements Bar, Baz {};',
  91. array(7 => true),
  92. ),
  93. array(
  94. '<?php class Foo { function bar() { return new class() {}; } }',
  95. array(1 => false, 19 => true),
  96. ),
  97. array(
  98. '<?php $a = new class(new class($d->a) implements B{}) extends C{};',
  99. array(7 => true, 11 => true),
  100. ),
  101. );
  102. }
  103. /**
  104. * @dataProvider provideIsLambdaCases
  105. */
  106. public function testIsLambda($source, array $expected)
  107. {
  108. $tokens = Tokens::fromCode($source);
  109. foreach ($expected as $index => $expectedValue) {
  110. $this->assertSame($expectedValue, $tokens->isLambda($index));
  111. }
  112. }
  113. public function provideIsLambdaCases()
  114. {
  115. return array(
  116. array(
  117. '<?php function foo () {};',
  118. array(1 => false),
  119. ),
  120. array(
  121. '<?php function /** foo */ foo () {};',
  122. array(1 => false),
  123. ),
  124. array(
  125. '<?php $foo = function () {};',
  126. array(5 => true),
  127. ),
  128. array(
  129. '<?php $foo = function /** foo */ () {};',
  130. array(5 => true),
  131. ),
  132. array(
  133. '<?php
  134. preg_replace_callback(
  135. "/(^|[a-z])/",
  136. function (array $matches) {
  137. return "a";
  138. },
  139. $string
  140. );',
  141. array(7 => true),
  142. ),
  143. array(
  144. '<?php $foo = function &() {};',
  145. array(5 => true),
  146. ),
  147. );
  148. }
  149. /**
  150. * @dataProvider provideIsLambdaCases70
  151. * @requires PHP 7.0
  152. */
  153. public function testIsLambda70($source, array $expected)
  154. {
  155. $tokens = Tokens::fromCode($source);
  156. foreach ($expected as $index => $expectedValue) {
  157. $this->assertSame($expectedValue, $tokens->isLambda($index));
  158. }
  159. }
  160. public function provideIsLambdaCases70()
  161. {
  162. return array(
  163. array(
  164. '<?php
  165. $a = function (): array {
  166. return [];
  167. };',
  168. array(6 => true),
  169. ),
  170. array(
  171. '<?php
  172. function foo (): array {
  173. return [];
  174. };',
  175. array(2 => false),
  176. ),
  177. );
  178. }
  179. /**
  180. * @dataProvider provideIsLambdaCases71
  181. * @requires PHP 7.1
  182. */
  183. public function testIsLambda71($source, array $expected)
  184. {
  185. $tokens = Tokens::fromCode($source);
  186. foreach ($expected as $index => $expectedValue) {
  187. $this->assertSame($expectedValue, $tokens->isLambda($index));
  188. }
  189. }
  190. public function provideIsLambdaCases71()
  191. {
  192. return array(
  193. array(
  194. '<?php
  195. $a = function (): void {
  196. return [];
  197. };',
  198. array(6 => true),
  199. ),
  200. array(
  201. '<?php
  202. function foo (): void {
  203. return [];
  204. };',
  205. array(2 => false),
  206. ),
  207. array(
  208. '<?php
  209. $a = function (): ?int {
  210. return [];
  211. };',
  212. array(6 => true),
  213. ),
  214. array(
  215. '<?php
  216. function foo (): ?int {
  217. return [];
  218. };',
  219. array(2 => false),
  220. ),
  221. );
  222. }
  223. /**
  224. * @dataProvider provideIsShortArrayCases
  225. */
  226. public function testIsShortArray($source, array $expected)
  227. {
  228. $tokens = Tokens::fromCode($source);
  229. foreach ($expected as $index => $expectedValue) {
  230. $this->assertSame($expectedValue, $tokens->isShortArray($index));
  231. }
  232. }
  233. public function provideIsShortArrayCases()
  234. {
  235. return array(
  236. array(
  237. '<?php [];',
  238. array(1 => true),
  239. ),
  240. array(
  241. '<?php [1, "foo"];',
  242. array(1 => true),
  243. ),
  244. array(
  245. '<?php [[]];',
  246. array(1 => true, 2 => true),
  247. ),
  248. array(
  249. '<?php ["foo", ["bar", "baz"]];',
  250. array(1 => true, 5 => true),
  251. ),
  252. array(
  253. '<?php (array) [1, 2];',
  254. array(3 => true),
  255. ),
  256. array(
  257. '<?php [1,2][$x];',
  258. array(1 => true, 6 => false),
  259. ),
  260. array(
  261. '<?php array();',
  262. array(1 => false),
  263. ),
  264. array(
  265. '<?php $x[] = 1;',
  266. array(2 => false),
  267. ),
  268. array(
  269. '<?php $x[1];',
  270. array(2 => false),
  271. ),
  272. array(
  273. '<?php $x [ 1 ];',
  274. array(3 => false),
  275. ),
  276. array(
  277. '<?php ${"x"}[1];',
  278. array(5 => false),
  279. ),
  280. array(
  281. '<?php FOO[1];',
  282. array(2 => false),
  283. ),
  284. array(
  285. '<?php array("foo")[1];',
  286. array(5 => false),
  287. ),
  288. array(
  289. '<?php foo()[1];',
  290. array(4 => false),
  291. ),
  292. array(
  293. '<?php "foo"[1];',
  294. array(2 => false),
  295. ),
  296. );
  297. }
  298. /**
  299. * @dataProvider provideIsUnarySuccessorOperator
  300. */
  301. public function testIsUnarySuccessorOperator($source, array $expected)
  302. {
  303. $tokens = Tokens::fromCode($source);
  304. foreach ($expected as $index => $expectedValue) {
  305. $this->assertSame($expectedValue, $tokens->isUnarySuccessorOperator($index));
  306. if ($expectedValue) {
  307. $this->assertFalse($tokens->isUnaryPredecessorOperator($index));
  308. $this->assertFalse($tokens->isBinaryOperator($index));
  309. }
  310. }
  311. }
  312. public function provideIsUnarySuccessorOperator()
  313. {
  314. return array(
  315. array(
  316. '<?php $a++;',
  317. array(2 => true),
  318. ),
  319. array(
  320. '<?php $a--;',
  321. array(2 => true),
  322. ),
  323. array(
  324. '<?php $a ++;',
  325. array(3 => true),
  326. ),
  327. array(
  328. '<?php $a++ + 1;',
  329. array(2 => true, 4 => false),
  330. ),
  331. array(
  332. '<?php ${"a"}++;',
  333. array(5 => true),
  334. ),
  335. array(
  336. '<?php $foo->bar++;',
  337. array(4 => true),
  338. ),
  339. array(
  340. '<?php $foo->{"bar"}++;',
  341. array(6 => true),
  342. ),
  343. array(
  344. '<?php $a["foo"]++;',
  345. array(5 => true),
  346. ),
  347. );
  348. }
  349. /**
  350. * @dataProvider provideIsUnaryPredecessorOperator
  351. */
  352. public function testIsUnaryPredecessorOperator($source, array $expected)
  353. {
  354. $tokens = Tokens::fromCode($source);
  355. foreach ($expected as $index => $expectedValue) {
  356. $this->assertSame($expectedValue, $tokens->isUnaryPredecessorOperator($index));
  357. if ($expectedValue) {
  358. $this->assertFalse($tokens->isUnarySuccessorOperator($index));
  359. $this->assertFalse($tokens->isBinaryOperator($index));
  360. }
  361. }
  362. }
  363. public function provideIsUnaryPredecessorOperator()
  364. {
  365. return array(
  366. array(
  367. '<?php ++$a;',
  368. array(1 => true),
  369. ),
  370. array(
  371. '<?php --$a;',
  372. array(1 => true),
  373. ),
  374. array(
  375. '<?php -- $a;',
  376. array(1 => true),
  377. ),
  378. array(
  379. '<?php $a + ++$b;',
  380. array(3 => false, 5 => true),
  381. ),
  382. array(
  383. '<?php !!$a;',
  384. array(1 => true, 2 => true),
  385. ),
  386. array(
  387. '<?php $a = &$b;',
  388. array(5 => true),
  389. ),
  390. array(
  391. '<?php function &foo() {}',
  392. array(3 => true),
  393. ),
  394. array(
  395. '<?php @foo();',
  396. array(1 => true),
  397. ),
  398. array(
  399. '<?php foo(+ $a, -$b);',
  400. array(3 => true, 8 => true),
  401. ),
  402. array(
  403. '<?php function foo(&$a, array &$b, Bar &$c) {}',
  404. array(5 => true, 11 => true, 17 => true),
  405. ),
  406. );
  407. }
  408. /**
  409. * @dataProvider provideIsUnaryPredecessorOperator56
  410. * @requires PHP 5.6
  411. */
  412. public function testIsUnaryPredecessorOperator56($source, array $expected)
  413. {
  414. $tokens = Tokens::fromCode($source);
  415. foreach ($expected as $index => $expectedValue) {
  416. $this->assertSame($expectedValue, $tokens->isUnaryPredecessorOperator($index));
  417. if ($expectedValue) {
  418. $this->assertFalse($tokens->isUnarySuccessorOperator($index));
  419. $this->assertFalse($tokens->isBinaryOperator($index));
  420. }
  421. }
  422. }
  423. public function provideIsUnaryPredecessorOperator56()
  424. {
  425. return array(
  426. array(
  427. '<?php function foo($a, ...$b) {};',
  428. array(8 => true),
  429. ),
  430. array(
  431. '<?php function foo(&...$b) {};',
  432. array(5 => true, 6 => true),
  433. ),
  434. array(
  435. '<?php function foo(array ...$b) {};',
  436. array(7 => true),
  437. ),
  438. array(
  439. '<?php $foo = function(...$a) {};',
  440. array(7 => true),
  441. ),
  442. array(
  443. '<?php $foo = function($a, ...$b) {};',
  444. array(10 => true),
  445. ),
  446. );
  447. }
  448. /**
  449. * @dataProvider provideIsBinaryOperator
  450. */
  451. public function testIsBinaryOperator($source, array $expected)
  452. {
  453. $tokens = Tokens::fromCode($source);
  454. foreach ($expected as $index => $expectedValue) {
  455. $this->assertSame($expectedValue, $tokens->isBinaryOperator($index));
  456. if ($expectedValue) {
  457. $this->assertFalse($tokens->isUnarySuccessorOperator($index));
  458. $this->assertFalse($tokens->isUnaryPredecessorOperator($index));
  459. }
  460. }
  461. }
  462. public function provideIsBinaryOperator()
  463. {
  464. $cases = array(
  465. array(
  466. '<?php $a + $b;',
  467. array(3 => true),
  468. ),
  469. array(
  470. '<?php 1 + $b;',
  471. array(3 => true),
  472. ),
  473. array(
  474. '<?php 0.2 + $b;',
  475. array(3 => true),
  476. ),
  477. array(
  478. '<?php $a[1] + $b;',
  479. array(6 => true),
  480. ),
  481. array(
  482. '<?php FOO + $b;',
  483. array(3 => true),
  484. ),
  485. array(
  486. '<?php foo() + $b;',
  487. array(5 => true),
  488. ),
  489. array(
  490. '<?php ${"foo"} + $b;',
  491. array(6 => true),
  492. ),
  493. array(
  494. '<?php $a+$b;',
  495. array(2 => true),
  496. ),
  497. array(
  498. '<?php $a /* foo */ + /* bar */ $b;',
  499. array(5 => true),
  500. ),
  501. array(
  502. '<?php $a =
  503. $b;',
  504. array(3 => true),
  505. ),
  506. array(
  507. '<?php $a
  508. = $b;',
  509. array(3 => true),
  510. ),
  511. array(
  512. '<?php $a = array("b" => "c", );',
  513. array(3 => true, 9 => true, 12 => false),
  514. ),
  515. array(
  516. '<?php $a * -$b;',
  517. array(3 => true, 5 => false),
  518. ),
  519. array(
  520. '<?php $a = -2 / +5;',
  521. array(3 => true, 5 => false, 8 => true, 10 => false),
  522. ),
  523. array(
  524. '<?php $a = &$b;',
  525. array(3 => true, 5 => false),
  526. ),
  527. array(
  528. '<?php $a++ + $b;',
  529. array(2 => false, 4 => true),
  530. ),
  531. array(
  532. '<?php $a = FOO & $bar;',
  533. array(7 => true),
  534. ),
  535. array(
  536. '<?php __LINE__ - 1;',
  537. array(3 => true),
  538. ),
  539. array(
  540. '<?php `echo 1` + 1;',
  541. array(5 => true),
  542. ),
  543. );
  544. $operators = array(
  545. '+', '-', '*', '/', '%', '<', '>', '|', '^', '&=', '&&', '||', '.=', '/=', '==', '>=', '===', '!=',
  546. '<>', '!==', '<=', 'and', 'or', 'xor', '-=', '%=', '*=', '|=', '+=', '<<', '<<=', '>>', '>>=', '^',
  547. );
  548. foreach ($operators as $operator) {
  549. $cases[] = array(
  550. '<?php $a '.$operator.' $b;',
  551. array(3 => true),
  552. );
  553. }
  554. return $cases;
  555. }
  556. /**
  557. * @dataProvider provideIsBinaryOperator56
  558. * @requires PHP 5.6
  559. */
  560. public function testIsBinaryOperator56($source, array $expected)
  561. {
  562. $tokens = Tokens::fromCode($source);
  563. foreach ($expected as $index => $expectedValue) {
  564. $this->assertSame($expectedValue, $tokens->isBinaryOperator($index));
  565. if ($expectedValue) {
  566. $this->assertFalse($tokens->isUnarySuccessorOperator($index));
  567. $this->assertFalse($tokens->isUnaryPredecessorOperator($index));
  568. }
  569. }
  570. }
  571. public function provideIsBinaryOperator56()
  572. {
  573. return array(
  574. array(
  575. '<?php $a ** $b;',
  576. array(3 => true),
  577. ),
  578. array(
  579. '<?php $a **= $b;',
  580. array(3 => true),
  581. ),
  582. );
  583. }
  584. /**
  585. * @dataProvider provideIsBinaryOperator70
  586. * @requires PHP 7.0
  587. */
  588. public function testIsBinaryOperator70($source, array $expected)
  589. {
  590. $tokens = Tokens::fromCode($source);
  591. foreach ($expected as $index => $expectedValue) {
  592. $this->assertSame($expectedValue, $tokens->isBinaryOperator($index));
  593. if ($expectedValue) {
  594. $this->assertFalse($tokens->isUnarySuccessorOperator($index));
  595. $this->assertFalse($tokens->isUnaryPredecessorOperator($index));
  596. }
  597. }
  598. }
  599. public function provideIsBinaryOperator70()
  600. {
  601. return array(
  602. array(
  603. '<?php $a <=> $b;',
  604. array(3 => true),
  605. ),
  606. array(
  607. '<?php $a ?? $b;',
  608. array(3 => true),
  609. ),
  610. );
  611. }
  612. /**
  613. * @dataProvider provideFindSequence
  614. */
  615. public function testFindSequence($source, $expected, array $params)
  616. {
  617. $tokens = Tokens::fromCode($source);
  618. $this->assertEqualsTokensArray($expected, call_user_func_array(array($tokens, 'findSequence'), $params));
  619. }
  620. public function provideFindSequence()
  621. {
  622. return array(
  623. array(
  624. '<?php $x = 1;',
  625. null,
  626. array(array(
  627. array(T_OPEN_TAG),
  628. array(T_VARIABLE, '$y'),
  629. )),
  630. ),
  631. array(
  632. '<?php $x = 1;',
  633. array(
  634. 0 => new Token(array(T_OPEN_TAG, '<?php ', 1)),
  635. 1 => new Token(array(T_VARIABLE, '$x', 1)),
  636. ),
  637. array(array(
  638. array(T_OPEN_TAG),
  639. array(T_VARIABLE, '$x'),
  640. )),
  641. ),
  642. array(
  643. '<?php $x = 1;',
  644. array(
  645. 3 => new Token('='),
  646. 5 => new Token(array(T_LNUMBER, '1', 1)),
  647. 6 => new Token(';'),
  648. ),
  649. array(array(
  650. '=',
  651. array(T_LNUMBER, '1'),
  652. ';',
  653. )),
  654. ),
  655. array(
  656. '<?php $x = 1;',
  657. array(
  658. 0 => new Token(array(T_OPEN_TAG, '<?php ', 1)),
  659. 1 => new Token(array(T_VARIABLE, '$x', 1)),
  660. ),
  661. array(array(
  662. array(T_OPEN_TAG),
  663. array(T_VARIABLE, '$x'),
  664. ), 0),
  665. ),
  666. array(
  667. '<?php $x = 1;',
  668. null,
  669. array(array(
  670. array(T_OPEN_TAG),
  671. array(T_VARIABLE, '$x'),
  672. ), 1),
  673. ),
  674. array(
  675. '<?php $x = 1;',
  676. array(
  677. 3 => new Token('='),
  678. 5 => new Token(array(T_LNUMBER, '1', 1)),
  679. 6 => new Token(';'),
  680. ),
  681. array(array(
  682. '=',
  683. array(T_LNUMBER, '1'),
  684. ';',
  685. ), 3, 6),
  686. ),
  687. array(
  688. '<?php $x = 1;',
  689. null,
  690. array(array(
  691. '=',
  692. array(T_LNUMBER, '1'),
  693. ';',
  694. ), 4, 6),
  695. ),
  696. array(
  697. '<?php $x = 1;',
  698. null,
  699. array(array(
  700. '=',
  701. array(T_LNUMBER, '1'),
  702. ';',
  703. ), 3, 5),
  704. ),
  705. array(
  706. '<?php $x = 1;',
  707. array(
  708. 0 => new Token(array(T_OPEN_TAG, '<?php ', 1)),
  709. 1 => new Token(array(T_VARIABLE, '$x', 1)),
  710. ),
  711. array(array(
  712. array(T_OPEN_TAG),
  713. array(T_VARIABLE, '$x'),
  714. ), 0, 1, true),
  715. ),
  716. array(
  717. '<?php $x = 1;',
  718. null,
  719. array(array(
  720. array(T_OPEN_TAG),
  721. array(T_VARIABLE, '$X'),
  722. ), 0, 1, true),
  723. ),
  724. array(
  725. '<?php $x = 1;',
  726. null,
  727. array(array(
  728. array(T_OPEN_TAG),
  729. array(T_VARIABLE, '$X'),
  730. ), 0, 1, array(true, true)),
  731. ),
  732. array(
  733. '<?php $x = 1;',
  734. array(
  735. 0 => new Token(array(T_OPEN_TAG, '<?php ', 1)),
  736. 1 => new Token(array(T_VARIABLE, '$x', 1)),
  737. ),
  738. array(array(
  739. array(T_OPEN_TAG),
  740. array(T_VARIABLE, '$X'),
  741. ), 0, 1, false),
  742. ),
  743. array(
  744. '<?php $x = 1;',
  745. array(
  746. 0 => new Token(array(T_OPEN_TAG, '<?php ', 1)),
  747. 1 => new Token(array(T_VARIABLE, '$x', 1)),
  748. ),
  749. array(array(
  750. array(T_OPEN_TAG),
  751. array(T_VARIABLE, '$X'),
  752. ), 0, 1, array(true, false)),
  753. ),
  754. array(
  755. '<?php $x = 1;',
  756. array(
  757. 0 => new Token(array(T_OPEN_TAG, '<?php ', 1)),
  758. 1 => new Token(array(T_VARIABLE, '$x', 1)),
  759. ),
  760. array(array(
  761. array(T_OPEN_TAG),
  762. array(T_VARIABLE, '$X'),
  763. ), 0, 1, array(1 => false)),
  764. ),
  765. array(
  766. '<?php $x = 1;',
  767. null,
  768. array(array(
  769. array(T_OPEN_TAG),
  770. array(T_VARIABLE, '$X'),
  771. ), 0, 1, array(2 => false)),
  772. ),
  773. );
  774. }
  775. /**
  776. * @expectedException \InvalidArgumentException
  777. * @dataProvider provideFindSequenceExceptions
  778. */
  779. public function testFindSequenceException($message, $sequence)
  780. {
  781. $tokens = Tokens::fromCode('<?php $x = 1;');
  782. try {
  783. $tokens->findSequence($sequence);
  784. } catch (\InvalidArgumentException $e) {
  785. $this->assertSame($message, $e->getMessage());
  786. throw $e;
  787. }
  788. }
  789. public function provideFindSequenceExceptions()
  790. {
  791. $emptyToken = new Token('!');
  792. $emptyToken->clear();
  793. return array(
  794. array('Invalid sequence.', array()),
  795. array('Non-meaningful token at position: 0.', array(
  796. array(T_WHITESPACE, ' '),
  797. )),
  798. array('Non-meaningful token at position: 1.', array(
  799. '{', array(T_COMMENT, '// Foo'), '}',
  800. )),
  801. array('Non-meaningful token at position: 2.', array(
  802. '{', '!', $emptyToken, '}',
  803. )),
  804. );
  805. }
  806. public function testClearRange()
  807. {
  808. $source = <<<'PHP'
  809. <?php
  810. class FooBar
  811. {
  812. public function foo()
  813. {
  814. return 'bar';
  815. }
  816. public function bar()
  817. {
  818. return 'foo';
  819. }
  820. }
  821. PHP;
  822. $tokens = Tokens::fromCode($source);
  823. $publicIndexes = array_keys($tokens->findGivenKind(T_PUBLIC));
  824. $fooIndex = $publicIndexes[0];
  825. $barIndex = $publicIndexes[1];
  826. $tokens->clearRange($fooIndex, $barIndex - 1);
  827. $newPublicIndexes = array_keys($tokens->findGivenKind(T_PUBLIC));
  828. $this->assertSame($barIndex, reset($newPublicIndexes));
  829. for ($i = $fooIndex; $i < $barIndex; ++$i) {
  830. $this->assertTrue($tokens[$i]->isWhitespace());
  831. }
  832. }
  833. /**
  834. * @dataProvider provideMonolithicPhpDetection
  835. *
  836. * @param string $source
  837. * @param bool $monolitic
  838. */
  839. public function testMonolithicPhpDetection($source, $monolitic)
  840. {
  841. $tokens = Tokens::fromCode($source);
  842. $this->assertSame($monolitic, $tokens->isMonolithicPhp());
  843. }
  844. public function provideMonolithicPhpDetection()
  845. {
  846. return array(
  847. array("<?php\n", true),
  848. array("<?php\n?>", true),
  849. array('', false),
  850. array(' ', false),
  851. array("#!/usr/bin/env php\n<?php\n", false),
  852. array(" <?php\n", false),
  853. array("<?php\n?> ", false),
  854. array("<?php\n?><?php\n", false),
  855. );
  856. }
  857. /**
  858. * @dataProvider provideShortOpenTagMonolithicPhpDetection
  859. *
  860. * @param string $source
  861. * @param bool $monolithic
  862. */
  863. public function testShortOpenTagMonolithicPhpDetection($source, $monolithic)
  864. {
  865. /*
  866. * short_open_tag setting is ignored by HHVM
  867. * @see https://github.com/facebook/hhvm/issues/4758
  868. */
  869. if (!ini_get('short_open_tag') && !defined('HHVM_VERSION')) {
  870. // Short open tag is parsed as T_INLINE_HTML
  871. $monolithic = false;
  872. }
  873. $tokens = Tokens::fromCode($source);
  874. $this->assertSame($monolithic, $tokens->isMonolithicPhp());
  875. }
  876. public function provideShortOpenTagMonolithicPhpDetection()
  877. {
  878. return array(
  879. array("<?\n", true),
  880. array("<?\n?>", true),
  881. array(" <?\n", false),
  882. array("<?\n?> ", false),
  883. array("<?\n?><?\n", false),
  884. array("<?\n?><?php\n", false),
  885. array("<?\n?><?=' ';\n", false),
  886. array("<?php\n?><?\n", false),
  887. array("<?=' '\n?><?\n", false),
  888. );
  889. }
  890. /**
  891. * @dataProvider provideShortOpenTagEchoMonolithicPhpDetection
  892. *
  893. * @param string $source
  894. * @param bool $monolithic
  895. */
  896. public function testShortOpenTagEchoMonolithicPhpDetection($source, $monolithic)
  897. {
  898. /*
  899. * short_open_tag setting is ignored by HHVM
  900. * @see https://github.com/facebook/hhvm/issues/4758
  901. */
  902. if (!ini_get('short_open_tag') && 50400 > PHP_VERSION_ID && !defined('HHVM_VERSION')) {
  903. // Short open tag echo is parsed as T_INLINE_HTML
  904. $monolithic = false;
  905. }
  906. $tokens = Tokens::fromCode($source);
  907. $this->assertSame($monolithic, $tokens->isMonolithicPhp());
  908. }
  909. public function provideShortOpenTagEchoMonolithicPhpDetection()
  910. {
  911. return array(
  912. array("<?=' ';\n", true),
  913. array("<?=' '?>", true),
  914. array(" <?=' ';\n", false),
  915. array("<?=' '?> ", false),
  916. array("<?php\n?><?=' ';\n", false),
  917. array("<?=' '\n?><?php\n", false),
  918. array("<?=' '\n?><?=' ';\n", false),
  919. );
  920. }
  921. /**
  922. * @dataProvider provideIsArray
  923. * @requires PHP 5.4
  924. */
  925. public function testIsArray($source, $tokenIndex, $isMultilineArray = false, $isShortArray = false)
  926. {
  927. $tokens = Tokens::fromCode($source);
  928. $this->assertTrue($tokens->isArray($tokenIndex), 'Expected to be an array.');
  929. $this->assertSame($isMultilineArray, $tokens->isArrayMultiLine($tokenIndex), sprintf('Expected %sto be a multiline array', $isMultilineArray ? '' : 'not '));
  930. $this->assertSame($isShortArray, $tokens->isShortArray($tokenIndex), sprintf('Expected %sto be a short array', $isShortArray ? '' : 'not '));
  931. }
  932. public function provideIsArray()
  933. {
  934. $cases = array(
  935. array(
  936. '<?php
  937. array("a" => 1);
  938. ',
  939. 2,
  940. ),
  941. array(
  942. // short array PHP 5.4 single line
  943. '<?php
  944. ["a" => 2];
  945. ',
  946. 2, false, true,
  947. ),
  948. array(
  949. '<?php
  950. array(
  951. "a" => 3
  952. );
  953. ',
  954. 2, true,
  955. ),
  956. array(
  957. // short array PHP 5.4 multi line
  958. '<?php
  959. [
  960. "a" => 4
  961. ];
  962. ',
  963. 2, true, true,
  964. ),
  965. array(
  966. '<?php
  967. array(
  968. "a" => array(5, 6, 7),
  969. 8 => new \Exception("Ellow")
  970. );
  971. ',
  972. 2, true,
  973. ),
  974. array(
  975. // mix short array syntax
  976. '<?php
  977. array(
  978. "a" => [9, 10, 11],
  979. 12 => new \Exception("Ellow")
  980. );
  981. ',
  982. 2, true,
  983. ),
  984. // Windows/Max EOL testing
  985. array(
  986. "<?php\r\narray('a' => 13);\r\n",
  987. 1,
  988. ),
  989. array(
  990. "<?php\r\n array(\r\n 'a' => 14,\r\n 'b' => 15\r\n );\r\n",
  991. 2, true,
  992. ),
  993. );
  994. return $cases;
  995. }
  996. /**
  997. * @dataProvider provideArrayExceptions
  998. */
  999. public function testIsNotArray($source, $tokenIndex)
  1000. {
  1001. $tokens = Tokens::fromCode($source);
  1002. $this->assertFalse($tokens->isArray($tokenIndex));
  1003. }
  1004. /**
  1005. * @dataProvider provideArrayExceptions
  1006. */
  1007. public function testIsNotShortArray($source, $tokenIndex)
  1008. {
  1009. $tokens = Tokens::fromCode($source);
  1010. $this->assertFalse($tokens->isShortArray($tokenIndex));
  1011. }
  1012. /**
  1013. * @expectedException \InvalidArgumentException
  1014. * @dataProvider provideArrayExceptions
  1015. */
  1016. public function testIsMultiLineArrayException($source, $tokenIndex)
  1017. {
  1018. $tokens = Tokens::fromCode($source);
  1019. $tokens->isArrayMultiLine($tokenIndex);
  1020. }
  1021. public function provideArrayExceptions()
  1022. {
  1023. $cases = array(
  1024. array('<?php $a;', 1),
  1025. array("<?php\n \$a = (0+1); // [0,1]", 4),
  1026. array('<?php $text = "foo $bbb[0] bar";', 8),
  1027. array('<?php $text = "foo ${aaa[123]} bar";', 9),
  1028. );
  1029. return $cases;
  1030. }
  1031. public function testFindGivenKind()
  1032. {
  1033. $source = <<<'PHP'
  1034. <?php
  1035. class FooBar
  1036. {
  1037. public function foo()
  1038. {
  1039. return 'bar';
  1040. }
  1041. public function bar()
  1042. {
  1043. return 'foo';
  1044. }
  1045. }
  1046. PHP;
  1047. $tokens = Tokens::fromCode($source);
  1048. /** @var Token[] $found */
  1049. $found = $tokens->findGivenKind(T_CLASS);
  1050. $this->assertInternalType('array', $found);
  1051. $this->assertCount(1, $found);
  1052. $this->assertArrayHasKey(1, $found);
  1053. $this->assertSame(T_CLASS, $found[1]->getId());
  1054. /** @var array $found */
  1055. $found = $tokens->findGivenKind(array(T_CLASS, T_FUNCTION));
  1056. $this->assertCount(2, $found);
  1057. $this->assertArrayHasKey(T_CLASS, $found);
  1058. $this->assertInternalType('array', $found[T_CLASS]);
  1059. $this->assertCount(1, $found[T_CLASS]);
  1060. $this->assertArrayHasKey(1, $found[T_CLASS]);
  1061. $this->assertSame(T_CLASS, $found[T_CLASS][1]->getId());
  1062. $this->assertArrayHasKey(T_FUNCTION, $found);
  1063. $this->assertInternalType('array', $found[T_FUNCTION]);
  1064. $this->assertCount(2, $found[T_FUNCTION]);
  1065. $this->assertArrayHasKey(9, $found[T_FUNCTION]);
  1066. $this->assertSame(T_FUNCTION, $found[T_FUNCTION][9]->getId());
  1067. $this->assertArrayHasKey(26, $found[T_FUNCTION]);
  1068. $this->assertSame(T_FUNCTION, $found[T_FUNCTION][26]->getId());
  1069. // test offset and limits of the search
  1070. $found = $tokens->findGivenKind(array(T_CLASS, T_FUNCTION), 10);
  1071. $this->assertCount(0, $found[T_CLASS]);
  1072. $this->assertCount(1, $found[T_FUNCTION]);
  1073. $this->assertArrayHasKey(26, $found[T_FUNCTION]);
  1074. $found = $tokens->findGivenKind(array(T_CLASS, T_FUNCTION), 2, 10);
  1075. $this->assertCount(0, $found[T_CLASS]);
  1076. $this->assertCount(1, $found[T_FUNCTION]);
  1077. $this->assertArrayHasKey(9, $found[T_FUNCTION]);
  1078. }
  1079. public function testIsMethodNameIsMagic()
  1080. {
  1081. $this->assertTrue(Tokens::isMethodNameIsMagic('__construct'));
  1082. $this->assertFalse(Tokens::isMethodNameIsMagic('testIsMethodNameIsMagic'));
  1083. }
  1084. /**
  1085. * @param string $source
  1086. * @param Token[] $expected tokens
  1087. * @param int[] $indexes to clear
  1088. *
  1089. * @dataProvider getClearTokenAndMergeSurroundingWhitespaceCases
  1090. */
  1091. public function testClearTokenAndMergeSurroundingWhitespace($source, array $indexes, array $expected)
  1092. {
  1093. $this->doTestClearTokens($source, $indexes, $expected);
  1094. if (count($indexes) > 1) {
  1095. $this->doTestClearTokens($source, array_reverse($indexes), $expected);
  1096. }
  1097. }
  1098. public function getClearTokenAndMergeSurroundingWhitespaceCases()
  1099. {
  1100. $clearToken = new Token(array(null, ''));
  1101. $clearToken->clear();
  1102. return array(
  1103. array(
  1104. '<?php if($a){}else{}',
  1105. array(7, 8, 9),
  1106. array(
  1107. new Token(array(T_OPEN_TAG, '<?php ')),
  1108. new Token(array(T_IF, 'if')),
  1109. new Token('('),
  1110. new Token(array(T_VARIABLE, '$a')),
  1111. new Token(')'),
  1112. new Token('{'),
  1113. new Token('}'),
  1114. $clearToken,
  1115. $clearToken,
  1116. $clearToken,
  1117. ),
  1118. ),
  1119. array(
  1120. '<?php $a;/**/;',
  1121. array(2),
  1122. array(
  1123. // <?php $a /**/;
  1124. new Token(array(T_OPEN_TAG, '<?php ')),
  1125. new Token(array(T_VARIABLE, '$a')),
  1126. $clearToken,
  1127. new Token(array(T_COMMENT, '/**/')),
  1128. new Token(';'),
  1129. ),
  1130. ),
  1131. array(
  1132. '<?php ; ; ;',
  1133. array(3),
  1134. array(
  1135. // <?php ; ;
  1136. new Token(array(T_OPEN_TAG, '<?php ')),
  1137. new Token(';'),
  1138. new Token(array(T_WHITESPACE, ' ')),
  1139. $clearToken,
  1140. $clearToken,
  1141. new Token(';'),
  1142. ),
  1143. ),
  1144. array(
  1145. '<?php ; ; ;',
  1146. array(1, 5),
  1147. array(
  1148. // <?php ;
  1149. new Token(array(T_OPEN_TAG, '<?php ')),
  1150. new Token(array(T_WHITESPACE, ' ')),
  1151. $clearToken,
  1152. new Token(';'),
  1153. new Token(array(T_WHITESPACE, ' ')),
  1154. $clearToken,
  1155. ),
  1156. ),
  1157. array(
  1158. '<?php ; ; ;',
  1159. array(1, 3),
  1160. array(
  1161. // <?php ;
  1162. new Token(array(T_OPEN_TAG, '<?php ')),
  1163. new Token(array(T_WHITESPACE, ' ')),
  1164. $clearToken,
  1165. $clearToken,
  1166. $clearToken,
  1167. new Token(';'),
  1168. ),
  1169. ),
  1170. array(
  1171. '<?php ; ; ;',
  1172. array(1),
  1173. array(
  1174. // <?php ; ;
  1175. new Token(array(T_OPEN_TAG, '<?php ')),
  1176. new Token(array(T_WHITESPACE, ' ')),
  1177. $clearToken,
  1178. new Token(';'),
  1179. new Token(array(T_WHITESPACE, ' ')),
  1180. new Token(';'),
  1181. ),
  1182. ),
  1183. );
  1184. }
  1185. /**
  1186. * @dataProvider getImportUseIndexesCases
  1187. */
  1188. public function testGetImportUseIndexes(array $expected, $input, $perNamespace = false)
  1189. {
  1190. $tokens = Tokens::fromCode($input);
  1191. $this->assertSame($expected, $tokens->getImportUseIndexes($perNamespace));
  1192. }
  1193. public function getImportUseIndexesCases()
  1194. {
  1195. return array(
  1196. array(
  1197. array(1, 8),
  1198. '<?php use E\F?><?php use A\B;',
  1199. ),
  1200. array(
  1201. array(array(1), array(14), array(29)),
  1202. '<?php
  1203. use T\A;
  1204. namespace A { use D\C; }
  1205. namespace b { use D\C; }
  1206. ',
  1207. true,
  1208. ),
  1209. array(
  1210. array(array(1, 8)),
  1211. '<?php use D\B; use A\C?>',
  1212. true,
  1213. ),
  1214. array(
  1215. array(1, 8),
  1216. '<?php use D\B; use A\C?>',
  1217. ),
  1218. array(
  1219. array(7, 22),
  1220. '<?php
  1221. namespace A { use D\C; }
  1222. namespace b { use D\C; }
  1223. ',
  1224. ),
  1225. array(
  1226. array(3, 10, 34, 45, 54, 59, 77, 95),
  1227. <<<'EOF'
  1228. use Zoo\Bar;
  1229. use Foo\Bar;
  1230. use Foo\Zar\Baz;
  1231. <?php
  1232. use Foo\Bar;
  1233. use Foo\Bar\Foo as Fooo, Foo\Bar\FooBar as FooBaz;
  1234. use Foo\Bir as FBB;
  1235. use Foo\Zar\Baz;
  1236. use SomeClass;
  1237. use Symfony\Annotation\Template, Symfony\Doctrine\Entities\Entity;
  1238. use Zoo\Bar;
  1239. $a = new someclass();
  1240. use Zoo\Tar;
  1241. class AnnotatedClass
  1242. {
  1243. }
  1244. EOF
  1245. ,
  1246. ),
  1247. );
  1248. }
  1249. /**
  1250. * @dataProvider getImportUseIndexesCasesPHP70
  1251. * @requires PHP 7.0
  1252. */
  1253. public function testGetImportUseIndexesPHP70(array $expected, $input, $perNamespace = false)
  1254. {
  1255. $tokens = Tokens::fromCode($input);
  1256. $this->assertSame($expected, $tokens->getImportUseIndexes($perNamespace));
  1257. }
  1258. public function getImportUseIndexesCasesPHP70()
  1259. {
  1260. return array(
  1261. array(
  1262. array(1, 22, 41),
  1263. '<?php
  1264. use some\a\{ClassA, ClassB, ClassC as C};
  1265. use function some\a\{fn_a, fn_b, fn_c};
  1266. use const some\a\{ConstA, ConstB, ConstC};
  1267. ',
  1268. ),
  1269. array(
  1270. array(array(1, 22, 41)),
  1271. '<?php
  1272. use some\a\{ClassA, ClassB, ClassC as C};
  1273. use function some\a\{fn_a, fn_b, fn_c};
  1274. use const some\a\{ConstA, ConstB, ConstC};
  1275. ',
  1276. true,
  1277. ),
  1278. );
  1279. }
  1280. /**
  1281. * @param Token[]|null $expected
  1282. * @param Token[]|null $input
  1283. */
  1284. private function assertEqualsTokensArray(array $expected = null, array $input = null)
  1285. {
  1286. if (null === $expected) {
  1287. $this->assertNull($input);
  1288. return;
  1289. }
  1290. $this->assertSame(array_keys($expected), array_keys($input), 'Both arrays need to have same keys.');
  1291. foreach ($expected as $index => $expectedToken) {
  1292. $this->assertTrue(
  1293. $expectedToken->equals($input[$index]),
  1294. sprintf('The token at index %d should be %s, got %s', $index, $expectedToken->toJson(), $input[$index]->toJson())
  1295. );
  1296. }
  1297. }
  1298. /**
  1299. * @param string $source
  1300. * @param int[] $indexes
  1301. * @param Token[] $expected
  1302. */
  1303. private function doTestClearTokens($source, array $indexes, array $expected)
  1304. {
  1305. Tokens::clearCache();
  1306. $tokens = Tokens::fromCode($source);
  1307. foreach ($indexes as $index) {
  1308. $tokens->clearTokenAndMergeSurroundingWhitespace($index);
  1309. }
  1310. $this->assertSame(count($expected), $tokens->count());
  1311. foreach ($expected as $index => $expectedToken) {
  1312. $token = $tokens[$index];
  1313. $expectedPrototype = $expectedToken->getPrototype();
  1314. if (is_array($expectedPrototype)) {
  1315. unset($expectedPrototype[2]); // don't compare token lines as our token mutations don't deal with line numbers
  1316. }
  1317. $this->assertTrue($token->equals($expectedPrototype), sprintf('The token at index %d should be %s, got %s', $index, json_encode($expectedPrototype), $token->toJson()));
  1318. }
  1319. }
  1320. }