From c0ad4e234dbcdb80a714e6d8b0328e1e13308f9a Mon Sep 17 00:00:00 2001 From: jfmcdowell <206422+jfmcdowell@users.noreply.github.com> Date: Mon, 13 Apr 2026 22:31:33 -0400 Subject: [PATCH 1/2] fix(markdown_parser): trim trailing newline in tight lists inside blockquotes MdQuotePrefix blocks leaked into list item content, causing last_is_paragraph to evaluate false and skip trailing newline trimming on the first
  • of tight lists nested in blockquotes. --- crates/biome_markdown_parser/src/to_html.rs | 74 ++++++++++++++++++++- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/crates/biome_markdown_parser/src/to_html.rs b/crates/biome_markdown_parser/src/to_html.rs index 2e8d049343de..172654c97a0e 100644 --- a/crates/biome_markdown_parser/src/to_html.rs +++ b/crates/biome_markdown_parser/src/to_html.rs @@ -645,12 +645,12 @@ impl<'a> HtmlRenderer<'a> { let first_is_paragraph = blocks .iter() - .find(|b| !is_newline_block(b)) + .find(|b| !is_transparent_block(b)) .is_some_and(is_paragraph_block); let last_is_paragraph = blocks .iter() .rev() - .find(|b| !is_newline_block(b)) + .find(|b| !is_transparent_block(b)) .is_some_and(is_paragraph_block); let leading_newline = !is_tight || !first_is_paragraph; @@ -1773,6 +1773,19 @@ fn is_newline_block(block: &AnyMdBlock) -> bool { ) } +/// Check if a block is structural-only and produces no rendered content. +/// This includes newline blocks, continuation indents, and quote prefix markers +/// that can appear as children of list item content when lists are nested +/// inside blockquotes. +fn is_transparent_block(block: &AnyMdBlock) -> bool { + matches!( + block, + AnyMdBlock::AnyMdLeafBlock( + AnyMdLeafBlock::MdNewline(_) | AnyMdLeafBlock::MdContinuationIndent(_), + ) | AnyMdBlock::MdQuotePrefix(_) + ) +} + /// Check if blocks are effectively empty (empty or only newlines). fn is_empty_content(blocks: &[AnyMdBlock]) -> bool { blocks.is_empty() || blocks.iter().all(is_newline_block) @@ -2116,4 +2129,61 @@ mod tests { "\n
      \n
    1. ordered
    2. \n
    \n" ); } + + #[test] + fn test_tight_bullet_list_in_blockquote() { + let parsed = parse_markdown("> - a\n> - b\n"); + let html = document_to_html( + &parsed.tree(), + parsed.list_tightness(), + parsed.list_item_indents(), + parsed.quote_indents(), + ); + assert_eq!( + html, + "
    \n\n
    \n" + ); + } + + #[test] + fn test_tight_ordered_list_in_blockquote() { + let parsed = parse_markdown("> 1. a\n> 2. b\n"); + let html = document_to_html( + &parsed.tree(), + parsed.list_tightness(), + parsed.list_item_indents(), + parsed.quote_indents(), + ); + assert_eq!( + html, + "
    \n
      \n
    1. a
    2. \n
    3. b
    4. \n
    \n
    \n" + ); + } + + #[test] + fn test_tight_three_item_bullet_list_in_blockquote() { + let parsed = parse_markdown("> - a\n> - b\n> - c\n"); + let html = document_to_html( + &parsed.tree(), + parsed.list_tightness(), + parsed.list_item_indents(), + parsed.quote_indents(), + ); + assert_eq!( + html, + "
    \n\n
    \n" + ); + } + + #[test] + fn test_tight_list_not_in_blockquote() { + let parsed = parse_markdown("- a\n- b\n"); + let html = document_to_html( + &parsed.tree(), + parsed.list_tightness(), + parsed.list_item_indents(), + parsed.quote_indents(), + ); + assert_eq!(html, "\n"); + } } From 4acaa14056b7850c23200a70647ec4b17f7fa53a Mon Sep 17 00:00:00 2001 From: jfmcdowell <206422+jfmcdowell@users.noreply.github.com> Date: Tue, 14 Apr 2026 10:13:01 -0400 Subject: [PATCH 2/2] refactor(markdown): reuse transparent block check for empty content --- crates/biome_markdown_parser/src/to_html.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/biome_markdown_parser/src/to_html.rs b/crates/biome_markdown_parser/src/to_html.rs index 172654c97a0e..2c39544428c9 100644 --- a/crates/biome_markdown_parser/src/to_html.rs +++ b/crates/biome_markdown_parser/src/to_html.rs @@ -1788,7 +1788,7 @@ fn is_transparent_block(block: &AnyMdBlock) -> bool { /// Check if blocks are effectively empty (empty or only newlines). fn is_empty_content(blocks: &[AnyMdBlock]) -> bool { - blocks.is_empty() || blocks.iter().all(is_newline_block) + blocks.is_empty() || blocks.iter().all(is_transparent_block) } fn list_item_required_indent(entry: &ListItemIndent) -> usize {