From 1c5701651ab344c81d69ca0efa31bd9df6e51f2e Mon Sep 17 00:00:00 2001 From: Kendell R Date: Sun, 9 Nov 2025 01:02:25 -0800 Subject: [PATCH] fix: improve handling of italics in the presence of links (#11064) * fix(formatters): don't escape * in links * add test for * -> * in url * `\S+` -> `\S*` (tested locally) * @Qjuh: handle italics both inside+outside s * more accurate matcher --------- Co-authored-by: Qjuh <76154676+Qjuh@users.noreply.github.com> --- .../formatters/__tests__/escapers.test.ts | 12 ++++++++++ packages/formatters/src/escapers.ts | 22 ++++++++++++------- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/packages/formatters/__tests__/escapers.test.ts b/packages/formatters/__tests__/escapers.test.ts index 280dc4d1a..a5d69763c 100644 --- a/packages/formatters/__tests__/escapers.test.ts +++ b/packages/formatters/__tests__/escapers.test.ts @@ -23,6 +23,8 @@ const testURLs = [ 'https://example.com/name_with_underscores', 'https://example.com/name__with__underscores', 'https://example.com/name_with_underscores_and__double__underscores', + 'https://*.example.com/globbed/*', + 'https://example.com/*before*/>/*after*', ]; describe('Markdown escapers', () => { @@ -87,6 +89,16 @@ describe('Markdown escapers', () => { test('url', () => { for (const url of testURLs) expect(escapeItalic(url)).toBe(url); }); + + test('after-url', () => { + const url = ''; + for (const [input, output] of [ + ['*hi*', '\\*hi\\*'], + ['_hi_', '\\_hi\\_'], + ]) { + expect(escapeItalic(url + input)).toBe(url + output); + } + }); }); describe('escapeUnderline', () => { diff --git a/packages/formatters/src/escapers.ts b/packages/formatters/src/escapers.ts index 118707616..b5378b007 100644 --- a/packages/formatters/src/escapers.ts +++ b/packages/formatters/src/escapers.ts @@ -212,15 +212,21 @@ export function escapeInlineCode(text: string): string { */ export function escapeItalic(text: string): string { let idx = 0; - const newText = text.replaceAll(/(?<=^|[^*])\*([^*]|\*\*|$)/g, (_, match) => { - if (match === '**') return ++idx % 2 ? `\\*${match}` : `${match}\\*`; - return `\\*${match}`; - }); + const newText = text.replaceAll( + /(?<=^|[^*])(?]*)\*([^*]|\*\*|$)/g, + (_, match) => { + if (match === '**') return ++idx % 2 ? `\\*${match}` : `${match}\\*`; + return `\\*${match}`; + }, + ); idx = 0; - return newText.replaceAll(/(?<=^|[^_])(?)([^_]|__|$)/g, (_, match) => { - if (match === '__') return ++idx % 2 ? `\\_${match}` : `${match}\\_`; - return `\\_${match}`; - }); + return newText.replaceAll( + /(?<=^|[^_])(?]*)_(?!:\d+>)([^_]|__|$)/g, + (_, match) => { + if (match === '__') return ++idx % 2 ? `\\_${match}` : `${match}\\_`; + return `\\_${match}`; + }, + ); } /**