From fbab61ec093dc9eb7ccc691e08a6dbca119a72ae Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sat, 12 Sep 2026 18:19:16 -0700 Subject: [PATCH] perf: skip unclosed suffixes when stripping review markdown tags (#20329) --- .../components/pr-sidebar/markdown-blocks.ts | 9 +++- ...markdown-tag-stripping-performance.test.ts | 47 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 mobile/src/components/pr-sidebar/markdown-tag-stripping-performance.test.ts diff --git a/mobile/src/components/pr-sidebar/markdown-blocks.ts b/mobile/src/components/pr-sidebar/markdown-blocks.ts index 1f53bbdbae9..ea317dd1223 100644 --- a/mobile/src/components/pr-sidebar/markdown-blocks.ts +++ b/mobile/src/components/pr-sidebar/markdown-blocks.ts @@ -44,7 +44,14 @@ const SUMMARY = /]*>([\s\S]*?)<\/summary>/i // show literally. Conservative: only matches `` / `` shapes, so a bare // "a < b" in prose is left alone. export function stripHtmlTags(text: string): string { - return text.replace(/<\/?[a-zA-Z][a-zA-Z0-9-]*(?:\s[^>]*)?\/?>/g, '') + const end = text.lastIndexOf('>') + 1 + if (end === 0) { + return text + } + // No tag can close in this suffix; keep it literal without retrying every opener. + return ( + text.slice(0, end).replace(/<\/?[a-zA-Z][a-zA-Z0-9-]*(?:\s[^>]*)?\/?>/g, '') + text.slice(end) + ) } export function parseMarkdownBlocks(content: string): MarkdownBlock[] { diff --git a/mobile/src/components/pr-sidebar/markdown-tag-stripping-performance.test.ts b/mobile/src/components/pr-sidebar/markdown-tag-stripping-performance.test.ts new file mode 100644 index 00000000000..782bcbd55e5 --- /dev/null +++ b/mobile/src/components/pr-sidebar/markdown-tag-stripping-performance.test.ts @@ -0,0 +1,47 @@ +import { runInNewContext } from 'node:vm' +import { describe, expect, it } from 'vitest' +import { parseInline, stripHtmlTags } from './markdown-blocks' + +const ORIGINAL_TAGS = /<\/?[a-zA-Z][a-zA-Z0-9-]*(?:\s[^>]*)?\/?>/g + +describe('review Markdown unclosed tags', () => { + it.each(['', 'before '])('preserves an unclosed suffix after %s', (prefix) => { + const suffix = ' { + const parts = [ + '', + '', + '', + '', + '<', + '>', + 'a', + ' ', + '/', + '\n', + '"', + '<1>', + '', + ' { + seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0 + return seed + } + for (let index = 0; index < 5000; index++) { + const text = Array.from( + { length: 1 + (random() % 40) }, + () => parts[random() % parts.length] + ).join('') + expect(stripHtmlTags(text), text).toBe(text.replace(ORIGINAL_TAGS, '')) + } + }) +})