From 1b58f9fd259b9368a70d5813e7e0245ad11128ec Mon Sep 17 00:00:00 2001 From: "Peter H. Boling" Date: Thu, 9 Apr 2026 00:21:49 -0600 Subject: [PATCH 1/2] Fix end_line for multi-line HTML blocks (types 1-5) HTML blocks types 1-5 have explicit closing markers (, -->, ?>, >, ]]>) that appear on the current line when the block is finalized. This is structurally identical to fenced code blocks, which were already handled by S_ends_on_current_line(). Without this fix, multi-line HTML blocks types 1-5 report end_line as one less than the actual last line, because finalize() falls through to the 'ended on previous line' case (line_number - 1). Types 6-7 correctly continue to use line_number - 1 since they end at a blank line (their last content is on the previous line). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ext/markly/blocks.c | 16 ++++++++- test/markly/node.rb | 85 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 97 insertions(+), 4 deletions(-) diff --git a/ext/markly/blocks.c b/ext/markly/blocks.c index 6f799f8..1dd6c40 100644 --- a/ext/markly/blocks.c +++ b/ext/markly/blocks.c @@ -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: \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\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\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\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("
\ncontent\n
\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 From bad4493fba293dbdb3c56512bed56cc38280778e Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 9 Apr 2026 18:47:32 +1200 Subject: [PATCH 2/2] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- test/markly/node.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/markly/node.rb b/test/markly/node.rb index 5f38f73..e36e9be 100644 --- a/test/markly/node.rb +++ b/test/markly/node.rb @@ -435,6 +435,20 @@ 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\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\n\nPara 2")