@paulirish/trace_engine 0.0.10 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/analyze-trace.mjs +1 -1
  2. package/core/platform/DevToolsPath.d.ts +4 -13
  3. package/core/platform/DevToolsPath.js +7 -4
  4. package/core/platform/DevToolsPath.js.map +1 -7
  5. package/core/platform/MimeType.d.ts +27 -0
  6. package/core/platform/MimeType.js +119 -86
  7. package/core/platform/MimeType.js.map +1 -7
  8. package/core/platform/Timing.d.ts +7 -0
  9. package/core/platform/Timing.js +7 -4
  10. package/core/platform/Timing.js.map +1 -7
  11. package/core/platform/UIString.d.ts +2 -5
  12. package/core/platform/UIString.js +5 -2
  13. package/core/platform/UIString.js.map +1 -7
  14. package/core/platform/UserVisibleError.js +19 -10
  15. package/core/platform/UserVisibleError.js.map +1 -7
  16. package/core/platform/array-utilities.d.ts +48 -10
  17. package/core/platform/array-utilities.js +160 -124
  18. package/core/platform/array-utilities.js.map +1 -7
  19. package/core/platform/brand.d.ts +14 -0
  20. package/core/platform/brand.js +5 -1
  21. package/core/platform/brand.js.map +1 -7
  22. package/core/platform/date-utilities.js +10 -6
  23. package/core/platform/date-utilities.js.map +1 -7
  24. package/core/platform/dom-utilities.d.ts +3 -1
  25. package/core/platform/dom-utilities.js +94 -83
  26. package/core/platform/dom-utilities.js.map +1 -7
  27. package/core/platform/keyboard-utilities.d.ts +2 -0
  28. package/core/platform/keyboard-utilities.js +15 -24
  29. package/core/platform/keyboard-utilities.js.map +1 -7
  30. package/core/platform/map-utilities.d.ts +4 -0
  31. package/core/platform/map-utilities.js +66 -60
  32. package/core/platform/map-utilities.js.map +1 -7
  33. package/core/platform/number-utilities.js +66 -55
  34. package/core/platform/number-utilities.js.map +1 -7
  35. package/core/platform/platform.d.ts +5 -1
  36. package/core/platform/platform.js +54 -37
  37. package/core/platform/platform.js.map +1 -7
  38. package/core/platform/promise-utilities.d.ts +10 -0
  39. package/core/platform/promise-utilities.js +16 -8
  40. package/core/platform/promise-utilities.js.map +1 -7
  41. package/core/platform/set-utilities.js +20 -17
  42. package/core/platform/set-utilities.js.map +1 -7
  43. package/core/platform/string-utilities.d.ts +32 -1
  44. package/core/platform/string-utilities.js +453 -379
  45. package/core/platform/string-utilities.js.map +1 -7
  46. package/core/platform/typescript-utilities.d.ts +5 -5
  47. package/core/platform/typescript-utilities.js +19 -7
  48. package/core/platform/typescript-utilities.js.map +1 -7
  49. package/generated/protocol.d.ts +2081 -347
  50. package/generated/protocol.js +5 -2230
  51. package/models/cpu_profile/CPUProfileDataModel.d.ts +77 -0
  52. package/models/cpu_profile/CPUProfileDataModel.js +492 -359
  53. package/models/cpu_profile/CPUProfileDataModel.js.map +1 -7
  54. package/models/cpu_profile/ProfileTreeModel.d.ts +29 -0
  55. package/models/cpu_profile/ProfileTreeModel.js +87 -82
  56. package/models/cpu_profile/ProfileTreeModel.js.map +1 -7
  57. package/models/cpu_profile/cpu_profile.d.ts +3 -0
  58. package/models/cpu_profile/cpu_profile.js +7 -7
  59. package/models/cpu_profile/cpu_profile.js.map +1 -7
  60. package/models/trace/EntriesFilter.d.ts +55 -0
  61. package/models/trace/EntriesFilter.js +227 -166
  62. package/models/trace/EntriesFilter.js.map +1 -7
  63. package/models/trace/LegacyTracingModel.js.map +1 -7
  64. package/models/trace/ModelImpl.d.ts +110 -0
  65. package/models/trace/ModelImpl.js +161 -102
  66. package/models/trace/ModelImpl.js.map +1 -7
  67. package/models/trace/Processor.d.ts +36 -0
  68. package/models/trace/Processor.js +197 -163
  69. package/models/trace/Processor.js.map +1 -7
  70. package/models/trace/TracingManager.js.map +1 -7
  71. package/models/trace/extras/FetchNodes.d.ts +46 -0
  72. package/models/trace/extras/FetchNodes.js +132 -91
  73. package/models/trace/extras/FetchNodes.js.map +1 -7
  74. package/models/trace/extras/FilmStrip.d.ts +19 -0
  75. package/models/trace/extras/FilmStrip.js +38 -31
  76. package/models/trace/extras/FilmStrip.js.map +1 -7
  77. package/models/trace/extras/MainThreadActivity.d.ts +2 -0
  78. package/models/trace/extras/MainThreadActivity.js +72 -56
  79. package/models/trace/extras/MainThreadActivity.js.map +1 -7
  80. package/models/trace/extras/Metadata.d.ts +2 -0
  81. package/models/trace/extras/Metadata.js +42 -26
  82. package/models/trace/extras/Metadata.js.map +1 -7
  83. package/models/trace/extras/extras.js.map +1 -7
  84. package/models/trace/handlers/AnimationHandler.d.ts +8 -0
  85. package/models/trace/handlers/AnimationHandler.js +22 -20
  86. package/models/trace/handlers/AnimationHandler.js.map +1 -7
  87. package/models/trace/handlers/AuctionWorkletsHandler.d.ts +8 -0
  88. package/models/trace/handlers/AuctionWorkletsHandler.js +143 -89
  89. package/models/trace/handlers/AuctionWorkletsHandler.js.map +1 -7
  90. package/models/trace/handlers/FramesHandler.d.ts +76 -0
  91. package/models/trace/handlers/FramesHandler.js +424 -355
  92. package/models/trace/handlers/FramesHandler.js.map +1 -7
  93. package/models/trace/handlers/GPUHandler.d.ts +11 -0
  94. package/models/trace/handlers/GPUHandler.js +41 -37
  95. package/models/trace/handlers/GPUHandler.js.map +1 -7
  96. package/models/trace/handlers/InitiatorsHandler.d.ts +10 -0
  97. package/models/trace/handlers/InitiatorsHandler.js +164 -113
  98. package/models/trace/handlers/InitiatorsHandler.js.map +1 -7
  99. package/models/trace/handlers/InvalidationsHandler.d.ts +10 -0
  100. package/models/trace/handlers/InvalidationsHandler.js +101 -79
  101. package/models/trace/handlers/InvalidationsHandler.js.map +1 -7
  102. package/models/trace/handlers/LargestImagePaintHandler.d.ts +5 -0
  103. package/models/trace/handlers/LargestImagePaintHandler.js +32 -12
  104. package/models/trace/handlers/LargestImagePaintHandler.js.map +1 -7
  105. package/models/trace/handlers/LargestTextPaintHandler.d.ts +5 -0
  106. package/models/trace/handlers/LargestTextPaintHandler.js +20 -12
  107. package/models/trace/handlers/LargestTextPaintHandler.js.map +1 -7
  108. package/models/trace/handlers/LayerTreeHandler.d.ts +13 -0
  109. package/models/trace/handlers/LayerTreeHandler.js +96 -70
  110. package/models/trace/handlers/LayerTreeHandler.js.map +1 -7
  111. package/models/trace/handlers/LayoutShiftsHandler.d.ts +44 -0
  112. package/models/trace/handlers/LayoutShiftsHandler.js +304 -227
  113. package/models/trace/handlers/LayoutShiftsHandler.js.map +1 -7
  114. package/models/trace/handlers/MemoryHandler.d.ts +7 -0
  115. package/models/trace/handlers/MemoryHandler.js +14 -11
  116. package/models/trace/handlers/MemoryHandler.js.map +1 -7
  117. package/models/trace/handlers/MetaHandler.d.ts +37 -0
  118. package/models/trace/handlers/MetaHandler.js +314 -226
  119. package/models/trace/handlers/MetaHandler.js.map +1 -7
  120. package/models/trace/handlers/ModelHandlers.d.ts +21 -0
  121. package/models/trace/handlers/ModelHandlers.js +25 -22
  122. package/models/trace/handlers/ModelHandlers.js.map +1 -7
  123. package/models/trace/handlers/NetworkRequestsHandler.d.ts +17 -0
  124. package/models/trace/handlers/NetworkRequestsHandler.js +342 -218
  125. package/models/trace/handlers/NetworkRequestsHandler.js.map +1 -7
  126. package/models/trace/handlers/PageLoadMetricsHandler.d.ts +67 -0
  127. package/models/trace/handlers/PageLoadMetricsHandler.js +357 -284
  128. package/models/trace/handlers/PageLoadMetricsHandler.js.map +1 -7
  129. package/models/trace/handlers/RendererHandler.d.ts +101 -0
  130. package/models/trace/handlers/RendererHandler.js +295 -191
  131. package/models/trace/handlers/RendererHandler.js.map +1 -7
  132. package/models/trace/handlers/SamplesHandler.d.ts +46 -0
  133. package/models/trace/handlers/SamplesHandler.js +195 -158
  134. package/models/trace/handlers/SamplesHandler.js.map +1 -7
  135. package/models/trace/handlers/ScreenshotsHandler.d.ts +7 -0
  136. package/models/trace/handlers/ScreenshotsHandler.js +63 -41
  137. package/models/trace/handlers/ScreenshotsHandler.js.map +1 -7
  138. package/models/trace/handlers/Threads.d.ts +33 -0
  139. package/models/trace/handlers/Threads.js +85 -67
  140. package/models/trace/handlers/Threads.js.map +1 -7
  141. package/models/trace/handlers/UserInteractionsHandler.d.ts +57 -0
  142. package/models/trace/handlers/UserInteractionsHandler.js +240 -141
  143. package/models/trace/handlers/UserInteractionsHandler.js.map +1 -7
  144. package/models/trace/handlers/UserTimingsHandler.d.ts +28 -0
  145. package/models/trace/handlers/UserTimingsHandler.js +91 -80
  146. package/models/trace/handlers/UserTimingsHandler.js.map +1 -7
  147. package/models/trace/handlers/WarningsHandler.d.ts +14 -0
  148. package/models/trace/handlers/WarningsHandler.js +100 -62
  149. package/models/trace/handlers/WarningsHandler.js.map +1 -7
  150. package/models/trace/handlers/WorkersHandler.d.ts +11 -0
  151. package/models/trace/handlers/WorkersHandler.js +40 -38
  152. package/models/trace/handlers/WorkersHandler.js.map +1 -7
  153. package/models/trace/handlers/handlers.d.ts +3 -0
  154. package/models/trace/handlers/handlers.js +7 -4
  155. package/models/trace/handlers/handlers.js.map +1 -7
  156. package/models/trace/handlers/types.d.ts +45 -0
  157. package/models/trace/handlers/types.js +15 -15
  158. package/models/trace/handlers/types.js.map +1 -7
  159. package/models/trace/helpers/SamplesIntegrator.d.ts +49 -0
  160. package/models/trace/helpers/SamplesIntegrator.js +381 -204
  161. package/models/trace/helpers/SamplesIntegrator.js.map +1 -7
  162. package/models/trace/helpers/Timing.d.ts +26 -0
  163. package/models/trace/helpers/Timing.js +131 -110
  164. package/models/trace/helpers/Timing.js.map +1 -7
  165. package/models/trace/helpers/Trace.d.ts +37 -0
  166. package/models/trace/helpers/Trace.js +200 -166
  167. package/models/trace/helpers/Trace.js.map +1 -7
  168. package/models/trace/helpers/TreeHelpers.d.ts +90 -0
  169. package/models/trace/helpers/TreeHelpers.js +203 -100
  170. package/models/trace/helpers/TreeHelpers.js.map +1 -7
  171. package/models/trace/helpers/helpers.d.ts +4 -0
  172. package/models/trace/helpers/helpers.js +8 -5
  173. package/models/trace/helpers/helpers.js.map +1 -7
  174. package/models/trace/root-causes/LayoutShift.d.ts +119 -0
  175. package/models/trace/root-causes/LayoutShift.js +470 -323
  176. package/models/trace/root-causes/LayoutShift.js.map +1 -7
  177. package/models/trace/root-causes/RootCauses.d.ts +14 -0
  178. package/models/trace/root-causes/RootCauses.js +9 -6
  179. package/models/trace/root-causes/RootCauses.js.map +1 -7
  180. package/models/trace/root-causes/root-causes.d.ts +1 -0
  181. package/models/trace/root-causes/root-causes.js +5 -2
  182. package/models/trace/root-causes/root-causes.js.map +1 -7
  183. package/models/trace/trace.d.ts +11 -0
  184. package/models/trace/trace.js +17 -23
  185. package/models/trace/trace.js.map +1 -7
  186. package/models/trace/types/Configuration.d.ts +33 -0
  187. package/models/trace/types/Configuration.js +25 -14
  188. package/models/trace/types/Configuration.js.map +1 -7
  189. package/models/trace/types/File.d.ts +23 -0
  190. package/models/trace/types/File.js +5 -6
  191. package/models/trace/types/File.js.map +1 -7
  192. package/models/trace/types/Timing.d.ts +25 -0
  193. package/models/trace/types/Timing.js +10 -11
  194. package/models/trace/types/Timing.js.map +1 -7
  195. package/models/trace/types/TraceEvents.d.ts +1571 -0
  196. package/models/trace/types/TraceEvents.js +174 -381
  197. package/models/trace/types/TraceEvents.js.map +1 -7
  198. package/models/trace/types/types.d.ts +4 -0
  199. package/models/trace/types/types.js +8 -5
  200. package/models/trace/types/types.js.map +1 -7
  201. package/package.json +1 -1
  202. package/TracingManager.js +0 -0
  203. package/extras/extras.js +0 -0
  204. package/trace.mjs +0 -6980
  205. package/trace.mjs.map +0 -8
