Skip to content

Commit 495bd8e

Browse files
committed
Fixes #1896
1 parent 4c5fc1d commit 495bd8e

File tree

2 files changed

+73
-1
lines changed

2 files changed

+73
-1
lines changed

client/markdown_parser/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export const wikiLinkRegex =
22
/(?<leadingTrivia>!?\[\[)(?<stringRef>.*?)(?:\|(?<alias>.*?))?(?<trailingTrivia>\]\])/g;
3-
export const mdLinkRegex = /!?\[(?<title>[^\]]*)\]\((?<url>.+)\)/g;
3+
export const mdLinkRegex = /!?\[(?<title>(?:[^\\\]]+|\\.)*)\]\((?<url>.+)\)/g;
44
export const tagRegex =
55
/#(?:(?:\d*[^\d\s!@#$%^&*(),.?":{}|<>\\][^\s!@#$%^&*(),.?":{}|<>\\]*)|(?:<[^>\n]+>))/;
66
export const nakedUrlRegex =

client/markdown_parser/parser.test.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
import { parseMarkdown } from "./parser.ts";
88
import { extractHashtag } from "../../plug-api/lib/tags.ts";
99
import { renderHashtag } from "../../plugs/index/tags.ts";
10+
import { mdLinkRegex } from "./constants.ts";
1011

1112
const sample1 = `---
1213
type: page
@@ -309,3 +310,74 @@ test("Test table parser does not treat {[...]} specially", () => {
309310
const row1Cells = collectNodesOfType(rows[0], "TableCell");
310311
expect(row1Cells.length).toBe(2);
311312
});
313+
314+
// Links with escaped square brackets
315+
test("Test markdown links with escaped square brackets", () => {
316+
// Parser should produce a Link node for escaped brackets
317+
const tree = parseMarkdown(`[\\[link\\]](address)`);
318+
const links = collectNodesOfType(tree, "Link");
319+
expect(links.length).toBe(1);
320+
321+
// Should contain Escape nodes for the brackets
322+
const escapes = collectNodesOfType(links[0], "Escape");
323+
expect(escapes.length).toBe(2);
324+
expect(escapes[0].children![0].text).toBe("\\[");
325+
expect(escapes[1].children![0].text).toBe("\\]");
326+
327+
// Should have a URL node
328+
const urlNode = findNodeOfType(links[0], "URL");
329+
expect(urlNode).not.toBeUndefined();
330+
expect(urlNode!.children![0].text).toBe("address");
331+
332+
// Full roundtrip
333+
expect(renderToText(tree)).toBe(`[\\[link\\]](address)`);
334+
});
335+
336+
test("Test mdLinkRegex with escaped square brackets", () => {
337+
// Normal link
338+
mdLinkRegex.lastIndex = 0;
339+
let match = mdLinkRegex.exec("[link](address)");
340+
expect(match).not.toBeNull();
341+
expect(match!.groups!.title).toBe("link");
342+
expect(match!.groups!.url).toBe("address");
343+
344+
// Escaped brackets in link text (issue #1896)
345+
mdLinkRegex.lastIndex = 0;
346+
match = mdLinkRegex.exec("[\\[link\\]](address)");
347+
expect(match).not.toBeNull();
348+
expect(match!.groups!.title).toBe("\\[link\\]");
349+
expect(match!.groups!.url).toBe("address");
350+
351+
// Escaped brackets with other text
352+
mdLinkRegex.lastIndex = 0;
353+
match = mdLinkRegex.exec("[see \\[ref\\] here](http://example.com)");
354+
expect(match).not.toBeNull();
355+
expect(match!.groups!.title).toBe("see \\[ref\\] here");
356+
expect(match!.groups!.url).toBe("http://example.com");
357+
358+
// Image with escaped brackets
359+
mdLinkRegex.lastIndex = 0;
360+
match = mdLinkRegex.exec("![\\[img\\]](image.png)");
361+
expect(match).not.toBeNull();
362+
expect(match!.groups!.title).toBe("\\[img\\]");
363+
expect(match!.groups!.url).toBe("image.png");
364+
365+
// Other escaped characters (backslash itself)
366+
mdLinkRegex.lastIndex = 0;
367+
match = mdLinkRegex.exec("[a\\\\b](url)");
368+
expect(match).not.toBeNull();
369+
expect(match!.groups!.title).toBe("a\\\\b");
370+
expect(match!.groups!.url).toBe("url");
371+
372+
// Normal link still works (no regressions)
373+
mdLinkRegex.lastIndex = 0;
374+
match = mdLinkRegex.exec("[simple text](http://example.com)");
375+
expect(match).not.toBeNull();
376+
expect(match!.groups!.title).toBe("simple text");
377+
378+
// Empty title still works
379+
mdLinkRegex.lastIndex = 0;
380+
match = mdLinkRegex.exec("[](url)");
381+
expect(match).not.toBeNull();
382+
expect(match!.groups!.title).toBe("");
383+
});

0 commit comments

Comments
 (0)