design-lint: twelfth review cycle fixes

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
garrytanandcapy-ai[bot] committed 2026-10-26 13:18:22 +00:00
1 parent e6ed269add
commit 734e07f0a5
2 files changed
+15 -11

No files matched your search

+9 -4
View File
@@ -86,12 +86,14 @@ export function selectorNames(selectors: string[]): { tags: Set<string>; classes
return { tags, classes };
}
/** A selector longer than this is a nesting blow-up (comma lists nested in comma lists, a parent repeated per '&'), not a stylesheet selector; a nested selector that would pass it stands without its parent, and every stored selector is cut there, so a sheet costs its length and not parts^depth. */
/** A selector longer than this is a nesting blow-up (comma lists nested in comma lists, a parent repeated per '&'), not a stylesheet selector; a nested selector that would pass it stands without its parent, and every stored selector is cut there. The sheet as a whole may store at most expansionBudget() selector characters: once that is spent, nested selectors stand unexpanded (their own text, `&` kept), so thousands of children under one long parent cost the sheet its length and not children × MAX_SELECTOR. */
const MAX_SELECTOR = 8192;
const expansionBudget = (src: string): number => src.length * 4 + 65536;
/** Expand a nested selector against its parent: `&` is replaced, a bare selector is descendant-joined. */
function nestSelector(sel: string, parent: string): string {
/** Expand a nested selector against its parent: `&` is replaced, a bare selector is descendant-joined; with expand off (the sheet's budget is spent) the selector stands as written. */
function nestSelector(sel: string, parent: string, expand = true): string {
const parts = splitTop(sel).map((part) => part.trim());
if (!expand) return parts.filter(Boolean).join(', ') || '&';
const substitutions = parts.reduce((n, part) => n + Math.max(1, part.split('&').length - 1), 0);
const wrapped = parent.includes(',') ? parent.length + 5 : parent.length;
if (sel.length + wrapped * substitutions > MAX_SELECTOR) return parts.map((part) => part.replace(/&/g, '').trim()).filter(Boolean).join(', ') || splitTop(parent.slice(0, 256))[0].trim();
@@ -118,6 +120,8 @@ export function parseCss(source: string, file: string, lineOffset = 0): CssRule[
const src = stripComments(source);
const lineOf = lineIndex(src, lineOffset);
const rules: CssRule[] = [];
const budget = expansionBudget(src);
let stored = 0;
const makeRule = (selector: string, s: number, e: number, media: string | null, keyframes: string | null): CssRule => {
const body = flatBody(src, s, e);
const decls: CssDecl[] = [];
@@ -168,8 +172,9 @@ export function parseCss(source: string, file: string, lineOffset = 0): CssRule[
if (depthLeft > 0) walk(blockStart, blockEnd, media, keyframes, null, depthLeft - 1);
}
} else {
const nested = parent && !keyframes ? nestSelector(selector, parent) : selector;
const nested = parent && !keyframes ? nestSelector(selector, parent, stored <= budget) : selector;
const sel = nested.length > MAX_SELECTOR ? nested.slice(0, MAX_SELECTOR) : nested;
stored += sel.length;
rules.push(makeRule(sel, blockStart, blockEnd, media, keyframes));
if (!keyframes && depthLeft > 0 && src.slice(blockStart, blockEnd).includes('{')) walk(blockStart, blockEnd, media, keyframes, sel, depthLeft - 1);
}
+6 -7
View File
@@ -103,19 +103,18 @@ function spansOf(lower: string, open: string, close: string, element: boolean):
function blankRanges(s: string, ranges: Array<[number, number]>, cut = false): string {
if (!ranges.length) return s;
let out = '', at = 0;
for (const [a, b] of ranges) {
if (a < at) continue;
out += s.slice(at, a) + (cut ? ' ' : pad(s.slice(a, b)));
at = b;
}
for (const [a, b] of ranges) if (a >= at) { out += s.slice(at, a) + (cut ? ' ' : pad(s.slice(a, b))); at = b; }
return out + s.slice(at);
}
/** s with every `<…>` run replaced by one space (or padded to spaces with keep set), in one linear pass; a `<` with no `>` after it is text. With a limit, the read stops once more than that many non-space characters are out, so a caller that only needs to know whether the text is short does not pay for an element that runs to the end of the file. */
/** s with every `<…>` run replaced by one space (or padded to spaces with keep set), in one linear pass; a `<` with no `>` after it is text. With a limit, the read stops once more than that many non-space characters are out, or once the walk has read 64 × limit + 4096 characters of the current text run without reaching the limit (a whitespace run, which the limit cannot stop), so a caller that only needs to know whether the text is short does not pay for an element that runs to the end of the file. */
export function stripTags(s: string, keep = false, limit = Infinity): string {
let out = '', i = 0, seen = 0;
for (;;) {
if (limit < Infinity) for (let k = i; k < s.length && s[k] !== '<'; k++) if (!/\s/.test(s[k]) && ++seen > limit) return out + s.slice(i, k + (s.charCodeAt(k) >= 0xd800 && s.charCodeAt(k) < 0xdc00 ? 2 : 1));
if (limit < Infinity) for (let k = i, c = 0; k < s.length && s[k] !== '<'; k++) {
if (k - i > 64 * limit + 4096) return out + s.slice(i, k);
if ((c = s.charCodeAt(k)) > 32 && (c < 128 || !/\s/.test(s[k])) && ++seen > limit) return out + s.slice(i, k + (c >= 0xd800 && c < 0xdc00 ? 2 : 1));
}
const lt = s.indexOf('<', i);
if (lt < 0) break;
const gt = s.indexOf('>', lt + 1);