Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion ext/markly/blocks.c
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,25 @@ static CMARK_INLINE bool S_is_space_or_tab(char c) {
// - Document node (special case)
// - Fenced code blocks (end on the closing fence line)
// - Setext headings (end on the underline)
// - Any block finalized on the same line it started (e.g., single-line HTML blocks)
// - HTML blocks types 1-5 per CommonMark spec §4.6 (end on the line
// containing the closing marker)
// - Any block finalized on the same line it started (e.g., single-line blocks)
static CMARK_INLINE bool S_ends_on_current_line(cmark_parser *parser, cmark_node *b) {
return S_type(b) == CMARK_NODE_DOCUMENT ||
(S_type(b) == CMARK_NODE_CODE_BLOCK && b->as.code.fenced) ||
(S_type(b) == CMARK_NODE_HEADING && b->as.heading.setext) ||
// HTML block types per CommonMark spec §4.6:
// 1: <script>, <pre>, <style>, <textarea> (ends at </tag>)
// 2: <!-- (ends at -->)
// 3: <? (ends at ?>)
// 4: <! + letter (ends at >)
// 5: <![CDATA[ (ends at ]]>)
// All five end on the line containing their closing marker,
// similar to fenced code blocks.
// Types 6-7 end at a blank line, so their last content line is
// the previous line and they should NOT match here.
(S_type(b) == CMARK_NODE_HTML_BLOCK && b->as.html_block_type >= 1 &&
b->as.html_block_type <= 5) ||
// Single-line blocks: finalized on same line they started
b->start_line == parser->line_number;
}
Expand Down
99 changes: 96 additions & 3 deletions test/markly/node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -365,16 +365,14 @@
end

it "has correct position for multi-line HTML block" do
# Multi-line HTML comment (4 lines total, but ends on line 4)
doc = Markly.parse("<!--\nLine 1\nLine 2\n-->")
html_node = doc.first_child
pos = html_node.source_position

expect(html_node.type).to be == :html
expect(pos[:start_line]).to be == 1
# The block starts on line 1 and the closing --> is on line 4
expect(pos[:end_line]).to be >= pos[:start_line]
expect(pos[:end_line]).to be >= 3 # At least line 3 or more
expect(pos[:end_line]).to be == 4
end

it "has correct position for fenced code block" do
Expand Down Expand Up @@ -423,6 +421,101 @@
expect(pos[:end_line]).to be == 1
end

it "has correct position for multi-line HTML comment (type 2)" do
doc = Markly.parse("Para 1\n\n<!--\nContent\n-->\n\nPara 2")

html_node = nil
doc.each do |node|
html_node = node if node.type == :html
end

expect(html_node).not.to be_nil
pos = html_node.source_position
expect(pos[:start_line]).to be == 3
expect(pos[:end_line]).to be == 5
end

it "has correct position for multi-line HTML declaration (type 4)" do
doc = Markly.parse("Para 1\n\n<!DOCTYPE\nhtml>\n\nPara 2")

html_node = nil
doc.each do |node|
html_node = node if node.type == :html
end

expect(html_node).not.to be_nil
pos = html_node.source_position
expect(pos[:start_line]).to be == 3
expect(pos[:end_line]).to be == 4
end

it "has correct position for multi-line script block (type 1)" do
doc = Markly.parse("Para 1\n\n<script>\nalert(1);\n</script>\n\nPara 2")

html_node = nil
doc.each do |node|
html_node = node if node.type == :html
end

expect(html_node).not.to be_nil
pos = html_node.source_position
expect(pos[:start_line]).to be == 3
expect(pos[:end_line]).to be == 5
end

it "has correct position for multi-line PHP block (type 3)" do
doc = Markly.parse("Para 1\n\n<?php\necho;\n?>\n\nPara 2")

html_node = nil
doc.each do |node|
html_node = node if node.type == :html
end

expect(html_node).not.to be_nil
pos = html_node.source_position
expect(pos[:start_line]).to be == 3
expect(pos[:end_line]).to be == 5
end

it "has correct position for multi-line declaration block (type 4)" do
doc = Markly.parse("Para 1\n\n<!DOCTYPE\nhtml>\n\nPara 2")

html_node = nil
doc.each do |node|
html_node = node if node.type == :html
end

expect(html_node).not.to be_nil
pos = html_node.source_position
expect(pos[:start_line]).to be == 3
expect(pos[:end_line]).to be == 4
end

it "has correct position for multi-line CDATA block (type 5)" do
doc = Markly.parse("Para 1\n\n<![CDATA[\ndata\n]]>\n\nPara 2")

html_node = nil
doc.each do |node|
html_node = node if node.type == :html
end

expect(html_node).not.to be_nil
pos = html_node.source_position
expect(pos[:start_line]).to be == 3
expect(pos[:end_line]).to be == 5
end

it "has correct position for blank-terminated HTML block (type 6)" do
# Types 6-7 end at a blank line, so end_line is the last content line
doc = Markly.parse("<div>\ncontent\n</div>\n\nPara 2")
html_node = doc.first_child
pos = html_node.source_position

expect(html_node.type).to be == :html
expect(pos[:start_line]).to be == 1
expect(pos[:end_line]).to be == 3
end

it "ensures all nodes have valid position ranges" do
# Comprehensive test: no node should have end_line < start_line
markdown = <<~MD
Expand Down
Loading