cursedbelt 5.103.0 โ 5.103.1
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guardrailsEnforce.d.ts","sourceRoot":"","sources":["../../scripts/guardrailsEnforce.ts"],"names":[],"mappings":";AAsQA,UAAU,SAAS;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB;;;sEAGkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,IAAI;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,SAAS,EAAE,CAAC;CACzD;AAED,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,EAAO,GAAG,MAAM,EAAE,CA6B9D;
|
|
1
|
+
{"version":3,"file":"guardrailsEnforce.d.ts","sourceRoot":"","sources":["../../scripts/guardrailsEnforce.ts"],"names":[],"mappings":";AAsQA,UAAU,SAAS;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB;;;sEAGkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,IAAI;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,SAAS,EAAE,CAAC;CACzD;AAED,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,EAAO,GAAG,MAAM,EAAE,CA6B9D;AAm1CD,iGAAiG;AACjG,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAE3D;AAoHD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAIpE;AA+6BD,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,GAAG,IAAI,CAEpF;AAID,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,CAoBhE;AAyKD,kGAAkG;AAClG,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAatE;AAqDD,UAAU,MAAM;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACb;AA0BD,+FAA+F;AAC/F,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAgBpD;AAID,wEAAwE;AACxE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAO5E;AAiJD,eAAO,MAAM,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAoCtC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,WAAW,CAAC,MAAM,CAAiE,CAAC;AAEtH,0EAA0E;AAC1E,eAAO,MAAM,SAAS,EAAE,SAAS,MAAM,EAAwE,CAAC;AAShH,eAAO,MAAM,uBAAuB,aAAkD,CAAC;AAGvF,MAAM,WAAW,SAAS;IACzB,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACb;AAED;2FAC2F;AAC3F,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CA+C3F;AAED,YAAY,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;AAgHhC,wBAAgB,0BAA0B,CAAC,SAAS,GAAE,MAAwB,GAAG,MAAM,EAAE,CAyBxF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cursedbelt",
|
|
3
|
-
"version": "5.103.
|
|
3
|
+
"version": "5.103.1",
|
|
4
4
|
"license": "ISC",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "The React design system of the cursedbelt split โ components, styles, theme. cursedbelt-core below it; server tier in cursedbelt-server.",
|
|
@@ -77,9 +77,41 @@ describe('no-raw-action-button', () => {
|
|
|
77
77
|
test('a CLOSED or self-closing group earlier in the file exempts nothing after it', () => {
|
|
78
78
|
const closed = `<Tabs value={t}><TabsList /></Tabs>\n<button role="tab">Loose</button>`;
|
|
79
79
|
expect(check('no-raw-action-button', closed)).toHaveLength(1);
|
|
80
|
-
const selfClosed = `<
|
|
80
|
+
const selfClosed = `<SegmentedControl options={o} />\n<button role="radio">Loose</button>`;
|
|
81
81
|
expect(check('no-raw-action-button', selfClosed)).toHaveLength(1);
|
|
82
82
|
});
|
|
83
|
+
|
|
84
|
+
// Task 4098: the type argument's `>` ended the tag, so the real `/>` went unread and the
|
|
85
|
+
// control stayed open for the rest of the file. Desk's rewind list passed on exactly this.
|
|
86
|
+
test('๐ด a self-closing GENERIC group exempts nothing after it', () => {
|
|
87
|
+
const union = `<SegmentedControl<"a" | "b"> value={v} onChange={set} options={o} />
|
|
88
|
+
<button role="radio">Loose</button>`;
|
|
89
|
+
expect(check('no-raw-action-button', union)).toHaveLength(1);
|
|
90
|
+
const multiline = `<SegmentedControl<
|
|
91
|
+
Record<string, "a" | "b">
|
|
92
|
+
>
|
|
93
|
+
value={v}
|
|
94
|
+
onChange={(next) => set(next)}
|
|
95
|
+
/>
|
|
96
|
+
<button role="tab">Loose</button>`;
|
|
97
|
+
expect(check('no-raw-action-button', multiline)).toHaveLength(1);
|
|
98
|
+
const functionType = `<Tabs<(next: string) => void> value={t} />\n<button role="switch">Loose</button>`;
|
|
99
|
+
expect(check('no-raw-action-button', functionType)).toHaveLength(1);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('a group the belt does not ship exempts nothing, whatever an app names it', () => {
|
|
103
|
+
const source = `<RadioGroup value={v}>
|
|
104
|
+
<button role="radio">A</button>
|
|
105
|
+
</RadioGroup>`;
|
|
106
|
+
expect(check('no-raw-action-button', source)).toHaveLength(1);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('a generic group left OPEN still exempts its own part', () => {
|
|
110
|
+
const source = `<SegmentedControl<"a" | "b"> value={v}>
|
|
111
|
+
<button role="radio" aria-checked={v === 'a'}>A</button>
|
|
112
|
+
</SegmentedControl>`;
|
|
113
|
+
expect(check('no-raw-action-button', source)).toHaveLength(0);
|
|
114
|
+
});
|
|
83
115
|
});
|
|
84
116
|
|
|
85
117
|
// Filed 2026-09-15 after three false positives in three repos, all one defect: the tag
|
|
@@ -122,11 +154,11 @@ describe('opening-tag extraction (comment-aware)', () => {
|
|
|
122
154
|
});
|
|
123
155
|
|
|
124
156
|
test('a prose angle bracket does not hide a role="radio" below it', () => {
|
|
125
|
-
const source = `<
|
|
157
|
+
const source = `<SegmentedControl value={v}><button
|
|
126
158
|
/* A radio-like list rather than a <select>. */
|
|
127
159
|
role="radio"
|
|
128
160
|
aria-checked={selected}
|
|
129
|
-
>Move here</button></
|
|
161
|
+
>Move here</button></SegmentedControl>`;
|
|
130
162
|
expect(check('no-raw-action-button', source)).toHaveLength(0);
|
|
131
163
|
});
|
|
132
164
|
|
|
@@ -206,12 +238,12 @@ describe('a presentational marker must be an ATTRIBUTE, not prose about one', ()
|
|
|
206
238
|
});
|
|
207
239
|
|
|
208
240
|
test('the REAL attributes still pass silently, prose and all', () => {
|
|
209
|
-
const source = `<
|
|
241
|
+
const source = `<SegmentedControl value={v}><button
|
|
210
242
|
// A radio-like list: \`role="radio"\` + \`aria-checked\` says so to a screen reader.
|
|
211
243
|
role="radio"
|
|
212
244
|
aria-checked={selected}
|
|
213
245
|
type="button"
|
|
214
|
-
>Move here</button></
|
|
246
|
+
>Move here</button></SegmentedControl>`;
|
|
215
247
|
expect(check('no-raw-action-button', source)).toHaveLength(0);
|
|
216
248
|
});
|
|
217
249
|
|
|
@@ -210,8 +210,8 @@
|
|
|
210
210
|
//
|
|
211
211
|
// A presentational ARIA role/attribute on the tag (role="tab" | "switch" | "radio" |
|
|
212
212
|
// "menuitemradio" | "presentation", or aria-selected) exempts a `<button>` from
|
|
213
|
-
// `no-raw-action-button` ONLY inside a belt `Tabs`/`TabsList`/`SegmentedControl
|
|
214
|
-
//
|
|
213
|
+
// `no-raw-action-button` ONLY inside a belt `Tabs`/`TabsList`/`SegmentedControl`
|
|
214
|
+
// (task 2381 โ a bare marker was the loophole; see the rule).
|
|
215
215
|
//
|
|
216
216
|
// ๐ด Every opt-out is COUNTED (`opt-out-count`) and must lead with its cause
|
|
217
217
|
// (`opt-out-needs-cause`): `guardrails-ignore <rule>: <cause> โ <six+ words of why>`, the
|
|
@@ -356,6 +356,38 @@ interface OpeningTag {
|
|
|
356
356
|
readonly code: string;
|
|
357
357
|
}
|
|
358
358
|
|
|
359
|
+
function endOfTagName(source: string, startIndex: number): number {
|
|
360
|
+
const name = /<[\w$.:-]+/y;
|
|
361
|
+
name.lastIndex = startIndex;
|
|
362
|
+
return name.test(source) ? name.lastIndex : startIndex;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** The index past a JSX type argument at `index` (`<SegmentedControl<"a" | "b"> โฆ`), or
|
|
366
|
+
* `index` when none opens there or it never closes. Its `>` is not the end of the tag: read
|
|
367
|
+
* as one, a self-closing generic control looked OPEN, and with no `</SegmentedControl>` to
|
|
368
|
+
* shut it every role-marked button below it passed `no-raw-action-button` (forge task 4098).
|
|
369
|
+
* The `>` of a `=>` inside a function type closes nothing. */
|
|
370
|
+
function endOfTypeArguments(source: string, index: number): number {
|
|
371
|
+
if (source[index] !== "<") return index;
|
|
372
|
+
let depth = 0;
|
|
373
|
+
let i = index;
|
|
374
|
+
while (i < source.length) {
|
|
375
|
+
const ch = source[i];
|
|
376
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
377
|
+
i = skipQuoted(source, i);
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
if (source.slice(i, i + 2) === "=>") {
|
|
381
|
+
i += 2;
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
if (ch === "<") depth++;
|
|
385
|
+
else if (ch === ">" && --depth === 0) return i + 1;
|
|
386
|
+
i++;
|
|
387
|
+
}
|
|
388
|
+
return index;
|
|
389
|
+
}
|
|
390
|
+
|
|
359
391
|
/** Read from a `<button` (or similar) match to the end of its opening tag, tolerating
|
|
360
392
|
* `{...}` JS expressions in attribute values that may themselves contain `>`
|
|
361
393
|
* (`onClick={() => โฆ}`, which works because that `>` sits inside braces).
|
|
@@ -372,6 +404,12 @@ function readOpeningTag(source: string, startIndex: number): OpeningTag {
|
|
|
372
404
|
let i = startIndex;
|
|
373
405
|
let braceDepth = 0;
|
|
374
406
|
let code = "";
|
|
407
|
+
const nameEnd = endOfTagName(source, startIndex);
|
|
408
|
+
const typeEnd = endOfTypeArguments(source, nameEnd);
|
|
409
|
+
if (typeEnd > nameEnd) {
|
|
410
|
+
code = source.slice(startIndex, nameEnd) + blank(source.slice(nameEnd, typeEnd));
|
|
411
|
+
i = typeEnd;
|
|
412
|
+
}
|
|
375
413
|
while (i < source.length) {
|
|
376
414
|
const two = source.slice(i, i + 2);
|
|
377
415
|
if (two === "//") {
|
|
@@ -754,14 +792,16 @@ const PRESENTATIONAL_MARKER_RE =
|
|
|
754
792
|
// Task 2381: a role marker used to exempt ANY raw button, so the cheapest way past this rule
|
|
755
793
|
// was `role="switch"` โ `apps/family/src/App.tsx:1786-1800` (`EditModeToggle`) says in its
|
|
756
794
|
// own comment the marker is there to satisfy it, beside the belt's `Switch`. A marker now
|
|
757
|
-
// exempts only a button INSIDE a belt group that owns that role (`Tabs`, `SegmentedControl
|
|
758
|
-
//
|
|
759
|
-
//
|
|
795
|
+
// exempts only a button INSIDE a belt group that owns that role (`Tabs`, `SegmentedControl`)
|
|
796
|
+
// โ the one place a raw button with it is the group's own part. `ChoiceGroup` takes no
|
|
797
|
+
// children, so nothing can sit inside it, and a `RadioGroup` or `ToggleGroup` is no belt
|
|
798
|
+
// component: naming them only exempted whatever an app happened to call that (forge task 4098).
|
|
799
|
+
// Anywhere else it is `Switch` / `Tabs` / `SegmentedControl` / `ChoiceGroup`, or an opt-out
|
|
760
800
|
// with its cause.
|
|
761
801
|
const BELT_OWN_SRC = `${join(import.meta.dir, "..", "src")}/`;
|
|
762
802
|
const isBeltOwnSource = (filePath: string) =>
|
|
763
803
|
resolve(filePath).startsWith(BELT_OWN_SRC) || /(^|\/)(?:packages|libs)\/cursedbelt\/src\//.test(filePath.replace(/\\/g, "/"));
|
|
764
|
-
const BELT_CHOICE_GROUPS = ["Tabs", "TabsList", "SegmentedControl"
|
|
804
|
+
const BELT_CHOICE_GROUPS = ["Tabs", "TabsList", "SegmentedControl"];
|
|
765
805
|
|
|
766
806
|
/** Is `index` inside an open (not yet closed) belt choice-group element in this file? */
|
|
767
807
|
function insideBeltChoiceGroup(scannable: string, index: number): boolean {
|
|
@@ -778,7 +818,7 @@ function insideBeltChoiceGroup(scannable: string, index: number): boolean {
|
|
|
778
818
|
const noRawActionButton: Rule = {
|
|
779
819
|
name: "no-raw-action-button",
|
|
780
820
|
description:
|
|
781
|
-
'DESIGN.md ยง2 (Component Architecture): no hand-rolled <button> โ use the cursedbelt Button family; a toggle is `Switch`, a tab `Tabs`, a choice `SegmentedControl`/`
|
|
821
|
+
'DESIGN.md ยง2 (Component Architecture): no hand-rolled <button> โ use the cursedbelt Button family; a toggle is `Switch`, a tab `Tabs`, a choice `SegmentedControl`/`ChoiceGroup`. A role marker exempts a button only inside `Tabs` or `SegmentedControl`.',
|
|
782
822
|
check(filePath, source) {
|
|
783
823
|
const violations: Violation[] = [];
|
|
784
824
|
const scannable = blankNonMarkupText(source, filePath);
|