Require space between hash/content in ATX heading (#1140)

While writing some Markdown documentation for Rails, I came across an
interesting case where trying to link to an instance method at the start
of a line would instead parse as an H1 heading:

```markdown
#response_body=
```

Expected:

```html
<a href=""><code>#response_body=</code></a>
```

Actual:

```html
<h1>response_body=</h1>
```

According to the CommonMark spec:

> At least one space or tab is required between the # characters and the
> heading’s contents, unless the heading is empty. Note that many
> implementations currently do not require the space. However, the space
> was required by the original ATX implementation, and it helps prevent
> things like the following from being parsed as headings:
>
> Example 64

So while some implementations do not follow this requirement, I believe
RDoc should because it makes it easy to write text similar to Example 64
(which was used in the new test) and it also enables automatically
linking to instance methods at the start of a line.
This commit is contained in:
Hartley McGuire 2024-07-17 21:39:08 +00:00 committed by Hiroshi SHIBATA
parent 239d54dfbc
commit d0c17cbd09
No known key found for this signature in database
GPG Key ID: F9CF13417264FAC2
2 changed files with 36 additions and 13 deletions

View File

@ -1158,7 +1158,7 @@ class RDoc::Markdown
return _tmp return _tmp
end end
# AtxHeading = AtxStart:s @Sp AtxInline+:a (@Sp /#*/ @Sp)? @Newline { RDoc::Markup::Heading.new(s, a.join) } # AtxHeading = AtxStart:s @Spacechar+ AtxInline+:a (@Sp /#*/ @Sp)? @Newline { RDoc::Markup::Heading.new(s, a.join) }
def _AtxHeading def _AtxHeading
_save = self.pos _save = self.pos
@ -1169,12 +1169,22 @@ class RDoc::Markdown
self.pos = _save self.pos = _save
break break
end end
_tmp = _Sp() _save1 = self.pos
_tmp = _Spacechar()
if _tmp
while true
_tmp = _Spacechar()
break unless _tmp
end
_tmp = true
else
self.pos = _save1
end
unless _tmp unless _tmp
self.pos = _save self.pos = _save
break break
end end
_save1 = self.pos _save2 = self.pos
_ary = [] _ary = []
_tmp = apply(:_AtxInline) _tmp = apply(:_AtxInline)
if _tmp if _tmp
@ -1187,37 +1197,37 @@ class RDoc::Markdown
_tmp = true _tmp = true
@result = _ary @result = _ary
else else
self.pos = _save1 self.pos = _save2
end end
a = @result a = @result
unless _tmp unless _tmp
self.pos = _save self.pos = _save
break break
end end
_save2 = self.pos
_save3 = self.pos _save3 = self.pos
_save4 = self.pos
while true # sequence while true # sequence
_tmp = _Sp() _tmp = _Sp()
unless _tmp unless _tmp
self.pos = _save3 self.pos = _save4
break break
end end
_tmp = scan(/\G(?-mix:#*)/) _tmp = scan(/\G(?-mix:#*)/)
unless _tmp unless _tmp
self.pos = _save3 self.pos = _save4
break break
end end
_tmp = _Sp() _tmp = _Sp()
unless _tmp unless _tmp
self.pos = _save3 self.pos = _save4
end end
break break
end # end sequence end # end sequence
unless _tmp unless _tmp
_tmp = true _tmp = true
self.pos = _save2 self.pos = _save3
end end
unless _tmp unless _tmp
self.pos = _save self.pos = _save
@ -16539,7 +16549,7 @@ class RDoc::Markdown
Rules[:_Plain] = rule_info("Plain", "Inlines:a { paragraph a }") Rules[:_Plain] = rule_info("Plain", "Inlines:a { paragraph a }")
Rules[:_AtxInline] = rule_info("AtxInline", "!@Newline !(@Sp /\#*/ @Sp @Newline) Inline") Rules[:_AtxInline] = rule_info("AtxInline", "!@Newline !(@Sp /\#*/ @Sp @Newline) Inline")
Rules[:_AtxStart] = rule_info("AtxStart", "< /\\\#{1,6}/ > { text.length }") Rules[:_AtxStart] = rule_info("AtxStart", "< /\\\#{1,6}/ > { text.length }")
Rules[:_AtxHeading] = rule_info("AtxHeading", "AtxStart:s @Sp AtxInline+:a (@Sp /\#*/ @Sp)? @Newline { RDoc::Markup::Heading.new(s, a.join) }") Rules[:_AtxHeading] = rule_info("AtxHeading", "AtxStart:s @Spacechar+ AtxInline+:a (@Sp /\#*/ @Sp)? @Newline { RDoc::Markup::Heading.new(s, a.join) }")
Rules[:_SetextHeading] = rule_info("SetextHeading", "(SetextHeading1 | SetextHeading2)") Rules[:_SetextHeading] = rule_info("SetextHeading", "(SetextHeading1 | SetextHeading2)")
Rules[:_SetextBottom1] = rule_info("SetextBottom1", "/={1,}/ @Newline") Rules[:_SetextBottom1] = rule_info("SetextBottom1", "/={1,}/ @Newline")
Rules[:_SetextBottom2] = rule_info("SetextBottom2", "/-{1,}/ @Newline") Rules[:_SetextBottom2] = rule_info("SetextBottom2", "/-{1,}/ @Newline")

View File

@ -414,10 +414,23 @@ two
end end
def test_parse_heading_atx def test_parse_heading_atx
doc = parse "# heading\n" # CommonMark Example 62
(1..6).each do |level|
doc = parse "#{"#" * level} heading\n"
expected = @RM::Document.new(
@RM::Heading.new(level, "heading"))
assert_equal expected, doc
end
# CommonMark Example 64
doc = parse "#5 bolt\n\n#hashtag\n"
expected = @RM::Document.new( expected = @RM::Document.new(
@RM::Heading.new(1, "heading")) para("#5 bolt"),
para("#hashtag"),
)
assert_equal expected, doc assert_equal expected, doc
end end