From 9922a4310be2a5057d02d26bf5fd567796736d27 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 10:58:23 +0000 Subject: [PATCH 1/2] fix: Markdown.ToMd uses minimal backtick fence for inline code with backticks When InlineCode body contains backtick characters, the previous single-backtick wrapping produced invalid Markdown that would not re-parse as the original inline code span. Fix: compute the longest run of consecutive backticks in the body and use a fence of (run + 1) backticks. When the body starts or ends with a backtick, add surrounding spaces so the fence and body do not merge. Add two round-trip tests covering single-backtick and double-backtick content. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- RELEASE_NOTES.md | 3 +++ .../MarkdownUtils.fs | 23 +++++++++++++++++- tests/FSharp.Markdown.Tests/Markdown.fs | 24 +++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 33214cbc..a5592ddf 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -2,6 +2,9 @@ ## [Unreleased] +### Fixed +* 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. + ## [22.0.0] - 2026-04-03 ### Fixed diff --git a/src/FSharp.Formatting.Markdown/MarkdownUtils.fs b/src/FSharp.Formatting.Markdown/MarkdownUtils.fs index 59bf09a3..4b6705a0 100644 --- a/src/FSharp.Formatting.Markdown/MarkdownUtils.fs +++ b/src/FSharp.Formatting.Markdown/MarkdownUtils.fs @@ -114,7 +114,28 @@ module internal MarkdownUtils = | IndirectImage(body, _, key, _) -> sprintf "![%s][%s]" body key | DirectImage(body, link, _, _) -> sprintf "![%s](%s)" body link | Strong(body, _) -> "**" + formatSpans ctx body + "**" - | InlineCode(body, _) -> "`" + body + "`" + | InlineCode(body, _) -> + // Pick the shortest backtick fence that does not appear in the body. + // E.g. body "``h``" needs a triple-backtick fence; body "a`b" needs double. + let maxConsecutiveBackticks = + body + |> Seq.fold + (fun (maxR, run) c -> + if c = '`' then + let run' = run + 1 + (max maxR run'), run' + else + maxR, 0) + (0, 0) + |> fst + + let fence = String.replicate (maxConsecutiveBackticks + 1) "`" + // Surround with spaces when the body starts or ends with a backtick so the + // fence and content do not merge (e.g. `` ``h`` `` would look like 4-backtick). + if body.Length > 0 && (body.[0] = '`' || body.[body.Length - 1] = '`') then + fence + " " + body + " " + fence + else + fence + body + fence | Emphasis(body, _) -> "*" + formatSpans ctx body + "*" /// Format a list of MarkdownSpan diff --git a/tests/FSharp.Markdown.Tests/Markdown.fs b/tests/FSharp.Markdown.Tests/Markdown.fs index 984047f7..bad34a01 100644 --- a/tests/FSharp.Markdown.Tests/Markdown.fs +++ b/tests/FSharp.Markdown.Tests/Markdown.fs @@ -1239,6 +1239,30 @@ let ``ToMd preserves strong (bold) text`` () = let ``ToMd preserves inline code`` () = "Use `printf` here." |> toMd |> should contain "`printf`" +[] +let ``ToMd round-trips inline code containing a single backtick`` () = + // "a`b" must be serialised with a double-backtick fence so it re-parses correctly. + let original = "`` a`b ``" + let md = Markdown.Parse original + let result = Markdown.ToMd md + // The serialised form must round-trip: re-parsing must yield the same InlineCode body. + let reparsed = Markdown.Parse result + + match reparsed.Paragraphs with + | [ Paragraph([ InlineCode("a`b", _) ], _) ] -> () + | _ -> Assert.Fail(sprintf "Expected InlineCode(\"a`b\") after round-trip, got: %A" reparsed.Paragraphs) + +[] +let ``ToMd round-trips inline code containing multiple backticks`` () = + // Body "``h``" contains double backticks — needs a triple-backtick fence. + let original = "` ``h`` `" + let md = Markdown.Parse original + let result = Markdown.ToMd md + + match (Markdown.Parse result).Paragraphs with + | [ Paragraph([ InlineCode("``h``", _) ], _) ] -> () + | _ -> Assert.Fail(sprintf "Expected InlineCode(\"``h``\") after round-trip, got: %A" result) + [] let ``ToMd preserves a direct link`` () = "[FSharp](https://fsharp.org)" From d6f1b582cc66e3b137532330fc3678a88443fa48 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 6 Apr 2026 10:58:26 +0000 Subject: [PATCH 2/2] ci: trigger checks