@bamboocss/reporter 1.11.1 → 1.11.2

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.
package/dist/index.mjs CHANGED
@@ -1,446 +1,570 @@
1
- // ../../node_modules/.pnpm/markdown-table@3.0.4/node_modules/markdown-table/index.js
1
+ import { table } from "table";
2
+ import Wordwrap from "wordwrapjs";
3
+ import { logger } from "@bamboocss/logger";
4
+ import { uniq } from "@bamboocss/shared";
5
+ //#region ../../node_modules/.pnpm/markdown-table@3.0.4/node_modules/markdown-table/index.js
6
+ /**
7
+ * @typedef {Options} MarkdownTableOptions
8
+ * Configuration.
9
+ */
10
+ /**
11
+ * @typedef Options
12
+ * Configuration.
13
+ * @property {boolean | null | undefined} [alignDelimiters=true]
14
+ * Whether to align the delimiters (default: `true`);
15
+ * they are aligned by default:
16
+ *
17
+ * ```markdown
18
+ * | Alpha | B |
19
+ * | ----- | ----- |
20
+ * | C | Delta |
21
+ * ```
22
+ *
23
+ * Pass `false` to make them staggered:
24
+ *
25
+ * ```markdown
26
+ * | Alpha | B |
27
+ * | - | - |
28
+ * | C | Delta |
29
+ * ```
30
+ * @property {ReadonlyArray<string | null | undefined> | string | null | undefined} [align]
31
+ * How to align columns (default: `''`);
32
+ * one style for all columns or styles for their respective columns;
33
+ * each style is either `'l'` (left), `'r'` (right), or `'c'` (center);
34
+ * other values are treated as `''`, which doesn’t place the colon in the
35
+ * alignment row but does align left;
36
+ * *only the lowercased first character is used, so `Right` is fine.*
37
+ * @property {boolean | null | undefined} [delimiterEnd=true]
38
+ * Whether to end each row with the delimiter (default: `true`).
39
+ *
40
+ * > 👉 **Note**: please don’t use this: it could create fragile structures
41
+ * > that aren’t understandable to some markdown parsers.
42
+ *
43
+ * When `true`, there are ending delimiters:
44
+ *
45
+ * ```markdown
46
+ * | Alpha | B |
47
+ * | ----- | ----- |
48
+ * | C | Delta |
49
+ * ```
50
+ *
51
+ * When `false`, there are no ending delimiters:
52
+ *
53
+ * ```markdown
54
+ * | Alpha | B
55
+ * | ----- | -----
56
+ * | C | Delta
57
+ * ```
58
+ * @property {boolean | null | undefined} [delimiterStart=true]
59
+ * Whether to begin each row with the delimiter (default: `true`).
60
+ *
61
+ * > 👉 **Note**: please don’t use this: it could create fragile structures
62
+ * > that aren’t understandable to some markdown parsers.
63
+ *
64
+ * When `true`, there are starting delimiters:
65
+ *
66
+ * ```markdown
67
+ * | Alpha | B |
68
+ * | ----- | ----- |
69
+ * | C | Delta |
70
+ * ```
71
+ *
72
+ * When `false`, there are no starting delimiters:
73
+ *
74
+ * ```markdown
75
+ * Alpha | B |
76
+ * ----- | ----- |
77
+ * C | Delta |
78
+ * ```
79
+ * @property {boolean | null | undefined} [padding=true]
80
+ * Whether to add a space of padding between delimiters and cells
81
+ * (default: `true`).
82
+ *
83
+ * When `true`, there is padding:
84
+ *
85
+ * ```markdown
86
+ * | Alpha | B |
87
+ * | ----- | ----- |
88
+ * | C | Delta |
89
+ * ```
90
+ *
91
+ * When `false`, there is no padding:
92
+ *
93
+ * ```markdown
94
+ * |Alpha|B |
95
+ * |-----|-----|
96
+ * |C |Delta|
97
+ * ```
98
+ * @property {((value: string) => number) | null | undefined} [stringLength]
99
+ * Function to detect the length of table cell content (optional);
100
+ * this is used when aligning the delimiters (`|`) between table cells;
101
+ * full-width characters and emoji mess up delimiter alignment when viewing
102
+ * the markdown source;
103
+ * to fix this, you can pass this function,
104
+ * which receives the cell content and returns its “visible” size;
105
+ * note that what is and isn’t visible depends on where the text is displayed.
106
+ *
107
+ * Without such a function, the following:
108
+ *
109
+ * ```js
110
+ * markdownTable([
111
+ * ['Alpha', 'Bravo'],
112
+ * ['中文', 'Charlie'],
113
+ * ['👩‍❤️‍👩', 'Delta']
114
+ * ])
115
+ * ```
116
+ *
117
+ * Yields:
118
+ *
119
+ * ```markdown
120
+ * | Alpha | Bravo |
121
+ * | - | - |
122
+ * | 中文 | Charlie |
123
+ * | 👩‍❤️‍👩 | Delta |
124
+ * ```
125
+ *
126
+ * With [`string-width`](https://github.com/sindresorhus/string-width):
127
+ *
128
+ * ```js
129
+ * import stringWidth from 'string-width'
130
+ *
131
+ * markdownTable(
132
+ * [
133
+ * ['Alpha', 'Bravo'],
134
+ * ['中文', 'Charlie'],
135
+ * ['👩‍❤️‍👩', 'Delta']
136
+ * ],
137
+ * {stringLength: stringWidth}
138
+ * )
139
+ * ```
140
+ *
141
+ * Yields:
142
+ *
143
+ * ```markdown
144
+ * | Alpha | Bravo |
145
+ * | ----- | ------- |
146
+ * | 中文 | Charlie |
147
+ * | 👩‍❤️‍👩 | Delta |
148
+ * ```
149
+ */
150
+ /**
151
+ * @param {string} value
152
+ * Cell value.
153
+ * @returns {number}
154
+ * Cell size.
155
+ */
2
156
  function defaultStringLength(value) {
3
- return value.length;
157
+ return value.length;
4
158
  }
