Utils.php 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  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;
  13. use PhpCsFixer\Fixer\FixerInterface;
  14. use PhpCsFixer\Tokenizer\Token;
  15. /**
  16. * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
  17. * @author Graham Campbell <hello@gjcampbell.co.uk>
  18. * @author Odín del Río <odin.drp@gmail.com>
  19. *
  20. * @internal
  21. *
  22. * @deprecated This is a God Class anti-pattern. Don't expand it. It is fine to use logic that is already here (that's why we don't trigger deprecation warnings), but over time logic should be moved to dedicated, single-responsibility classes.
  23. */
  24. final class Utils
  25. {
  26. /**
  27. * @var array<string, true>
  28. */
  29. private static array $deprecations = [];
  30. private function __construct()
  31. {
  32. // cannot create instance of util. class
  33. }
  34. /**
  35. * Converts a camel cased string to a snake cased string.
  36. */
  37. public static function camelCaseToUnderscore(string $string): string
  38. {
  39. return mb_strtolower(Preg::replace('/(?<!^)(?<!_)((?=[\p{Lu}][^\p{Lu}])|(?<![\p{Lu}])(?=[\p{Lu}]))/', '_', $string));
  40. }
  41. /**
  42. * Calculate the trailing whitespace.
  43. *
  44. * What we're doing here is grabbing everything after the final newline.
  45. */
  46. public static function calculateTrailingWhitespaceIndent(Token $token): string
  47. {
  48. if (!$token->isWhitespace()) {
  49. throw new \InvalidArgumentException(sprintf('The given token must be whitespace, got "%s".', $token->getName()));
  50. }
  51. $str = strrchr(
  52. str_replace(["\r\n", "\r"], "\n", $token->getContent()),
  53. "\n"
  54. );
  55. if (false === $str) {
  56. return '';
  57. }
  58. return ltrim($str, "\n");
  59. }
  60. /**
  61. * Perform stable sorting using provided comparison function.
  62. *
  63. * Stability is ensured by using Schwartzian transform.
  64. *
  65. * @template T
  66. * @template R
  67. *
  68. * @param list<T> $elements
  69. * @param callable(T): R $getComparedValue a callable that takes a single element and returns the value to compare
  70. * @param callable(R, R): int $compareValues a callable that compares two values
  71. *
  72. * @return list<T>
  73. */
  74. public static function stableSort(array $elements, callable $getComparedValue, callable $compareValues): array
  75. {
  76. array_walk($elements, static function (&$element, int $index) use ($getComparedValue): void {
  77. $element = [$element, $index, $getComparedValue($element)];
  78. });
  79. usort($elements, static function ($a, $b) use ($compareValues): int {
  80. $comparison = $compareValues($a[2], $b[2]);
  81. if (0 !== $comparison) {
  82. return $comparison;
  83. }
  84. return $a[1] <=> $b[1];
  85. });
  86. return array_map(static fn (array $item) => $item[0], $elements);
  87. }
  88. /**
  89. * Sort fixers by their priorities.
  90. *
  91. * @param list<FixerInterface> $fixers
  92. *
  93. * @return list<FixerInterface>
  94. */
  95. public static function sortFixers(array $fixers): array
  96. {
  97. // Schwartzian transform is used to improve the efficiency and avoid
  98. // `usort(): Array was modified by the user comparison function` warning for mocked objects.
  99. return self::stableSort(
  100. $fixers,
  101. static fn (FixerInterface $fixer): int => $fixer->getPriority(),
  102. static fn (int $a, int $b): int => $b <=> $a
  103. );
  104. }
  105. /**
  106. * Join names in natural language using specified wrapper (double quote by default).
  107. *
  108. * @param list<string> $names
  109. *
  110. * @throws \InvalidArgumentException
  111. */
  112. public static function naturalLanguageJoin(array $names, string $wrapper = '"'): string
  113. {
  114. if (0 === \count($names)) {
  115. throw new \InvalidArgumentException('Array of names cannot be empty.');
  116. }
  117. if (\strlen($wrapper) > 1) {
  118. throw new \InvalidArgumentException('Wrapper should be a single-char string or empty.');
  119. }
  120. $names = array_map(static fn (string $name): string => sprintf('%2$s%1$s%2$s', $name, $wrapper), $names);
  121. $last = array_pop($names);
  122. if (\count($names) > 0) {
  123. return implode(', ', $names).' and '.$last;
  124. }
  125. return $last;
  126. }
  127. /**
  128. * Join names in natural language wrapped in backticks, e.g. `a`, `b` and `c`.
  129. *
  130. * @param list<string> $names
  131. *
  132. * @throws \InvalidArgumentException
  133. */
  134. public static function naturalLanguageJoinWithBackticks(array $names): string
  135. {
  136. return self::naturalLanguageJoin($names, '`');
  137. }
  138. public static function isFutureModeEnabled(): bool
  139. {
  140. return filter_var(
  141. getenv('PHP_CS_FIXER_FUTURE_MODE'),
  142. FILTER_VALIDATE_BOOL
  143. );
  144. }
  145. public static function triggerDeprecation(\Exception $futureException): void
  146. {
  147. if (self::isFutureModeEnabled()) {
  148. throw new \RuntimeException(
  149. 'Your are using something deprecated, see previous exception. Aborting execution because `PHP_CS_FIXER_FUTURE_MODE` environment variable is set.',
  150. 0,
  151. $futureException
  152. );
  153. }
  154. $message = $futureException->getMessage();
  155. self::$deprecations[$message] = true;
  156. @trigger_error($message, E_USER_DEPRECATED);
  157. }
  158. /**
  159. * @return list<string>
  160. */
  161. public static function getTriggeredDeprecations(): array
  162. {
  163. $triggeredDeprecations = array_keys(self::$deprecations);
  164. sort($triggeredDeprecations);
  165. return $triggeredDeprecations;
  166. }
  167. /**
  168. * @param mixed $value
  169. */
  170. public static function toString($value): string
  171. {
  172. return \is_array($value)
  173. ? self::arrayToString($value)
  174. : self::scalarToString($value);
  175. }
  176. /**
  177. * @param mixed $value
  178. */
  179. private static function scalarToString($value): string
  180. {
  181. $str = var_export($value, true);
  182. return Preg::replace('/\bNULL\b/', 'null', $str);
  183. }
  184. /**
  185. * @param array<array-key, mixed> $value
  186. */
  187. private static function arrayToString(array $value): string
  188. {
  189. if (0 === \count($value)) {
  190. return '[]';
  191. }
  192. $isHash = !array_is_list($value);
  193. $str = '[';
  194. foreach ($value as $k => $v) {
  195. if ($isHash) {
  196. $str .= self::scalarToString($k).' => ';
  197. }
  198. $str .= \is_array($v)
  199. ? self::arrayToString($v).', '
  200. : self::scalarToString($v).', ';
  201. }
  202. return substr($str, 0, -2).']';
  203. }
  204. }