diff --git a/packages/commons/core-utils/src/__tests__/sanitizeChangelogEntry.test.ts b/packages/commons/core-utils/src/__tests__/sanitizeChangelogEntry.test.ts index 96bb50bc8f9e..f17cce2f4c5a 100644 --- a/packages/commons/core-utils/src/__tests__/sanitizeChangelogEntry.test.ts +++ b/packages/commons/core-utils/src/__tests__/sanitizeChangelogEntry.test.ts @@ -74,4 +74,23 @@ describe("sanitizeChangelogEntry", () => { const expected = "Added `Optional` support.\nAlso changed `Map` handling."; expect(sanitizeChangelogEntry(input)).toBe(expected); }); + + it("does not double-wrap types inside backtick spans after a fenced code block", () => { + const input = + "Intro text.\n\n```java\nws.connect();\n```\n\n**Generic `onMessage(Consumer)` handler** fires."; + const expected = + "Intro text.\n\n```java\nws.connect();\n```\n\n**Generic `onMessage(Consumer)` handler** fires."; + expect(sanitizeChangelogEntry(input)).toBe(expected); + }); + + it("preserves fenced code blocks verbatim", () => { + const input = "Before.\n\n```java\nList items = new ArrayList();\n```\n\nAfter."; + expect(sanitizeChangelogEntry(input)).toBe(input); + }); + + it("wraps types outside a code fence but not inside", () => { + const input = "Use Optional.\n\n```java\nOptional x;\n```\n\nAlso Map."; + const expected = "Use `Optional`.\n\n```java\nOptional x;\n```\n\nAlso `Map`."; + expect(sanitizeChangelogEntry(input)).toBe(expected); + }); }); diff --git a/packages/commons/core-utils/src/sanitizeChangelogEntry.ts b/packages/commons/core-utils/src/sanitizeChangelogEntry.ts index 0dc424b59751..5f7e0b33d6e8 100644 --- a/packages/commons/core-utils/src/sanitizeChangelogEntry.ts +++ b/packages/commons/core-utils/src/sanitizeChangelogEntry.ts @@ -9,9 +9,30 @@ * Already-backticked spans are left untouched. */ export function sanitizeChangelogEntry(entry: string): string { + // First, split on triple-backtick fenced code blocks so they are never + // processed by the inline-backtick logic (which only understands single + // backtick pairs and gets confused by the three-backtick delimiters). + const fenceParts = entry.split(/(```[^\n]*\n[\s\S]*?\n```)/g); + return fenceParts + .map((part) => { + // Fenced code blocks are preserved verbatim. + if (part.startsWith("```") && part.endsWith("```")) { + return part; + } + return sanitizeInlinePart(part); + }) + .join(""); +} + +/** + * Processes a text segment that is outside any fenced code block. + * Splits on single-backtick inline code spans and only applies + * angle-bracket wrapping to plain-text segments. + */ +function sanitizeInlinePart(text: string): string { // Split the text into alternating segments: plain text and backtick-delimited code spans. // Odd-indexed segments are inside backticks and must be preserved as-is. - const parts = entry.split(/(`[^`]+`)/g); + const parts = text.split(/(`[^`]+`)/g); return parts .map((part) => { // If this part is a code span, leave it as-is