NativeConstantInvocationFixerTest.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  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. * @extends AbstractFixerTestCase<\PhpCsFixer\Fixer\ConstantNotation\NativeConstantInvocationFixer>
  23. *
  24. * @phpstan-import-type _AutogeneratedInputConfiguration from \PhpCsFixer\Fixer\ConstantNotation\NativeConstantInvocationFixer
  25. */
  26. final class NativeConstantInvocationFixerTest extends AbstractFixerTestCase
  27. {
  28. public function testConfigureRejectsUnknownConfigurationKey(): void
  29. {
  30. $key = 'foo';
  31. $this->expectException(InvalidConfigurationException::class);
  32. $this->expectExceptionMessage(\sprintf('[native_constant_invocation] Invalid configuration: The option "%s" does not exist.', $key));
  33. $this->fixer->configure([
  34. $key => 'bar',
  35. ]);
  36. }
  37. /**
  38. * @dataProvider provideInvalidConfigurationElementCases
  39. *
  40. * @param mixed $element
  41. */
  42. public function testConfigureRejectsInvalidExcludeConfigurationElement($element, string $expectedExceptionMessage): void
  43. {
  44. $this->expectException(InvalidConfigurationException::class);
  45. $this->expectExceptionMessage($expectedExceptionMessage);
  46. $this->fixer->configure([
  47. 'exclude' => [
  48. $element,
  49. ],
  50. ]);
  51. }
  52. /**
  53. * @dataProvider provideInvalidConfigurationElementCases
  54. *
  55. * @param mixed $element
  56. */
  57. public function testConfigureRejectsInvalidIncludeConfigurationElement($element, string $expectedExceptionMessage): void
  58. {
  59. $this->expectException(InvalidConfigurationException::class);
  60. $this->expectExceptionMessage(
  61. str_replace('"exclude"', '"include"', $expectedExceptionMessage)
  62. );
  63. $this->fixer->configure([
  64. 'include' => [
  65. $element,
  66. ],
  67. ]);
  68. }
  69. public static function provideInvalidConfigurationElementCases(): iterable
  70. {
  71. yield 'null' => [
  72. null,
  73. '[native_constant_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".',
  74. ];
  75. yield 'false' => [
  76. false,
  77. '[native_constant_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".',
  78. ];
  79. yield 'true' => [
  80. true,
  81. '[native_constant_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".',
  82. ];
  83. yield 'int' => [
  84. 1,
  85. '[native_constant_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".',
  86. ];
  87. yield 'array' => [
  88. [],
  89. '[native_constant_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".',
  90. ];
  91. yield 'float' => [
  92. 0.1,
  93. '[native_constant_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".',
  94. ];
  95. yield 'object' => [
  96. new \stdClass(),
  97. '[native_constant_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".',
  98. ];
  99. yield 'not-trimmed' => [
  100. ' M_PI ',
  101. '[native_constant_invocation] Invalid configuration: Each element must be a non-empty, trimmed string, got "string" instead.',
  102. ];
  103. }
  104. public function testConfigureResetsExclude(): void
  105. {
  106. $this->fixer->configure([
  107. 'exclude' => [
  108. 'M_PI',
  109. ],
  110. ]);
  111. $before = '<?php var_dump(m_pi, M_PI);';
  112. $after = '<?php var_dump(m_pi, \M_PI);';
  113. $this->doTest($before);
  114. $this->fixer->configure([]);
  115. $this->doTest($after, $before);
  116. }
  117. /**
  118. * @dataProvider provideFixWithDefaultConfigurationCases
  119. */
  120. public function testFixWithDefaultConfiguration(string $expected, ?string $input = null): void
  121. {
  122. $this->doTest($expected, $input);
  123. }
  124. /**
  125. * @return iterable<array{0: string, 1?: string}>
  126. */
  127. public static function provideFixWithDefaultConfigurationCases(): iterable
  128. {
  129. yield ['<?php var_dump(NULL, FALSE, TRUE, 1);'];
  130. yield ['<?php echo CUSTOM_DEFINED_CONSTANT_123;'];
  131. yield ['<?php echo m_pi; // Constant are case sensitive'];
  132. yield ['<?php namespace M_PI;'];
  133. yield ['<?php namespace Foo; use M_PI;'];
  134. yield ['<?php class M_PI {}'];
  135. yield ['<?php class Foo extends M_PI {}'];
  136. yield ['<?php class Foo implements M_PI {}'];
  137. yield ['<?php interface M_PI {};'];
  138. yield ['<?php trait M_PI {};'];
  139. yield ['<?php class Foo { const M_PI = 1; }'];
  140. yield ['<?php class Foo { use M_PI; }'];
  141. yield ['<?php class Foo { public $M_PI = 1; }'];
  142. yield ['<?php class Foo { function M_PI($M_PI) {} }'];
  143. yield ['<?php class Foo { function bar() { $M_PI = M_PI() + self::M_PI(); } }'];
  144. yield ['<?php class Foo { function bar() { $this->M_PI(self::M_PI); } }'];
  145. yield ['<?php namespace Foo; use Bar as M_PI;'];
  146. yield ['<?php echo Foo\M_PI\Bar;'];
  147. yield ['<?php M_PI::foo();'];
  148. yield ['<?php function x(M_PI $foo, M_PI &$bar, M_PI ...$baz) {}'];
  149. yield ['<?php $foo instanceof M_PI;'];
  150. yield ['<?php class x implements FOO, M_PI, BAZ {}'];
  151. yield ['<?php class Foo { use Bar, M_PI { Bar::baz insteadof M_PI; } }'];
  152. yield ['<?php M_PI: goto M_PI;'];
  153. yield [
  154. '<?php echo \M_PI;',
  155. '<?php echo M_PI;',
  156. ];
  157. yield [
  158. '<?php namespace Foo; use M_PI; echo \M_PI;',
  159. '<?php namespace Foo; use M_PI; echo M_PI;',
  160. ];
  161. yield [
  162. // Here we are just testing the algorithm.
  163. // A user likely would add this M_PI to its excluded list.
  164. '<?php namespace M_PI; const M_PI = 1; return \M_PI;',
  165. '<?php namespace M_PI; const M_PI = 1; return M_PI;',
  166. ];
  167. yield [
  168. '<?php foo(\E_DEPRECATED | \E_USER_DEPRECATED);',
  169. '<?php foo(E_DEPRECATED | E_USER_DEPRECATED);',
  170. ];
  171. yield ['<?php function foo(): M_PI {}'];
  172. yield ['<?php use X\Y\{FOO, BAR as BAR2, M_PI};'];
  173. yield [
  174. '<?php
  175. try {
  176. foo(\JSON_ERROR_DEPTH|\JSON_PRETTY_PRINT|JOB_QUEUE_PRIORITY_HIGH);
  177. } catch (\Exception | \InvalidArgumentException|\UnexpectedValueException|LogicException $e) {
  178. }
  179. ',
  180. '<?php
  181. try {
  182. foo(\JSON_ERROR_DEPTH|JSON_PRETTY_PRINT|\JOB_QUEUE_PRIORITY_HIGH);
  183. } catch (\Exception | \InvalidArgumentException|\UnexpectedValueException|LogicException $e) {
  184. }
  185. ',
  186. ];
  187. }
  188. /**
  189. * @dataProvider provideFixWithConfiguredCustomIncludeCases
  190. */
  191. public function testFixWithConfiguredCustomInclude(string $expected, ?string $input = null): void
  192. {
  193. $this->fixer->configure([
  194. 'include' => [
  195. 'FOO_BAR_BAZ',
  196. ],
  197. ]);
  198. $this->doTest($expected, $input);
  199. }
  200. /**
  201. * @return iterable<array{string, string}>
  202. */
  203. public static function provideFixWithConfiguredCustomIncludeCases(): iterable
  204. {
  205. yield [
  206. '<?php echo \FOO_BAR_BAZ . \M_PI;',
  207. '<?php echo FOO_BAR_BAZ . M_PI;',
  208. ];
  209. yield [
  210. '<?php class Foo { public function bar($foo) { return \FOO_BAR_BAZ . \M_PI; } }',
  211. '<?php class Foo { public function bar($foo) { return FOO_BAR_BAZ . M_PI; } }',
  212. ];
  213. }
  214. /**
  215. * @dataProvider provideFixWithConfiguredOnlyIncludeCases
  216. */
  217. public function testFixWithConfiguredOnlyInclude(string $expected, ?string $input = null): void
  218. {
  219. $this->fixer->configure([
  220. 'fix_built_in' => false,
  221. 'include' => [
  222. 'M_PI',
  223. ],
  224. ]);
  225. $this->doTest($expected, $input);
  226. }
  227. /**
  228. * @return iterable<array{string, string}>
  229. */
  230. public static function provideFixWithConfiguredOnlyIncludeCases(): iterable
  231. {
  232. yield [
  233. '<?php echo PHP_SAPI . FOO_BAR_BAZ . \M_PI;',
  234. '<?php echo PHP_SAPI . FOO_BAR_BAZ . M_PI;',
  235. ];
  236. yield [
  237. '<?php class Foo { public function bar($foo) { return PHP_SAPI . FOO_BAR_BAZ . \M_PI; } }',
  238. '<?php class Foo { public function bar($foo) { return PHP_SAPI . FOO_BAR_BAZ . M_PI; } }',
  239. ];
  240. }
  241. /**
  242. * @dataProvider provideFixWithConfiguredExcludeCases
  243. */
  244. public function testFixWithConfiguredExclude(string $expected, ?string $input = null): void
  245. {
  246. $this->fixer->configure([
  247. 'exclude' => [
  248. 'M_PI',
  249. ],
  250. ]);
  251. $this->doTest($expected, $input);
  252. }
  253. /**
  254. * @return iterable<array{string, string}>
  255. */
  256. public static function provideFixWithConfiguredExcludeCases(): iterable
  257. {
  258. yield [
  259. '<?php echo \PHP_SAPI . M_PI;',
  260. '<?php echo PHP_SAPI . M_PI;',
  261. ];
  262. yield [
  263. '<?php class Foo { public function bar($foo) { return \PHP_SAPI . M_PI; } }',
  264. '<?php class Foo { public function bar($foo) { return PHP_SAPI . M_PI; } }',
  265. ];
  266. }
  267. public function testNullTrueFalseAreCaseInsensitive(): void
  268. {
  269. $this->fixer->configure([
  270. 'fix_built_in' => false,
  271. 'include' => [
  272. 'null',
  273. 'false',
  274. 'M_PI',
  275. 'M_pi',
  276. ],
  277. 'exclude' => [],
  278. ]);
  279. $expected = <<<'EOT'
  280. <?php
  281. var_dump(
  282. \null,
  283. \NULL,
  284. \Null,
  285. \nUlL,
  286. \false,
  287. \FALSE,
  288. true,
  289. TRUE,
  290. \M_PI,
  291. \M_pi,
  292. m_pi,
  293. m_PI
  294. );
  295. EOT;
  296. $input = <<<'EOT'
  297. <?php
  298. var_dump(
  299. null,
  300. NULL,
  301. Null,
  302. nUlL,
  303. false,
  304. FALSE,
  305. true,
  306. TRUE,
  307. M_PI,
  308. M_pi,
  309. m_pi,
  310. m_PI
  311. );
  312. EOT;
  313. $this->doTest($expected, $input);
  314. }
  315. public function testDoNotIncludeUserConstantsUnlessExplicitlyListed(): void
  316. {
  317. $uniqueConstantName = uniqid(self::class);
  318. $uniqueConstantName = preg_replace('/\W+/', '_', $uniqueConstantName);
  319. $uniqueConstantName = strtoupper($uniqueConstantName);
  320. $dontFixMe = 'DONTFIXME_'.$uniqueConstantName;
  321. $fixMe = 'FIXME_'.$uniqueConstantName;
  322. \define($dontFixMe, 1);
  323. \define($fixMe, 1);
  324. $this->fixer->configure([
  325. 'fix_built_in' => true,
  326. 'include' => [
  327. $fixMe,
  328. ],
  329. 'exclude' => [],
  330. ]);
  331. $expected = <<<EOT
  332. <?php
  333. var_dump(
  334. \\null,
  335. {$dontFixMe},
  336. \\{$fixMe}
  337. );
  338. EOT;
  339. $input = <<<EOT
  340. <?php
  341. var_dump(
  342. null,
  343. {$dontFixMe},
  344. {$fixMe}
  345. );
  346. EOT;
  347. $this->doTest($expected, $input);
  348. }
  349. public function testDoNotFixImportedConstants(): void
  350. {
  351. $this->fixer->configure([
  352. 'fix_built_in' => false,
  353. 'include' => [
  354. 'M_PI',
  355. 'M_EULER',
  356. ],
  357. 'exclude' => [],
  358. ]);
  359. $expected = <<<'EOT'
  360. <?php
  361. namespace Foo;
  362. use const M_EULER;
  363. var_dump(
  364. null,
  365. \M_PI,
  366. M_EULER
  367. );
  368. EOT;
  369. $input = <<<'EOT'
  370. <?php
  371. namespace Foo;
  372. use const M_EULER;
  373. var_dump(
  374. null,
  375. M_PI,
  376. M_EULER
  377. );
  378. EOT;
  379. $this->doTest($expected, $input);
  380. }
  381. public function testFixScopedOnly(): void
  382. {
  383. $this->fixer->configure(['scope' => 'namespaced']);
  384. $expected = <<<'EOT'
  385. <?php
  386. namespace space1 {
  387. echo \PHP_VERSION;
  388. }
  389. namespace {
  390. echo PHP_VERSION;
  391. }
  392. EOT;
  393. $input = <<<'EOT'
  394. <?php
  395. namespace space1 {
  396. echo PHP_VERSION;
  397. }
  398. namespace {
  399. echo PHP_VERSION;
  400. }
  401. EOT;
  402. $this->doTest($expected, $input);
  403. }
  404. public function testFixScopedOnlyNoNamespace(): void
  405. {
  406. $this->fixer->configure(['scope' => 'namespaced']);
  407. $expected = <<<'EOT'
  408. <?php
  409. echo PHP_VERSION . PHP_EOL;
  410. EOT;
  411. $this->doTest($expected);
  412. }
  413. public function testFixStrictOption(): void
  414. {
  415. $this->fixer->configure(['strict' => true]);
  416. $this->doTest(
  417. '<?php
  418. echo \PHP_VERSION . \PHP_EOL; // built-in constants to have backslash
  419. echo MY_FRAMEWORK_MAJOR_VERSION . MY_FRAMEWORK_MINOR_VERSION; // non-built-in constants not to have backslash
  420. echo \Dont\Touch\Namespaced\CONSTANT;
  421. ',
  422. '<?php
  423. echo \PHP_VERSION . PHP_EOL; // built-in constants to have backslash
  424. echo \MY_FRAMEWORK_MAJOR_VERSION . MY_FRAMEWORK_MINOR_VERSION; // non-built-in constants not to have backslash
  425. echo \Dont\Touch\Namespaced\CONSTANT;
  426. '
  427. );
  428. }
  429. /**
  430. * @requires PHP <8.0
  431. */
  432. public function testFixPrePHP80(): void
  433. {
  434. $this->doTest(
  435. '<?php
  436. echo \/**/M_PI;
  437. echo \ M_PI;
  438. echo \#
  439. #
  440. M_PI;
  441. echo \M_PI;
  442. ',
  443. '<?php
  444. echo \/**/M_PI;
  445. echo \ M_PI;
  446. echo \#
  447. #
  448. M_PI;
  449. echo M_PI;
  450. '
  451. );
  452. }
  453. /**
  454. * @dataProvider provideFixPhp80Cases
  455. *
  456. * @requires PHP 8.0
  457. */
  458. public function testFixPhp80(string $expected): void
  459. {
  460. $this->fixer->configure(['strict' => true]);
  461. $this->doTest($expected);
  462. }
  463. /**
  464. * @return iterable<array{string}>
  465. */
  466. public static function provideFixPhp80Cases(): iterable
  467. {
  468. yield [
  469. '<?php
  470. try {
  471. } catch (\Exception) {
  472. }',
  473. ];
  474. yield ['<?php try { foo(); } catch(\InvalidArgumentException|\LogicException $e) {}'];
  475. yield ['<?php try { foo(); } catch(\InvalidArgumentException|\LogicException) {}'];
  476. }
  477. /**
  478. * @dataProvider provideFixPhp81Cases
  479. *
  480. * @requires PHP 8.1
  481. */
  482. public function testFixPhp81(string $expected): void
  483. {
  484. $this->fixer->configure(['strict' => true]);
  485. $this->doTest($expected);
  486. }
  487. /**
  488. * @return iterable<array{string}>
  489. */
  490. public static function provideFixPhp81Cases(): iterable
  491. {
  492. yield [
  493. '<?php enum someEnum: int
  494. {
  495. case E_ALL = 123;
  496. }',
  497. ];
  498. }
  499. /**
  500. * @dataProvider provideFixPhp82Cases
  501. *
  502. * @requires PHP 8.2
  503. */
  504. public function testFixPhp82(string $expected): void
  505. {
  506. $this->fixer->configure(['strict' => true]);
  507. $this->doTest($expected);
  508. }
  509. /**
  510. * @return iterable<array{0: string}>
  511. */
  512. public static function provideFixPhp82Cases(): iterable
  513. {
  514. yield ['<?php class Foo { public (\A&B)|(C&\D)|E\F|\G|(A&H\I)|(A&\J\K) $var; }'];
  515. yield ['<?php function foo ((\A&B)|(C&\D)|E\F|\G|(A&H\I)|(A&\J\K) $var) {}'];
  516. }
  517. /**
  518. * @dataProvider provideFixPhp83Cases
  519. *
  520. * @requires PHP 8.3
  521. */
  522. public function testFixPhp83(string $expected, string $input): void
  523. {
  524. $this->fixer->configure(['strict' => true]);
  525. $this->doTest($expected, $input);
  526. }
  527. /**
  528. * @return iterable<array{0: string, 1: string}>
  529. */
  530. public static function provideFixPhp83Cases(): iterable
  531. {
  532. yield [
  533. '<?php class Foo {
  534. public const string C1 = \PHP_EOL;
  535. protected const string|int C2 = \PHP_EOL;
  536. private const string|(A&B) C3 = BAR;
  537. public const EnumA C4 = EnumA::FOO;
  538. private const array CONNECTION_TIMEOUT = [\'foo\'];
  539. }',
  540. '<?php class Foo {
  541. public const string C1 = PHP_EOL;
  542. protected const string|int C2 = \PHP_EOL;
  543. private const string|(A&B) C3 = \BAR;
  544. public const EnumA C4 = EnumA::FOO;
  545. private const array CONNECTION_TIMEOUT = [\'foo\'];
  546. }',
  547. ];
  548. }
  549. }