Skip to content

Commit ce49b8b

Browse files
committed
Stop blockquote lazy continuation from consuming block-level elements
The BlockQuoteRaw rule's lazy continuation greedily absorbed any non-blank, non-blockquote line into the blockquote. This caused block-level elements like lists, headings, and code fences to be incorrectly consumed when they appeared on the line after a blockquote marker without a blank line separator. Add negative lookaheads for AtxStart (headings), Bullet (unordered lists), Enumerator (ordered lists), and Ticks3 (code fences) to the lazy continuation rule. Paragraph continuation text still works correctly per GFM's laziness clause.
1 parent c59a7a8 commit ce49b8b

File tree

3 files changed

+155
-27
lines changed

3 files changed

+155
-27
lines changed

lib/rdoc/markdown.kpeg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -617,7 +617,7 @@ BlockQuote = BlockQuoteRaw:a
617617

618618
BlockQuoteRaw = @StartList:a
619619
(( ">" " "? Line:l { a << l } )
620-
( !">" !@BlankLine Line:c { a << c } )*
620+
( !">" !@BlankLine !AtxStart !Bullet !Enumerator !Ticks3 Line:c { a << c } )*
621621
( @BlankLine:n { a << n } )*
622622
)+
623623
{ inner_parse a.join }

lib/rdoc/markdown.rb

Lines changed: 90 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1656,7 +1656,7 @@ def _BlockQuote
16561656
return _tmp
16571657
end
16581658

1659-
# BlockQuoteRaw = @StartList:a (">" " "? Line:l { a << l } (!">" !@BlankLine Line:c { a << c })* (@BlankLine:n { a << n })*)+ { inner_parse a.join }
1659+
# BlockQuoteRaw = @StartList:a (">" " "? Line:l { a << l } (!">" !@BlankLine !AtxStart !Bullet !Enumerator !Ticks3 Line:c { a << c })* (@BlankLine:n { a << n })*)+ { inner_parse a.join }
16601660
def _BlockQuoteRaw
16611661

16621662
_save = self.pos
@@ -1718,6 +1718,38 @@ def _BlockQuoteRaw
17181718
self.pos = _save5
17191719
break
17201720
end
1721+
_save8 = self.pos
1722+
_tmp = apply(:_AtxStart)
1723+
_tmp = _tmp ? nil : true
1724+
self.pos = _save8
1725+
unless _tmp
1726+
self.pos = _save5
1727+
break
1728+
end
1729+
_save9 = self.pos
1730+
_tmp = apply(:_Bullet)
1731+
_tmp = _tmp ? nil : true
1732+
self.pos = _save9
1733+
unless _tmp
1734+
self.pos = _save5
1735+
break
1736+
end
1737+
_save10 = self.pos
1738+
_tmp = apply(:_Enumerator)
1739+
_tmp = _tmp ? nil : true
1740+
self.pos = _save10
1741+
unless _tmp
1742+
self.pos = _save5
1743+
break
1744+
end
1745+
_save11 = self.pos
1746+
_tmp = apply(:_Ticks3)
1747+
_tmp = _tmp ? nil : true
1748+
self.pos = _save11
1749+
unless _tmp
1750+
self.pos = _save5
1751+
break
1752+
end
17211753
_tmp = apply(:_Line)
17221754
c = @result
17231755
unless _tmp
@@ -1741,18 +1773,18 @@ def _BlockQuoteRaw
17411773
end
17421774
while true
17431775

1744-
_save9 = self.pos
1776+
_save13 = self.pos
17451777
while true # sequence
17461778
_tmp = _BlankLine()
17471779
n = @result
17481780
unless _tmp
1749-
self.pos = _save9
1781+
self.pos = _save13
17501782
break
17511783
end
17521784
@result = begin; a << n ; end
17531785
_tmp = true
17541786
unless _tmp
1755-
self.pos = _save9
1787+
self.pos = _save13
17561788
end
17571789
break
17581790
end # end sequence
@@ -1769,65 +1801,97 @@ def _BlockQuoteRaw
17691801
if _tmp
17701802
while true
17711803