@@ -1,439 +1,513 @@
1
+ // Copyright (c) 2020 The Chromium Authors. All rights reserved.
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
1
4
  export const escapeCharacters = (inputString, charsToEscape) => {
2
- let foundChar = false;
3
- for (let i = 0; i < charsToEscape.length; ++i) {
4
- if (inputString.indexOf(charsToEscape.charAt(i)) !== -1) {
5
- foundChar = true;
6
- break;
7
- }
8
- }
9
- if (!foundChar) {
10
- return String(inputString);
11
- }
12
- let result = "";
13
- for (let i = 0; i < inputString.length; ++i) {
14
- if (charsToEscape.indexOf(inputString.charAt(i)) !== -1) {
15
- result += "\\";
16
- }
17
- result += inputString.charAt(i);
18
- }
19
- return result;
5
+ let foundChar = false;
6
+ for (let i = 0; i < charsToEscape.length; ++i) {
7
+ if (inputString.indexOf(charsToEscape.charAt(i)) !== -1) {
8
+ foundChar = true;
9
+ break;
10
+ }
11
+ }
12
+ if (!foundChar) {
13
+ return String(inputString);
14
+ }
15
+ let result = '';
16
+ for (let i = 0; i < inputString.length; ++i) {
17
+ if (charsToEscape.indexOf(inputString.charAt(i)) !== -1) {
18
+ result += '\\';
19
+ }
20
+ result += inputString.charAt(i);
21
+ }
22
+ return result;
20
23
  };
