/** * The code view — the input slice the `'code'`-scoped deterministic rules scan * (see {@link RuleScope} in types.ts). It resolves the "quoting ≠ violating" * problem: the class-utility rules used to regex the whole file, so a docs or * marketing page that *shows* an anti-pattern in prose (`✗ [raw-tailwind-color] * \`bg-green-500\`` as rendered linter output, `focus:ring-2` in a paragraph) * failed the gate exactly like a page that commits it. * * The view is a same-length string (offsets and line numbers identical to the * source) in which only class-bearing content survives: * * - `class="…"` string-attribute values (any attribute whose name contains * "class": `class`, `slotClasses`, a consumer's `inputClass`, …) — verbatim, * so Svelte's `class="gap-{x}"` interpolation stays visible; * - `class:name` directive names (the part after `class:` is a real class); * - string/template literals inside every `{…}` expression attribute * (`class={cond ? 'a' : 'b'}`, `slotClasses={{ base: '…' }}`) — with their * delimiters, so quote-anchored rules (deep-internal-import) keep matching; * - string/template literals in ``) so this agrees * with {@link maskHtmlComments}' neighbour in markup.ts about where a block ends. * While the two disagreed, a `` made markup.ts blank the region and this * reader miss it — and a class literal inside that body silently stopped being * linted, which is a finding lost rather than one invented. */ function blockBodySpans(src: string, tag: 'script' | 'style'): Span[] { const re = new RegExp(`(<${tag}\\b[^>]*>)([\\s\\S]*?)`, 'gi'); const spans: Span[] = []; for (const m of src.matchAll(re)) { const bodyStart = (m.index ?? 0) + (m[1] ?? '').length; spans.push([bodyStart, bodyStart + (m[2] ?? '').length]); } return spans; } /** Materialise kept spans over an all-blank canvas (newlines preserved → offsets/lines identical). */ function materialise(src: string, spans: Span[]): string { const out = blankRegion(src).split(''); for (const [start, end] of spans) { for (let p = Math.max(0, start); p < Math.min(end, src.length); p++) { const c = src[p]; if (c !== undefined && c !== '\n') out[p] = c; } } return out.join(''); } /** * Build the code view for one source (see the module doc). `mode: 'code'` treats * the whole input as a TS/JS module (every literal kept); `mode: 'markup'` * applies the Svelte/HTML document semantics. */ export function buildCodeView(source: string, mode: LintMode): string { const spans: Span[] = []; if (mode === 'code') { collectLiteralSpans(source, 0, source.length, spans); return materialise(source, spans); } // Structure discovery on a comment-blanked copy so a commented-out `