1772-
_save10 = self.pos
1804+
_save14 = self.pos
17731805
while true # sequence
17741806
_tmp = match_string(">")
17751807
unless _tmp
1776-
self.pos = _save10
1808+
self.pos = _save14
17771809
break
17781810
end
1779-
_save11 = self.pos
1811+
_save15 = self.pos
17801812
_tmp = match_string(" ")
17811813
unless _tmp
17821814
_tmp = true
1783-
self.pos = _save11
1815+
self.pos = _save15
17841816
end
17851817
unless _tmp
1786-
self.pos = _save10
1818+
self.pos = _save14
17871819
break
17881820
end
17891821
_tmp = apply(:_Line)
17901822
l = @result
17911823
unless _tmp
1792-
self.pos = _save10
1824+
self.pos = _save14
17931825
break
17941826
end
17951827
@result = begin; a << l ; end
17961828
_tmp = true
17971829
unless _tmp
1798-
self.pos = _save10
1830+
self.pos = _save14
17991831
break
18001832
end
18011833
while true
18021834

1803-
_save13 = self.pos
1835+
_save17 = self.pos
18041836
while true # sequence
1805-
_save14 = self.pos
1837+
_save18 = self.pos
18061838
_tmp = match_string(">")
18071839
_tmp = _tmp ? nil : true
1808-
self.pos = _save14
1840+
self.pos = _save18
18091841
unless _tmp
1810-
self.pos = _save13
1842+
self.pos = _save17
18111843
break
18121844
end
1813-
_save15 = self.pos
1845+
_save19 = self.pos
18141846
_tmp = _BlankLine()
18151847
_tmp = _tmp ? nil : true
1816-
self.pos = _save15
1848+
self.pos = _save19
1849+
unless _tmp
1850+
self.pos = _save17
1851+
break
1852+
end
1853+
_save20 = self.pos
1854+
_tmp = apply(:_AtxStart)
1855+
_tmp = _tmp ? nil : true
1856+
self.pos = _save20
1857+
unless _tmp
1858+
self.pos = _save17
1859+
break
1860+
end
1861+
_save21 = self.pos
1862+
_tmp = apply(:_Bullet)
1863+
_tmp = _tmp ? nil : true
1864+
self.pos = _save21
18171865
unless _tmp
1818-
self.pos = _save13
1866+
self.pos = _save17
1867+
break
1868+
end
1869+
_save22 = self.pos
1870+
_tmp = apply(:_Enumerator)
1871+
_tmp = _tmp ? nil : true
1872+
self.pos = _save22
1873+
unless _tmp
1874+
self.pos = _save17
1875+
break
1876+
end
1877+
_save23 = self.pos
1878+
_tmp = apply(:_Ticks3)
1879+
_tmp = _tmp ? nil : true
1880+
self.pos = _save23
1881+
unless _tmp
1882+
self.pos = _save17
18191883
break
18201884
end
18211885
_tmp = apply(:_Line)
18221886
c = @result
18231887
unless _tmp
1824-
self.pos = _save13
1888+
self.pos = _save17
18251889
break
18261890
end
18271891
@result = begin; a << c ; end
18281892
_tmp = true
18291893
unless _tmp
1830-
self.pos = _save13
1894+
self.pos = _save17
18311895
end
18321896
break
18331897
end # end sequence
@@ -1836,23 +1900,23 @@ def _BlockQuoteRaw
18361900
end
18371901
_tmp = true
18381902
unless _tmp
1839-
self.pos = _save10
1903+
self.pos = _save14
18401904
break
18411905
end
18421906
while true
18431907

