UtilsTest.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  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;
  13. use PhpCsFixer\Fixer\FixerInterface;
  14. use PhpCsFixer\Tokenizer\Token;
  15. use PhpCsFixer\Utils;
  16. use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
  17. /**
  18. * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
  19. * @author Graham Campbell <hello@gjcampbell.co.uk>
  20. * @author Odín del Río <odin.drp@gmail.com>
  21. *
  22. * @internal
  23. *
  24. * @covers \PhpCsFixer\Utils
  25. */
  26. final class UtilsTest extends TestCase
  27. {
  28. use ExpectDeprecationTrait;
  29. /**
  30. * @var null|false|string
  31. */
  32. private $originalValueOfFutureMode;
  33. protected function setUp(): void
  34. {
  35. $this->originalValueOfFutureMode = getenv('PHP_CS_FIXER_FUTURE_MODE');
  36. }
  37. protected function tearDown(): void
  38. {
  39. putenv("PHP_CS_FIXER_FUTURE_MODE={$this->originalValueOfFutureMode}");
  40. }
  41. /**
  42. * @param string $expected Camel case string
  43. *
  44. * @dataProvider provideCamelCaseToUnderscoreCases
  45. */
  46. public function testCamelCaseToUnderscore(string $expected, string $input = null): void
  47. {
  48. if (null !== $input) {
  49. self::assertSame($expected, Utils::camelCaseToUnderscore($input));
  50. }
  51. self::assertSame($expected, Utils::camelCaseToUnderscore($expected));
  52. }
  53. public static function provideCamelCaseToUnderscoreCases(): iterable
  54. {
  55. return [
  56. [
  57. 'dollar_close_curly_braces',
  58. 'DollarCloseCurlyBraces',
  59. ],
  60. [
  61. 'utf8_encoder_fixer',
  62. 'utf8EncoderFixer',
  63. ],
  64. [
  65. 'terminated_with_number10',
  66. 'TerminatedWithNumber10',
  67. ],
  68. [
  69. 'utf8_encoder_fixer',
  70. ],
  71. [
  72. 'a',
  73. 'A',
  74. ],
  75. [
  76. 'aa',
  77. 'AA',
  78. ],
  79. [
  80. 'foo',
  81. 'FOO',
  82. ],
  83. [
  84. 'foo_bar_baz',
  85. 'FooBarBAZ',
  86. ],
  87. [
  88. 'foo_bar_baz',
  89. 'FooBARBaz',
  90. ],
  91. [
  92. 'foo_bar_baz',
  93. 'FOOBarBaz',
  94. ],
  95. [
  96. 'mr_t',
  97. 'MrT',
  98. ],
  99. [
  100. 'voyage_éclair',
  101. 'VoyageÉclair',
  102. ],
  103. ];
  104. }
  105. /**
  106. * @param array{int, string}|string $input token prototype
  107. *
  108. * @dataProvider provideCalculateTrailingWhitespaceIndentCases
  109. */
  110. public function testCalculateTrailingWhitespaceIndent(string $spaces, $input): void
  111. {
  112. $token = new Token($input);
  113. self::assertSame($spaces, Utils::calculateTrailingWhitespaceIndent($token));
  114. }
  115. public static function provideCalculateTrailingWhitespaceIndentCases(): iterable
  116. {
  117. return [
  118. [' ', [T_WHITESPACE, "\n\n "]],
  119. [' ', [T_WHITESPACE, "\r\n\r\r\r "]],
  120. ["\t", [T_WHITESPACE, "\r\n\t"]],
  121. ['', [T_WHITESPACE, "\t\n\r"]],
  122. ['', [T_WHITESPACE, "\n"]],
  123. ['', ''],
  124. ];
  125. }
  126. public function testCalculateTrailingWhitespaceIndentFail(): void
  127. {
  128. $this->expectException(\InvalidArgumentException::class);
  129. $this->expectExceptionMessage('The given token must be whitespace, got "T_STRING".');
  130. $token = new Token([T_STRING, 'foo']);
  131. Utils::calculateTrailingWhitespaceIndent($token);
  132. }
  133. /**
  134. * @param list<mixed> $expected
  135. * @param list<mixed> $elements
  136. *
  137. * @dataProvider provideStableSortCases
  138. */
  139. public function testStableSort(
  140. array $expected,
  141. array $elements,
  142. callable $getComparableValueCallback,
  143. callable $compareValuesCallback
  144. ): void {
  145. self::assertSame(
  146. $expected,
  147. Utils::stableSort($elements, $getComparableValueCallback, $compareValuesCallback)
  148. );
  149. }
  150. public static function provideStableSortCases(): iterable
  151. {
  152. return [
  153. [
  154. ['a', 'b', 'c', 'd', 'e'],
  155. ['b', 'd', 'e', 'a', 'c'],
  156. static fn ($element) => $element,
  157. 'strcmp',
  158. ],
  159. [
  160. ['b', 'd', 'e', 'a', 'c'],
  161. ['b', 'd', 'e', 'a', 'c'],
  162. static fn (): string => 'foo',
  163. 'strcmp',
  164. ],
  165. [
  166. ['b', 'd', 'e', 'a', 'c'],
  167. ['b', 'd', 'e', 'a', 'c'],
  168. static fn ($element) => $element,
  169. static fn (): int => 0,
  170. ],
  171. [
  172. ['bar1', 'baz1', 'foo1', 'bar2', 'baz2', 'foo2'],
  173. ['foo1', 'foo2', 'bar1', 'bar2', 'baz1', 'baz2'],
  174. static fn ($element) => preg_replace('/([a-z]+)(\d+)/', '$2$1', $element),
  175. 'strcmp',
  176. ],
  177. ];
  178. }
  179. public function testSortFixers(): void
  180. {
  181. $fixers = [
  182. $this->createFixerDouble('f1', 0),
  183. $this->createFixerDouble('f2', -10),
  184. $this->createFixerDouble('f3', 10),
  185. $this->createFixerDouble('f4', -10),
  186. ];
  187. self::assertSame(
  188. [
  189. $fixers[2],
  190. $fixers[0],
  191. $fixers[1],
  192. $fixers[3],
  193. ],
  194. Utils::sortFixers($fixers)
  195. );
  196. }
  197. public function testNaturalLanguageJoinThrowsInvalidArgumentExceptionForEmptyArray(): void
  198. {
  199. $this->expectException(\InvalidArgumentException::class);
  200. $this->expectExceptionMessage('Array of names cannot be empty.');
  201. Utils::naturalLanguageJoin([]);
  202. }
  203. public function testNaturalLanguageJoinThrowsInvalidArgumentExceptionForMoreThanOneCharWrapper(): void
  204. {
  205. $this->expectException(\InvalidArgumentException::class);
  206. $this->expectExceptionMessage('Wrapper should be a single-char string or empty.');
  207. Utils::naturalLanguageJoin(['a', 'b'], 'foo');
  208. }
  209. /**
  210. * @dataProvider provideNaturalLanguageJoinCases
  211. *
  212. * @param list<string> $names
  213. */
  214. public function testNaturalLanguageJoin(string $joined, array $names, string $wrapper = '"'): void
  215. {
  216. self::assertSame($joined, Utils::naturalLanguageJoin($names, $wrapper));
  217. }
  218. /**
  219. * @return iterable<array<null|array<string>|string>>
  220. */
  221. public static function provideNaturalLanguageJoinCases(): iterable
  222. {
  223. yield [
  224. '"a"',
  225. ['a'],
  226. ];
  227. yield [
  228. '"a" and "b"',
  229. ['a', 'b'],
  230. ];
  231. yield [
  232. '"a", "b" and "c"',
  233. ['a', 'b', 'c'],
  234. ];
  235. yield [
  236. '\'a\'',
  237. ['a'],
  238. '\'',
  239. ];
  240. yield [
  241. '\'a\' and \'b\'',
  242. ['a', 'b'],
  243. '\'',
  244. ];
  245. yield [
  246. '\'a\', \'b\' and \'c\'',
  247. ['a', 'b', 'c'],
  248. '\'',
  249. ];
  250. yield [
  251. '?a?',
  252. ['a'],
  253. '?',
  254. ];
  255. yield [
  256. '?a? and ?b?',
  257. ['a', 'b'],
  258. '?',
  259. ];
  260. yield [
  261. '?a?, ?b? and ?c?',
  262. ['a', 'b', 'c'],
  263. '?',
  264. ];
  265. yield [
  266. 'a',
  267. ['a'],
  268. '',
  269. ];
  270. yield [
  271. 'a and b',
  272. ['a', 'b'],
  273. '',
  274. ];
  275. yield [
  276. 'a, b and c',
  277. ['a', 'b', 'c'],
  278. '',
  279. ];
  280. }
  281. public function testNaturalLanguageJoinWithBackticksThrowsInvalidArgumentExceptionForEmptyArray(): void
  282. {
  283. $this->expectException(\InvalidArgumentException::class);
  284. Utils::naturalLanguageJoinWithBackticks([]);
  285. }
  286. /**
  287. * @param list<string> $names
  288. *
  289. * @dataProvider provideNaturalLanguageJoinWithBackticksCases
  290. */
  291. public function testNaturalLanguageJoinWithBackticks(string $joined, array $names): void
  292. {
  293. self::assertSame($joined, Utils::naturalLanguageJoinWithBackticks($names));
  294. }
  295. public static function provideNaturalLanguageJoinWithBackticksCases(): iterable
  296. {
  297. return [
  298. [
  299. '`a`',
  300. ['a'],
  301. ],
  302. [
  303. '`a` and `b`',
  304. ['a', 'b'],
  305. ],
  306. [
  307. '`a`, `b` and `c`',
  308. ['a', 'b', 'c'],
  309. ],
  310. ];
  311. }
  312. /**
  313. * @group legacy
  314. */
  315. public function testTriggerDeprecationWhenFutureModeIsOff(): void
  316. {
  317. putenv('PHP_CS_FIXER_FUTURE_MODE=0');
  318. $message = __METHOD__.'::The message';
  319. $this->expectDeprecation($message);
  320. Utils::triggerDeprecation(new \DomainException($message));
  321. $triggered = Utils::getTriggeredDeprecations();
  322. self::assertContains($message, $triggered);
  323. }
  324. public function testTriggerDeprecationWhenFutureModeIsOn(): void
  325. {
  326. putenv('PHP_CS_FIXER_FUTURE_MODE=1');
  327. $message = __METHOD__.'::The message';
  328. $exception = new \DomainException($message);
  329. $futureModeException = null;
  330. try {
  331. Utils::triggerDeprecation($exception);
  332. } catch (\Exception $futureModeException) {
  333. }
  334. self::assertInstanceOf(\RuntimeException::class, $futureModeException);
  335. self::assertSame($exception, $futureModeException->getPrevious());
  336. $triggered = Utils::getTriggeredDeprecations();
  337. self::assertNotContains($message, $triggered);
  338. }
  339. /**
  340. * @param mixed $input
  341. *
  342. * @dataProvider provideToStringCases
  343. */
  344. public function testToString(string $expected, $input): void
  345. {
  346. self::assertSame($expected, Utils::toString($input));
  347. }
  348. public static function provideToStringCases(): iterable
  349. {
  350. yield ["['a' => 3, 'b' => 'c']", ['a' => 3, 'b' => 'c']];
  351. yield ['[[1], [2]]', [[1], [2]]];
  352. yield ['[0 => [1], \'a\' => [2]]', [[1], 'a' => [2]]];
  353. yield ['[1, 2, \'foo\', null]', [1, 2, 'foo', null]];
  354. yield ['[1, 2]', [1, 2]];
  355. yield ['[]', []];
  356. yield ['1.5', 1.5];
  357. yield ['false', false];
  358. yield ['true', true];
  359. yield ['1', 1];
  360. yield ["'foo'", 'foo'];
  361. }
  362. private function createFixerDouble(string $name, int $priority): FixerInterface
  363. {
  364. $fixer = $this->prophesize(FixerInterface::class);
  365. $fixer->getName()->willReturn($name);
  366. $fixer->getPriority()->willReturn($priority);
  367. return $fixer->reveal();
  368. }
  369. }