guard-my-design-system 1.6.0 → 1.8.0
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/README.md +12 -2
- package/package.json +2 -2
- package/src/judge.mjs +58 -3
- package/src/report.mjs +6 -2
package/README.md
CHANGED
|
@@ -59,8 +59,18 @@ it updates that same comment. It never adds more comments:
|
|
|
59
59
|
- **A palette colour where a theme variable exists.** On a shadcn repo whose
|
|
60
60
|
theme file holds the variables, `text-slate-500` in the app's own code is
|
|
61
61
|
flagged and the theme file named. Off on utility-class installs.
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
- **A colour or a pixel size written onto a kit component.** On a product
|
|
63
|
+
built on MUI, Mantine, Chakra UI or Ant Design, `color: '#667085'` in an
|
|
64
|
+
`sx` prop or a style object is flagged and the finding says whether the
|
|
65
|
+
theme already holds that colour, or tells you to add it there once. A
|
|
66
|
+
pixel size such as `p: '12px'` is turned into the theme's spacing step. The
|
|
67
|
+
advice is in the kit's own words: `sx` paths on MUI, props on Mantine,
|
|
68
|
+
style props on Chakra, the theme config on Ant Design.
|
|
69
|
+
|
|
70
|
+
It reads the repo the way the roast report does. On a kit repo the theme's
|
|
71
|
+
colours are the token set, and a colour the theme already holds is flagged
|
|
72
|
+
on a kit component all the same: writing it by hand is the exact mistake the
|
|
73
|
+
check exists for. On a shadcn repo the
|
|
64
74
|
installed catalogue, installed registries and kit blocks are not judged:
|
|
65
75
|
`shadcn add` is not a sin. On a repo that publishes a shadcn registry only
|
|
66
76
|
the published folders are judged. `!important` in an embedded widget's
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "guard-my-design-system",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "Your design system dies one pull request at a time. This makes sure it doesn't. A guard that judges only the lines a change adds, against the system the repo already has, and names the on-system value the author probably meant.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"src/"
|
|
12
12
|
],
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"roast-my-design-system": "
|
|
14
|
+
"roast-my-design-system": "8.4.6"
|
|
15
15
|
},
|
|
16
16
|
"keywords": [
|
|
17
17
|
"design-system",
|
package/src/judge.mjs
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
isCodeFile, isStyleFile, typefaceOf, GENERIC_FONTS,
|
|
13
13
|
definedComponents, exemptReason,
|
|
14
14
|
EXTRA_KINDS, extraValue, fontDeclarations,
|
|
15
|
-
WIDGET_CSS_RE, isLibraryClass, PALETTE_CLASS_RE,
|
|
15
|
+
WIDGET_CSS_RE, isLibraryClass, PALETTE_CLASS_RE, blankComments, kitPaintFindings,
|
|
16
16
|
} from 'roast-my-design-system/engine';
|
|
17
17
|
|
|
18
18
|
// Folder membership, the way the engine's own splits do it.
|
|
@@ -106,6 +106,20 @@ export function judge(added, system, { readFile } = {}) {
|
|
|
106
106
|
};
|
|
107
107
|
const isWidgetFile = (file) => underAny(file, prof.widgetDirs) || WIDGET_CSS_RE.test(wholeText(file) ?? '');
|
|
108
108
|
const paletteRe = new RegExp(PALETTE_CLASS_RE.source, 'g');
|
|
109
|
+
// The added line with its comments blanked, the way the report and the
|
|
110
|
+
// live checks read a file before matching (roast 8.4.4): a class named in
|
|
111
|
+
// a comment paints nothing. Blanked from the whole file when it is at hand,
|
|
112
|
+
// so a block comment opened on an earlier line still counts as a comment.
|
|
113
|
+
const blanked = new Map();
|
|
114
|
+
const codeText = (file, lineNo, text) => {
|
|
115
|
+
const w = wholeText(file);
|
|
116
|
+
if (w == null) return blankComments(text);
|
|
117
|
+
if (!blanked.has(file)) blanked.set(file, blankComments(w).split('\n'));
|
|
118
|
+
// blanking keeps every character's place, so the file's line is the
|
|
119
|
+
// diff's line only if the lengths match; otherwise the file has moved on
|
|
120
|
+
const l = blanked.get(file)[lineNo - 1];
|
|
121
|
+
return l != null && l.length === text.length ? l : blankComments(text);
|
|
122
|
+
};
|
|
109
123
|
|
|
110
124
|
// The system was learned from the tree that already CONTAINS these added
|
|
111
125
|
// lines, so a new value would vouch for itself. A value is only "known"
|
|
@@ -171,6 +185,35 @@ export function judge(added, system, { readFile } = {}) {
|
|
|
171
185
|
}
|
|
172
186
|
}
|
|
173
187
|
|
|
188
|
+
// A product built on a kit (MUI, Mantine, Chakra UI, Ant Design), roast
|
|
189
|
+
// 8.4.6: a colour or a pixel size written onto a kit component where the
|
|
190
|
+
// theme has a value. The engine judges the whole file (the import that makes
|
|
191
|
+
// it a kit file sits at the top, where the diff never looks) and the guard
|
|
192
|
+
// keeps the hits on added lines. On a kit file the kit rule owns colours
|
|
193
|
+
// and the pixel sizes it named, so the generic rules stay quiet about the
|
|
194
|
+
// same value: one line, one finding, the same words as the roast report's
|
|
195
|
+
// live checks.
|
|
196
|
+
const kit = prof.kit ?? null;
|
|
197
|
+
const kitJudged = new Map();
|
|
198
|
+
const kitLines = (file) => {
|
|
199
|
+
if (!kit) return null;
|
|
200
|
+
if (!kitJudged.has(file)) {
|
|
201
|
+
const w = wholeText(file);
|
|
202
|
+
const j = w == null ? null : kitPaintFindings(w, kit, { file });
|
|
203
|
+
if (!j || j.exempt) kitJudged.set(file, null);
|
|
204
|
+
else {
|
|
205
|
+
const byLine = new Map();
|
|
206
|
+
for (const f of j.findings) {
|
|
207
|
+
const ln = w.slice(0, f.index).split('\n').length;
|
|
208
|
+
if (!byLine.has(ln)) byLine.set(ln, []);
|
|
209
|
+
byLine.get(ln).push(f);
|
|
210
|
+
}
|
|
211
|
+
kitJudged.set(file, byLine);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return kitJudged.get(file);
|
|
215
|
+
};
|
|
216
|
+
|
|
174
217
|
const findings = [];
|
|
175
218
|
|
|
176
219
|
for (const { file, line, text } of added) {
|
|
@@ -180,7 +223,18 @@ export function judge(added, system, { readFile } = {}) {
|
|
|
180
223
|
|
|
181
224
|
const seen = extractStyling(text, { css });
|
|
182
225
|
|
|
226
|
+
const onKit = css ? null : kitLines(file);
|
|
227
|
+
const kitPx = new Set();
|
|
228
|
+
for (const f of onKit?.get(line) ?? []) {
|
|
229
|
+
if (f.rule === 'kit-px') kitPx.add(f.value.split(': ')[1]);
|
|
230
|
+
findings.push({
|
|
231
|
+
file, line, kind: f.rule, value: f.value, label: f.label,
|
|
232
|
+
advice: f.note ? `${f.note}. ${f.fix.replace(/\.$/, '')}` : f.fix.replace(/\.$/, ''),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
183
236
|
for (const c of seen.colors) {
|
|
237
|
+
if (onKit) break; // the kit rule owns colours on a kit file
|
|
184
238
|
if (tokenSet.has(c.value)) continue; // disciplined token use
|
|
185
239
|
const near = c.value.startsWith('#') ? nearestColor(c.value, system.tokens) : null;
|
|
186
240
|
findings.push({
|
|
@@ -194,6 +248,7 @@ export function judge(added, system, { readFile } = {}) {
|
|
|
194
248
|
}
|
|
195
249
|
|
|
196
250
|
for (const s of seen.spacing) {
|
|
251
|
+
if (kitPx.has(s.value)) continue; // the kit rule said it
|
|
197
252
|
if (knownLengths.has(s.value)) continue; // the codebase already uses it
|
|
198
253
|
const near = nearestLength(s.value, [...knownLengths]);
|
|
199
254
|
findings.push({
|
|
@@ -282,10 +337,10 @@ export function judge(added, system, { readFile } = {}) {
|
|
|
282
337
|
// CSS-variable mode): paint from a tin. The same pattern the report
|
|
283
338
|
// counts per 100 files; here, per added line.
|
|
284
339
|
if (!css && prof.paletteReady) {
|
|
285
|
-
for (const m of text.matchAll(paletteRe)) {
|
|
340
|
+
for (const m of codeText(file, line, text).matchAll(paletteRe)) {
|
|
286
341
|
findings.push({
|
|
287
342
|
file, line, kind: 'palette', value: m[0],
|
|
288
|
-
advice: `a theme
|
|
343
|
+
advice: `a theme token covers this; use it as the class (bg-primary, text-muted-foreground), or add one${prof.sheetFile ? ` to ${prof.sheetFile}` : ' to the theme'} once`,
|
|
289
344
|
});
|
|
290
345
|
}
|
|
291
346
|
}
|
package/src/report.mjs
CHANGED
|
@@ -16,7 +16,11 @@ const KIND_LABEL = {
|
|
|
16
16
|
inline: 'inline style block',
|
|
17
17
|
component: 'second definition of',
|
|
18
18
|
palette: 'palette colour where a theme variable exists',
|
|
19
|
+
// on a kit repo the engine words the label with the kit's name (f.label)
|
|
20
|
+
'kit-colour': 'colour written onto a kit component',
|
|
21
|
+
'kit-px': 'pixel size on a kit component',
|
|
19
22
|
};
|
|
23
|
+
const labelOf = (f) => f.label ?? KIND_LABEL[f.kind];
|
|
20
24
|
|
|
21
25
|
// Kinds whose label already says everything; printing the value repeats it.
|
|
22
26
|
const VALUELESS = new Set(['important', 'inline']);
|
|
@@ -29,7 +33,7 @@ export function terminalReport(findings) {
|
|
|
29
33
|
}
|
|
30
34
|
const lines = [`guard-my-design-system: ${findings.length} new issue${findings.length === 1 ? '' : 's'} in this change\n`];
|
|
31
35
|
for (const f of findings) {
|
|
32
|
-
lines.push(` ${f.file}:${f.line} · ${
|
|
36
|
+
lines.push(` ${f.file}:${f.line} · ${labelOf(f)} ${VALUELESS.has(f.kind) ? '' : f.value}`.trimEnd() + `. ${capitalise(f.advice)}.`);
|
|
33
37
|
}
|
|
34
38
|
lines.push('');
|
|
35
39
|
lines.push(' Only lines added in this change were counted. The existing codebase was not judged.');
|
|
@@ -49,7 +53,7 @@ export function markdownReport(findings) {
|
|
|
49
53
|
}
|
|
50
54
|
const out = [`**🛡 guard-my-design-system: ${findings.length} new issue${findings.length === 1 ? '' : 's'} in this pull request**`, ''];
|
|
51
55
|
for (const f of findings) {
|
|
52
|
-
out.push(`- \`${f.file}:${f.line}\` · ${
|
|
56
|
+
out.push(`- \`${f.file}:${f.line}\` · ${labelOf(f)} ${VALUELESS.has(f.kind) ? '' : `\`${f.value}\``}`.trimEnd() + `. ${capitalise(f.advice)}.`);
|
|
53
57
|
}
|
|
54
58
|
out.push('');
|
|
55
59
|
out.push(`<sub>Only added lines are checked; the existing codebase is never judged. ${FOOTER}</sub>`);
|