mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-09 18:28:15 +01:00
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:
1 parent
e6ed269add
commit
734e07f0a5
2 files changed
+15
-11
No files matched your search
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user