Skip to content

Commit b9833cb

Browse files
Repo AssistCopilot
authored andcommitted
Improve CommonMark ATX heading compliance (#191)
- Reject ATX headings with no space after '#' (e.g. '#NoSpace' → paragraph) - Reject ATX headings with more than 6 '#' characters (→ paragraph) - Support 0–3 optional leading spaces before the opening '#' sequence - Fix empty heading body when entire content is a closing '###' sequence - Enable additional passing CommonMark spec sections: Blank lines, Inlines, Soft line breaks, Textual content, ATX headings (26 new test cases) - Update test expectations and reference HTML files to match CommonMark spec Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 8e3b463 commit b9833cb

8 files changed

Lines changed: 64 additions & 26 deletions

File tree

RELEASE_NOTES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* Strip `#if SYMBOL` / `#endif // SYMBOL` marker lines from `LiterateCode` source before syntax-highlighting so they do not appear in formatted output. [#693](https://github.com/fsprojects/FSharp.Formatting/issues/693)
1515
* Fix incorrect column ranges for inline spans (links, images, inline code) in the Markdown parser — spans and subsequent literals now report correct `StartColumn`/`EndColumn` values. [#744](https://github.com/fsprojects/FSharp.Formatting/issues/744)
1616
* Normalize `--projects` paths to absolute paths before passing to the project cracker, fixing failures when relative paths are supplied. [#793](https://github.com/fsprojects/FSharp.Formatting/issues/793)
17+
* Improve CommonMark compliance for ATX headings: reject `#` not followed by a space (e.g. `#NoSpace` is now a paragraph), reject more than 6 `#` characters as a heading, support 0–3 leading spaces before the opening `#` sequence, and fix empty content when the entire header body is a closing `###` sequence. [#191](https://github.com/fsprojects/FSharp.Formatting/issues/191)
1718

1819
### Changed
1920
* Update FCS to 43.10.100. [#935](https://github.com/fsprojects/FSharp.Formatting/pull/966)

src/FSharp.Formatting.Markdown/MarkdownParser.fs

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -572,21 +572,49 @@ let (|Heading|_|) lines =
572572
| ((StringPosition.TrimBoth header) as line1) :: ((StringPosition.TrimEnd(StringPosition.EqualsRepeated("-",
573573
MarkdownRange.zero))) as line2) :: rest ->
574574
Some(2, header, [ line1; line2 ], rest)
575-
| (StringPosition.StartsWithRepeated "#" (n, StringPosition.TrimBoth(header, ln)) as line1) :: rest ->
576-
let header =
577-
// Drop "##" at the end, but only when it is preceded by some whitespace
578-
// (For example "## Hello F#" should be "Hello F#")
579-
if header.EndsWith '#' then
580-
let noHash = header.TrimEnd [| '#' |]
581-
582-
if noHash.Length > 0 && Char.IsWhiteSpace(noHash.Chars(noHash.Length - 1)) then
583-
noHash
575+
| ((line1text, ln1) as line1) :: rest ->
576+
// ATX heading (CommonMark): optional 0–3 leading spaces, then 1–6 '#' characters,
577+
// then a space or end of line (a tab or other char after '#' is not valid).
578+
let mutable i = 0
579+
580+
while i < 3 && i < line1text.Length && line1text.[i] = ' ' do
581+
i <- i + 1
582+
583+
let hstart = i
584+
585+
while i < line1text.Length && line1text.[i] = '#' do
586+
i <- i + 1
587+
588+
let n = i - hstart
589+
590+
if n < 1 || n > 6 || (i < line1text.Length && line1text.[i] <> ' ') then
591+
None
592+
else
593+
let contentStart = if i < line1text.Length then i + 1 else i
594+
let content = (line1text.Substring(contentStart)).TrimEnd()
595+
// Remove optional closing sequence of '#' preceded by space (or empty '#'-only content).
596+
// For example "## Hello F#" keeps the '#' because it is not preceded by a space.
597+
let header =
598+
if content.EndsWith('#') then
599+
let noHash = content.TrimEnd([| '#' |])
600+
601+
if noHash = "" || (noHash.Length > 0 && noHash.[noHash.Length - 1] = ' ') then
602+
noHash.Trim()
603+
else
604+
content.Trim()
584605
else
585-
header
586-
else
587-
header
606+
content.Trim()
607+
608+
let rawContent = line1text.Substring(contentStart)
609+
let leadingContentSpaces = rawContent.Length - rawContent.TrimStart(' ').Length
610+
let headerStart = ln1.StartColumn + contentStart + leadingContentSpaces
611+
612+
let headerLn =
613+
{ ln1 with
614+
StartColumn = headerStart
615+
EndColumn = headerStart + header.Length }
588616

589-
Some(n, (header, ln), [ line1 ], rest)
617+
Some(n, (header, headerLn), [ line1 ], rest)
590618
| _rest -> None
591619

592620
let (|YamlFrontmatter|_|) lines =

tests/Benchmarks/testfiles/mstest-0.1/line-endings-cr.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<h2>Header</h2>
1+
<p>##Header</p>
22

33
<hr />
44

tests/Benchmarks/testfiles/mstest-0.1/line-endings-crlf.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<h2>Header</h2>
1+
<p>##Header</p>
22

33
<hr />
44

tests/Benchmarks/testfiles/mstest-0.1/line-endings-lf.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<h2>Header</h2>
1+
<p>##Header</p>
22

33
<hr />
44

tests/Benchmarks/testfiles/php-markdown/Tight blocks.html

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,19 @@
22
* ciao</p>
33

44
<p>Paragraph and 1 space:
5-
* ciao</p>
5+
* ciao</p>
66

77
<p>Paragraph and 3 spaces:
8-
* ciao</p>
8+
* ciao</p>
99

1010
<p>Paragraph and 4 spaces:
11-
* ciao</p>
12-
13-
<p>Paragraph before header:</p>
11+
* ciao</p>
1412

15-
<h1>Header</h1>
13+
<p>Paragraph before header:
14+
#Header</p>
1615

1716
<p>Paragraph before blockquote:</p>
1817

1918
<blockquote>
20-
<p>Some quote.</p>
19+
<p>Some quote.</p>
2120
</blockquote>

tests/FSharp.Markdown.Tests/CommonMarkSpecTest.fs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,17 @@ open FSharp.Formatting.Markdown
2121
let properNewLines (text: string) =
2222
text.Replace("\r\n", "\n").Replace("\n", System.Environment.NewLine)
2323

24-
let enabledSections = [ "Fenced code blocks"; "Indented code blocks"; "Paragraphs"; "Precedence"; "Tabs" ]
24+
let enabledSections =
25+
[ "Fenced code blocks"
26+
"Indented code blocks"
27+
"Paragraphs"
28+
"Precedence"
29+
"Tabs"
30+
"Blank lines"
31+
"Inlines"
32+
"Soft line breaks"
33+
"Textual content"
34+
"ATX headings" ]
2535

2636
let getTests () =
2737
sample

tests/FSharp.Markdown.Tests/Markdown.fs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -582,15 +582,15 @@ let ``Transform bulleted lists correctly`` () =
582582
let ``Transform header 1 correctly`` () =
583583
let doc = "#Header 1\nHeader 1\r\n========"
584584

585-
let expected = "<h1>Header 1</h1>\r\n<h1>Header 1</h1>\r\n" |> properNewLines
585+
let expected = "<p>#Header 1</p>\r\n<h1>Header 1</h1>\r\n" |> properNewLines
586586

587587
Markdown.ToHtml doc |> shouldEqual expected
588588

589589
[<Test>]
590590
let ``Transform header 2 correctly`` () =
591591
let doc = "##Header 2\nHeader 2\r\n--------"
592592

593-
let expected = "<h2>Header 2</h2>\r\n<h2>Header 2</h2>\r\n" |> properNewLines
593+
let expected = "<p>##Header 2</p>\r\n<h2>Header 2</h2>\r\n" |> properNewLines
594594

595595
Markdown.ToHtml doc |> shouldEqual expected
596596

0 commit comments

Comments
 (0)