TokensAnalyzerTest.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634
  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\Tokens;
  12. use Symfony\CS\Tokenizer\TokensAnalyzer;
  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. * @internal
  19. */
  20. final class TokensAnalyzerTest 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. $tokensAnalyzer = new TokensAnalyzer($tokens);
  49. $elements = array_values($tokensAnalyzer->getClassyElements());
  50. $this->assertCount(6, $elements);
  51. $this->assertSame('property', $elements[0]['type']);
  52. $this->assertSame('property', $elements[1]['type']);
  53. $this->assertSame('property', $elements[2]['type']);
  54. $this->assertSame('property', $elements[3]['type']);
  55. $this->assertSame('method', $elements[4]['type']);
  56. $this->assertSame('method', $elements[5]['type']);
  57. }
  58. /**
  59. * @dataProvider provideIsLambdaCases
  60. */
  61. public function testIsLambda($source, array $expected)
  62. {
  63. $tokensAnalyzer = new TokensAnalyzer(Tokens::fromCode($source));
  64. foreach ($expected as $index => $isLambda) {
  65. $this->assertSame($isLambda, $tokensAnalyzer->isLambda($index));
  66. }
  67. }
  68. public function provideIsLambdaCases()
  69. {
  70. return array(
  71. array(
  72. '<?php function foo () {}',
  73. array(1 => false),
  74. ),
  75. array(
  76. '<?php function /** foo */ foo () {}',
  77. array(1 => false),
  78. ),
  79. array(
  80. '<?php $foo = function () {}',
  81. array(5 => true),
  82. ),
  83. array(
  84. '<?php $foo = function /** foo */ () {}',
  85. array(5 => true),
  86. ),
  87. array(
  88. '<?php
  89. preg_replace_callback(
  90. "/(^|[a-z])/",
  91. function (array $matches) {
  92. return "a";
  93. },
  94. $string
  95. );',
  96. array(7 => true),
  97. ),
  98. array(
  99. '<?php $foo = function &() {}',
  100. array(5 => true),
  101. ),
  102. );
  103. }
  104. /**
  105. * @dataProvider provideIsUnarySuccessorOperator
  106. */
  107. public function testIsUnarySuccessorOperator($source, array $expected)
  108. {
  109. $tokensAnalyzer = new TokensAnalyzer(Tokens::fromCode($source));
  110. foreach ($expected as $index => $isUnary) {
  111. $this->assertSame($isUnary, $tokensAnalyzer->isUnarySuccessorOperator($index));
  112. if ($isUnary) {
  113. $this->assertFalse($tokensAnalyzer->isUnaryPredecessorOperator($index));
  114. $this->assertFalse($tokensAnalyzer->isBinaryOperator($index));
  115. }
  116. }
  117. }
  118. public function provideIsUnarySuccessorOperator()
  119. {
  120. return array(
  121. array(
  122. '<?php $a++;',
  123. array(2 => true),
  124. ),
  125. array(
  126. '<?php $a--',
  127. array(2 => true),
  128. ),
  129. array(
  130. '<?php $a ++;',
  131. array(3 => true),
  132. ),
  133. array(
  134. '<?php $a++ + 1;',
  135. array(2 => true, 4 => false),
  136. ),
  137. array(
  138. '<?php ${"a"}++',
  139. array(5 => true),
  140. ),
  141. array(
  142. '<?php $foo->bar++',
  143. array(4 => true),
  144. ),
  145. array(
  146. '<?php $foo->{"bar"}++',
  147. array(6 => true),
  148. ),
  149. array(
  150. '<?php $a["foo"]++',
  151. array(5 => true),
  152. ),
  153. );
  154. }
  155. /**
  156. * @dataProvider provideIsUnaryPredecessorOperator
  157. */
  158. public function testIsUnaryPredecessorOperator($source, array $expected)
  159. {
  160. $tokensAnalyzer = new TokensAnalyzer(Tokens::fromCode($source));
  161. foreach ($expected as $index => $isUnary) {
  162. $this->assertSame($isUnary, $tokensAnalyzer->isUnaryPredecessorOperator($index));
  163. if ($isUnary) {
  164. $this->assertFalse($tokensAnalyzer->isUnarySuccessorOperator($index));
  165. $this->assertFalse($tokensAnalyzer->isBinaryOperator($index));
  166. }
  167. }
  168. }
  169. public function provideIsUnaryPredecessorOperator()
  170. {
  171. return array(
  172. array(
  173. '<?php ++$a;',
  174. array(1 => true),
  175. ),
  176. array(
  177. '<?php --$a',
  178. array(1 => true),
  179. ),
  180. array(
  181. '<?php -- $a;',
  182. array(1 => true),
  183. ),
  184. array(
  185. '<?php $a + ++$b;',
  186. array(3 => false, 5 => true),
  187. ),
  188. array(
  189. '<?php !!$a;',
  190. array(1 => true, 2 => true),
  191. ),
  192. array(
  193. '<?php $a = &$b;',
  194. array(5 => true),
  195. ),
  196. array(
  197. '<?php function &foo() {}',
  198. array(3 => true),
  199. ),
  200. array(
  201. '<?php @foo();',
  202. array(1 => true),
  203. ),
  204. array(
  205. '<?php foo(+ $a, -$b);',
  206. array(3 => true, 8 => true),
  207. ),
  208. array(
  209. '<?php function foo(&$a, array &$b, Bar &$c) {}',
  210. array(5 => true, 11 => true, 17 => true),
  211. ),
  212. );
  213. }
  214. /**
  215. * @dataProvider provideIsUnaryPredecessorOperator56
  216. * @requires PHP 5.6
  217. */
  218. public function testIsUnaryPredecessorOperator56($source, array $expected)
  219. {
  220. $tokensAnalyzer = new TokensAnalyzer(Tokens::fromCode($source));
  221. foreach ($expected as $index => $isUnary) {
  222. $this->assertSame($isUnary, $tokensAnalyzer->isUnaryPredecessorOperator($index));
  223. if ($isUnary) {
  224. $this->assertFalse($tokensAnalyzer->isUnarySuccessorOperator($index));
  225. $this->assertFalse($tokensAnalyzer->isBinaryOperator($index));
  226. }
  227. }
  228. }
  229. public function provideIsUnaryPredecessorOperator56()
  230. {
  231. return array(
  232. array(
  233. '<?php function foo($a, ...$b);',
  234. array(8 => true),
  235. ),
  236. array(
  237. '<?php function foo(&...$b);',
  238. array(5 => true, 6 => true),
  239. ),
  240. array(
  241. '<?php function foo(array ...$b);',
  242. array(7 => true),
  243. ),
  244. array(
  245. '<?php foo(...$a);',
  246. array(3 => true),
  247. ),
  248. array(
  249. '<?php foo($a, ...$b);',
  250. array(6 => true),
  251. ),
  252. );
  253. }
  254. /**
  255. * @dataProvider provideIsBinaryOperator
  256. */
  257. public function testIsBinaryOperator($source, array $expected)
  258. {
  259. $tokensAnalyzer = new TokensAnalyzer(Tokens::fromCode($source));
  260. foreach ($expected as $index => $isBinary) {
  261. $this->assertSame($isBinary, $tokensAnalyzer->isBinaryOperator($index));
  262. if ($isBinary) {
  263. $this->assertFalse($tokensAnalyzer->isUnarySuccessorOperator($index));
  264. $this->assertFalse($tokensAnalyzer->isUnaryPredecessorOperator($index));
  265. }
  266. }
  267. }
  268. public function provideIsBinaryOperator()
  269. {
  270. $cases = array(
  271. array(
  272. '<?php [] + [];',
  273. array(4 => true),
  274. ),
  275. array(
  276. '<?php $a + $b;',
  277. array(3 => true),
  278. ),
  279. array(
  280. '<?php 1 + $b;',
  281. array(3 => true),
  282. ),
  283. array(
  284. '<?php 0.2 + $b;',
  285. array(3 => true),
  286. ),
  287. array(
  288. '<?php $a[1] + $b;',
  289. array(6 => true),
  290. ),
  291. array(
  292. '<?php FOO + $b;',
  293. array(3 => true),
  294. ),
  295. array(
  296. '<?php foo() + $b;',
  297. array(5 => true),
  298. ),
  299. array(
  300. '<?php ${"foo"} + $b;',
  301. array(6 => true),
  302. ),
  303. array(
  304. '<?php $a+$b;',
  305. array(2 => true),
  306. ),
  307. array(
  308. '<?php $a /* foo */ + /* bar */ $b;',
  309. array(5 => true),
  310. ),
  311. array(
  312. '<?php $a =
  313. $b;',
  314. array(3 => true),
  315. ),
  316. array(
  317. '<?php $a
  318. = $b;',
  319. array(3 => true),
  320. ),
  321. array(
  322. '<?php $a = array("b" => "c", );',
  323. array(3 => true, 9 => true, 12 => false),
  324. ),
  325. array(
  326. '<?php $a * -$b;',
  327. array(3 => true, 5 => false),
  328. ),
  329. array(
  330. '<?php $a = -2 / +5;',
  331. array(3 => true, 5 => false, 8 => true, 10 => false),
  332. ),
  333. array(
  334. '<?php $a = &$b;',
  335. array(3 => true, 5 => false),
  336. ),
  337. array(
  338. '<?php $a++ + $b;',
  339. array(2 => false, 4 => true),
  340. ),
  341. array(
  342. '<?php $a = FOO & $bar;',
  343. array(7 => true),
  344. ),
  345. array(
  346. '<?php __LINE__ - 1;',
  347. array(3 => true),
  348. ),
  349. array(
  350. '<?php `echo 1` + 1;',
  351. array(5 => true),
  352. ),
  353. );
  354. $operators = array(
  355. '+', '-', '*', '/', '%', '<', '>', '|', '^', '&=', '&&', '||', '.=', '/=', '==', '>=', '===', '!=',
  356. '<>', '!==', '<=', 'and', 'or', 'xor', '-=', '%=', '*=', '|=', '+=', '<<', '<<=', '>>', '>>=', '^',
  357. );
  358. foreach ($operators as $operator) {
  359. $cases[] = array(
  360. '<?php $a '.$operator.' $b;',
  361. array(3 => true),
  362. );
  363. }
  364. return $cases;
  365. }
  366. /**
  367. * @dataProvider provideIsBinaryOperator56
  368. * @requires PHP 5.6
  369. */
  370. public function testIsBinaryOperator56($source, array $expected)
  371. {
  372. $tokensAnalyzer = new TokensAnalyzer(Tokens::fromCode($source));
  373. foreach ($expected as $index => $isBinary) {
  374. $this->assertSame($isBinary, $tokensAnalyzer->isBinaryOperator($index));
  375. if ($isBinary) {
  376. $this->assertFalse($tokensAnalyzer->isUnarySuccessorOperator($index));
  377. $this->assertFalse($tokensAnalyzer->isUnaryPredecessorOperator($index));
  378. }
  379. }
  380. }
  381. public function provideIsBinaryOperator56()
  382. {
  383. return array(
  384. array(
  385. '<?php $a ** $b;',
  386. array(3 => true),
  387. ),
  388. array(
  389. '<?php $a **= $b;',
  390. array(3 => true),
  391. ),
  392. );
  393. }
  394. /**
  395. * @dataProvider provideIsBinaryOperator70
  396. * @requires PHP 7.0
  397. */
  398. public function testIsBinaryOperator70($source, array $expected)
  399. {
  400. $tokensAnalyzer = new TokensAnalyzer(Tokens::fromCode($source));
  401. foreach ($expected as $index => $isBinary) {
  402. $this->assertSame($isBinary, $tokensAnalyzer->isBinaryOperator($index));
  403. if ($isBinary) {
  404. $this->assertFalse($tokensAnalyzer->isUnarySuccessorOperator($index));
  405. $this->assertFalse($tokensAnalyzer->isUnaryPredecessorOperator($index));
  406. }
  407. }
  408. }
  409. public function provideIsBinaryOperator70()
  410. {
  411. return array(
  412. array(
  413. '<?php $a <=> $b;',
  414. array(3 => true),
  415. ),
  416. array(
  417. '<?php $a ?? $b;',
  418. array(3 => true),
  419. ),
  420. );
  421. }
  422. /**
  423. * @dataProvider provideIsArray
  424. * @requires PHP 5.4
  425. */
  426. public function testIsArray($source, $tokenIndex, $isMultilineArray = false)
  427. {
  428. $tokens = Tokens::fromCode($source);
  429. $tokensAnalyzer = new TokensAnalyzer($tokens);
  430. $this->assertTrue($tokensAnalyzer->isArray($tokenIndex), 'Expected to be an array.');
  431. $this->assertSame($isMultilineArray, $tokensAnalyzer->isArrayMultiLine($tokenIndex), sprintf('Expected %sto be a multiline array', $isMultilineArray ? '' : 'not '));
  432. }
  433. public function provideIsArray()
  434. {
  435. $cases = array(
  436. array(
  437. '<?php
  438. array("a" => 1);
  439. ',
  440. 2,
  441. ),
  442. array(
  443. // short array PHP 5.4 single line
  444. '<?php
  445. ["a" => 2];
  446. ',
  447. 2, false,
  448. ),
  449. array(
  450. '<?php
  451. array(
  452. "a" => 3
  453. );
  454. ',
  455. 2, true,
  456. ),
  457. array(
  458. // short array PHP 5.4 multi line
  459. '<?php
  460. [
  461. "a" => 4
  462. ];
  463. ',
  464. 2, true,
  465. ),
  466. array(
  467. '<?php
  468. array(
  469. "a" => array(5, 6, 7),
  470. 8 => new \Exception(\'Ellow\')
  471. );
  472. ',
  473. 2, true,
  474. ),
  475. array(
  476. // mix short array syntax
  477. '<?php
  478. array(
  479. "a" => [9, 10, 11],
  480. 12 => new \Exception(\'Ellow\')
  481. );
  482. ',
  483. 2, true,
  484. ),
  485. // Windows/Max EOL testing
  486. array(
  487. "<?php\r\narray('a' => 13);\r\n",
  488. 1,
  489. ),
  490. array(
  491. "<?php\r\n array(\r\n 'a' => 14,\r\n 'b' => 15\r\n );\r\n",
  492. 2, true,
  493. ),
  494. );
  495. return $cases;
  496. }
  497. /**
  498. * @dataProvider provideArrayExceptions
  499. */
  500. public function testIsNotArray($source, $tokenIndex)
  501. {
  502. $tokens = Tokens::fromCode($source);
  503. $tokensAnalyzer = new TokensAnalyzer($tokens);
  504. $this->assertFalse($tokensAnalyzer->isArray($tokenIndex));
  505. }
  506. /**
  507. * @expectedException \InvalidArgumentException
  508. * @dataProvider provideArrayExceptions
  509. */
  510. public function testIsMultiLineArrayException($source, $tokenIndex)
  511. {
  512. $tokens = Tokens::fromCode($source);
  513. $tokensAnalyzer = new TokensAnalyzer($tokens);
  514. $tokensAnalyzer->isArrayMultiLine($tokenIndex);
  515. }
  516. public function provideArrayExceptions()
  517. {
  518. $cases = array(
  519. array('<?php $a;', 1),
  520. array("<?php\n \$a = (0+1); // [0,1]", 4),
  521. array('<?php $text = "foo $bbb[0] bar";', 8),
  522. array('<?php $text = "foo ${aaa[123]} bar";', 9),
  523. );
  524. return $cases;
  525. }
  526. /**
  527. * @dataProvider provideGetFunctionProperties
  528. */
  529. public function testGetFunctionProperties($source, $index, $expected)
  530. {
  531. $tokens = Tokens::fromCode($source);
  532. $tokensAnalyzer = new TokensAnalyzer($tokens);
  533. $attributes = $tokensAnalyzer->getMethodAttributes($index);
  534. $this->assertSame($expected, $attributes);
  535. }
  536. public function provideGetFunctionProperties()
  537. {
  538. $defaultAttributes = array(
  539. 'visibility' => null,
  540. 'static' => false,
  541. 'abstract' => false,
  542. 'final' => false,
  543. );
  544. $template = '
  545. <?php
  546. class TestClass {
  547. %s function a() {
  548. //
  549. }
  550. }
  551. ';
  552. $cases = array();
  553. $attributes = $defaultAttributes;
  554. $attributes['visibility'] = T_PRIVATE;
  555. $cases[] = array(sprintf($template, 'private'), 10, $attributes);
  556. $attributes = $defaultAttributes;
  557. $attributes['visibility'] = T_PUBLIC;
  558. $cases[] = array(sprintf($template, 'public'), 10, $attributes);
  559. $attributes = $defaultAttributes;
  560. $attributes['visibility'] = T_PROTECTED;
  561. $cases[] = array(sprintf($template, 'protected'), 10, $attributes);
  562. $attributes = $defaultAttributes;
  563. $attributes['visibility'] = null;
  564. $attributes['static'] = true;
  565. $cases[] = array(sprintf($template, 'static'), 10, $attributes);
  566. $attributes = $defaultAttributes;
  567. $attributes['visibility'] = T_PUBLIC;
  568. $attributes['static'] = true;
  569. $attributes['final'] = true;
  570. $cases[] = array(sprintf($template, 'final public static'), 14, $attributes);
  571. $attributes = $defaultAttributes;
  572. $attributes['visibility'] = null;
  573. $attributes['abstract'] = true;
  574. $cases[] = array(sprintf($template, 'abstract'), 10, $attributes);
  575. $attributes = $defaultAttributes;
  576. $attributes['visibility'] = T_PUBLIC;
  577. $attributes['abstract'] = true;
  578. $cases[] = array(sprintf($template, 'abstract public'), 12, $attributes);
  579. $attributes = $defaultAttributes;
  580. $cases[] = array(sprintf($template, ''), 8, $attributes);
  581. return $cases;
  582. }
  583. }