ArgumentsAnalyzerTest.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4. * This file is part of PHP CS Fixer.
  5. *
  6. * (c) Fabien Potencier <fabien@symfony.com>
  7. * Dariusz Rumiński <dariusz.ruminski@gmail.com>
  8. *
  9. * This source file is subject to the MIT license that is bundled
  10. * with this source code in the file LICENSE.
  11. */
  12. namespace PhpCsFixer\Tests\Tokenizer\Analyzer;
  13. use PhpCsFixer\Tests\TestCase;
  14. use PhpCsFixer\Tokenizer\Analyzer\Analysis\ArgumentAnalysis;
  15. use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis;
  16. use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer;
  17. use PhpCsFixer\Tokenizer\Tokens;
  18. /**
  19. * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
  20. *
  21. * @internal
  22. *
  23. * @covers \PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer
  24. */
  25. final class ArgumentsAnalyzerTest extends TestCase
  26. {
  27. /**
  28. * @param array<int, int> $arguments
  29. *
  30. * @dataProvider provideArgumentsCases
  31. */
  32. public function testArguments(string $code, int $openIndex, int $closeIndex, array $arguments): void
  33. {
  34. $tokens = Tokens::fromCode($code);
  35. $analyzer = new ArgumentsAnalyzer();
  36. self::assertSame(\count($arguments), $analyzer->countArguments($tokens, $openIndex, $closeIndex));
  37. self::assertSame($arguments, $analyzer->getArguments($tokens, $openIndex, $closeIndex));
  38. }
  39. public static function provideArgumentsCases(): iterable
  40. {
  41. yield ['<?php function(){};', 2, 3, []];
  42. yield ['<?php foo();', 2, 3, []];
  43. yield ['<?php function($a){};', 2, 4, [3 => 3]];
  44. yield ['<?php \foo($a);', 3, 5, [4 => 4]];
  45. yield ['<?php function($a, $b){};', 2, 7, [3 => 3, 5 => 6]];
  46. yield ['<?php function($a, $b = array(1,2), $c = 3){};', 2, 23, [3 => 3, 5 => 15, 17 => 22]];
  47. yield 'non condition (hardcoded)' => [
  48. '<?php $x = strpos("foo", 123);',
  49. 6,
  50. 11,
  51. [7 => 7, 9 => 10],
  52. ];
  53. yield ['<?php \test($a+$b++, !$c);', 3, 12, [4 => 7, 9 => 11]];
  54. yield ['<?php $a = function(array &$a = array()){};', 6, 17, [7 => 16]];
  55. yield ['<?php $a = function( ... $z){};', 6, 11, [7 => 10]];
  56. yield ['<?php $a = function(array ... $a){};', 6, 12, [7 => 11]];
  57. yield ['<?php $a = function(\Foo\Bar $a, \Foo\Bar $b){};', 6, 21, [7 => 12, 14 => 20]];
  58. yield ['<?php foo($a,);', 2, 5, [3 => 3]];
  59. yield ['<?php foo($a,/**/);', 2, 6, [3 => 3]];
  60. yield ['<?php foo($a(1,2,3,4,5),);', 2, 16, [3 => 14]];
  61. yield ['<?php foo($a(1,2,3,4,5,),);', 2, 17, [3 => 15]];
  62. yield ['<?php foo($a(1,2,3,4,5,),);', 4, 15, [5 => 5, 7 => 7, 9 => 9, 11 => 11, 13 => 13]];
  63. yield ['<?php bar($a, $b , ) ;', 2, 10, [3 => 3, 5 => 7]];
  64. }
  65. /**
  66. * @param array<int, int> $arguments
  67. *
  68. * @requires PHP 8.0
  69. *
  70. * @dataProvider provideArguments80Cases
  71. */
  72. public function testArguments80(string $code, int $openIndex, int $closeIndex, array $arguments): void
  73. {
  74. $this->testArguments($code, $openIndex, $closeIndex, $arguments);
  75. }
  76. public static function provideArguments80Cases(): iterable
  77. {
  78. yield ['<?php class Foo { public function __construct(public ?string $param = null) {} }', 12, 23, [13 => 22]];
  79. yield ['<?php class Foo { public function __construct(protected ?string $param = null) {} }', 12, 23, [13 => 22]];
  80. yield ['<?php class Foo { public function __construct(private ?string $param = null) {} }', 12, 23, [13 => 22]];
  81. yield ['<?php $a = function(?\Foo\Bar $a, ?\Foo\Bar $b){};', 6, 23, [7 => 13, 15 => 22]];
  82. yield ['<?php function setFoo(null|int $param1, ?int $param2){}', 4, 16, [5 => 9, 11 => 15]];
  83. }
  84. /**
  85. * @param array<int, int> $arguments
  86. *
  87. * @requires PHP 8.1
  88. *
  89. * @dataProvider provideArguments81Cases
  90. */
  91. public function testArguments81(string $code, int $openIndex, int $closeIndex, array $arguments): void
  92. {
  93. $this->testArguments($code, $openIndex, $closeIndex, $arguments);
  94. }
  95. public static function provideArguments81Cases(): iterable
  96. {
  97. yield ['<?php function setFoo(\A\B&C $param1, C&D $param2){}', 4, 20, [5 => 12, 14 => 19]];
  98. }
  99. /**
  100. * @dataProvider provideArgumentInfoCases
  101. */
  102. public function testArgumentInfo(string $code, int $openIndex, int $closeIndex, ArgumentAnalysis $expected): void
  103. {
  104. $tokens = Tokens::fromCode($code);
  105. $analyzer = new ArgumentsAnalyzer();
  106. self::assertArgumentAnalysis($expected, $analyzer->getArgumentInfo($tokens, $openIndex, $closeIndex));
  107. }
  108. /**
  109. * @return iterable<array{string, int, int, ArgumentAnalysis}>
  110. */
  111. public static function provideArgumentInfoCases(): iterable
  112. {
  113. yield ['<?php function($a){};', 3, 3, new ArgumentAnalysis(
  114. '$a',
  115. 3,
  116. null,
  117. null
  118. )];
  119. yield ['<?php \test($a);', 4, 4, new ArgumentAnalysis(
  120. '$a',
  121. 4,
  122. null,
  123. null
  124. )];
  125. yield ['<?php function($a, $b){};', 5, 6, new ArgumentAnalysis(
  126. '$b',
  127. 6,
  128. null,
  129. null
  130. )];
  131. yield ['<?php foo($a, $b)?>', 5, 6, new ArgumentAnalysis(
  132. '$b',
  133. 6,
  134. null,
  135. null
  136. )];
  137. yield ['<?php foo($a, "b")?>', 5, 6, new ArgumentAnalysis(
  138. null,
  139. null,
  140. null,
  141. null
  142. )];
  143. yield ['<?php function($a, $b = array(1,2), $c = 3){};', 3, 3, new ArgumentAnalysis(
  144. '$a',
  145. 3,
  146. null,
  147. null
  148. )];
  149. yield ['<?php function($a, $b = array(1, /* */ 2), $c = 3){};', 5, 18, new ArgumentAnalysis(
  150. '$b',
  151. 6,
  152. 'array(1,2)',
  153. null
  154. )];
  155. yield ['<?php function($a, $b = array(1,2), $c = 3){};', 17, 22, new ArgumentAnalysis(
  156. '$c',
  157. 18,
  158. '3',
  159. null
  160. )];
  161. yield ['<?php function(array $a = array()){};', 3, 11, new ArgumentAnalysis(
  162. '$a',
  163. 5,
  164. 'array()',
  165. new TypeAnalysis(
  166. 'array',
  167. 3,
  168. 3
  169. )
  170. )];
  171. yield ['<?php function(array &$a = array()){};', 3, 12, new ArgumentAnalysis(
  172. '$a',
  173. 6,
  174. 'array()',
  175. new TypeAnalysis(
  176. 'array',
  177. 3,
  178. 3
  179. )
  180. )];
  181. yield ['<?php function( ... $z){};', 3, 6, new ArgumentAnalysis(
  182. '$z',
  183. 6,
  184. null,
  185. null
  186. )];
  187. yield ['<?php function(array ... $a){};', 3, 7, new ArgumentAnalysis(
  188. '$a',
  189. 7,
  190. null,
  191. new TypeAnalysis(
  192. 'array',
  193. 3,
  194. 3
  195. )
  196. )];
  197. yield ['<?php function(\Foo\Bar $a){};', 3, 8, new ArgumentAnalysis(
  198. '$a',
  199. 8,
  200. null,
  201. new TypeAnalysis(
  202. '\Foo\Bar',
  203. 3,
  204. 6
  205. )
  206. )];
  207. yield [
  208. '<?php function(?\Foo\Bar $a){};', 3, 9, new ArgumentAnalysis(
  209. '$a',
  210. 9,
  211. null,
  212. new TypeAnalysis(
  213. '?\Foo\Bar',
  214. 3,
  215. 7
  216. )
  217. ),
  218. ];
  219. yield ['<?php function($a, $b = \'\'){};', 5, 10, new ArgumentAnalysis(
  220. '$b',
  221. 6,
  222. "''",
  223. null
  224. )];
  225. }
  226. /**
  227. * @requires PHP 8.0
  228. *
  229. * @dataProvider provideArgumentInfo80Cases
  230. */
  231. public function testArgumentInfo80(string $code, int $openIndex, int $closeIndex, ArgumentAnalysis $expected): void
  232. {
  233. $this->testArgumentInfo($code, $openIndex, $closeIndex, $expected);
  234. }
  235. /**
  236. * @return iterable<array{string, int, int, ArgumentAnalysis}>
  237. */
  238. public static function provideArgumentInfo80Cases(): iterable
  239. {
  240. yield [
  241. '<?php function foo(#[AnAttribute] ?string $param = null) {}',
  242. 5,
  243. 16,
  244. new ArgumentAnalysis(
  245. '$param',
  246. 12,
  247. 'null',
  248. new TypeAnalysis(
  249. '?string',
  250. 9,
  251. 10
  252. )
  253. ),
  254. ];
  255. foreach (['public', 'protected', 'private'] as $visibility) {
  256. yield [
  257. \sprintf('<?php class Foo { public function __construct(%s ?string $param = null) {} }', $visibility),
  258. 13,
  259. 22,
  260. new ArgumentAnalysis(
  261. '$param',
  262. 18,
  263. 'null',
  264. new TypeAnalysis(
  265. '?string',
  266. 15,
  267. 16
  268. )
  269. ),
  270. ];
  271. }
  272. }
  273. /**
  274. * @requires PHP 8.1
  275. *
  276. * @dataProvider provideArgumentInfo81Cases
  277. */
  278. public function testArgumentInfo81(string $code, int $openIndex, int $closeIndex, ArgumentAnalysis $expected): void
  279. {
  280. $this->testArgumentInfo($code, $openIndex, $closeIndex, $expected);
  281. }
  282. /**
  283. * @return iterable<array{string, int, int, ArgumentAnalysis}>
  284. */
  285. public static function provideArgumentInfo81Cases(): iterable
  286. {
  287. yield [
  288. '<?php
  289. class Foo
  290. {
  291. public function __construct(
  292. protected readonly ?bool $nullable = true,
  293. ) {}
  294. }
  295. ',
  296. 13,
  297. 25,
  298. new ArgumentAnalysis(
  299. '$nullable',
  300. 21,
  301. 'true',
  302. new TypeAnalysis(
  303. '?bool',
  304. 18,
  305. 19
  306. )
  307. ),
  308. ];
  309. }
  310. private static function assertArgumentAnalysis(ArgumentAnalysis $expected, ArgumentAnalysis $actual): void
  311. {
  312. self::assertSame($expected->getDefault(), $actual->getDefault(), 'Default.');
  313. self::assertSame($expected->getName(), $actual->getName(), 'Name.');
  314. self::assertSame($expected->getNameIndex(), $actual->getNameIndex(), 'Name index.');
  315. self::assertSame($expected->hasDefault(), $actual->hasDefault(), 'Has default.');
  316. self::assertSame($expected->hasTypeAnalysis(), $actual->hasTypeAnalysis(), 'Has type analysis.');
  317. if ($expected->hasTypeAnalysis()) {
  318. $expectedTypeAnalysis = $expected->getTypeAnalysis();
  319. $actualTypeAnalysis = $actual->getTypeAnalysis();
  320. self::assertSame($expectedTypeAnalysis->getEndIndex(), $actualTypeAnalysis->getEndIndex(), 'Type analysis end index.');
  321. self::assertSame($expectedTypeAnalysis->getName(), $actualTypeAnalysis->getName(), 'Type analysis name.');
  322. self::assertSame($expectedTypeAnalysis->getStartIndex(), $actualTypeAnalysis->getStartIndex(), 'Type analysis start index.');
  323. self::assertSame($expectedTypeAnalysis->isNullable(), $actualTypeAnalysis->isNullable(), 'Type analysis nullable.');
  324. self::assertSame($expectedTypeAnalysis->isReservedType(), $actualTypeAnalysis->isReservedType(), 'Type analysis reserved type.');
  325. } else {
  326. self::assertNull($actual->getTypeAnalysis());
  327. }
  328. self::assertSame(serialize($expected), serialize($actual));
  329. }
  330. }