/**
* 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]*?)${tag}\\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 `