Skip to content

Commit 7a04e88

Browse files
dcramercodex
andcommitted
fix(slack): Harden mrkdwn normalization edges
Only unwrap raw URLs when formatting encloses a standalone URL token. This avoids corrupting emphasized prose that merely starts with a link. Strip full HTML comments until stable and escape any leftover opener fragments so comment removal cannot reintroduce a literal comment start. Co-Authored-By: Codex GPT-5 <noreply@openai.com>
1 parent a9c5642 commit 7a04e88

2 files changed

Lines changed: 74 additions & 11 deletions

File tree

packages/junior/src/chat/slack/mrkdwn.ts

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,26 @@ function normalizeCommonMarkEmphasis(text: string): string {
4848
.replace(/~~([^\s~](?:[^\n]*?[^\s~])?)~~/g, "~$1~");
4949
}
5050

51+
function removeMarkdownHtmlComments(text: string): string {
52+
let normalized = text;
53+
54+
while (true) {
55+
const start = normalized.indexOf("<!--");
56+
if (start === -1) {
57+
return normalized;
58+
}
59+
60+
const end = normalized.indexOf("-->", start + 4);
61+
if (end === -1) {
62+
return normalized;
63+
}
64+
65+
normalized = `${normalized.slice(0, start)}${normalized.slice(end + 3)}`;
66+
}
67+
}
68+
5169
function stripMarkdownHtmlComments(text: string): string {
52-
return text.replace(/<!--[\s\S]*?-->/g, "");
70+
return removeMarkdownHtmlComments(text).replaceAll("<!--", "&lt;!--");
5371
}
5472

5573
function escapeSlackLinkLabel(text: string): string {
@@ -317,18 +335,51 @@ function splitSlackUrlSuffix(text: string): { suffix: string; url: string } {
317335
return { suffix, url };
318336
}
319337

338+
function splitWrappedRawUrlToken(token: string): {
339+
after: string;
340+
before: string;
341+
core: string;
342+
} {
343+
let start = 0;
344+
while (/[([{'"']/.test(token[start] ?? "")) {
345+
start += 1;
346+
}
347+
348+
let end = token.length;
349+
while (end > start && /[)\]}"',.!?;:]/.test(token[end - 1] ?? "")) {
350+
end -= 1;
351+
}
352+
353+
return {
354+
after: token.slice(end),
355+
before: token.slice(0, start),
356+
core: token.slice(start, end),
357+
};
358+
}
359+
360+
function normalizeWrappedRawUrlToken(token: string): string {
361+
const { after, before, core } = splitWrappedRawUrlToken(token);
362+
const match = core.match(/^([*_~`]+)(https?:\/\/[^\s<]+?)([*_~`]+)$/);
363+
if (!match) {
364+
return token;
365+
}
366+
367+
const { suffix, url } = splitSlackUrlSuffix(match[2] ?? "");
368+
return `${before}${formatSlackLink(url)}${suffix}${after}`;
369+
}
370+
320371
function normalizeWrappedRawUrls(text: string): string {
321-
return text.replace(
322-
/([*_~`]+)?(https?:\/\/[^\s<]+)([*_~`]+)?/g,
323-
(match, leading, rawUrl, trailing) => {
324-
if (!leading && !trailing) {
325-
return match;
326-
}
372+
let out = "";
373+
let lastIndex = 0;
327374

328-
const { suffix, url } = splitSlackUrlSuffix(String(rawUrl));
329-
return `${formatSlackLink(url)}${suffix}`;
330-
},
331-
);
375+
for (const match of text.matchAll(/\S+/g)) {
376+
const index = match.index ?? 0;
377+
out += text.slice(lastIndex, index);
378+
out += normalizeWrappedRawUrlToken(match[0]);
379+
lastIndex = index + match[0].length;
380+
}
381+
382+
return `${out}${text.slice(lastIndex)}`;
332383
}
333384

334385
function parseMarkdownTableRow(line: string): string[] | null {

packages/junior/tests/unit/misc/output.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,24 @@ describe("renderSlackMrkdwn", () => {
9696
);
9797
});
9898

99+
it("leaves raw urls inside larger formatted prose untouched", () => {
100+
expect(renderSlackMrkdwn("*https://docs.slack.dev/ more context*")).toBe(
101+
"*https://docs.slack.dev/ more context*",
102+
);
103+
});
104+
99105
it("strips html comments outside code fences", () => {
100106
expect(
101107
renderSlackMrkdwn("Intro\n<!-- internal note -->\n## Summary\nDone"),
102108
).toBe("Intro\n\n*Summary*\n\nDone");
103109
});
104110

111+
it("escapes dangling html comment openers after stripping comments", () => {
112+
expect(renderSlackMrkdwn("Intro <!<!-- internal note -->-- outro")).toBe(
113+
"Intro &lt;!-- outro",
114+
);
115+
});
116+
105117
it("preserves already-valid Slack links, mentions, and formatting", () => {
106118
expect(
107119
renderSlackMrkdwn("<https://docs.slack.dev/|docs> <@U123> *ready*"),

0 commit comments

Comments
 (0)