From a00c4e6cffb755d263fc9b07601c55db66a33eb1 Mon Sep 17 00:00:00 2001 From: bfs2021 <101915083+bfs2021@users.noreply.github.com> Date: Tue, 10 Nov 2026 11:16:51 +0800 Subject: [PATCH] Stop bare URLs at curly quotes in the page renderer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bare-URL autolinker stopped at a straight quote but not at a curly one, so an evidence note that quoted a URL published the closing quote as part of the href: https://game-icons.net” resolved to a host that does not exist. The link check already ends URLs at curly quotes (#274), so the checker and the renderer disagreed on where a URL ends. Add ‘ ’ “ ” to the excluded characters, matching evidenceUrls, and cover both quote styles with renderInline tests. Fixes #285 --- site/lib/lib.test.mjs | 2 ++ site/lib/markdown.mjs | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/site/lib/lib.test.mjs b/site/lib/lib.test.mjs index 59e6331..c53c4e0 100644 --- a/site/lib/lib.test.mjs +++ b/site/lib/lib.test.mjs @@ -513,6 +513,8 @@ eq("bare url in bold", renderInline("**https://a.test/x**", passLink), ' eq("bare url keeps a balanced paren", renderInline("see https://a.test/wiki/Foo_(bar).", passLink), 'see https://a.test/wiki/Foo_(bar).'); eq("angle-bracket autolink", renderInline("", passLink), 'https://a.test/x'); eq("bare url stops at a code span", renderInline("https://a.test/x`y`", passLink), 'https://a.test/xy'); +eq("bare url stops at curly double quotes", renderInline("on “https://a.test/x”", passLink), 'on “https://a.test/x”'); +eq("bare url stops at curly single quotes", renderInline("see ‘https://a.test/y’", passLink), 'see ‘https://a.test/y’'); throws("malformed escape is a link error", () => resolve("bad%E0.md"), 'link target "bad%E0.md" is malformed'); eq("query on a relative link is kept", resolve("../../docs/provenance.md?plain=1").href, "https://github.com/o/r/blob/main/docs/provenance.md?plain=1"); diff --git a/site/lib/markdown.mjs b/site/lib/markdown.mjs index 99d4f06..9c856ae 100644 --- a/site/lib/markdown.mjs +++ b/site/lib/markdown.mjs @@ -105,8 +105,10 @@ export function renderInline(text, resolveLink) { s = s.replace(/<(https?:\/\/[^\s<>]+)>/g, (_, url) => keep(`${esc(url)}`)); // Bare URLs, leaving trailing sentence punctuation outside the link. A URL // stops at `*` (bold around it) and at a slot (a code span right after it), - // and keeps a closing paren that balances one inside it (Foo_(bar)). - s = s.replace(/https?:\/\/[^\s<>"*\u0000]+/g, (url) => { + // and keeps a closing paren that balances one inside it (Foo_(bar)). Curly + // quotes end it too, as they do in the link check: quoted licence text wraps + // a URL in them (#274, #285). + s = s.replace(/https?:\/\/[^\s<>"*‘’“”\u0000]+/g, (url) => { let [, core, tail] = url.match(/^(.*?)([.,;:!?)\]]*)$/); while (tail.startsWith(")") && count(core, "(") > count(core, ")")) { core += ")";