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