@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 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;
@@ -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 };