21
24
  const toHexadecimal = (charCode, padToLength) => {
22
- return charCode.toString(16).toUpperCase().padStart(padToLength, "0");
23
- };
24
- const escapedReplacements = /* @__PURE__ */ new Map([
25
- ["\b", "\\b"],
26
- ["\f", "\\f"],
27
- ["\n", "\\n"],
28
- ["\r", "\\r"],
29
- [" ", "\\t"],
30
- ["\v", "\\v"],
31
- ["'", "\\'"],
32
- ["\\", "\\\\"],
33
- ["<!--", "\\x3C!--"],
34
- ["<script", "\\x3Cscript"],
35
- ["<\/script", "\\x3C/script"]
25
+ return charCode.toString(16).toUpperCase().padStart(padToLength, '0');
26
+ };
27
+ // Remember to update the third group in the regexps patternsToEscape and
28
+ // patternsToEscapePlusSingleQuote when adding new entries in this map.
29
+ const escapedReplacements = new Map([
30
+ ['\b', '\\b'],
31
+ ['\f', '\\f'],
32
+ ['\n', '\\n'],
33
+ ['\r', '\\r'],
34
+ ['\t', '\\t'],
35
+ ['\v', '\\v'],
36
+ ['\'', '\\\''],
37
+ ['\\', '\\\\'],
38
+ ['<!--', '\\x3C!--'],
39
+ ['<script', '\\x3Cscript'],
40
+ ['</script', '\\x3C/script'],
36
41
  ]);
37
42
  export const formatAsJSLiteral = (content) => {
38
- const patternsToEscape = /(\\|<(?:!--|\/?script))|(\p{Control})|(\p{Surrogate})/gu;
39
- const patternsToEscapePlusSingleQuote = /(\\|'|<(?:!--|\/?script))|(\p{Control})|(\p{Surrogate})/gu;
40
- const escapePattern = (match, pattern, controlChar, loneSurrogate) => {
41
- if (controlChar) {
42
- if (escapedReplacements.has(controlChar)) {
43
- return escapedReplacements.get(controlChar);
44
- }
45
- const twoDigitHex = toHexadecimal(controlChar.charCodeAt(0), 2);
46
- return "\\x" + twoDigitHex;
47
- }
48
- if (loneSurrogate) {
49
- const fourDigitHex = toHexadecimal(loneSurrogate.charCodeAt(0), 4);
50
- return "\\u" + fourDigitHex;
51
- }
52
- if (pattern) {
53
- return escapedReplacements.get(pattern) || "";
54
- }
55
- return match;
56
- };
57
- let escapedContent = "";
58
- let quote = "";
59
- if (!content.includes("'")) {
60
- quote = "'";
61
- escapedContent = content.replaceAll(patternsToEscape, escapePattern);
62
- } else if (!content.includes('"')) {
63
- quote = '"';
64
- escapedContent = content.replaceAll(patternsToEscape, escapePattern);
65
- } else if (!content.includes("`") && !content.includes("${")) {
66
- quote = "`";
67
- escapedContent = content.replaceAll(patternsToEscape, escapePattern);
68
- } else {
69
- quote = "'";
70
- escapedContent = content.replaceAll(patternsToEscapePlusSingleQuote, escapePattern);
71
- }
72
- return `${quote}${escapedContent}${quote}`;
73
- };
74
- export const sprintf = (fmt, ...args) => {
75
- let argIndex = 0;
76
- const RE = /%(?:(\d+)\$)?(?:\.(\d*))?([%dfs])/g;
77
- return fmt.replaceAll(RE, (_, index, precision, specifier) => {
78
- if (specifier === "%") {
79
- return "%";
43
+ const patternsToEscape = /(\\|<(?:!--|\/?script))|(\p{Control})|(\p{Surrogate})/gu;
44
+ const patternsToEscapePlusSingleQuote = /(\\|'|<(?:!--|\/?script))|(\p{Control})|(\p{Surrogate})/gu;
45
+ const escapePattern = (match, pattern, controlChar, loneSurrogate) => {
46
+ if (controlChar) {
47
+ if (escapedReplacements.has(controlChar)) {
48
+ // @ts-ignore https://github.com/microsoft/TypeScript/issues/13086
49
+ return escapedReplacements.get(controlChar);
50
+ }
51
+ const twoDigitHex = toHexadecimal(controlChar.charCodeAt(0), 2);
52
+ return '\\x' + twoDigitHex;
53
+ }
54
+ if (loneSurrogate) {
55
+ const fourDigitHex = toHexadecimal(loneSurrogate.charCodeAt(0), 4);
56
+ return '\\u' + fourDigitHex;
57
+ }
58
+ if (pattern) {
59
+ return escapedReplacements.get(pattern) || '';
60
+ }
61
+ return match;
62
+ };
63
+ let escapedContent = '';
64
+ let quote = '';
65
+ if (!content.includes('\'')) {
66
+ quote = '\'';
67
+ escapedContent = content.replaceAll(patternsToEscape, escapePattern);
68
+ }
69
+ else if (!content.includes('"')) {
70
+ quote = '"';
71
+ escapedContent = content.replaceAll(patternsToEscape, escapePattern);
80
72
  }
81
- if (index !== void 0) {
82
- argIndex = parseInt(index, 10) - 1;
83
- if (argIndex < 0) {
84
- throw new RangeError(`Invalid parameter index ${argIndex + 1}`);
85
- }
73
+ else if (!content.includes('`') && !content.includes('${')) {
74
+ quote = '`';
75
+ escapedContent = content.replaceAll(patternsToEscape, escapePattern);
86
76
  }
87
- if (argIndex >= args.length) {
88
- throw new RangeError(`Expected at least ${argIndex + 1} format parameters, but only ${args.length} where given.`);
77
+ else {
78
+ quote = '\'';
79
+ escapedContent = content.replaceAll(patternsToEscapePlusSingleQuote, escapePattern);
80
+ }
81
+ return `${quote}${escapedContent}${quote}`;
82
+ };
83
+ /**
84
+ * This implements a subset of the sprintf() function described in the Single UNIX
85
+ * Specification. It supports the %s, %f, %d, and %% formatting specifiers, and
86
+ * understands the %m$d notation to select the m-th parameter for this substitution,
87
+ * as well as the optional precision for %s, %f, and %d.
88
+ *
89
+ * @param fmt format string.
90
+ * @param args parameters to the format string.
91
+ * @returns the formatted output string.
92
+ */
93
+ export const sprintf = (fmt, ...args) => {
94
+ let argIndex = 0;
95
+ const RE = /%(?:(\d+)\$)?(?:\.(\d*))?([%dfs])/g;
96
+ return fmt.replaceAll(RE, (_, index, precision, specifier) => {
97
+ if (specifier === '%') {
98
+ return '%';
99
+ }
100
+ if (index !== undefined) {
101
+ argIndex = parseInt(index, 10) - 1;
102
+ if (argIndex < 0) {
103
+ throw new RangeError(`Invalid parameter index ${argIndex + 1}`);
104
+ }
105
+ }
106
+ if (argIndex >= args.length) {
107
+ throw new RangeError(`Expected at least ${argIndex + 1} format parameters, but only ${args.length} where given.`);
108
+ }
109
+ if (specifier === 's') {
110
+ const argValue = String(args[argIndex++]);
111
+ if (precision !== undefined) {
112
+ return argValue.substring(0, Number(precision));
113
+ }
114
+ return argValue;
115
+ }
116
+ let argValue = Number(args[argIndex++]);
117
+ if (isNaN(argValue)) {
118
+ argValue = 0;
119
+ }
120
+ if (specifier === 'd') {
121
+ return String(Math.floor(argValue)).padStart(Number(precision), '0');
122
+ }
123
+ if (precision !== undefined) {
124
+ return argValue.toFixed(Number(precision));
125
+ }
126
+ return String(argValue);
127
+ });
128
+ };
129
+ export const toBase64 = (inputString) => {
130
+ /* note to the reader: we can't use btoa here because we need to
131
+ * support Unicode correctly. See the test cases for this function and
132
+ * also
133
+ * https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_Unicode_Problem
134
+ */
135
+ function encodeBits(b) {
136
+ return b < 26 ? b + 65 : b < 52 ? b + 71 : b < 62 ? b - 4 : b === 62 ? 43 : b === 63 ? 47 : 65;
89
137
  }
90
- if (specifier === "s") {
91
- const argValue2 = String(args[argIndex++]);
92
- if (precision !== void 0) {
93
- return argValue2.substring(0, Number(precision));
94
- }
95
- return argValue2;
138
+ const encoder = new TextEncoder();
139
+ const data = encoder.encode(inputString.toString());
140
+ const n = data.length;
141
+ let encoded = '';
142
+ if (n === 0) {
143
+ return encoded;
96
144
  }
97
- let argValue = Number(args[argIndex++]);
98
- if (isNaN(argValue)) {
99
- argValue = 0;
145
+ let shift;
146
+ let v = 0;
147
+ for (let i = 0; i < n; i++) {
148
+ shift = i % 3;
149
+ v |= data[i] << (16 >>> shift & 24);
150
+ if (shift === 2) {
151
+ encoded += String.fromCharCode(encodeBits(v >>> 18 & 63), encodeBits(v >>> 12 & 63), encodeBits(v >>> 6 & 63), encodeBits(v & 63));
152
+ v = 0;
153
+ }
100
154
  }
101
- if (specifier === "d") {
102
- return String(Math.floor(argValue)).padStart(Number(precision), "0");
155
+ if (shift === 0) {
156
+ encoded += String.fromCharCode(encodeBits(v >>> 18 & 63), encodeBits(v >>> 12 & 63), 61, 61);
103
157
  }
104
- if (precision !== void 0) {
105
- return argValue.toFixed(Number(precision));
158
+ else if (shift === 1) {
159
+ encoded += String.fromCharCode(encodeBits(v >>> 18 & 63), encodeBits(v >>> 12 & 63), encodeBits(v >>> 6 & 63), 61);
106
160
  }
107
- return String(argValue);
108
- });
109
- };
110
- export const toBase64 = (inputString) => {
111
- function encodeBits(b) {
112
- return b < 26 ? b + 65 : b < 52 ? b + 71 : b < 62 ? b - 4 : b === 62 ? 43 : b === 63 ? 47 : 65;
113
- }
114
- const encoder = new TextEncoder();
115
- const data = encoder.encode(inputString.toString());
116
- const n = data.length;
117
- let encoded = "";
118
- if (n === 0) {
119
161
  return encoded;
120
- }
121
- let shift;
122
- let v = 0;
123
- for (let i = 0; i < n; i++) {
124
- shift = i % 3;
125
- v |= data[i] << (16 >>> shift & 24);
126
- if (shift === 2) {
127
- encoded += String.fromCharCode(encodeBits(v >>> 18 & 63), encodeBits(v >>> 12 & 63), encodeBits(v >>> 6 & 63), encodeBits(v & 63));
128
- v = 0;
129
- }
130
- }
131
- if (shift === 0) {
132
- encoded += String.fromCharCode(encodeBits(v >>> 18 & 63), encodeBits(v >>> 12 & 63), 61, 61);
133
- } else if (shift === 1) {
134
- encoded += String.fromCharCode(encodeBits(v >>> 18 & 63), encodeBits(v >>> 12 & 63), encodeBits(v >>> 6 & 63), 61);
135
- }
136
- return encoded;
137
162
  };
138
163
  export const findIndexesOfSubString = (inputString, searchString) => {
139
- const matches = [];
140
- let i = inputString.indexOf(searchString);
141
- while (i !== -1) {
142
- matches.push(i);
143
- i = inputString.indexOf(searchString, i + searchString.length);
144
- }
145
- return matches;
164
+ const matches = [];
165
+ let i = inputString.indexOf(searchString);
166
+ while (i !== -1) {
167
+ matches.push(i);
168
+ i = inputString.indexOf(searchString, i + searchString.length);
169
+ }
170
+ return matches;
146
171
  };
147
172
  export const findLineEndingIndexes = (inputString) => {
148
- const endings = findIndexesOfSubString(inputString, "\n");
149
- endings.push(inputString.length);
150
- return endings;
173
+ const endings = findIndexesOfSubString(inputString, '\n');
174
+ endings.push(inputString.length);
175
+ return endings;
151
176
  };
152
177
  export const isWhitespace = (inputString) => {
153
- return /^\s*$/.test(inputString);
178
+ return /^\s*$/.test(inputString);
154
179
  };
155
180
  export const trimURL = (url, baseURLDomain) => {
156
- let result = url.replace(/^(https|http|file):\/\//i, "");
157
- if (baseURLDomain) {
158
- if (result.toLowerCase().startsWith(baseURLDomain.toLowerCase())) {
159
- result = result.substr(baseURLDomain.length);
181
+ let result = url.replace(/^(https|http|file):\/\//i, '');
182
+ if (baseURLDomain) {
183
+ if (result.toLowerCase().startsWith(baseURLDomain.toLowerCase())) {
184
+ result = result.substr(baseURLDomain.length);
185
+ }
160
186
  }
161
- }
162
- return result;
187
+ return result;
163
188
  };
164
189
  export const collapseWhitespace = (inputString) => {
165
- return inputString.replace(/[\s\xA0]+/g, " ");
190
+ return inputString.replace(/[\s\xA0]+/g, ' ');
166
191
  };
167
192
  export const reverse = (inputString) => {
168
- return inputString.split("").reverse().join("");
193
+ return inputString.split('').reverse().join('');
169
194
  };
170
195
  export const replaceControlCharacters = (inputString) => {
171
- return inputString.replace(/[\0-\x08\x0B\f\x0E-\x1F\x80-\x9F]/g, "\uFFFD");
196
+ // Replace C0 and C1 control character sets with replacement character.
197
+ // Do not replace '\t', \n' and '\r'.
198
+ return inputString.replace(/[\0-\x08\x0B\f\x0E-\x1F\x80-\x9F]/g, '\uFFFD');
172
199
  };
173
200
  export const countWtf8Bytes = (inputString) => {
174
- let count = 0;
175
- for (let i = 0; i < inputString.length; i++) {
176
- const c = inputString.charCodeAt(i);
177
- if (c <= 127) {
178
- count++;
179
- } else if (c <= 2047) {
180
- count += 2;
181
- } else if (c < 55296 || 57343 < c) {
182
- count += 3;
183
- } else {
184
- if (c <= 56319 && i + 1 < inputString.length) {
185
- const next = inputString.charCodeAt(i + 1);
186
- if (56320 <= next && next <= 57343) {
187
- count += 4;
188
- i++;
189
- continue;
190
- }
191
- }
192
- count += 3;
193
- }
194
- }
195
- return count;
201
+ let count = 0;
202
+ for (let i = 0; i < inputString.length; i++) {
203
+ const c = inputString.charCodeAt(i);
204
+ if (c <= 0x7F) {
205
+ count++;
206
+ }
207
+ else if (c <= 0x07FF) {
208
+ count += 2;
209
+ }
210
+ else if (c < 0xD800 || 0xDFFF < c) {
211
+ count += 3;
212
+ }
213
+ else {
214
+ if (c <= 0xDBFF && i + 1 < inputString.length) {
215
+ // The current character is a leading surrogate, and there is a
216
+ // next character.
217
+ const next = inputString.charCodeAt(i + 1);
218
+ if (0xDC00 <= next && next <= 0xDFFF) {
219
+ // The next character is a trailing surrogate, meaning this
220
+ // is a surrogate pair.
221
+ count += 4;
222
+ i++;
223
+ continue;
224
+ }
225
+ }
226
+ count += 3;
227
+ }
228
+ }
229
+ return count;
196
230
  };
197
231
  export const stripLineBreaks = (inputStr) => {
198
- return inputStr.replace(/(\r)?\n/g, "");
232
+ return inputStr.replace(/(\r)?\n/g, '');
199
233
  };
200
234
  const EXTENDED_KEBAB_CASE_REGEXP = /^([a-z0-9]+(?:-[a-z0-9]+)*\.)*[a-z0-9]+(?:-[a-z0-9]+)*$/;
235
+ /**
236
+ * Tests if the `inputStr` is following the extended Kebab Case naming convetion,
237
+ * where words are separated with either a dash (`-`) or a dot (`.`), and all
238
+ * characters must be lower-case alphanumeric.
239
+ *
240
+ * For example, it will yield `true` for `'my.amazing-string.literal'`, but `false`
241
+ * for `'Another.AmazingLiteral'` or '`another_amazing_literal'`.
242
+ *
243
+ * @param inputStr the input string to test.
244
+ * @return `true` if the `inputStr` follows the extended Kebab Case convention.
245
+ */
201
246
  export const isExtendedKebabCase = (inputStr) => {
202
- return EXTENDED_KEBAB_CASE_REGEXP.test(inputStr);
247
+ return EXTENDED_KEBAB_CASE_REGEXP.test(inputStr);
203
248
  };
204
249
  export const toTitleCase = (inputStr) => {
205
- return inputStr.substring(0, 1).toUpperCase() + inputStr.substring(1);
250
+ return inputStr.substring(0, 1).toUpperCase() + inputStr.substring(1);
206
251
  };
207
252
  export const removeURLFragment = (inputStr) => {
208
- const url = new URL(inputStr);
209
- url.hash = "";
210
- return url.toString();
211
- };
212
- const SPECIAL_REGEX_CHARACTERS = "^[]{}()\\.^$*+?|-,";
213
- export const regexSpecialCharacters = function() {
214
- return SPECIAL_REGEX_CHARACTERS;
215
- };
216
- export const filterRegex = function(query) {
217
- let regexString = "^(?:.*\\0)?";
218
- for (let i = 0; i < query.length; ++i) {
219
- let c = query.charAt(i);
220
- if (SPECIAL_REGEX_CHARACTERS.indexOf(c) !== -1) {
221
- c = "\\" + c;
222
- }
223
- regexString += "[^\\0" + c + "]*" + c;
224
- }
225
- return new RegExp(regexString, "i");
226
- };
227
- export const createSearchRegex = function(query, caseSensitive, isRegex, matchWholeWord = false) {
228
- const regexFlags = caseSensitive ? "g" : "gi";
229
- let regexObject;
230
- if (isRegex) {
231
- try {
232
- regexObject = new RegExp(query, regexFlags);
233
- } catch (e) {
234
- }
235
- }
236
- if (!regexObject) {
237
- regexObject = createPlainTextSearchRegex(query, regexFlags);
238
- }
239
- if (matchWholeWord && regexObject) {
240
- regexObject = new RegExp(`\\b${regexObject.source}\\b`, regexFlags);
241
- }
242
- return regexObject;
243
- };
244
- export const caseInsensetiveComparator = function(a, b) {
245
- a = a.toUpperCase();
246
- b = b.toUpperCase();
247
- if (a === b) {
248
- return 0;
249
- }
250
- return a > b ? 1 : -1;
253
+ const url = new URL(inputStr);
254
+ url.hash = '';
255
+ return url.toString();
256
+ };
257
+ const SPECIAL_REGEX_CHARACTERS = '^[]{}()\\.^$*+?|-,';
258
+ export const regexSpecialCharacters = function () {
259
+ return SPECIAL_REGEX_CHARACTERS;
260
+ };
261
+ export const filterRegex = function (query) {
262
+ let regexString = '^(?:.*\\0)?'; // Start from beginning or after a \0
263
+ for (let i = 0; i < query.length; ++i) {
264
+ let c = query.charAt(i);
265
+ if (SPECIAL_REGEX_CHARACTERS.indexOf(c) !== -1) {
266
+ c = '\\' + c;
267
+ }
268
+ regexString += '[^\\0' + c + ']*' + c;
269
+ }
270
+ return new RegExp(regexString, 'i');
271
+ };
272
+ export const createSearchRegex = function (query, caseSensitive, isRegex, matchWholeWord = false) {
273
+ const regexFlags = caseSensitive ? 'g' : 'gi';
274
+ let regexObject;
275
+ if (isRegex) {
276
+ try {
277
+ regexObject = new RegExp(query, regexFlags);
278
+ }
279
+ catch (e) {
280
+ // Silent catch.
281
+ }
282
+ }
283
+ if (!regexObject) {
284
+ regexObject = createPlainTextSearchRegex(query, regexFlags);
285
+ }
286
+ if (matchWholeWord && regexObject) {
287
+ regexObject = new RegExp(`\\b${regexObject.source}\\b`, regexFlags);
288
+ }
289
+ return regexObject;
251
290
  };
252
- export const hashCode = function(string) {
253
- if (!string) {
254
- return 0;
255
- }
256
- const p = (1 << 30) * 4 - 5;
257
- const z = 1345575271;
258
- const z2 = 1506996573;
259
- let s = 0;
260
- let zi = 1;
261
- for (let i = 0; i < string.length; i++) {
262
- const xi = string.charCodeAt(i) * z2;
263
- s = (s + zi * xi) % p;
264
- zi = zi * z % p;
265
- }
266
- s = (s + zi * (p - 1)) % p;
267
- return Math.abs(s | 0);
291
+ export const caseInsensetiveComparator = function (a, b) {
292
+ a = a.toUpperCase();
293
+ b = b.toUpperCase();
294
+ if (a === b) {
295
+ return 0;
296
+ }
297
+ return a > b ? 1 : -1;
298
+ };
299
+ export const hashCode = function (string) {
300
+ if (!string) {
301
+ return 0;
302
+ }
303
+ // Hash algorithm for substrings is described in "Über die Komplexität der Multiplikation in
304
+ // eingeschränkten Branchingprogrammmodellen" by Woelfe.
305
+ // http://opendatastructures.org/versions/edition-0.1d/ods-java/node33.html#SECTION00832000000000000000
306
+ const p = ((1 << 30) * 4 - 5); // prime: 2^32 - 5
307
+ const z = 0x5033d967; // 32 bits from random.org
308
+ const z2 = 0x59d2f15d; // random odd 32 bit number
309
+ let s = 0;
310
+ let zi = 1;
311
+ for (let i = 0; i < string.length; i++) {
312
+ const xi = string.charCodeAt(i) * z2;
313
+ s = (s + zi * xi) % p;
314
+ zi = (zi * z) % p;
315
+ }
316
+ s = (s + zi * (p - 1)) % p;
317
+ return Math.abs(s | 0);
268
318
  };
269
319
  export const compare = (a, b) => {
270
- if (a > b) {
271
- return 1;
272
- }
273
- if (a < b) {
274
- return -1;
275
- }
276
- return 0;
320
+ if (a > b) {
321
+ return 1;
322
+ }
323
+ if (a < b) {
324
+ return -1;
325
+ }
326
+ return 0;
277
327
  };
278
328
  export const trimMiddle = (str, maxLength) => {
279
- if (str.length <= maxLength) {
280
- return String(str);
281
- }
282
- let leftHalf = maxLength >> 1;
283
- let rightHalf = maxLength - leftHalf - 1;
284
- if (str.codePointAt(str.length - rightHalf - 1) >= 65536) {
285
- --rightHalf;
286
- ++leftHalf;
287
- }
288
- if (leftHalf > 0 && str.codePointAt(leftHalf - 1) >= 65536) {
289
- --leftHalf;
290
- }
291
- return str.substr(0, leftHalf) + "\u2026" + str.substr(str.length - rightHalf, rightHalf);
329
+ if (str.length <= maxLength) {
330
+ return String(str);
331
+ }
332
+ let leftHalf = maxLength >> 1;
333
+ let rightHalf = maxLength - leftHalf - 1;
334
+ if (str.codePointAt(str.length - rightHalf - 1) >= 0x10000) {
335
+ --rightHalf;
336
+ ++leftHalf;
337
+ }
338
+ if (leftHalf > 0 && str.codePointAt(leftHalf - 1) >= 0x10000) {
339
+ --leftHalf;
340
+ }
341
+ return str.substr(0, leftHalf) + '…' + str.substr(str.length - rightHalf, rightHalf);
292
342
  };
293
343
  export const trimEndWithMaxLength = (str, maxLength) => {
294
- if (str.length <= maxLength) {
295
- return String(str);
296
- }
297
- return str.substr(0, maxLength - 1) + "\u2026";
344
+ if (str.length <= maxLength) {
345
+ return String(str);
346
+ }
347
+ return str.substr(0, maxLength - 1) + '…';
298
348
  };
299
349
  export const escapeForRegExp = (str) => {
300
- return escapeCharacters(str, SPECIAL_REGEX_CHARACTERS);
350
+ return escapeCharacters(str, SPECIAL_REGEX_CHARACTERS);
301
351
  };
302
352
  export const naturalOrderComparator = (a, b) => {
303
- const chunk = /^\d+|^\D+/;
304
- let chunka, chunkb, anum, bnum;
305
- while (true) {
306
- if (a) {
307
- if (!b) {
308
- return 1;
309
- }
310
- } else {
311
- if (b) {
312
- return -1;
313
- }
314
- return 0;
315
- }
316
- chunka = a.match(chunk)[0];
317
- chunkb = b.match(chunk)[0];
318
- anum = !Number.isNaN(Number(chunka));
319
- bnum = !Number.isNaN(Number(chunkb));
320
- if (anum && !bnum) {
321
- return -1;
322
- }
323
- if (bnum && !anum) {
324
- return 1;
325
- }
326
- if (anum && bnum) {
327
- const diff = Number(chunka) - Number(chunkb);
328
- if (diff) {
329
- return diff;
330
- }
331
- if (chunka.length !== chunkb.length) {
332
- if (!Number(chunka) && !Number(chunkb)) {
333
- return chunka.length - chunkb.length;
334
- }
335
- return chunkb.length - chunka.length;
336
- }
337
- } else if (chunka !== chunkb) {
338
- return chunka < chunkb ? -1 : 1;
339
- }
340
- a = a.substring(chunka.length);
341
- b = b.substring(chunkb.length);
342
- }
343
- };
344
- export const base64ToSize = function(content) {
345
- if (!content) {
346
- return 0;
347
- }
348
- let size = content.length * 3 / 4;
349
- if (content[content.length - 1] === "=") {
350
- size--;
351
- }
352
- if (content.length > 1 && content[content.length - 2] === "=") {
353
- size--;
354
- }
355
- return size;
356
- };
357
- export const SINGLE_QUOTE = "'";
353
+ const chunk = /^\d+|^\D+/;
354
+ let chunka, chunkb, anum, bnum;
355
+ while (true) {
356
+ if (a) {
357
+ if (!b) {
358
+ return 1;
359
+ }
360
+ }
361
+ else {
362
+ if (b) {
363
+ return -1;
364
+ }
365
+ return 0;
366
+ }
367
+ chunka = a.match(chunk)[0];
368
+ chunkb = b.match(chunk)[0];
369
+ anum = !Number.isNaN(Number(chunka));
370
+ bnum = !Number.isNaN(Number(chunkb));
371
+ if (anum && !bnum) {
372
+ return -1;
373
+ }
374
+ if (bnum && !anum) {
375
+ return 1;
376
+ }
377
+ if (anum && bnum) {
378
+ const diff = Number(chunka) - Number(chunkb);
379
+ if (diff) {
380
+ return diff;
381
+ }
382
+ if (chunka.length !== chunkb.length) {
383
+ if (!Number(chunka) && !Number(chunkb)) { // chunks are strings of all 0s (special case)
384
+ return chunka.length - chunkb.length;
385
+ }
386
+ return chunkb.length - chunka.length;
387
+ }
388
+ }
389
+ else if (chunka !== chunkb) {
390
+ return (chunka < chunkb) ? -1 : 1;
391
+ }
392
+ a = a.substring(chunka.length);
393
+ b = b.substring(chunkb.length);
394
+ }
395
+ };
396
+ export const base64ToSize = function (content) {
397
+ if (!content) {
398
+ return 0;
399
+ }
400
+ let size = content.length * 3 / 4;
401
+ if (content[content.length - 1] === '=') {
402
+ size--;
403
+ }
404
+ if (content.length > 1 && content[content.length - 2] === '=') {
405
+ size--;
406
+ }
407
+ return size;
408
+ };
409
+ export const SINGLE_QUOTE = '\'';
358
410
  export const DOUBLE_QUOTE = '"';
359
- const BACKSLASH = "\\";
360
- export const findUnclosedCssQuote = function(str) {
361
- let unmatchedQuote = "";
362
- for (let i = 0; i < str.length; ++i) {
363
- const char = str[i];
364
- if (char === BACKSLASH) {
365
- i++;
366
- continue;
367
- }
368
- if (char === SINGLE_QUOTE || char === DOUBLE_QUOTE) {
369
- if (unmatchedQuote === char) {
370
- unmatchedQuote = "";
371
- } else if (unmatchedQuote === "") {
372
- unmatchedQuote = char;
373
- }
374
- }
375
- }
376
- return unmatchedQuote;
411
+ const BACKSLASH = '\\';
412
+ export const findUnclosedCssQuote = function (str) {
413
+ let unmatchedQuote = '';
414
+ for (let i = 0; i < str.length; ++i) {
415
+ const char = str[i];
416
+ if (char === BACKSLASH) {
417
+ i++;
418
+ continue;
419
+ }
420
+ if (char === SINGLE_QUOTE || char === DOUBLE_QUOTE) {
421
+ if (unmatchedQuote === char) {
422
+ unmatchedQuote = '';
423
+ }
424
+ else if (unmatchedQuote === '') {
425
+ unmatchedQuote = char;
426
+ }
427
+ }
428
+ }
429
+ return unmatchedQuote;
377
430
  };
378
431
  export const countUnmatchedLeftParentheses = (str) => {
379
- let unmatchedCount = 0;
380
- for (const c of str) {
381
- if (c === "(") {
382
- unmatchedCount++;
383
- } else if (c === ")" && unmatchedCount > 0) {
384
- unmatchedCount--;
385
- }
386
- }
387
- return unmatchedCount;
388
- };
389
- export const createPlainTextSearchRegex = function(query, flags) {
390
- let regex = "";
391
- for (let i = 0; i < query.length; ++i) {
392
- const c = query.charAt(i);
393
- if (regexSpecialCharacters().indexOf(c) !== -1) {
394
- regex += "\\";
395
- }
396
- regex += c;
397
- }
398
- return new RegExp(regex, flags || "");
432
+ let unmatchedCount = 0;
433
+ for (const c of str) {
434
+ if (c === '(') {
435
+ unmatchedCount++;
436
+ }
437
+ else if (c === ')' && unmatchedCount > 0) {
438
+ unmatchedCount--;
439
+ }
440
+ }
441
+ return unmatchedCount;
442
+ };
443
+ export const createPlainTextSearchRegex = function (query, flags) {
444
+ // This should be kept the same as the one in StringUtil.cpp.
445
+ let regex = '';
446
+ for (let i = 0; i < query.length; ++i) {
447
+ const c = query.charAt(i);
448
+ if (regexSpecialCharacters().indexOf(c) !== -1) {
449
+ regex += '\\';
450
+ }
451
+ regex += c;
452
+ }
453
+ return new RegExp(regex, flags || '');
399
454
  };
400
455
  class LowerCaseStringTag {
401
- lowerCaseStringTag;
456
+ lowerCaseStringTag;
402
457
  }
403
- export const toLowerCaseString = function(input) {
404
- return input.toLowerCase();
458
+ export const toLowerCaseString = function (input) {
459
+ return input.toLowerCase();
405
460
  };
406
461
  const WORD = /[A-Z]{2,}(?=[A-Z0-9][a-z0-9]+|\b)|[A-Za-z][0-9]+[a-z]|[A-Z]?[a-z]+|[0-9][A-Za-z]+|[A-Z]|[0-9]+|[.]/g;
407
- export const toKebabCase = function(input) {
408
- return input.match?.(WORD)?.map((w) => w.toLowerCase()).join("-").replaceAll("-.-", ".") || input;
409
- };
462
+ // <---1---><-----------2----------> <--------3--------> <-----4----> <------5-----> <-----6----> <7>
463
+ // 1: two or more consecutive uppercase letters. This is useful for identifying acronyms
464
+ // 2: lookahead assertion that matches a word boundary
465
+ // 3: numeronym: single letter followed by number and another letter
466
+ // 4: word starting with an optional uppercase letter
467
+ // 5: single digit followed by word to handle '3D' or '2px' (this might be controverial)
468
+ // 6: single uppercase letter or number
469
+ // 7: a dot character. We extract it into a separate word and remove dashes around it later.
470
+ // This is makes more sense conceptually and allows accounting for all possible word variants.
471
+ // Making dot a part of a word prevent us from handling acronyms or numeronyms after the word
472
+ // correctly without making the RegExp prohibitively complicated.
473
+ // https://regex101.com/r/LtFugp/1
474
+ export const toKebabCase = function (input) {
475
+ return (input.match?.(WORD)?.map(w => w.toLowerCase()).join('-').replaceAll('-.-', '.') || input);
476
+ };
477
+ /* eslint-disable @typescript-eslint/no-explicit-any */
410
478
  export function toKebabCaseKeys(settingValue) {
411
- const result = {};
412
- for (const [key, value] of Object.entries(settingValue)) {
413
- result[toKebabCase(key)] = value;
414
- }
415
- return result;
479
+ const result = {};
480
+ for (const [key, value] of Object.entries(settingValue)) {
481
+ result[toKebabCase(key)] = value;
482
+ }
483
+ return result;
416
484
  }
417
- export const replaceLast = function(input, search, replacement) {
418
- const replacementStartIndex = input.lastIndexOf(search);
419
- if (replacementStartIndex === -1) {
420
- return input;
421
- }
422
- return input.slice(0, replacementStartIndex) + input.slice(replacementStartIndex).replace(search, replacement);
423
- };
424
- export const stringifyWithPrecision = function stringifyWithPrecision2(s, precision = 2) {
425
- if (precision === 0) {
426
- return s.toFixed(0);
427
- }
428
- const string = s.toFixed(precision).replace(/\.?0*$/, "");
429
- return string === "-0" ? "0" : string;
430
- };
431
- export const concatBase64 = function(lhs, rhs) {
432
- if (lhs.length === 0 || !lhs.endsWith("=")) {
433
- return lhs + rhs;
434
- }
435
- const lhsLeaveAsIs = lhs.substring(0, lhs.length - 4);
436
- const lhsToDecode = lhs.substring(lhs.length - 4);
437
- return lhsLeaveAsIs + window.btoa(window.atob(lhsToDecode) + window.atob(rhs));
438
- };
439
- //# sourceMappingURL=string-utilities.js.map
485
+ /* eslint-enable @typescript-eslint/no-explicit-any */
486
+ // Replaces the last ocurrence of parameter `search` with parameter `replacement` in `input`
487
+ export const replaceLast = function (input, search, replacement) {
488
+ const replacementStartIndex = input.lastIndexOf(search);
489
+ if (replacementStartIndex === -1) {
490
+ return input;
491
+ }
492
+ return input.slice(0, replacementStartIndex) + input.slice(replacementStartIndex).replace(search, replacement);
493
+ };
494
+ export const stringifyWithPrecision = function stringifyWithPrecision(s, precision = 2) {
495
+ if (precision === 0) {
496
+ return s.toFixed(0);
497
+ }
498
+ const string = s.toFixed(precision).replace(/\.?0*$/, '');
499
+ return string === '-0' ? '0' : string;
500
+ };
501
+ /**
502
+ * Somewhat efficiently concatenates 2 base64 encoded strings.
503
+ */
504
+ export const concatBase64 = function (lhs, rhs) {
505
+ if (lhs.length === 0 || !lhs.endsWith('=')) {
506
+ // Empty string or no padding, we can straight-up concatenate.
507
+ return lhs + rhs;
508
+ }
509
+ const lhsLeaveAsIs = lhs.substring(0, lhs.length - 4);
510
+ const lhsToDecode = lhs.substring(lhs.length - 4);
511
+ return lhsLeaveAsIs + window.btoa(window.atob(lhsToDecode) + window.atob(rhs));
512
+ };
513
+ //# sourceMappingURL=string-utilities.js.map