fix(make-pdf): stop URLs swallowing smartypants placeholders

A bare autolinked URL (<a href="X">X</a>) has zero whitespace between
the URL text and its own closing tag. TAG_RE carves that </a> into a
NUL-delimited SMARTPANTS_PRESERVED placeholder BEFORE the URL pass
runs, and URL_RE's \S+ swallowed the adjacent placeholder into the URL
match. The restore pass is single-shot, so the inner placeholder never
restored: raw "SMARTPANTS_PRESERVED_N" text leaked into the rendered
link, the </a> vanished, and link-blue styling bled into the rest of
the document (#2084). Excluding the NUL sentinel (\u0000) from the URL
character class stops the match from crossing into an already-carved
zone.

Contributed by @marshaung (PR #2280; PR #2339 by @BrendaB24 covered the
same smartypants defect).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-14 20:20:51 -07:00
co-authored by Claude Fable 5
parent de08279969
commit 7b71a62e40
2 changed files with 34 additions and 1 deletions
+6 -1
View File
@@ -20,7 +20,12 @@
const CODE_ZONE_RE = /<(pre|code|script|style)\b[^>]*>[\s\S]*?<\/\1>/gi;
const TAG_RE = /<[^>]+>/g;
const URL_RE = /\bhttps?:\/\/\S+/g;
// \u0000 is the placeholder sentinel (see PLACEHOLDER below). It must be
// excluded here: URLs are carved AFTER tags, so a URL sitting flush against
// a tag (`<a href="...">https://ex.com</a>`) would otherwise let \S+ swallow
// the placeholder standing in for `</a>` — that placeholder then never
// restores (restore is a single pass) and the tag is lost.
const URL_RE = /\bhttps?:\/\/[^\s\u0000]+/g;
/**
* Apply smartypants to an HTML string. Zones that should not be touched: