NativeFunctionInvocationFixerTest.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
  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\Fixer\FunctionNotation;
  13. use PhpCsFixer\ConfigurationException\InvalidConfigurationException;
  14. use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException;
  15. use PhpCsFixer\Fixer\FunctionNotation\NativeFunctionInvocationFixer;
  16. use PhpCsFixer\Tests\Test\AbstractFixerTestCase;
  17. /**
  18. * @author Andreas Möller <am@localheinz.com>
  19. *
  20. * @internal
  21. *
  22. * @covers \PhpCsFixer\Fixer\FunctionNotation\NativeFunctionInvocationFixer
  23. */
  24. final class NativeFunctionInvocationFixerTest extends AbstractFixerTestCase
  25. {
  26. public function testConfigureRejectsUnknownConfigurationKey(): void
  27. {
  28. $key = 'foo';
  29. $this->expectException(InvalidConfigurationException::class);
  30. $this->expectExceptionMessage(sprintf(
  31. '[native_function_invocation] Invalid configuration: The option "%s" does not exist.',
  32. $key
  33. ));
  34. $this->fixer->configure([
  35. $key => 'bar',
  36. ]);
  37. }
  38. /**
  39. * @dataProvider provideConfigureRejectsInvalidConfigurationElementCases
  40. *
  41. * @param mixed $element
  42. */
  43. public function testConfigureRejectsInvalidConfigurationElement($element): void
  44. {
  45. $this->expectException(InvalidConfigurationException::class);
  46. $this->expectExceptionMessage(sprintf(
  47. 'Each element must be a non-empty, trimmed string, got "%s" instead.',
  48. get_debug_type($element)
  49. ));
  50. $this->fixer->configure([
  51. 'exclude' => [
  52. $element,
  53. ],
  54. ]);
  55. }
  56. public static function provideConfigureRejectsInvalidConfigurationElementCases(): iterable
  57. {
  58. yield 'null' => [null];
  59. yield 'false' => [false];
  60. yield 'true' => [false];
  61. yield 'int' => [1];
  62. yield 'array' => [[]];
  63. yield 'float' => [0.1];
  64. yield 'object' => [new \stdClass()];
  65. yield 'not-trimmed' => [' is_string '];
  66. }
  67. /**
  68. * @param string[] $include
  69. *
  70. * @dataProvider provideConfigureIncludeSetsCases
  71. */
  72. public function testConfigureIncludeSets(
  73. array $include,
  74. ?string $expectedExceptionClass = null,
  75. ?string $expectedExceptionMessage = null
  76. ): void {
  77. if (null !== $expectedExceptionClass) {
  78. $this->expectException($expectedExceptionClass);
  79. $this->expectExceptionMessageMatches(sprintf('#^%s$#', preg_quote($expectedExceptionMessage, '#')));
  80. }
  81. $this->fixer->configure(['include' => $include]);
  82. if (null === $expectedExceptionClass) {
  83. $this->addToAssertionCount(1);
  84. }
  85. }
  86. public static function provideConfigureIncludeSetsCases(): iterable
  87. {
  88. yield [['foo', 'bar']];
  89. yield [[NativeFunctionInvocationFixer::SET_ALL]];
  90. yield [[NativeFunctionInvocationFixer::SET_ALL, 'bar']];
  91. yield [
  92. ['@xxx'],
  93. InvalidFixerConfigurationException::class,
  94. '[native_function_invocation] Invalid configuration: Unknown set "@xxx", known sets are "@all", "@internal" and "@compiler_optimized".',
  95. ];
  96. yield [
  97. [' x '],
  98. InvalidFixerConfigurationException::class,
  99. '[native_function_invocation] Invalid configuration: Each element must be a non-empty, trimmed string, got "string" instead.',
  100. ];
  101. }
  102. public function testConfigureResetsExclude(): void
  103. {
  104. $this->fixer->configure([
  105. 'exclude' => [
  106. 'is_string',
  107. ],
  108. ]);
  109. $before = <<<'PHP'
  110. <?php
  111. namespace WithClassNotPrefixed;
  112. class Bar
  113. {
  114. public function baz($foo)
  115. {
  116. if (isset($foo)) {
  117. is_string($foo);
  118. }
  119. }
  120. }
  121. PHP;
  122. $after = <<<'PHP'
  123. <?php
  124. namespace WithClassNotPrefixed;
  125. class Bar
  126. {
  127. public function baz($foo)
  128. {
  129. if (isset($foo)) {
  130. \is_string($foo);
  131. }
  132. }
  133. }
  134. PHP;
  135. $this->doTest($before);
  136. $this->fixer->configure([]);
  137. $this->doTest($after, $before);
  138. }
  139. /**
  140. * @dataProvider provideFixWithDefaultConfigurationCases
  141. */
  142. public function testFixWithDefaultConfiguration(string $expected, ?string $input = null): void
  143. {
  144. $this->doTest($expected, $input);
  145. }
  146. public static function provideFixWithDefaultConfigurationCases(): iterable
  147. {
  148. yield [
  149. '<?php
  150. \is_string($foo);
  151. ',
  152. ];
  153. yield [
  154. '<?php
  155. \is_string($foo);
  156. ',
  157. '<?php
  158. is_string($foo);
  159. ',
  160. ];
  161. yield [
  162. '<?php
  163. class Foo
  164. {
  165. public function bar($foo)
  166. {
  167. return \is_string($foo);
  168. }
  169. }
  170. ',
  171. ];
  172. yield [
  173. '<?php
  174. json_encode($foo);
  175. \strlen($foo);
  176. ',
  177. '<?php
  178. json_encode($foo);
  179. strlen($foo);
  180. ',
  181. ];
  182. yield [
  183. '<?php
  184. class Foo
  185. {
  186. public function bar($foo)
  187. {
  188. return \IS_STRING($foo);
  189. }
  190. }
  191. ',
  192. '<?php
  193. class Foo
  194. {
  195. public function bar($foo)
  196. {
  197. return IS_STRING($foo);
  198. }
  199. }
  200. ',
  201. ];
  202. yield 'fix multiple calls in single code' => [
  203. '<?php
  204. json_encode($foo);
  205. \strlen($foo);
  206. \strlen($foo);
  207. ',
  208. '<?php
  209. json_encode($foo);
  210. strlen($foo);
  211. strlen($foo);
  212. ',
  213. ];
  214. yield [
  215. '<?php $name = \get_class($foo, );',
  216. '<?php $name = get_class($foo, );',
  217. ];
  218. }
  219. /**
  220. * @dataProvider provideFixWithConfiguredExcludeCases
  221. */
  222. public function testFixWithConfiguredExclude(string $expected, ?string $input = null): void
  223. {
  224. $this->fixer->configure([
  225. 'exclude' => [
  226. 'is_string',
  227. ],
  228. ]);
  229. $this->doTest($expected, $input);
  230. }
  231. public static function provideFixWithConfiguredExcludeCases(): iterable
  232. {
  233. yield [
  234. '<?php
  235. is_string($foo);
  236. ',
  237. ];
  238. yield [
  239. '<?php
  240. class Foo
  241. {
  242. public function bar($foo)
  243. {
  244. return is_string($foo);
  245. }
  246. }
  247. ',
  248. ];
  249. }
  250. /**
  251. * @dataProvider provideFixWithNamespaceConfigurationCases
  252. */
  253. public function testFixWithNamespaceConfiguration(string $expected, ?string $input = null, string $scope = 'namespaced'): void
  254. {
  255. $this->fixer->configure(['scope' => $scope]);
  256. $this->doTest($expected, $input);
  257. }
  258. public static function provideFixWithNamespaceConfigurationCases(): iterable
  259. {
  260. yield [
  261. '<?php echo count([1]);',
  262. ];
  263. yield [
  264. '<?php
  265. namespace space1 { ?>
  266. <?php echo \count([2]) ?>
  267. <?php }namespace {echo count([1]);}
  268. ',
  269. '<?php
  270. namespace space1 { ?>
  271. <?php echo count([2]) ?>
  272. <?php }namespace {echo count([1]);}
  273. ',
  274. ];
  275. yield [
  276. '<?php
  277. namespace Bar {
  278. echo \strLEN("in 1");
  279. }
  280. namespace {
  281. echo strlen("out 1");
  282. }
  283. namespace {
  284. echo strlen("out 2");
  285. }
  286. namespace Bar{
  287. echo \strlen("in 2");
  288. }
  289. namespace {
  290. echo strlen("out 3");
  291. }
  292. ',
  293. '<?php
  294. namespace Bar {
  295. echo strLEN("in 1");
  296. }
  297. namespace {
  298. echo strlen("out 1");
  299. }
  300. namespace {
  301. echo strlen("out 2");
  302. }
  303. namespace Bar{
  304. echo strlen("in 2");
  305. }
  306. namespace {
  307. echo strlen("out 3");
  308. }
  309. ',
  310. ];
  311. yield [
  312. '<?php
  313. namespace space11 ?>
  314. <?php
  315. echo \strlen(__NAMESPACE__);
  316. namespace space2;
  317. echo \strlen(__NAMESPACE__);
  318. ',
  319. '<?php
  320. namespace space11 ?>
  321. <?php
  322. echo strlen(__NAMESPACE__);
  323. namespace space2;
  324. echo strlen(__NAMESPACE__);
  325. ',
  326. ];
  327. yield [
  328. '<?php namespace PhpCsFixer\Tests\Fixer\Casing;\count([1]);',
  329. '<?php namespace PhpCsFixer\Tests\Fixer\Casing;count([1]);',
  330. ];
  331. yield [
  332. '<?php
  333. namespace Space12;
  334. echo \count([1]);
  335. namespace Space2;
  336. echo \count([1]);
  337. ?>
  338. ',
  339. '<?php
  340. namespace Space12;
  341. echo count([1]);
  342. namespace Space2;
  343. echo count([1]);
  344. ?>
  345. ',
  346. ];
  347. yield [
  348. '<?php namespace {echo strlen("out 2");}',
  349. ];
  350. yield [
  351. '<?php
  352. namespace space13 {
  353. echo \strlen("in 1");
  354. }
  355. namespace space2 {
  356. echo \strlen("in 2");
  357. }
  358. namespace { // global
  359. echo strlen("global 1");
  360. }
  361. ',
  362. '<?php
  363. namespace space13 {
  364. echo strlen("in 1");
  365. }
  366. namespace space2 {
  367. echo strlen("in 2");
  368. }
  369. namespace { // global
  370. echo strlen("global 1");
  371. }
  372. ',
  373. ];
  374. yield [
  375. '<?php
  376. namespace space1 {
  377. echo \count([1]);
  378. }
  379. namespace {
  380. echo \count([1]);
  381. }
  382. ',
  383. '<?php
  384. namespace space1 {
  385. echo count([1]);
  386. }
  387. namespace {
  388. echo count([1]);
  389. }
  390. ',
  391. 'all',
  392. ];
  393. }
  394. /**
  395. * @param array<string, mixed> $configuration
  396. *
  397. * @dataProvider provideFixWithConfiguredIncludeCases
  398. */
  399. public function testFixWithConfiguredInclude(string $expected, ?string $input = null, array $configuration = []): void
  400. {
  401. $this->fixer->configure($configuration);
  402. $this->doTest($expected, $input);
  403. }
  404. public static function provideFixWithConfiguredIncludeCases(): iterable
  405. {
  406. yield 'include set + 1, exclude 1' => [
  407. '<?php
  408. echo \count([1]);
  409. \some_other($a, 3);
  410. echo strlen($a);
  411. not_me();
  412. ',
  413. '<?php
  414. echo count([1]);
  415. some_other($a, 3);
  416. echo strlen($a);
  417. not_me();
  418. ',
  419. [
  420. 'include' => [NativeFunctionInvocationFixer::SET_INTERNAL, 'some_other'],
  421. 'exclude' => ['strlen'],
  422. ],
  423. ];
  424. yield 'include @all' => [
  425. '<?php
  426. echo \count([1]);
  427. \some_other($a, 3);
  428. echo \strlen($a);
  429. \me_as_well();
  430. ',
  431. '<?php
  432. echo count([1]);
  433. some_other($a, 3);
  434. echo strlen($a);
  435. me_as_well();
  436. ',
  437. [
  438. 'include' => [NativeFunctionInvocationFixer::SET_ALL],
  439. ],
  440. ];
  441. yield 'include @compiler_optimized' => [
  442. '<?php
  443. // do not fix
  444. $a = strrev($a);
  445. $a .= str_repeat($a, 4);
  446. $b = already_prefixed_function();
  447. // fix
  448. $c = \get_class($d);
  449. $e = \intval($f);
  450. ',
  451. '<?php
  452. // do not fix
  453. $a = strrev($a);
  454. $a .= str_repeat($a, 4);
  455. $b = \already_prefixed_function();
  456. // fix
  457. $c = get_class($d);
  458. $e = intval($f);
  459. ',
  460. [
  461. 'include' => [NativeFunctionInvocationFixer::SET_COMPILER_OPTIMIZED],
  462. ],
  463. ];
  464. yield [
  465. '<?php class Foo {
  466. public function & strlen($name) {
  467. }
  468. }
  469. ',
  470. ];
  471. yield 'scope namespaced and strict enabled' => [
  472. '<?php
  473. $a = not_compiler_optimized_function();
  474. $b = intval($c);
  475. ',
  476. '<?php
  477. $a = \not_compiler_optimized_function();
  478. $b = \intval($c);
  479. ',
  480. [
  481. 'scope' => 'namespaced',
  482. 'strict' => true,
  483. ],
  484. ];
  485. yield [
  486. '<?php
  487. use function foo\json_decode;
  488. json_decode($base);
  489. ',
  490. null,
  491. [
  492. 'include' => [NativeFunctionInvocationFixer::SET_ALL],
  493. ],
  494. ];
  495. if (\PHP_VERSION_ID < 8_00_00) {
  496. yield 'include @compiler_optimized with strict enabled' => [
  497. '<?php
  498. $a = not_compiler_optimized_function();
  499. $b = not_compiler_optimized_function();
  500. $c = \intval($d);
  501. ',
  502. '<?php
  503. $a = \not_compiler_optimized_function();
  504. $b = \ not_compiler_optimized_function();
  505. $c = intval($d);
  506. ',
  507. [
  508. 'include' => [NativeFunctionInvocationFixer::SET_COMPILER_OPTIMIZED],
  509. 'strict' => true,
  510. ],
  511. ];
  512. }
  513. }
  514. /**
  515. * @requires PHP <8.0
  516. */
  517. public function testFixPrePHP80(): void
  518. {
  519. $this->doTest(
  520. '<?php
  521. echo \/**/strlen($a);
  522. echo \ strlen($a);
  523. echo \#
  524. #
  525. strlen($a);
  526. echo \strlen($a);
  527. ',
  528. '<?php
  529. echo \/**/strlen($a);
  530. echo \ strlen($a);
  531. echo \#
  532. #
  533. strlen($a);
  534. echo strlen($a);
  535. '
  536. );
  537. }
  538. /**
  539. * @param array<string, mixed> $config
  540. *
  541. * @dataProvider provideFix80Cases
  542. *
  543. * @requires PHP 8.0
  544. */
  545. public function testFix80(string $expected, ?string $input = null, array $config = []): void
  546. {
  547. $this->fixer->configure($config);
  548. $this->doTest($expected, $input);
  549. }
  550. public static function provideFix80Cases(): iterable
  551. {
  552. yield 'attribute and strict' => [
  553. '<?php
  554. #[\Attribute(\Attribute::TARGET_CLASS)]
  555. class Foo {}
  556. ',
  557. null,
  558. ['strict' => true],
  559. ];
  560. yield 'null safe operator' => ['<?php $x?->count();'];
  561. yield 'multiple function-calls-like in attribute' => [
  562. '<?php
  563. #[Foo(), Bar(), Baz()]
  564. class Foo {}
  565. ',
  566. null,
  567. ['include' => ['@all']],
  568. ];
  569. }
  570. }