NativeConstantInvocationFixerTest.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  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\ConstantNotation;
  13. use PhpCsFixer\ConfigurationException\InvalidConfigurationException;
  14. use PhpCsFixer\Tests\Test\AbstractFixerTestCase;
  15. /**
  16. * @author Filippo Tessarotto <zoeslam@gmail.com>
  17. *
  18. * @internal
  19. *
  20. * @covers \PhpCsFixer\Fixer\ConstantNotation\NativeConstantInvocationFixer
  21. */
  22. final class NativeConstantInvocationFixerTest extends AbstractFixerTestCase
  23. {
  24. public function testConfigureRejectsUnknownConfigurationKey(): void
  25. {
  26. $key = 'foo';
  27. $this->expectException(InvalidConfigurationException::class);
  28. $this->expectExceptionMessage(sprintf('[native_constant_invocation] Invalid configuration: The option "%s" does not exist.', $key));
  29. $this->fixer->configure([
  30. $key => 'bar',
  31. ]);
  32. }
  33. /**
  34. * @dataProvider provideInvalidConfigurationElementCases
  35. *
  36. * @param mixed $element
  37. */
  38. public function testConfigureRejectsInvalidExcludeConfigurationElement($element): void
  39. {
  40. $this->expectException(InvalidConfigurationException::class);
  41. $this->expectExceptionMessage(sprintf(
  42. 'Each element must be a non-empty, trimmed string, got "%s" instead.',
  43. get_debug_type($element)
  44. ));
  45. $this->fixer->configure([
  46. 'exclude' => [
  47. $element,
  48. ],
  49. ]);
  50. }
  51. /**
  52. * @dataProvider provideInvalidConfigurationElementCases
  53. *
  54. * @param mixed $element
  55. */
  56. public function testConfigureRejectsInvalidIncludeConfigurationElement($element): void
  57. {
  58. $this->expectException(InvalidConfigurationException::class);
  59. $this->expectExceptionMessage(sprintf(
  60. 'Each element must be a non-empty, trimmed string, got "%s" instead.',
  61. get_debug_type($element)
  62. ));
  63. $this->fixer->configure([
  64. 'include' => [
  65. $element,
  66. ],
  67. ]);
  68. }
  69. public static function provideInvalidConfigurationElementCases(): iterable
  70. {
  71. yield 'null' => [null];
  72. yield 'false' => [false];
  73. yield 'true' => [true];
  74. yield 'int' => [1];
  75. yield 'array' => [[]];
  76. yield 'float' => [0.1];
  77. yield 'object' => [new \stdClass()];
  78. yield 'not-trimmed' => [' M_PI '];
  79. }
  80. public function testConfigureResetsExclude(): void
  81. {
  82. $this->fixer->configure([
  83. 'exclude' => [
  84. 'M_PI',
  85. ],
  86. ]);
  87. $before = '<?php var_dump(m_pi, M_PI);';
  88. $after = '<?php var_dump(m_pi, \\M_PI);';
  89. $this->doTest($before);
  90. $this->fixer->configure([]);
  91. $this->doTest($after, $before);
  92. }
  93. /**
  94. * @dataProvider provideFixWithDefaultConfigurationCases
  95. */
  96. public function testFixWithDefaultConfiguration(string $expected, ?string $input = null): void
  97. {
  98. $this->doTest($expected, $input);
  99. }
  100. public static function provideFixWithDefaultConfigurationCases(): iterable
  101. {
  102. yield ['<?php var_dump(NULL, FALSE, TRUE, 1);'];
  103. yield ['<?php echo CUSTOM_DEFINED_CONSTANT_123;'];
  104. yield ['<?php echo m_pi; // Constant are case sensitive'];
  105. yield ['<?php namespace M_PI;'];
  106. yield ['<?php namespace Foo; use M_PI;'];
  107. yield ['<?php class M_PI {}'];
  108. yield ['<?php class Foo extends M_PI {}'];
  109. yield ['<?php class Foo implements M_PI {}'];
  110. yield ['<?php interface M_PI {};'];
  111. yield ['<?php trait M_PI {};'];
  112. yield ['<?php class Foo { const M_PI = 1; }'];
  113. yield ['<?php class Foo { use M_PI; }'];
  114. yield ['<?php class Foo { public $M_PI = 1; }'];
  115. yield ['<?php class Foo { function M_PI($M_PI) {} }'];
  116. yield ['<?php class Foo { function bar() { $M_PI = M_PI() + self::M_PI(); } }'];
  117. yield ['<?php class Foo { function bar() { $this->M_PI(self::M_PI); } }'];
  118. yield ['<?php namespace Foo; use M_PI;'];
  119. yield ['<?php namespace Foo; use Bar as M_PI;'];
  120. yield ['<?php echo Foo\\M_PI\\Bar;'];
  121. yield ['<?php M_PI::foo();'];
  122. yield ['<?php function x(M_PI $foo, M_PI &$bar, M_PI ...$baz) {}'];
  123. yield ['<?php $foo instanceof M_PI;'];
  124. yield ['<?php class x implements FOO, M_PI, BAZ {}'];
  125. yield ['<?php class Foo { use Bar, M_PI { Bar::baz insteadof M_PI; } }'];
  126. yield ['<?php M_PI: goto M_PI;'];
  127. yield [
  128. '<?php echo \\M_PI;',
  129. '<?php echo M_PI;',
  130. ];
  131. yield [
  132. '<?php namespace Foo; use M_PI; echo \\M_PI;',
  133. '<?php namespace Foo; use M_PI; echo M_PI;',
  134. ];
  135. yield [
  136. // Here we are just testing the algorithm.
  137. // A user likely would add this M_PI to its excluded list.
  138. '<?php namespace M_PI; const M_PI = 1; return \\M_PI;',
  139. '<?php namespace M_PI; const M_PI = 1; return M_PI;',
  140. ];
  141. yield [
  142. '<?php foo(\E_DEPRECATED | \E_USER_DEPRECATED);',
  143. '<?php foo(E_DEPRECATED | E_USER_DEPRECATED);',
  144. ];
  145. yield ['<?php function foo(): M_PI {}'];
  146. yield ['<?php use X\Y\{FOO, BAR as BAR2, M_PI};'];
  147. yield [
  148. '<?php
  149. try {
  150. foo(\JSON_ERROR_DEPTH|\JSON_PRETTY_PRINT|JOB_QUEUE_PRIORITY_HIGH);
  151. } catch (\Exception | \InvalidArgumentException|\UnexpectedValueException|LogicException $e) {
  152. }
  153. ',
  154. '<?php
  155. try {
  156. foo(\JSON_ERROR_DEPTH|JSON_PRETTY_PRINT|\JOB_QUEUE_PRIORITY_HIGH);
  157. } catch (\Exception | \InvalidArgumentException|\UnexpectedValueException|LogicException $e) {
  158. }
  159. ',
  160. ];
  161. }
  162. /**
  163. * @dataProvider provideFixWithConfiguredCustomIncludeCases
  164. */
  165. public function testFixWithConfiguredCustomInclude(string $expected, ?string $input = null): void
  166. {
  167. $this->fixer->configure([
  168. 'include' => [
  169. 'FOO_BAR_BAZ',
  170. ],
  171. ]);
  172. $this->doTest($expected, $input);
  173. }
  174. public static function provideFixWithConfiguredCustomIncludeCases(): iterable
  175. {
  176. yield [
  177. '<?php echo \\FOO_BAR_BAZ . \\M_PI;',
  178. '<?php echo FOO_BAR_BAZ . M_PI;',
  179. ];
  180. yield [
  181. '<?php class Foo { public function bar($foo) { return \\FOO_BAR_BAZ . \\M_PI; } }',
  182. '<?php class Foo { public function bar($foo) { return FOO_BAR_BAZ . M_PI; } }',
  183. ];
  184. }
  185. /**
  186. * @dataProvider provideFixWithConfiguredOnlyIncludeCases
  187. */
  188. public function testFixWithConfiguredOnlyInclude(string $expected, ?string $input = null): void
  189. {
  190. $this->fixer->configure([
  191. 'fix_built_in' => false,
  192. 'include' => [
  193. 'M_PI',
  194. ],
  195. ]);
  196. $this->doTest($expected, $input);
  197. }
  198. public static function provideFixWithConfiguredOnlyIncludeCases(): iterable
  199. {
  200. yield [
  201. '<?php echo PHP_SAPI . FOO_BAR_BAZ . \\M_PI;',
  202. '<?php echo PHP_SAPI . FOO_BAR_BAZ . M_PI;',
  203. ];
  204. yield [
  205. '<?php class Foo { public function bar($foo) { return PHP_SAPI . FOO_BAR_BAZ . \\M_PI; } }',
  206. '<?php class Foo { public function bar($foo) { return PHP_SAPI . FOO_BAR_BAZ . M_PI; } }',
  207. ];
  208. }
  209. /**
  210. * @dataProvider provideFixWithConfiguredExcludeCases
  211. */
  212. public function testFixWithConfiguredExclude(string $expected, ?string $input = null): void
  213. {
  214. $this->fixer->configure([
  215. 'exclude' => [
  216. 'M_PI',
  217. ],
  218. ]);
  219. $this->doTest($expected, $input);
  220. }
  221. public static function provideFixWithConfiguredExcludeCases(): iterable
  222. {
  223. yield [
  224. '<?php echo \\PHP_SAPI . M_PI;',
  225. '<?php echo PHP_SAPI . M_PI;',
  226. ];
  227. yield [
  228. '<?php class Foo { public function bar($foo) { return \\PHP_SAPI . M_PI; } }',
  229. '<?php class Foo { public function bar($foo) { return PHP_SAPI . M_PI; } }',
  230. ];
  231. }
  232. public function testNullTrueFalseAreCaseInsensitive(): void
  233. {
  234. $this->fixer->configure([
  235. 'fix_built_in' => false,
  236. 'include' => [
  237. 'null',
  238. 'false',
  239. 'M_PI',
  240. 'M_pi',
  241. ],
  242. 'exclude' => [],
  243. ]);
  244. $expected = <<<'EOT'
  245. <?php
  246. var_dump(
  247. \null,
  248. \NULL,
  249. \Null,
  250. \nUlL,
  251. \false,
  252. \FALSE,
  253. true,
  254. TRUE,
  255. \M_PI,
  256. \M_pi,
  257. m_pi,
  258. m_PI
  259. );
  260. EOT;
  261. $input = <<<'EOT'
  262. <?php
  263. var_dump(
  264. null,
  265. NULL,
  266. Null,
  267. nUlL,
  268. false,
  269. FALSE,
  270. true,
  271. TRUE,
  272. M_PI,
  273. M_pi,
  274. m_pi,
  275. m_PI
  276. );
  277. EOT;
  278. $this->doTest($expected, $input);
  279. }
  280. public function testDoNotIncludeUserConstantsUnlessExplicitlyListed(): void
  281. {
  282. $uniqueConstantName = uniqid(self::class);
  283. $uniqueConstantName = preg_replace('/\W+/', '_', $uniqueConstantName);
  284. $uniqueConstantName = strtoupper($uniqueConstantName);
  285. $dontFixMe = 'DONTFIXME_'.$uniqueConstantName;
  286. $fixMe = 'FIXME_'.$uniqueConstantName;
  287. \define($dontFixMe, 1);
  288. \define($fixMe, 1);
  289. $this->fixer->configure([
  290. 'fix_built_in' => true,
  291. 'include' => [
  292. $fixMe,
  293. ],
  294. 'exclude' => [],
  295. ]);
  296. $expected = <<<EOT
  297. <?php
  298. var_dump(
  299. \\null,
  300. {$dontFixMe},
  301. \\{$fixMe}
  302. );
  303. EOT;
  304. $input = <<<EOT
  305. <?php
  306. var_dump(
  307. null,
  308. {$dontFixMe},
  309. {$fixMe}
  310. );
  311. EOT;
  312. $this->doTest($expected, $input);
  313. }
  314. public function testDoNotFixImportedConstants(): void
  315. {
  316. $this->fixer->configure([
  317. 'fix_built_in' => false,
  318. 'include' => [
  319. 'M_PI',
  320. 'M_EULER',
  321. ],
  322. 'exclude' => [],
  323. ]);
  324. $expected = <<<'EOT'
  325. <?php
  326. namespace Foo;
  327. use const M_EULER;
  328. var_dump(
  329. null,
  330. \M_PI,
  331. M_EULER
  332. );
  333. EOT;
  334. $input = <<<'EOT'
  335. <?php
  336. namespace Foo;
  337. use const M_EULER;
  338. var_dump(
  339. null,
  340. M_PI,
  341. M_EULER
  342. );
  343. EOT;
  344. $this->doTest($expected, $input);
  345. }
  346. public function testFixScopedOnly(): void
  347. {
  348. $this->fixer->configure(['scope' => 'namespaced']);
  349. $expected = <<<'EOT'
  350. <?php
  351. namespace space1 {
  352. echo \PHP_VERSION;
  353. }
  354. namespace {
  355. echo PHP_VERSION;
  356. }
  357. EOT;
  358. $input = <<<'EOT'
  359. <?php
  360. namespace space1 {
  361. echo PHP_VERSION;
  362. }
  363. namespace {
  364. echo PHP_VERSION;
  365. }
  366. EOT;
  367. $this->doTest($expected, $input);
  368. }
  369. public function testFixScopedOnlyNoNamespace(): void
  370. {
  371. $this->fixer->configure(['scope' => 'namespaced']);
  372. $expected = <<<'EOT'
  373. <?php
  374. echo PHP_VERSION . PHP_EOL;
  375. EOT;
  376. $this->doTest($expected);
  377. }
  378. public function testFixStrictOption(): void
  379. {
  380. $this->fixer->configure(['strict' => true]);
  381. $this->doTest(
  382. '<?php
  383. echo \PHP_VERSION . \PHP_EOL; // built-in constants to have backslash
  384. echo MY_FRAMEWORK_MAJOR_VERSION . MY_FRAMEWORK_MINOR_VERSION; // non-built-in constants not to have backslash
  385. echo \Dont\Touch\Namespaced\CONSTANT;
  386. ',
  387. '<?php
  388. echo \PHP_VERSION . PHP_EOL; // built-in constants to have backslash
  389. echo \MY_FRAMEWORK_MAJOR_VERSION . MY_FRAMEWORK_MINOR_VERSION; // non-built-in constants not to have backslash
  390. echo \Dont\Touch\Namespaced\CONSTANT;
  391. '
  392. );
  393. }
  394. /**
  395. * @requires PHP <8.0
  396. */
  397. public function testFixPrePHP80(): void
  398. {
  399. $this->doTest(
  400. '<?php
  401. echo \\/**/M_PI;
  402. echo \\ M_PI;
  403. echo \\#
  404. #
  405. M_PI;
  406. echo \\M_PI;
  407. ',
  408. '<?php
  409. echo \\/**/M_PI;
  410. echo \\ M_PI;
  411. echo \\#
  412. #
  413. M_PI;
  414. echo M_PI;
  415. '
  416. );
  417. }
  418. /**
  419. * @dataProvider provideFixPhp80Cases
  420. *
  421. * @requires PHP 8.0
  422. */
  423. public function testFixPhp80(string $expected): void
  424. {
  425. $this->fixer->configure(['strict' => true]);
  426. $this->doTest($expected);
  427. }
  428. public static function provideFixPhp80Cases(): iterable
  429. {
  430. yield [
  431. '<?php
  432. try {
  433. } catch (\Exception) {
  434. }',
  435. ];
  436. yield ['<?php try { foo(); } catch(\InvalidArgumentException|\LogicException $e) {}'];
  437. yield ['<?php try { foo(); } catch(\InvalidArgumentException|\LogicException) {}'];
  438. }
  439. }