diff --git a/src/telegram/format.ts b/src/telegram/format.ts index cdeb3002a888..7feab62aeadf 100644 --- a/src/telegram/format.ts +++ b/src/telegram/format.ts @@ -187,13 +187,13 @@ export function wrapFileReferencesInHtml(html: string): string { return `${prefix}${escapeHtml(filename)}`; }); - // Update tag depth + // Update tag depth (clamp at 0 for malformed HTML with stray closing tags) if (tagName === "code") { - codeDepth += isClosing ? -1 : 1; + codeDepth = isClosing ? Math.max(0, codeDepth - 1) : codeDepth + 1; } else if (tagName === "pre") { - preDepth += isClosing ? -1 : 1; + preDepth = isClosing ? Math.max(0, preDepth - 1) : preDepth + 1; } else if (tagName === "a") { - anchorDepth += isClosing ? -1 : 1; + anchorDepth = isClosing ? Math.max(0, anchorDepth - 1) : anchorDepth + 1; } // Add the tag itself @@ -236,14 +236,16 @@ export function wrapFileReferencesInHtml(html: string): string { if (lastOpen > lastClose) { return m; // Inside a tag attribute } - // Skip if inside code/pre tags (count opens vs closes before offset) + // Skip if inside code/pre/anchor tags (count opens vs closes before offset) const textBefore = snapshot.slice(0, offset); const codeOpens = (textBefore.match(/ codeCloses || preOpens > preCloses) { - return m; // Inside code/pre content + const anchorOpens = (textBefore.match(/]/gi) || []).length; + const anchorCloses = (textBefore.match(/<\/a/gi) || []).length; + if (codeOpens > codeCloses || preOpens > preCloses || anchorOpens > anchorCloses) { + return m; // Inside code/pre/anchor content } return `${prefix}${escapeHtml(tld)}`; }); diff --git a/src/telegram/format.wrap-md.test.ts b/src/telegram/format.wrap-md.test.ts index 36e59970a2e6..e882ef837b70 100644 --- a/src/telegram/format.wrap-md.test.ts +++ b/src/telegram/format.wrap-md.test.ts @@ -336,6 +336,27 @@ describe("edge cases", () => { expect(result).not.toContain(""); }); + it("does not wrap orphaned TLD inside anchor link text", () => { + // R&D.md inside anchor text should NOT have D.md wrapped + const input = 'R&D.md'; + const result = wrapFileReferencesInHtml(input); + expect(result).toBe(input); + expect(result).not.toContain("D.md"); + }); + + it("handles malformed HTML with stray closing tags (negative depth)", () => { + // Stray before content shouldn't break protection logic + // (depth should clamp at 0, not go negative) + const input = "README.mdinside after.md"; + const result = wrapFileReferencesInHtml(input); + // README.md should be wrapped (codeDepth = 0 after clamping stray close) + expect(result).toContain("README.md"); + // after.md should be wrapped (codeDepth = 0 after proper close) + expect(result).toContain("after.md"); + // Should not have nested code tags + expect(result).not.toContain(""); + }); + it("does not wrap orphaned TLD inside href attributes", () => { // D.md inside href should NOT be wrapped const input = 'link';