NativeFunctionInvocationFixerTest.php 17 KB

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