1844-
_save17 = self.pos
1908+
_save25 = self.pos
18451909
while true # sequence
18461910
_tmp = _BlankLine()
18471911
n = @result
18481912
unless _tmp
1849-
self.pos = _save17
1913+
self.pos = _save25
18501914
break
18511915
end
18521916
@result = begin; a << n ; end
18531917
_tmp = true
18541918
unless _tmp
1855-
self.pos = _save17
1919+
self.pos = _save25
18561920
end
18571921
break
18581922
end # end sequence
@@ -1861,7 +1925,7 @@ def _BlockQuoteRaw
18611925
end
18621926
_tmp = true
18631927
unless _tmp
1864-
self.pos = _save10
1928+
self.pos = _save14
18651929
end
18661930
break
18671931
end # end sequence
@@ -16457,7 +16521,7 @@ def _DefinitionListDefinition
1645716521
Rules[:_SetextHeading2] = rule_info("SetextHeading2", "&(@RawLine SetextBottom2) @StartList:a (!@Endline Inline:b { a << b })+ @Sp @Newline SetextBottom2 { RDoc::Markup::Heading.new(2, a.join) }")
1645816522
Rules[:_Heading] = rule_info("Heading", "(SetextHeading | AtxHeading)")
1645916523
Rules[:_BlockQuote] = rule_info("BlockQuote", "BlockQuoteRaw:a { RDoc::Markup::BlockQuote.new(*a) }")
16460-
Rules[:_BlockQuoteRaw] = rule_info("BlockQuoteRaw", "@StartList:a (\">\" \" \"? Line:l { a << l } (!\">\" !@BlankLine Line:c { a << c })* (@BlankLine:n { a << n })*)+ { inner_parse a.join }")
16524+
Rules[:_BlockQuoteRaw] = rule_info("BlockQuoteRaw", "@StartList:a (\">\" \" \"? Line:l { a << l } (!\">\" !@BlankLine !AtxStart !Bullet !Enumerator !Ticks3 Line:c { a << c })* (@BlankLine:n { a << n })*)+ { inner_parse a.join }")
1646116525
Rules[:_NonblankIndentedLine] = rule_info("NonblankIndentedLine", "!@BlankLine IndentedLine")
1646216526
Rules[:_VerbatimChunk] = rule_info("VerbatimChunk", "@BlankLine*:a NonblankIndentedLine+:b { a.concat b }")
1646316527
Rules[:_Verbatim] = rule_info("Verbatim", "VerbatimChunk+:a { RDoc::Markup::Verbatim.new(*a.flatten) }")

test/rdoc/rdoc_markdown_test.rb

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,70 @@ def test_parse_block_quote_separate
126126
assert_equal expected, doc
127127
end
128128

129+
def test_parse_block_quote_no_lazy_continuation_for_list
130+
doc = parse <<-BLOCK_QUOTE
131+
> foo
132+
- bar
133+
BLOCK_QUOTE
134+
135+
expected =
136+
doc(
137+
block(
138+
para("foo")),
139+
list(:BULLET,
140+
item(nil, para("bar"))))
141+
142+
assert_equal expected, doc
143+
end
144+
145+
def test_parse_block_quote_no_lazy_continuation_for_ordered_list
146+
doc = parse <<-BLOCK_QUOTE
147+
> foo
148+
1. bar
149+
BLOCK_QUOTE
150+
151+
expected =
152+
doc(
153+
block(
154+
para("foo")),
155+
list(:NUMBER,
156+
item(nil, para("bar"))))
157+
158+
assert_equal expected, doc
159+
end
160+
161+
def test_parse_block_quote_no_lazy_continuation_for_heading
162+
doc = parse <<-BLOCK_QUOTE
163+
> foo
164+
# bar
165+
BLOCK_QUOTE
166+
167+
expected =
168+
doc(
169+
block(
170+
para("foo")),
171+
head(1, "bar"))
172+
173+
assert_equal expected, doc
174+
end
175+
176+
def test_parse_block_quote_no_lazy_continuation_for_code_fence
177+
doc = parse <<~BLOCK_QUOTE
178+
> foo
179+
```
180+
code
181+
```
182+
BLOCK_QUOTE
183+
184+
expected =
185+
doc(
186+
block(
187+
para("foo")),
188+
verb("code\n"))
189+
190+
assert_equal expected, doc
191+
end
192+
129193
def test_parse_char_entity
130194
doc = parse '&pi; &nn;'
131195

0 commit comments

Comments
 (0)