Skip to content

Commit 2d8a1b8

Browse files
github-actions[bot]Copilotdsyme
authored
[Repo Assist] fix: Markdown.ToMd preserves DirectLink and DirectImage titles (#1150)
* fix: Markdown.ToMd preserves DirectLink and DirectImage titles When serialising a document with ToMd, link and image titles were silently dropped. A Markdown fragment such as [text](url "My Title") would round-trip as [text](url) because the `DirectLink` / `DirectImage` formatSpan cases matched the title field with a wildcard. Fix: give DirectLink and DirectImage their own branches so the optional title can be appended in CommonMark form ( \"title\"). Three new round-trip tests cover: - DirectLink with title - DirectLink without title (unchanged behaviour) - DirectImage with title All 284 Markdown tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * ci: trigger checks --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Don Syme <dsyme@users.noreply.github.com>
1 parent 5c5f73b commit 2d8a1b8

3 files changed

Lines changed: 39 additions & 2 deletions

File tree

RELEASE_NOTES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## [Unreleased]
44

55
### Fixed
6+
* Fix `Markdown.ToMd` dropping link titles in `DirectLink` and `DirectImage` spans. Links with a title attribute (e.g. `[text](url "title")`) now round-trip correctly; without this fix the title was silently discarded on serialisation.
67
* Fix `Markdown.ToMd` serialising inline code spans that contain backtick characters. Previously, `InlineCode` was always wrapped in single backticks, producing syntactically incorrect Markdown when the code body contained backticks. Now the serialiser selects the shortest backtick fence that does not collide with the body content (e.g. a double-backtick fence for bodies containing single backticks, triple for double, etc.), matching the CommonMark spec.
78

89
## [22.0.0] - 2026-04-03

src/FSharp.Formatting.Markdown/MarkdownUtils.fs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,13 +106,27 @@ module internal MarkdownUtils =
106106
| HardLineBreak(_) -> "\n"
107107

108108
| AnchorLink _ -> ""
109+
| DirectLink(body, link, title, _) ->
110+
let t =
111+
title
112+
|> Option.map (fun t -> sprintf " \"%s\"" (t.Replace("\"", "\\\"")))
113+
|> Option.defaultValue ""
114+
115+
"[" + formatSpans ctx body + "](" + link + t + ")"
116+
109117
| IndirectLink(body, _, LookupKey ctx.Links (link, _), _)
110-
| DirectLink(body, link, _, _)
111118
| IndirectLink(body, link, _, _) -> "[" + formatSpans ctx body + "](" + link + ")"
112119

113120
| IndirectImage(body, _, LookupKey ctx.Links (link, _), _) -> sprintf "![%s](%s)" body link
114121
| IndirectImage(body, _, key, _) -> sprintf "![%s][%s]" body key
115-
| DirectImage(body, link, _, _) -> sprintf "![%s](%s)" body link
122+
123+
| DirectImage(body, link, title, _) ->
124+
let t =
125+
title
126+
|> Option.map (fun t -> sprintf " \"%s\"" (t.Replace("\"", "\\\"")))
127+
|> Option.defaultValue ""
128+
129+
sprintf "![%s](%s)" body (link + t)
116130
| Strong(body, _) -> "**" + formatSpans ctx body + "**"
117131
| InlineCode(body, _) ->
118132
// Pick the shortest backtick fence that does not appear in the body.

tests/FSharp.Markdown.Tests/Markdown.fs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1269,10 +1269,32 @@ let ``ToMd preserves a direct link`` () =
12691269
|> toMd
12701270
|> should contain "[FSharp](https://fsharp.org)"
12711271

1272+
[<Test>]
1273+
let ``ToMd preserves a direct link with title`` () =
1274+
let md = "[FSharp](https://fsharp.org \"F# language\")"
1275+
let result = toMd md
1276+
result |> should contain "[FSharp]("
1277+
result |> should contain "https://fsharp.org"
1278+
result |> should contain "\"F# language\""
1279+
1280+
[<Test>]
1281+
let ``ToMd preserves a direct link without title unchanged`` () =
1282+
let result = "[link](http://example.com)" |> toMd
1283+
result |> should contain "[link](http://example.com)"
1284+
result |> should not' (contain "\"")
1285+
12721286
[<Test>]
12731287
let ``ToMd preserves a direct image`` () =
12741288
"![alt text](image.png)" |> toMd |> should contain "![alt text](image.png)"
12751289

1290+
[<Test>]
1291+
let ``ToMd preserves a direct image with title`` () =
1292+
let md = "![photo](image.png \"My Photo\")"
1293+
let result = toMd md
1294+
result |> should contain "![photo]("
1295+
result |> should contain "image.png"
1296+
result |> should contain "\"My Photo\""
1297+
12761298
[<Test>]
12771299
let ``ToMd preserves an unordered list`` () =
12781300
let md = "* apple\n* banana\n* cherry"

0 commit comments

Comments
 (0)