diff --git a/.changeset/cleanbody-multiline-tags.md b/.changeset/cleanbody-multiline-tags.md new file mode 100644 index 0000000..c6f11f1 --- /dev/null +++ b/.changeset/cleanbody-multiline-tags.md @@ -0,0 +1,7 @@ +--- +"@dualmark/core": patch +--- + +Fix `cleanBody` leaking raw tags when a tag spans multiple lines. + +The HTML-tag replacement (e.g. `` to `**…**`, and any custom `htmlTagReplacements`) used a regex without the dotAll flag, so `.` never matched newlines. A tag whose content spanned more than one line was left untouched, leaking raw markup into the cleaned markdown that AI clients read. The regex now uses the `s` flag while keeping the lazy match, so multi-line tags are converted and adjacent tags are not merged. diff --git a/packages/core/src/text.ts b/packages/core/src/text.ts index 54f277d..3cfe86b 100644 --- a/packages/core/src/text.ts +++ b/packages/core/src/text.ts @@ -70,7 +70,11 @@ export function cleanBody(body: string, opts: CleanBodyOptions = {}): string { let out = stripImg ? stripImages(body) : body; for (const [tag, marker] of Object.entries(replacements)) { - const re = new RegExp(`<${tag}>(.*?)<\\/${tag}>`, "g"); + // `s` (dotAll) so a tag whose content spans multiple lines is still + // replaced; without it a multi-line `` leaks its raw markup + // into the cleaned markdown. `.*?` stays lazy, so it still stops at the + // first closing tag. + const re = new RegExp(`<${tag}>(.*?)<\\/${tag}>`, "gs"); out = out.replace(re, `${marker}$1${marker}`); } diff --git a/packages/core/test/text.test.ts b/packages/core/test/text.test.ts index e4bbbb0..f2b1574 100644 --- a/packages/core/test/text.test.ts +++ b/packages/core/test/text.test.ts @@ -86,6 +86,18 @@ describe("cleanBody", () => { expect(cleanBody("see this")).toBe("see **this**"); }); + it("converts a tag whose content spans multiple lines", () => { + expect(cleanBody("see multi\nline end")).toBe( + "see **multi\nline** end", + ); + }); + + it("keeps tag replacement lazy across multiple tags", () => { + expect(cleanBody("a mid b", { htmlTagReplacements: { Em: "*" } })).toBe( + "*a* mid *b*", + ); + }); + it("converts
to newline", () => { expect(cleanBody("line1
line2
line3")).toBe("line1\nline2\nline3"); });