From 9723cc590bb45f7ee03615124d879e503288e5b0 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 be609b4b1..217f8869c 100644 --- a/packages/formatters/__tests__/escapers.test.ts +++ b/packages/formatters/__tests__/escapers.test.ts @@ -25,6 +25,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', () => { @@ -89,6 +91,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 04daffb38..a48053a7b 100644 --- a/packages/formatters/src/escapers.ts +++ b/packages/formatters/src/escapers.ts @@ -234,15 +234,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}`; + }, + ); } /**