5
- function markdownTable(table2, options) {
6
- const settings = options || {};
7
- const align = (settings.align || []).concat();
8
- const stringLength = settings.stringLength || defaultStringLength;
9
- const alignments = [];
10
- const cellMatrix = [];
11
- const sizeMatrix = [];
12
- const longestCellByColumn = [];
13
- let mostCellsPerRow = 0;
14
- let rowIndex = -1;
15
- while (++rowIndex < table2.length) {
16
- const row2 = [];
17
- const sizes2 = [];
18
- let columnIndex2 = -1;
19
- if (table2[rowIndex].length > mostCellsPerRow) {
20
- mostCellsPerRow = table2[rowIndex].length;
21
- }
22
- while (++columnIndex2 < table2[rowIndex].length) {
23
- const cell = serialize(table2[rowIndex][columnIndex2]);
24
- if (settings.alignDelimiters !== false) {
25
- const size = stringLength(cell);
26
- sizes2[columnIndex2] = size;
27
- if (longestCellByColumn[columnIndex2] === void 0 || size > longestCellByColumn[columnIndex2]) {
28
- longestCellByColumn[columnIndex2] = size;
29
- }
30
- }
31
- row2.push(cell);
32
- }
33
- cellMatrix[rowIndex] = row2;
34
- sizeMatrix[rowIndex] = sizes2;
35
- }
36
- let columnIndex = -1;
37
- if (typeof align === "object" && "length" in align) {
38
- while (++columnIndex < mostCellsPerRow) {
39
- alignments[columnIndex] = toAlignment(align[columnIndex]);
40
- }
41
- } else {
42
- const code = toAlignment(align);
43
- while (++columnIndex < mostCellsPerRow) {
44
- alignments[columnIndex] = code;
45
- }
46
- }
47
- columnIndex = -1;
48
- const row = [];
49
- const sizes = [];
50
- while (++columnIndex < mostCellsPerRow) {
51
- const code = alignments[columnIndex];
52
- let before = "";
53
- let after = "";
54
- if (code === 99) {
55
- before = ":";
56
- after = ":";
57
- } else if (code === 108) {
58
- before = ":";
59
- } else if (code === 114) {
60
- after = ":";
61
- }
62
- let size = settings.alignDelimiters === false ? 1 : Math.max(
63
- 1,
64
- longestCellByColumn[columnIndex] - before.length - after.length
65
- );
66
- const cell = before + "-".repeat(size) + after;
67
- if (settings.alignDelimiters !== false) {
68
- size = before.length + size + after.length;
69
- if (size > longestCellByColumn[columnIndex]) {
70
- longestCellByColumn[columnIndex] = size;
71
- }
72
- sizes[columnIndex] = size;
73
- }
74
- row[columnIndex] = cell;
75
- }
76
- cellMatrix.splice(1, 0, row);
77
- sizeMatrix.splice(1, 0, sizes);
78
- rowIndex = -1;
79
- const lines = [];
80
- while (++rowIndex < cellMatrix.length) {
81
- const row2 = cellMatrix[rowIndex];
82
- const sizes2 = sizeMatrix[rowIndex];
83
- columnIndex = -1;
84
- const line = [];
85
- while (++columnIndex < mostCellsPerRow) {
86
- const cell = row2[columnIndex] || "";
87
- let before = "";
88
- let after = "";
89
- if (settings.alignDelimiters !== false) {
90
- const size = longestCellByColumn[columnIndex] - (sizes2[columnIndex] || 0);
91
- const code = alignments[columnIndex];
92
- if (code === 114) {
93
- before = " ".repeat(size);
94
- } else if (code === 99) {
95
- if (size % 2) {
96
- before = " ".repeat(size / 2 + 0.5);
97
- after = " ".repeat(size / 2 - 0.5);
98
- } else {
99
- before = " ".repeat(size / 2);
100
- after = before;
101
- }
102
- } else {
103
- after = " ".repeat(size);
104
- }
105
- }
106
- if (settings.delimiterStart !== false && !columnIndex) {
107
- line.push("|");
108
- }
109
- if (settings.padding !== false && // Don’t add the opening space if we’re not aligning and the cell is
110
- // empty: there will be a closing space.
111
- !(settings.alignDelimiters === false && cell === "") && (settings.delimiterStart !== false || columnIndex)) {
112
- line.push(" ");
113
- }
114
- if (settings.alignDelimiters !== false) {
115
- line.push(before);
116
- }
117
- line.push(cell);
118
- if (settings.alignDelimiters !== false) {
119
- line.push(after);
120
- }
121
- if (settings.padding !== false) {
122
- line.push(" ");
123
- }
124
- if (settings.delimiterEnd !== false || columnIndex !== mostCellsPerRow - 1) {
125
- line.push("|");
126
- }
127
- }
128
- lines.push(
129
- settings.delimiterEnd === false ? line.join("").replace(/ +$/, "") : line.join("")
130
- );
131
- }
132
- return lines.join("\n");
159
+ /**
160
+ * Generate a markdown
161
+ * ([GFM](https://docs.github.com/en/github/writing-on-github/working-with-advanced-formatting/organizing-information-with-tables))
162
+ * table.
163
+ *
164
+ * @param {ReadonlyArray<ReadonlyArray<string | null | undefined>>} table
165
+ * Table data (matrix of strings).
166
+ * @param {Readonly<Options> | null | undefined} [options]
167
+ * Configuration (optional).
168
+ * @returns {string}
169
+ * Result.
170
+ */
171
+ function markdownTable(table, options) {
172
+ const settings = options || {};
173
+ const align = (settings.align || []).concat();
174
+ const stringLength = settings.stringLength || defaultStringLength;
175
+ /** @type {Array<number>} Character codes as symbols for alignment per column. */
176
+ const alignments = [];
177
+ /** @type {Array<Array<string>>} Cells per row. */
178
+ const cellMatrix = [];
179
+ /** @type {Array<Array<number>>} Sizes of each cell per row. */
180
+ const sizeMatrix = [];
181
+ /** @type {Array<number>} */
182
+ const longestCellByColumn = [];
183
+ let mostCellsPerRow = 0;
184
+ let rowIndex = -1;
185
+ while (++rowIndex < table.length) {
186
+ /** @type {Array<string>} */
187
+ const row = [];
188
+ /** @type {Array<number>} */
189
+ const sizes = [];
190
+ let columnIndex = -1;
191
+ if (table[rowIndex].length > mostCellsPerRow) mostCellsPerRow = table[rowIndex].length;
192
+ while (++columnIndex < table[rowIndex].length) {
193
+ const cell = serialize(table[rowIndex][columnIndex]);
194
+ if (settings.alignDelimiters !== false) {
195
+ const size = stringLength(cell);
196
+ sizes[columnIndex] = size;
197
+ if (longestCellByColumn[columnIndex] === void 0 || size > longestCellByColumn[columnIndex]) longestCellByColumn[columnIndex] = size;
198
+ }
199
+ row.push(cell);
200
+ }
201
+ cellMatrix[rowIndex] = row;
202
+ sizeMatrix[rowIndex] = sizes;
203
+ }
204
+ let columnIndex = -1;
205
+ if (typeof align === "object" && "length" in align) while (++columnIndex < mostCellsPerRow) alignments[columnIndex] = toAlignment(align[columnIndex]);
206
+ else {
207
+ const code = toAlignment(align);
208
+ while (++columnIndex < mostCellsPerRow) alignments[columnIndex] = code;
209
+ }
210
+ columnIndex = -1;
211
+ /** @type {Array<string>} */
212
+ const row = [];
213
+ /** @type {Array<number>} */
214
+ const sizes = [];
215
+ while (++columnIndex < mostCellsPerRow) {
216
+ const code = alignments[columnIndex];
217
+ let before = "";
218
+ let after = "";
219
+ if (code === 99) {
220
+ before = ":";
221
+ after = ":";
222
+ } else if (code === 108) before = ":";
223
+ else if (code === 114) after = ":";
224
+ let size = settings.alignDelimiters === false ? 1 : Math.max(1, longestCellByColumn[columnIndex] - before.length - after.length);
225
+ const cell = before + "-".repeat(size) + after;
226
+ if (settings.alignDelimiters !== false) {
227
+ size = before.length + size + after.length;
228
+ if (size > longestCellByColumn[columnIndex]) longestCellByColumn[columnIndex] = size;
229
+ sizes[columnIndex] = size;
230
+ }
231
+ row[columnIndex] = cell;
232
+ }
233
+ cellMatrix.splice(1, 0, row);
234
+ sizeMatrix.splice(1, 0, sizes);
235
+ rowIndex = -1;
236
+ /** @type {Array<string>} */
237
+ const lines = [];
238
+ while (++rowIndex < cellMatrix.length) {
239
+ const row = cellMatrix[rowIndex];
240
+ const sizes = sizeMatrix[rowIndex];
241
+ columnIndex = -1;
242
+ /** @type {Array<string>} */
243
+ const line = [];
244
+ while (++columnIndex < mostCellsPerRow) {
245
+ const cell = row[columnIndex] || "";
246
+ let before = "";
247
+ let after = "";
248
+ if (settings.alignDelimiters !== false) {
249
+ const size = longestCellByColumn[columnIndex] - (sizes[columnIndex] || 0);
250
+ const code = alignments[columnIndex];
251
+ if (code === 114) before = " ".repeat(size);
252
+ else if (code === 99) if (size % 2) {
253
+ before = " ".repeat(size / 2 + .5);
254
+ after = " ".repeat(size / 2 - .5);
255
+ } else {
256
+ before = " ".repeat(size / 2);
257
+ after = before;
258
+ }
259
+ else after = " ".repeat(size);
260
+ }
261
+ if (settings.delimiterStart !== false && !columnIndex) line.push("|");
262
+ if (settings.padding !== false && !(settings.alignDelimiters === false && cell === "") && (settings.delimiterStart !== false || columnIndex)) line.push(" ");
263
+ if (settings.alignDelimiters !== false) line.push(before);
264
+ line.push(cell);
265
+ if (settings.alignDelimiters !== false) line.push(after);
266
+ if (settings.padding !== false) line.push(" ");
267
+ if (settings.delimiterEnd !== false || columnIndex !== mostCellsPerRow - 1) line.push("|");
268
+ }
269
+ lines.push(settings.delimiterEnd === false ? line.join("").replace(/ +$/, "") : line.join(""));
270
+ }
271
+ return lines.join("\n");
133
272
  }
273
+ /**
274
+ * @param {string | null | undefined} [value]
275
+ * Value to serialize.
276
+ * @returns {string}
277
+ * Result.
278
+ */
134
279
  function serialize(value) {
135
- return value === null || value === void 0 ? "" : String(value);
280
+ return value === null || value === void 0 ? "" : String(value);
136
281
  }
282
+ /**
283
+ * @param {string | null | undefined} value
284
+ * Value.
285
+ * @returns {number}
286
+ * Alignment.
287
+ */
137
288
  function toAlignment(value) {
138
- const code = typeof value === "string" ? value.codePointAt(0) : 0;
139
- return code === 67 || code === 99 ? 99 : code === 76 || code === 108 ? 108 : code === 82 || code === 114 ? 114 : 0;
289
+ const code = typeof value === "string" ? value.codePointAt(0) : 0;
290
+ return code === 67 || code === 99 ? 99 : code === 76 || code === 108 ? 108 : code === 82 || code === 114 ? 114 : 0;
140
291
  }
141
-
142
- // src/report-format.ts
143
- import { table } from "table";
144
- import Wordwrap from "wordwrapjs";
145
- var plural = (count, singular) => {
146
- const pr = new Intl.PluralRules("en-US").select(count);
147
- const plural2 = pr === "one" || count === 0 ? singular : `${singular}s`;
148
- return `${count} ${plural2}`;
292
+ //#endregion
293
+ //#region src/report-format.ts
294
+ const plural = (count, singular) => {
295
+ return `${count} ${new Intl.PluralRules("en-US").select(count) === "one" || count === 0 ? singular : `${singular}s`}`;
149
296
  };
150
- var createWrapFn = (enabled) => (str) => enabled ? Wordwrap.wrap(str, { width: 20 }) : str;
297
+ const createWrapFn = (enabled) => (str) => enabled ? Wordwrap.wrap(str, { width: 20 }) : str;
151
298
  function formatTokenReport(result, format) {
152
- const headers = ["Token", "Usage %", "Most used", "Hardcoded", "Found in"];
153
- function getFormatted(entry, wrap) {
154
- const wrapFn = createWrapFn(wrap);
155
- return [
156
- `${entry.category} (${plural(entry.count, "token")})`,
157
- `${entry.percentUsed}% (${plural(entry.usedCount, "token")})`,
158
- wrapFn(entry.mostUsedNames.join(", ")),
159
- entry.hardcoded.toString(),
160
- `${plural(entry.usedInXFiles, "file")}`
161
- ];
162
- }
163
- switch (format) {
164
- case "json":
165
- return JSON.stringify(result, null, 2);
166
- case "markdown": {
167
- return markdownTable([headers, ...result.map((entry) => getFormatted(entry, true))]);
168
- }
169
- case "csv": {
170
- return [headers.join(","), ...result.map((entry) => getFormatted(entry, false).join(","))].join("\n");
171
- }
172
- case "table": {
173
- return table([headers, ...result.map((entry) => getFormatted(entry, true))]);
174
- }
175
- case "text":
176
- default: {
177
- const formatted = result.map((entry) => getFormatted(entry, false));
178
- return headers.map((header, index) => `${header}: ${formatted[index]}`).join("\n");
179
- }
180
- }
299
+ const headers = [
300
+ "Token",
301
+ "Usage %",
302
+ "Most used",
303
+ "Hardcoded",
304
+ "Found in"
305
+ ];
306
+ function getFormatted(entry, wrap) {
307
+ const wrapFn = createWrapFn(wrap);
308
+ return [
309
+ `${entry.category} (${plural(entry.count, "token")})`,
310
+ `${entry.percentUsed}% (${plural(entry.usedCount, "token")})`,
311
+ wrapFn(entry.mostUsedNames.join(", ")),
312
+ entry.hardcoded.toString(),
313
+ `${plural(entry.usedInXFiles, "file")}`
314
+ ];
315
+ }
316
+ switch (format) {
317
+ case "json": return JSON.stringify(result, null, 2);
318
+ case "markdown": return markdownTable([headers, ...result.map((entry) => getFormatted(entry, true))]);
319
+ case "csv": return [headers.join(","), ...result.map((entry) => getFormatted(entry, false).join(","))].join("\n");
320
+ case "table": return table([headers, ...result.map((entry) => getFormatted(entry, true))]);
321
+ default: {
322
+ const formatted = result.map((entry) => getFormatted(entry, false));
323
+ return headers.map((header, index) => `${header}: ${formatted[index]}`).join("\n");
324
+ }
325
+ }
181
326
  }
182
327
  function formatRecipeReport(result, format) {
183
- function getFormatted(entry, wrap) {
184
- const wrapFn = createWrapFn(wrap);
185
- return [
186
- `${entry.recipeName} (${plural(entry.variantCount, "variant")})`,
187
- `${plural(entry.possibleCombinations.length, "value")}`,
188
- `${entry.percentUsed}% (${plural(entry.usedCombinations, "value")})`,
189
- wrapFn(entry.mostUsedCombinations.join(", ")),
190
- `${plural(entry.usedInXFiles, "file")}`,
191
- `jsx: ${entry.jsxPercentUsed}%
192
- fn: ${entry.fnPercentUsed}%`
193
- ];
194
- }
195
- const headers = ["Recipe", "Variant values", "Usage %", "Most used", "Found in", "Used as"];
196
- switch (format) {
197
- case "json": {
198
- return JSON.stringify(result, null, 2);
199
- }
200
- case "markdown": {
201
- return table([headers, ...result.map((entry) => getFormatted(entry, true))]);
202
- }
203
- case "csv": {
204
- return [headers.join(","), ...result.map((entry) => getFormatted(entry, false).join(","))].join("\n");
205
- }
206
- case "table": {
207
- return table([headers, ...result.map((entry) => getFormatted(entry, true))]);
208
- }
209
- case "text":
210
- default: {
211
- const formatted = result.map((entry) => getFormatted(entry, false));
212
- return headers.map((header, index) => `${header}: ${formatted[index]}`).join("\n");
213
- }
214
- }
328
+ function getFormatted(entry, wrap) {
329
+ const wrapFn = createWrapFn(wrap);
330
+ return [
331
+ `${entry.recipeName} (${plural(entry.variantCount, "variant")})`,
332
+ `${plural(entry.possibleCombinations.length, "value")}`,
333
+ `${entry.percentUsed}% (${plural(entry.usedCombinations, "value")})`,
334
+ wrapFn(entry.mostUsedCombinations.join(", ")),
335
+ `${plural(entry.usedInXFiles, "file")}`,
336
+ `jsx: ${entry.jsxPercentUsed}%\nfn: ${entry.fnPercentUsed}%`
337
+ ];
338
+ }
339
+ const headers = [
340
+ "Recipe",
341
+ "Variant values",
342
+ "Usage %",
343
+ "Most used",
344
+ "Found in",
345
+ "Used as"
346
+ ];
347
+ switch (format) {
348
+ case "json": return JSON.stringify(result, null, 2);
349
+ case "markdown": return table([headers, ...result.map((entry) => getFormatted(entry, true))]);
350
+ case "csv": return [headers.join(","), ...result.map((entry) => getFormatted(entry, false).join(","))].join("\n");
351
+ case "table": return table([headers, ...result.map((entry) => getFormatted(entry, true))]);
352
+ default: {
353
+ const formatted = result.map((entry) => getFormatted(entry, false));
354
+ return headers.map((header, index) => `${header}: ${formatted[index]}`).join("\n");
355
+ }
356
+ }
215
357
  }
216
-
217
- // src/reporter.ts
218
- import { logger } from "@bamboocss/logger";
219
-
220
- // package.json
221
- var version = "1.11.1";
222
-
223
- // src/reporter-recipe.ts
358
+ //#endregion
359
+ //#region package.json
360
+ var version = "1.11.2";
361
+ //#endregion
362
+ //#region src/reporter-recipe.ts
224
363
  function analyzeRecipes(ctx, result) {
225
- const recipesReportItems = Array.from(result.componentByIndex.values()).filter(
226
- (reportItem) => reportItem.reportItemType === "recipe" || reportItem.reportItemType === "jsx-recipe"
227
- );
228
- const recipeReportMap = /* @__PURE__ */ new Map();
229
- recipesReportItems.forEach((reportItem) => {
230
- const recipeOrComponentName = reportItem.componentName;
231
- const recipe = ctx.recipes.details.find(
232
- (node) => node.match.test(recipeOrComponentName) || node.baseName === recipeOrComponentName
233
- );
234
- if (!recipe) return;
235
- const recipeName = recipe?.baseName;
236
- if (!recipeReportMap.has(recipeName)) {
237
- recipeReportMap.set(recipeName, /* @__PURE__ */ new Set());
238
- }
239
- recipeReportMap.get(recipeName).add(reportItem);
240
- });
241
- const reportMap = Array.from(recipeReportMap.entries());
242
- const normalizedReportMap = reportMap.map(
243
- ([recipeName, reportItems]) => [recipeName, Array.from(reportItems)]
244
- );
245
- return normalizedReportMap.map(([recipeName, reportItems]) => {
246
- const usedCombinations = reportItems.map(
247
- (component) => component.contains.map((id) => {
248
- const reportItem = result.propByIndex.get(id);
249
- const recipe2 = ctx.recipes.getRecipe(recipeName);
250
- if (!recipe2?.variantKeys.includes(reportItem.propName)) return;
251
- return reportItem.propName + "." + reportItem.value;
252
- }).filter(Boolean)
253
- ).flat();
254
- const distinctUsedCombinations = Array.from(new Set(usedCombinations)).sort();
255
- const usedCount = reportItems.length;
256
- const recipe = ctx.recipes.getRecipe(recipeName);
257
- const variantMap = recipe.variantKeyMap ?? {};
258
- const possibleCombinations = Object.keys(variantMap).reduce((acc, variantName) => {
259
- return acc.concat(variantMap[variantName].map((value) => `${variantName}.${value}`));
260
- }, []);
261
- const variantCount = recipe.variantKeys.length;
262
- const percentUsed = Math.ceil(distinctUsedCombinations.length / (possibleCombinations.length || 1) * 1e4) / 100;
263
- const jsxUsage = reportItems.filter((component) => component.reportItemType === "jsx-recipe");
264
- const fnUsage = reportItems.filter((component) => component.reportItemType === "recipe");
265
- const jsxPercentUsed = Math.ceil(jsxUsage.length / (reportItems.length || 1) * 100);
266
- const fnPercentUsed = Math.ceil(fnUsage.length / (reportItems.length || 1) * 100);
267
- const usedInXFiles = new Set(reportItems.flatMap((component) => component.filepath)).size;
268
- return {
269
- recipeName,
270
- usedInXFiles,
271
- usedCount,
272
- variantCount,
273
- possibleCombinations,
274
- usedCombinations: distinctUsedCombinations.length,
275
- percentUsed,
276
- jsxPercentUsed,
277
- fnPercentUsed,
278
- unusedCombinations: possibleCombinations.length - distinctUsedCombinations.length,
279
- mostUsedCombinations: distinctUsedCombinations.slice(0, 5)
280
- };
281
- }).sort((a, b) => b.percentUsed - a.percentUsed);
364
+ const recipesReportItems = Array.from(result.componentByIndex.values()).filter((reportItem) => reportItem.reportItemType === "recipe" || reportItem.reportItemType === "jsx-recipe");
365
+ const recipeReportMap = /* @__PURE__ */ new Map();
366
+ recipesReportItems.forEach((reportItem) => {
367
+ const recipeOrComponentName = reportItem.componentName;
368
+ const recipe = ctx.recipes.details.find((node) => node.match.test(recipeOrComponentName) || node.baseName === recipeOrComponentName);
369
+ if (!recipe) return;
370
+ const recipeName = recipe?.baseName;
371
+ if (!recipeReportMap.has(recipeName)) recipeReportMap.set(recipeName, /* @__PURE__ */ new Set());
372
+ recipeReportMap.get(recipeName).add(reportItem);
373
+ });
374
+ return Array.from(recipeReportMap.entries()).map(([recipeName, reportItems]) => [recipeName, Array.from(reportItems)]).map(([recipeName, reportItems]) => {
375
+ const usedCombinations = reportItems.map((component) => component.contains.map((id) => {
376
+ const reportItem = result.propByIndex.get(id);
377
+ if (!ctx.recipes.getRecipe(recipeName)?.variantKeys.includes(reportItem.propName)) return;
378
+ return reportItem.propName + "." + reportItem.value;
379
+ }).filter(Boolean)).flat();
380
+ const distinctUsedCombinations = Array.from(new Set(usedCombinations)).sort();
381
+ const usedCount = reportItems.length;
382
+ const recipe = ctx.recipes.getRecipe(recipeName);
383
+ const variantMap = recipe.variantKeyMap ?? {};
384
+ const possibleCombinations = Object.keys(variantMap).reduce((acc, variantName) => {
385
+ return acc.concat(variantMap[variantName].map((value) => `${variantName}.${value}`));
386
+ }, []);
387
+ const variantCount = recipe.variantKeys.length;
388
+ const percentUsed = Math.ceil(distinctUsedCombinations.length / (possibleCombinations.length || 1) * 1e4) / 100;
389
+ const jsxUsage = reportItems.filter((component) => component.reportItemType === "jsx-recipe");
390
+ const fnUsage = reportItems.filter((component) => component.reportItemType === "recipe");
391
+ const jsxPercentUsed = Math.ceil(jsxUsage.length / (reportItems.length || 1) * 100);
392
+ const fnPercentUsed = Math.ceil(fnUsage.length / (reportItems.length || 1) * 100);
393
+ return {
394
+ recipeName,
395
+ usedInXFiles: new Set(reportItems.flatMap((component) => component.filepath)).size,
396
+ usedCount,
397
+ variantCount,
398
+ possibleCombinations,
399
+ usedCombinations: distinctUsedCombinations.length,
400
+ percentUsed,
401
+ jsxPercentUsed,
402
+ fnPercentUsed,
403
+ unusedCombinations: possibleCombinations.length - distinctUsedCombinations.length,
404
+ mostUsedCombinations: distinctUsedCombinations.slice(0, 5)
405
+ };
406
+ }).sort((a, b) => b.percentUsed - a.percentUsed);
282
407
  }
283
-
284
- // src/reporter-token.ts
285
- import { uniq } from "@bamboocss/shared";
286
- var formatter = new Intl.NumberFormat("en-US", {
287
- maximumFractionDigits: 2,
288
- minimumFractionDigits: 2
408
+ //#endregion
409
+ //#region src/reporter-token.ts
410
+ const formatter = new Intl.NumberFormat("en-US", {
411
+ maximumFractionDigits: 2,
412
+ minimumFractionDigits: 2
289
413
  });
290
- var getPercent = (used, total) => {
291
- return Number(formatter.format(used / (total || 1) * 100));
414
+ const getPercent = (used, total) => {
415
+ return Number(formatter.format(used / (total || 1) * 100));
292
416
  };
293
417
  function analyzeTokens(ctx, result) {
294
- const categoryMap = result.derived.globalMaps.byTokenType;
295
- const categoryEntries = Array.from(categoryMap.entries());
296
- const usageMap = /* @__PURE__ */ new Map();
297
- const totalMap = /* @__PURE__ */ new Map();
298
- categoryEntries.forEach(([category, categoryIds]) => {
299
- const usage = usageMap.get(category) || usageMap.set(category, []).get(category);
300
- categoryIds.forEach((id) => {
301
- const item = result.propByIndex.get(id);
302
- if (item?.value == null) return;
303
- const type = item.isKnownValue ? "token" : "nonToken";
304
- const value = item.value.toString();
305
- const filePath = item.filepath;
306
- const loc = item.range ? {
307
- line: item.range.startLineNumber,
308
- column: item.range.startColumn
309
- } : null;
310
- usage.push({ category, value, filePath, loc, type });
311
- });
312
- const totalTokens = ctx.tokens.view.categoryMap.get(category)?.size ?? 0;
313
- totalMap.set(category, totalTokens);
314
- });
315
- const usageEntries = Array.from(usageMap.entries());
316
- const percentMap = usageEntries.reduce((map, [category, usage]) => {
317
- const total = totalMap.get(category) ?? 0;
318
- const tokens = usage.reduce((acc, item) => {
319
- return item.type === "token" ? acc.add(item.value) : acc;
320
- }, /* @__PURE__ */ new Set());
321
- const percent = getPercent(tokens.size, total);
322
- return map.set(category, {
323
- total,
324
- used: tokens.size,
325
- unused: total - tokens.size,
326
- percent
327
- });
328
- }, /* @__PURE__ */ new Map());
329
- const tokenNameMap = usageEntries.reduce((map, [category, usage]) => {
330
- const existing = map.get(category) ?? [];
331
- usage.forEach(({ value, type }) => {
332
- if (type === "token") existing.push(value);
333
- });
334
- const sorted = uniq(existing).sort(
335
- (a, b) => (result.derived.globalMaps.byTokenName.get(b)?.size ?? 0) - (result.derived.globalMaps.byTokenName.get(a)?.size ?? 0)
336
- );
337
- return map.set(category, sorted);
338
- }, /* @__PURE__ */ new Map());
339
- const fileUsageMap = usageEntries.reduce((map, [category, usage]) => {
340
- const existing = map.get(category) ?? /* @__PURE__ */ new Set();
341
- usage.forEach(({ filePath }) => {
342
- if (filePath.startsWith("@config")) return;
343
- existing.add(filePath);
344
- });
345
- return map.set(category, existing);
346
- }, /* @__PURE__ */ new Map());
347
- const hardcodedTokenMap = usageEntries.reduce((map, [category, usage]) => {
348
- const items = /* @__PURE__ */ new Set();
349
- usage.forEach(({ type, value }) => {
350
- if (type === "nonToken") items.add(value);
351
- });
352
- return map.set(category, items.size);
353
- }, /* @__PURE__ */ new Map());
354
- return {
355
- usageMap,
356
- percentMap,
357
- tokenNameMap,
358
- fileUsageMap,
359
- hardcodedTokenMap,
360
- totalMap,
361
- getSummary() {
362
- const summary = categoryEntries.map(([category]) => {
363
- const percent = percentMap.get(category);
364
- return {
365
- category,
366
- count: percent?.total ?? 0,
367
- usedInXFiles: fileUsageMap.get(category)?.size ?? 0,
368
- usedCount: percent?.used ?? 0,
369
- percentUsed: percent?.percent ?? 0,
370
- hardcoded: hardcodedTokenMap.get(category) ?? 0,
371
- mostUsedNames: tokenNameMap.get(category)?.slice(0, 5) ?? []
372
- };
373
- });
374
- return summary.sort((a, b) => b.percentUsed - a.percentUsed);
375
- }
376
- };
418
+ const categoryMap = result.derived.globalMaps.byTokenType;
419
+ const categoryEntries = Array.from(categoryMap.entries());
420
+ const usageMap = /* @__PURE__ */ new Map();
421
+ const totalMap = /* @__PURE__ */ new Map();
422
+ categoryEntries.forEach(([category, categoryIds]) => {
423
+ const usage = usageMap.get(category) || usageMap.set(category, []).get(category);
424
+ categoryIds.forEach((id) => {
425
+ const item = result.propByIndex.get(id);
426
+ if (item?.value == null) return;
427
+ const type = item.isKnownValue ? "token" : "nonToken";
428
+ const value = item.value.toString();
429
+ const filePath = item.filepath;
430
+ const loc = item.range ? {
431
+ line: item.range.startLineNumber,
432
+ column: item.range.startColumn
433
+ } : null;
434
+ usage.push({
435
+ category,
436
+ value,
437
+ filePath,
438
+ loc,
439
+ type
440
+ });
441
+ });
442
+ const totalTokens = ctx.tokens.view.categoryMap.get(category)?.size ?? 0;
443
+ totalMap.set(category, totalTokens);
444
+ });
445
+ const usageEntries = Array.from(usageMap.entries());
446
+ const percentMap = usageEntries.reduce((map, [category, usage]) => {
447
+ const total = totalMap.get(category) ?? 0;
448
+ const tokens = usage.reduce((acc, item) => {
449
+ return item.type === "token" ? acc.add(item.value) : acc;
450
+ }, /* @__PURE__ */ new Set());
451
+ const percent = getPercent(tokens.size, total);
452
+ return map.set(category, {
453
+ total,
454
+ used: tokens.size,
455
+ unused: total - tokens.size,
456
+ percent
457
+ });
458
+ }, /* @__PURE__ */ new Map());
459
+ const tokenNameMap = usageEntries.reduce((map, [category, usage]) => {
460
+ const existing = map.get(category) ?? [];
461
+ usage.forEach(({ value, type }) => {
462
+ if (type === "token") existing.push(value);
463
+ });
464
+ const sorted = uniq(existing).sort((a, b) => (result.derived.globalMaps.byTokenName.get(b)?.size ?? 0) - (result.derived.globalMaps.byTokenName.get(a)?.size ?? 0));
465
+ return map.set(category, sorted);
466
+ }, /* @__PURE__ */ new Map());
467
+ const fileUsageMap = usageEntries.reduce((map, [category, usage]) => {
468
+ const existing = map.get(category) ?? /* @__PURE__ */ new Set();
469
+ usage.forEach(({ filePath }) => {
470
+ if (filePath.startsWith("@config")) return;
471
+ existing.add(filePath);
472
+ });
473
+ return map.set(category, existing);
474
+ }, /* @__PURE__ */ new Map());
475
+ const hardcodedTokenMap = usageEntries.reduce((map, [category, usage]) => {
476
+ const items = /* @__PURE__ */ new Set();
477
+ usage.forEach(({ type, value }) => {
478
+ if (type === "nonToken") items.add(value);
479
+ });
480
+ return map.set(category, items.size);
481
+ }, /* @__PURE__ */ new Map());
482
+ return {
483
+ usageMap,
484
+ percentMap,
485
+ tokenNameMap,
486
+ fileUsageMap,
487
+ hardcodedTokenMap,
488
+ totalMap,
489
+ getSummary() {
490
+ return categoryEntries.map(([category]) => {
491
+ const percent = percentMap.get(category);
492
+ return {
493
+ category,
494
+ count: percent?.total ?? 0,
495
+ usedInXFiles: fileUsageMap.get(category)?.size ?? 0,
496
+ usedCount: percent?.used ?? 0,
497
+ percentUsed: percent?.percent ?? 0,
498
+ hardcoded: hardcodedTokenMap.get(category) ?? 0,
499
+ mostUsedNames: tokenNameMap.get(category)?.slice(0, 5) ?? []
500
+ };
501
+ }).sort((a, b) => b.percentUsed - a.percentUsed);
502
+ }
503
+ };
377
504
  }
378
-
379
- // src/reporter.ts
505
+ //#endregion
506
+ //#region src/reporter.ts
380
507
  var Reporter = class {
381
- constructor(ctx, options) {
382
- this.ctx = ctx;
383
- this.options = options;
384
- }
385
- #parserResults = /* @__PURE__ */ new Map();
386
- #extractTimes = /* @__PURE__ */ new Map();
387
- #sheet;
388
- #report;
389
- setup = () => {
390
- this.#sheet = this.ctx.createSheet();
391
- this.ctx.appendLayerParams(this.#sheet);
392
- this.ctx.appendBaselineCss(this.#sheet);
393
- this.parseFiles();
394
- this.ctx.appendParserCss(this.#sheet);
395
- };
396
- get report() {
397
- return this.#report;
398
- }
399
- parseFiles = () => {
400
- const { getFiles } = this.options;
401
- const files = getFiles();
402
- logger.info("analyze", `Analyzing ${files.length} file(s) for token and recipe usage...`);
403
- for (const file of files) {
404
- this.parseFile(file);
405
- }
406
- };
407
- parseFile = (file) => {
408
- const { project, getRelativePath, onResult } = this.options;
409
- const { config } = this.ctx;
410
- const start = performance.now();
411
- const result = project.parseSourceFile?.(file);
412
- const extractMs = performance.now() - start;
413
- const filePath = getRelativePath(config.cwd, file);
414
- this.#extractTimes.set(filePath, extractMs);
415
- logger.debug("analyze", `Parsed ${file} in ${extractMs}ms`);
416
- if (result) {
417
- this.#parserResults.set(filePath, result);
418
- onResult?.(file, result);
419
- }
420
- };
421
- init = () => {
422
- const { project } = this.options;
423
- this.setup();
424
- const classify = project.classify(this.#parserResults);
425
- this.#report = {
426
- schemaVersion: version,
427
- details: classify.details,
428
- propByIndex: classify.propById,
429
- componentByIndex: classify.componentById,
430
- derived: classify.derived
431
- };
432
- };
433
- getTokenReport = () => {
434
- const { project } = this.options;
435
- return analyzeTokens(project.parserOptions, this.#report);
436
- };
437
- getRecipeReport = () => {
438
- const { project } = this.options;
439
- return analyzeRecipes(project.parserOptions, this.#report);
440
- };
441
- };
442
- export {
443
- Reporter,
444
- formatRecipeReport,
445
- formatTokenReport
508
+ ctx;
509
+ options;
510
+ #parserResults = /* @__PURE__ */ new Map();
511
+ #extractTimes = /* @__PURE__ */ new Map();
512
+ #sheet;
513
+ #report;
514
+ constructor(ctx, options) {
515
+ this.ctx = ctx;
516
+ this.options = options;
517
+ }
518
+ setup = () => {
519
+ this.#sheet = this.ctx.createSheet();
520
+ this.ctx.appendLayerParams(this.#sheet);
521
+ this.ctx.appendBaselineCss(this.#sheet);
522
+ this.parseFiles();
523
+ this.ctx.appendParserCss(this.#sheet);
524
+ };
525
+ get report() {
526
+ return this.#report;
527
+ }
528
+ parseFiles = () => {
529
+ const { getFiles } = this.options;
530
+ const files = getFiles();
531
+ logger.info("analyze", `Analyzing ${files.length} file(s) for token and recipe usage...`);
532
+ for (const file of files) this.parseFile(file);
533
+ };
534
+ parseFile = (file) => {
535
+ const { project, getRelativePath, onResult } = this.options;
536
+ const { config } = this.ctx;
537
+ const start = performance.now();
538
+ const result = project.parseSourceFile?.(file);
539
+ const extractMs = performance.now() - start;
540
+ const filePath = getRelativePath(config.cwd, file);
541
+ this.#extractTimes.set(filePath, extractMs);
542
+ logger.debug("analyze", `Parsed ${file} in ${extractMs}ms`);
543
+ if (result) {
544
+ this.#parserResults.set(filePath, result);
545
+ onResult?.(file, result);
546
+ }
547
+ };
548
+ init = () => {
549
+ const { project } = this.options;
550
+ this.setup();
551
+ const classify = project.classify(this.#parserResults);
552
+ this.#report = {
553
+ schemaVersion: version,
554
+ details: classify.details,
555
+ propByIndex: classify.propById,
556
+ componentByIndex: classify.componentById,
557
+ derived: classify.derived
558
+ };
559
+ };
560
+ getTokenReport = () => {
561
+ const { project } = this.options;
562
+ return analyzeTokens(project.parserOptions, this.#report);
563
+ };
564
+ getRecipeReport = () => {
565
+ const { project } = this.options;
566
+ return analyzeRecipes(project.parserOptions, this.#report);
567
+ };
446
568
  };
569
+ //#endregion
570
+ export { Reporter, formatRecipeReport, formatTokenReport };