conventions.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Gengkun He @ahabhgk
  4. */
  5. "use strict";
  6. /**
  7. * @import {
  8. * CssGeneratorExportsConvention
  9. * } from "../../declarations/WebpackOptions"
  10. */
  11. // Copy from css-loader
  12. /**
  13. * Preserve camel case.
  14. * @param {string} string string
  15. * @returns {string} result
  16. */
  17. const preserveCamelCase = (string) => {
  18. let result = string;
  19. let isLastCharLower = false;
  20. let isLastCharUpper = false;
  21. let isLastLastCharUpper = false;
  22. for (let i = 0; i < result.length; i++) {
  23. const character = result[i];
  24. if (isLastCharLower && /\p{Lu}/u.test(character)) {
  25. result = `${result.slice(0, i)}-${result.slice(i)}`;
  26. isLastCharLower = false;
  27. isLastLastCharUpper = isLastCharUpper;
  28. isLastCharUpper = true;
  29. i += 1;
  30. } else if (
  31. isLastCharUpper &&
  32. isLastLastCharUpper &&
  33. /\p{Ll}/u.test(character)
  34. ) {
  35. result = `${result.slice(0, i - 1)}-${result.slice(i - 1)}`;
  36. isLastLastCharUpper = isLastCharUpper;
  37. isLastCharUpper = false;
  38. isLastCharLower = true;
  39. } else {
  40. isLastCharLower =
  41. character.toLowerCase() === character &&
  42. character.toUpperCase() !== character;
  43. isLastLastCharUpper = isLastCharUpper;
  44. isLastCharUpper =
  45. character.toUpperCase() === character &&
  46. character.toLowerCase() !== character;
  47. }
  48. }
  49. return result;
  50. };
  51. // Copy from css-loader
  52. /**
  53. * Returns result.
  54. * @param {string} input input
  55. * @returns {string} result
  56. */
  57. module.exports.camelCase = (input) => {
  58. let result = input.trim();
  59. if (result.length === 0) {
  60. return "";
  61. }
  62. if (result.length === 1) {
  63. return result.toLowerCase();
  64. }
  65. const hasUpperCase = result !== result.toLowerCase();
  66. if (hasUpperCase) {
  67. result = preserveCamelCase(result);
  68. }
  69. return result
  70. .replace(/^[_.\- ]+/, "")
  71. .toLowerCase()
  72. .replace(/[_.\- ]+([\p{Alpha}\p{N}_]|$)/gu, (_, p1) => p1.toUpperCase())
  73. .replace(/\d+([\p{Alpha}\p{N}_]|$)/gu, (m) => m.toUpperCase());
  74. };
  75. /**
  76. * Safely stringify an arbitrary value for an error message — falls back to
  77. * `String(...)` when JSON.stringify would throw (BigInt, circular, etc.).
  78. * @param {EXPECTED_ANY} value value to stringify
  79. * @returns {string} stringified value
  80. */
  81. const safeStringify = (value) => {
  82. try {
  83. const json = JSON.stringify(value);
  84. if (json !== undefined) return json;
  85. } catch (_err) {
  86. // fall through to String fallback
  87. }
  88. try {
  89. return String(value);
  90. } catch (_err) {
  91. return "[value cannot be converted to string]";
  92. }
  93. };
  94. /**
  95. * Returns results.
  96. * @param {string} input input
  97. * @param {CssGeneratorExportsConvention | undefined} convention convention
  98. * @returns {string[]} results
  99. */
  100. module.exports.cssExportConvention = (input, convention) => {
  101. /** @type {Set<string>} */
  102. const set = new Set();
  103. if (typeof convention === "function") {
  104. const result = convention(input);
  105. const validate = (/** @type {string} */ name) => {
  106. if (typeof name !== "string" || name.length === 0) {
  107. throw new Error(
  108. `exportsConvention function must return a non-empty string or an array of non-empty strings, got ${safeStringify(result)}`
  109. );
  110. }
  111. };
  112. if (Array.isArray(result)) {
  113. if (result.length === 0) {
  114. throw new Error(
  115. "exportsConvention function returned an empty array; it must return at least one name"
  116. );
  117. }
  118. for (const name of result) {
  119. validate(name);
  120. set.add(name);
  121. }
  122. } else {
  123. validate(result);
  124. set.add(result);
  125. }
  126. } else {
  127. switch (convention) {
  128. case "camel-case": {
  129. set.add(input);
  130. set.add(module.exports.camelCase(input));
  131. break;
  132. }
  133. case "camel-case-only": {
  134. set.add(module.exports.camelCase(input));
  135. break;
  136. }
  137. case "dashes": {
  138. set.add(input);
  139. set.add(module.exports.dashesCamelCase(input));
  140. break;
  141. }
  142. case "dashes-only": {
  143. set.add(module.exports.dashesCamelCase(input));
  144. break;
  145. }
  146. case "as-is": {
  147. set.add(input);
  148. break;
  149. }
  150. }
  151. }
  152. return [...set];
  153. };
  154. // Copy from css-loader
  155. /**
  156. * Returns result.
  157. * @param {string} input input
  158. * @returns {string} result
  159. */
  160. module.exports.dashesCamelCase = (input) =>
  161. input.replace(/-+(\w)/g, (match, firstLetter) => firstLetter.toUpperCase());