foo `, `` — where CommonMark says the line is HTML content, not a * fence) was previously dismissed here as "not an argument either way". THAT * WAS WRONG: it is precisely the residual fail-open. The intersection guard * below only reconciles DISAGREEMENT, so when BOTH engines open the same bogus * fence the guard is a no-op and a live `` inside it is pushed * verbatim, swallowing the rest of the message (reproduced for all three tags). * What actually closes it is the CARVE BALANCE GUARD in * `escapeUnknownHtmlTags`: a protected span may contain no UNBALANCED RAWTEXT * opener. Neither engine needs to learn about HTML blocks for that to hold. * * What makes keeping two engines SAFE is therefore the pair of guards in * `escapeUnknownHtmlTags`: a carve span the mask did not blank is escaped * rather than pushed through verbatim, and a span carrying an unbalanced * RAWTEXT opener is escaped even when both engines agree. Over-detection can * then only cost cosmetics. Before the first guard the regex's info-string-tolerant * closer let it desync and open a span from a line CommonMark treats as * ordinary text, sheltering a live ` ` from escaping entirely * (`mismatched-fence-carve-does-not-shelter-opener`). The remaining tradeoff is * pinned by `unclosed-fence-body-renders-escaped` rather than left as prose. */ function buildCloserHaystack(text: string): string { const folded = foldAsciiCase(text) const lines = toMaskLines(folded) // 1. Inline code spans (the only non-line-state code region). Uncapped and // backtracking-free — see `findInlineCodeRanges`; an over-cap span used to // be skipped entirely and sheltered a live RAWTEXT opener. let masked = blankRanges(folded, findInlineCodeRanges(folded)) // 2. Every BLOCK-level code form, derived from line state over `folded`: // fences (tracker-accurate, closed and EOF-terminated alike), indented // code, blockquoted code, and HTML comments. Each of these carried a // reproduced live-textarea swallow before it was masked. masked = blankFencedRegions(masked, lines) // The indented pass walks the CURRENT mask (not `folded`) so a list marker // written inside a fence cannot shift its content-column stack — see its // SCAN-SOURCE INVERSION note. `blankQuotedCode` still gets the unmasked // lines because its NESTED fence scan needs them, and applies the same // inversion internally. masked = blankIndentedCode(masked, remapToMask(masked, lines)) // …and LINK REFERENCE DEFINITIONS, which remark consumes whole and emits // nothing for, so a ` ` in a destination or title is not a closer. masked = blankLinkDefinitions(masked, lines) // …and the GFM FOOTNOTE definitions that pass deliberately refuses, but only // the UNREFERENCED ones: remark-gfm drops those whole, so their bodies are // not document text either. It reads the CURRENT mask for DEFINITIONS and a // separate, more-blanked copy for REFERENCES (`footnoteReferenceMask`), both // behind a `[^` guard. // // ITS SLOT IS CONSTRAINED ON BOTH SIDES, and neither bound is cosmetic: // · it may not run EARLIER than the code passes, whose output is the // definition source; // · it may not simply be MOVED after `blankInlineLinkPayloads` / // `blankBracketLabels` to pick up the phantom-reference fix, because // `blankBracketLabels` blanks footnote labels "reference AND definition // alike" — after it, EVERY reference is gone and every referenced // definition would be over-blanked into escaped source. Hence the // separate scratch copy instead of a reorder. masked = blankUnreferencedFootnotes(masked, lines, folded) // …and the INLINE link/image spelling of the same shelter, which remark // likewise turns into href/title attributes. Container-agnostic, so like // the definition pass it needs exactly one top-level call. masked = blankInlineLinkPayloads(masked, folded) // …and the BRACKET half of that same class — an image's alt, a reference // label, a footnote label — which remark consumes into an attribute or an // identifier. Container-agnostic, so likewise exactly one top-level call. masked = blankBracketLabels(masked, folded) masked = blankQuotedCode(masked, lines) // …and the LIST-container analogue, for items whose content column exceeds // the 3-column fence-indent cap. Monotonic, so its position among the // block passes is not load-bearing. masked = blankListItemCode(masked, lines) // Comments scan the MASKED copy, not `folded` — see `blankComments`. Must // stay LAST: it relies on every code region already being blanked. masked = blankComments(masked, masked) // 3. Attribute regions. Blanking the WHOLE tag would blank real `` // closers too (and break the closed-form fixtures), so only the // attribute run between the tag name and the `>` is cleared. masked = blankTagAttributes(masked) return masked } /** Exported for the length-preservation invariant test only. */ export const __buildCloserHaystackForTest = buildCloserHaystack /** * True when a well-formed `` (optional trailing whitespace) occurs at * or after `from` in the MASKED lowercased source (see `buildCloserHaystack`). * Substring search rather than a per-tag `RegExp` — the tag comes from * `RAWTEXT_TAGS`, but building regexes from tag names in a hot path invites * an injection footgun on the next edit. */ function hasLaterCloser(lowerSource: string, tag: string, from: number): boolean { const needle = `${tag}` let cursor = from for (;;) { const at = lowerSource.indexOf(needle, cursor) if (at === -1) return false // Only `` or `` closes it; `` is a different tag. if (/^\s*>/.test(lowerSource.slice(at + needle.length, at + needle.length + 64))) return true cursor = at + needle.length } } /** * True when the mask considers `[from, to)` entirely code — every character * blanked to a space (newlines are never blanked, so they count as blank). * Both strings are the same length by construction (see `foldAsciiCase`). */ function isMaskedBlank(lowerSource: string, from: number, to: number): boolean { for (let i = from; i < to; i++) { const c = lowerSource[i] if (c !== ' ' && c !== '\n') return false } return true } /** * True when `span` contains a RAWTEXT opener with no matching closer INSIDE * the span — the self-containment test the carve applies before pushing a * protected span through verbatim. See the CARVE BALANCE GUARD in * `escapeUnknownHtmlTags`. * * A closer with no opener before it is harmless (it cannot start a RAWTEXT * span), so the counter floors at zero rather than going negative. * * SELF-CLOSING IS AN OPENER (round 11). HTML ignores the self-closing flag on * non-void, non-foreign elements, so parse5 tokenizes `` as a START * tag and enters RAWTEXT exactly like ` `. Keying on `selfClose === ''` * therefore made this guard — and the closer check in `escapeOutsideFences` — * blind to the self-closed spelling of EVERY shape they defend against; the * round-9 HTML-block fixtures passed only because they used the bare spelling. * See the matching note on `escapeOutsideFences` for the one cosmetic cost. */ function hasUnbalancedRawtextOpener(span: string): boolean { if (span.indexOf('<') === -1) return false const open = new Map () TAG_LIKE_REGEX.lastIndex = 0 let m: RegExpExecArray | null while ((m = TAG_LIKE_REGEX.exec(span)) !== null) { const [, slash, tag] = m const lower = tag.toLowerCase() if (!RAWTEXT_TAGS.has(lower)) continue if (slash === '') { open.set(lower, (open.get(lower) ?? 0) + 1) } else { open.set(lower, Math.max(0, (open.get(lower) ?? 0) - 1)) } } for (const count of open.values()) if (count > 0) return true return false } /** * --------------------------------------------------------------------------- * CommonMark HTML BLOCK ranges — the property the CARVE BALANCE GUARD gates on * --------------------------------------------------------------------------- * The guard exists because a protected span sitting inside an HTML BLOCK is not * really code: CommonMark says an HTML block runs to its own terminator, so * every line inside it is HTML CONTENT. Round 9 discovered that through the * FENCE spelling (a ``` line inside ` ` is content, but both fence engines * call it a fence and shelter what follows). Round 11 then scoped the guard to * fences — and reopened the identical hole through INLINE CODE, whose * "an inline span can shelter nothing, remark emits it as an `inlineCode` TEXT * node" justification is precisely the invariant that fails inside an HTML * block, where remark emits raw HTML and backticks are not code at all. * * Gating on the span's FLAVOR was therefore the wrong property in both * directions. This walk supplies the right one: HTML-block membership, which * covers both spellings, while `` Use the `` element `` in ordinary * prose keeps rendering verbatim (round 11's regression stays fixed). * * FAIL DIRECTION: a detected range only makes the guard ESCAPE a span, and * escaping inside a GENUINE HTML block is invisible (the surrounding content is * raw HTML, where `<` is decoded as `<`). Over-detection is therefore * cosmetic ONLY when we are wrong about the block — so the walk tracks * CommonMark closely rather than blanket-detecting. * * START CONDITIONS IMPLEMENTED: all seven (1 `