@bamboocss/reporter 1.11.1 → 1.11.3
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.mjs
CHANGED
|
@@ -1,446 +1,570 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
157
|
+
return value.length;
|
|
4
158
|
}
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
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
|
-
|
|
139
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
297
|
+
const createWrapFn = (enabled) => (str) => enabled ? Wordwrap.wrap(str, { width: 20 }) : str;
|
|
151
298
|
function formatTokenReport(result, format) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
var version = "1.11.1";
|
|
222
|
-
|
|
223
|
-
// src/reporter-recipe.ts
|
|
358
|
+
//#endregion
|
|
359
|
+
//#region package.json
|
|
360
|
+
var version = "1.11.3";
|
|
361
|
+
//#endregion
|
|
362
|
+
//#region src/reporter-recipe.ts
|
|
224
363
|
function analyzeRecipes(ctx, result) {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
291
|
-
|
|
414
|
+
const getPercent = (used, total) => {
|
|
415
|
+
return Number(formatter.format(used / (total || 1) * 100));
|
|
292
416
|
};
|
|
293
417
|
function analyzeTokens(ctx, result) {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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
|
-
|
|
505
|
+
//#endregion
|
|
506
|
+
//#region src/reporter.ts
|
|
380
507
|
var Reporter = class {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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 };
|