From bdadbffca3fb3b2bc2bd06f7e7e09cc1ae00c521 Mon Sep 17 00:00:00 2001 From: Vivek Date: Thu, 9 Jul 2026 13:44:32 +0530 Subject: [PATCH 1/2] fix: keep parens in markdown link URLs instead of truncating The blog/compare markdown renderer's link regex captured the URL with `[^)]+`, which stops at the first `)`, so a link whose address contains a paren (e.g. a Wikipedia `..._(programming_language)` URL) was cut off and its `href` pointed at a broken address. Allow one level of balanced nested parens in the URL group so the address is captured whole. Add a test with the paren-containing URL. --- website/modules/blog/utils/render-post.ts | 5 ++++- website/test/ssr/compare-ssr.test.ts | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/website/modules/blog/utils/render-post.ts b/website/modules/blog/utils/render-post.ts index 8cf55856..3bad655a 100644 --- a/website/modules/blog/utils/render-post.ts +++ b/website/modules/blog/utils/render-post.ts @@ -24,7 +24,10 @@ function inline(s: string): string { .replace(/&/g, '&') .replace(//g, '>'); - out = out.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, t, u) => { + // The URL group allows one level of balanced nested parens, so a link whose + // address contains a `(...)` (e.g. a Wikipedia `..._(programming_language)` + // URL) is captured whole instead of truncated at the first `)`. + out = out.replace(/\[([^\]]+)\]\(((?:[^()]|\([^()]*\))*)\)/g, (_m, t, u) => { // Absolute (off-site) links open in a new tab so the reader keeps the // article; internal links (starting with /) navigate in place. Escape any // `"` in the URL so it cannot break out of the href attribute. diff --git a/website/test/ssr/compare-ssr.test.ts b/website/test/ssr/compare-ssr.test.ts index 93878dc1..6d414175 100644 --- a/website/test/ssr/compare-ssr.test.ts +++ b/website/test/ssr/compare-ssr.test.ts @@ -36,6 +36,16 @@ test('a double quote in a link URL cannot break out of the href attribute', () = assert.match(out, /%22/, 'the quote is percent-escaped in the href'); }); +test('a link URL containing balanced parens is captured whole, not truncated', () => { + const out = renderPostBody('[Ruby](https://en.wikipedia.org/wiki/Ruby_(programming_language))'); + // Counterfactual: the old `[^)]+` capture stopped at the first `)`, so the + // href lost its `)` tail. The full URL must survive. + assert.match(out, /href="https:\/\/en\.wikipedia\.org\/wiki\/Ruby_\(programming_language\)"/, 'href keeps the full parenthesized URL'); + assert.match(out, /target="_blank"/, 'still classified as an external link'); + // The stray trailing `)` from the truncated form must not leak as text. + assert.doesNotMatch(out, />Ruby<\/a>\)/, 'no orphaned closing paren after the link'); +}); + test('the compare page shows a visible outbound link to the competitor site in a new tab', async () => { const out = await renderToString(await ComparePage({ params: { slug: 'webjs-vs-nextjs' } })); // A visible, underlined "Visit the Next.js site" link (distinguishable while From bc0d23996b74e98569184b8730af05be3950961c Mon Sep 17 00:00:00 2001 From: Vivek Date: Thu, 9 Jul 2026 13:48:30 +0530 Subject: [PATCH 2/2] fix: keep empty-URL links literal and strengthen the paren test Address review of the paren-URL fix: - Require at least one char in the URL group (`+` not `*`) so a malformed `[x]()` stays literal text instead of emitting an empty-href anchor, matching the prior behavior. - Make the orphan-paren assertion real (the previous one never matched because of the injected screen-reader span) and add an empty-URL test. - Update the renderer header comment to document balanced-paren URLs. --- website/modules/blog/utils/render-post.ts | 9 +++++---- website/test/ssr/compare-ssr.test.ts | 11 +++++++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/website/modules/blog/utils/render-post.ts b/website/modules/blog/utils/render-post.ts index 3bad655a..0a11c863 100644 --- a/website/modules/blog/utils/render-post.ts +++ b/website/modules/blog/utils/render-post.ts @@ -14,9 +14,10 @@ * - `> ` blockquotes * - `- ` bulleted lists (custom-positioned markers via `before:` pseudo-element) * - ```fenced``` code blocks - * - Inline: **bold**, *italic*, `code`, [text](url). An absolute or - * protocol-relative URL opens in a new tab (with a screen-reader cue); - * an internal `/path` link navigates in place. + * - Inline: **bold**, *italic*, `code`, [text](url). The URL may contain + * one level of balanced parens (e.g. a Wikipedia `..._(language)` link). + * An absolute or protocol-relative URL opens in a new tab (with a + * screen-reader cue); an internal `/path` link navigates in place. */ function inline(s: string): string { @@ -27,7 +28,7 @@ function inline(s: string): string { // The URL group allows one level of balanced nested parens, so a link whose // address contains a `(...)` (e.g. a Wikipedia `..._(programming_language)` // URL) is captured whole instead of truncated at the first `)`. - out = out.replace(/\[([^\]]+)\]\(((?:[^()]|\([^()]*\))*)\)/g, (_m, t, u) => { + out = out.replace(/\[([^\]]+)\]\(((?:[^()]|\([^()]*\))+)\)/g, (_m, t, u) => { // Absolute (off-site) links open in a new tab so the reader keeps the // article; internal links (starting with /) navigate in place. Escape any // `"` in the URL so it cannot break out of the href attribute. diff --git a/website/test/ssr/compare-ssr.test.ts b/website/test/ssr/compare-ssr.test.ts index 6d414175..94f7fba0 100644 --- a/website/test/ssr/compare-ssr.test.ts +++ b/website/test/ssr/compare-ssr.test.ts @@ -42,8 +42,15 @@ test('a link URL containing balanced parens is captured whole, not truncated', ( // href lost its `)` tail. The full URL must survive. assert.match(out, /href="https:\/\/en\.wikipedia\.org\/wiki\/Ruby_\(programming_language\)"/, 'href keeps the full parenthesized URL'); assert.match(out, /target="_blank"/, 'still classified as an external link'); - // The stray trailing `)` from the truncated form must not leak as text. - assert.doesNotMatch(out, />Ruby<\/a>\)/, 'no orphaned closing paren after the link'); + // Counterfactual: the truncated form left a stray `)` right after the closing + // . The whole-URL form emits no such orphan. + assert.doesNotMatch(out, /<\/a>\)/, 'no orphaned closing paren after the link'); +}); + +test('an empty link URL stays literal text (no empty-href anchor)', () => { + const out = renderPostBody('see [x]() here'); + assert.doesNotMatch(out, / {