diff --git a/.claude/skills/formatter-development/SKILL.md b/.claude/skills/formatter-development/SKILL.md index f19ab144435b..760c19a9927c 100644 --- a/.claude/skills/formatter-development/SKILL.md +++ b/.claude/skills/formatter-development/SKILL.md @@ -1,6 +1,6 @@ --- name: formatter-development -description: Guide for implementing formatting rules using Biome's IR-based formatter infrastructure. Use when implementing formatting for new syntax nodes, handling comments in formatted output, or comparing Biome's formatting against Prettier for JavaScript, CSS, JSON, HTML, or other languages. +description: Guide for implementing formatting rules using Biome's IR-based formatter infrastructure. Use when implementing formatting for new syntax nodes, handling comments in formatted output, writing or debugging formatter snapshot tests, diagnosing idempotency failures, or comparing Biome's formatting against Prettier for JavaScript, CSS, JSON, HTML, Markdown, or other languages. compatibility: Designed for coding agents working on the Biome codebase (github.com/biomejs/biome). --- @@ -156,6 +156,60 @@ echo 'const x = 1' | bun packages/prettier-compare/bin/prettier-compare.js --reb **Always use `--rebuild`** to ensure WASM bundle matches your Rust changes. +### Format and Build + +After changes: + +```shell +just f # Format Rust code +just l # Lint +just gen-formatter # Regenerate formatter infrastructure if needed +``` + +## Testing infrastructure + +The testing infrastructure of the formatters is divided in two main pieces. Internal and external. + +The testing infrastructure is designed for catching idempotency cases, which means that each file inside the infrastructure is designed to fail if: +- the final **printed** output differs on a second formatting run. +- the final **IR** output differs on a second formatting run. + +**Both must be fixed.** + +Run `cargo t` twice. The first run may write or update snapshots; the second run re-formats the just-written output and confirms it is stable. Skipping the second run hides idempotency bugs — a broken formatter can look green on a single pass because the snapshot it wrote matches itself. + +### `quick_test.rs` + +- Use `quick_test.rs` inside the crate for testing theories and formatting. +- Only modify the `source` string literal inside the test. Do not change the parse/format/assert scaffolding around it — that scaffolding already verifies idempotency and prints the CST and IR you need for debugging. + +### External infra + +The external infra relies on a human pulling the tests inside the repository, inside the folder `/tests/prettier`. + +Once the tests are ported, the infrastructure produces two files for each original file: +- `..prettier-snap` which contains the output generated by Prettier at the moment the test was ported. +- `..snap` which contains three sections + - the input source + - the list of diffs between Prettier and Biome + - the output generated by Biome + +The `.snap` file is only created when Biome's output differs from Prettier's. When the two agree, no `.snap` file is written. + +The absence of a `.snap` file is **positive** — it means Biome matches Prettier for that input. + +### Internal infrastructure + +The internal infrastructure relies on creating new test files. For each test, place two snippets in the same file: +- a piece of source code **already formatted** the way Biome should produce it +- the same code in an **unformatted** shape + +After running the formatter, both snippets should produce identical output. That identity proves the formatter converges on a canonical form and is idempotent. + +**Always create new test cases when implementing a feature or fixing a bug.** Internal tests exercise the exact shape you care about and survive even if the Prettier corpus changes. + +**Do not rely on Prettier `.snap` files disappearing as proof of correctness.** A missing `.snap` only means Biome and Prettier agree on that specific ported input. It does not cover the edge cases you introduced — write internal tests for those, and do not delete a Prettier `.snap` to make a diff "go away". + ### Create Snapshot Tests Create test files in `tests/specs/` organized by feature: @@ -219,16 +273,6 @@ Create `options.json` in the test folder: This applies to all test files in that folder. -### Format and Build - -After changes: - -```shell -just f # Format Rust code -just l # Lint -just gen-formatter # Regenerate formatter infrastructure if needed -``` - ## Tips - **format_verbatim_node**: Initial generated code uses this - replace it with proper IR as you implement formatting diff --git a/.gitignore b/.gitignore index 2c4aeac03e7a..419ac0814ffc 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,8 @@ dhat-heap.json /crates/biome_css_formatter/report.* /crates/biome_graphql_formatter/report.* /crates/biome_js_formatter/report.* +/crates/biome_markdown_formatter/report.* +/crates/biome_html_formatter/report.* /crates/biome_json_formatter/report.* /crates/biome_json_formatter/report_incompatible.* diff --git a/crates/biome_html_formatter/src/html/any/attribute_initializer.rs b/crates/biome_html_formatter/src/html/any/attribute_initializer.rs index f3e9c62a4ebf..9c163f33bebc 100644 --- a/crates/biome_html_formatter/src/html/any/attribute_initializer.rs +++ b/crates/biome_html_formatter/src/html/any/attribute_initializer.rs @@ -1,32 +1,17 @@ //! This is a generated file. Don't modify it by hand! Run 'cargo codegen formatter' to re-generate the file. use crate::prelude::*; -use biome_formatter::FormatRuleWithOptions; use biome_html_syntax::AnyHtmlAttributeInitializer; #[derive(Debug, Clone, Default)] -pub(crate) struct FormatAnyHtmlAttributeInitializer { - compact: bool, -} - +pub(crate) struct FormatAnyHtmlAttributeInitializer; impl FormatRule for FormatAnyHtmlAttributeInitializer { type Context = HtmlFormatContext; fn fmt(&self, node: &AnyHtmlAttributeInitializer, f: &mut HtmlFormatter) -> FormatResult<()> { match node { AnyHtmlAttributeInitializer::HtmlAttributeSingleTextExpression(node) => { - node.format().with_options(self.compact).fmt(f) - } - AnyHtmlAttributeInitializer::HtmlString(node) => { - node.format().with_options(self.compact).fmt(f) + node.format().fmt(f) } + AnyHtmlAttributeInitializer::HtmlString(node) => node.format().fmt(f), } } } - -impl FormatRuleWithOptions for FormatAnyHtmlAttributeInitializer { - type Options = bool; - - fn with_options(mut self, options: Self::Options) -> Self { - self.compact = options; - self - } -} diff --git a/crates/biome_html_formatter/src/html/auxiliary/attribute_initializer_clause.rs b/crates/biome_html_formatter/src/html/auxiliary/attribute_initializer_clause.rs index 7b8d59088660..7f1bc3cb1e77 100644 --- a/crates/biome_html_formatter/src/html/auxiliary/attribute_initializer_clause.rs +++ b/crates/biome_html_formatter/src/html/auxiliary/attribute_initializer_clause.rs @@ -1,6 +1,7 @@ use std::fmt::Debug; use crate::prelude::*; +use crate::shared::FmtAnyAttributeInitializer; use biome_formatter::{CstFormatContext, FormatRuleWithOptions, write}; use biome_html_syntax::{ AnyHtmlAttributeInitializer, HtmlAttributeInitializerClause, @@ -155,10 +156,11 @@ impl FormatNodeRule for FormatHtmlAttributeIniti CompactKind::Remove => { let eq_token = eq_token.clone()?; let value = value.clone()?; - write!( - f, - [format_removed(&eq_token), value.format().with_options(true),] - )?; + let fmt = FmtAnyAttributeInitializer { + node: value, + compact: true, + }; + write!(f, [format_removed(&eq_token), &fmt,])?; Ok(()) } } diff --git a/crates/biome_html_formatter/src/lib.rs b/crates/biome_html_formatter/src/lib.rs index 515cc971a19a..2308e13a9c52 100644 --- a/crates/biome_html_formatter/src/lib.rs +++ b/crates/biome_html_formatter/src/lib.rs @@ -21,6 +21,7 @@ mod generated; mod html; pub(crate) mod prelude; pub(crate) mod separated; +pub(crate) mod shared; mod svelte; mod trivia; pub mod utils; diff --git a/crates/biome_html_formatter/src/shared.rs b/crates/biome_html_formatter/src/shared.rs new file mode 100644 index 000000000000..b81c245900d5 --- /dev/null +++ b/crates/biome_html_formatter/src/shared.rs @@ -0,0 +1,42 @@ +use crate::AsFormat; +use crate::context::HtmlFormatContext; +use biome_formatter::formatter::Formatter; +use biome_formatter::{Format, FormatResult}; +use biome_html_syntax::{AnyHtmlAttributeInitializer, AnySvelteBindingProperty}; + +pub(crate) struct FmtAnyAttributeInitializer { + pub(crate) node: AnyHtmlAttributeInitializer, + pub(crate) compact: bool, +} + +impl Format for FmtAnyAttributeInitializer { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + match &self.node { + AnyHtmlAttributeInitializer::HtmlAttributeSingleTextExpression(node) => { + node.format().with_options(self.compact).fmt(f) + } + AnyHtmlAttributeInitializer::HtmlString(node) => { + node.format().with_options(self.compact).fmt(f) + } + } + } +} + +pub(crate) struct FmtAnySvelteBindingProperty { + pub(crate) node: AnySvelteBindingProperty, + pub(crate) compact: bool, +} + +impl Format for FmtAnySvelteBindingProperty { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + match &self.node { + AnySvelteBindingProperty::SvelteLiteral(node) => { + node.format().with_options(self.compact).fmt(f) + } + AnySvelteBindingProperty::SvelteMemberProperty(node) => node.format().fmt(f), + AnySvelteBindingProperty::SvelteName(node) => { + node.format().with_options(self.compact).fmt(f) + } + } + } +} diff --git a/crates/biome_html_formatter/src/svelte/any/binding_property.rs b/crates/biome_html_formatter/src/svelte/any/binding_property.rs index c39388bc5342..b8a9a30f1280 100644 --- a/crates/biome_html_formatter/src/svelte/any/binding_property.rs +++ b/crates/biome_html_formatter/src/svelte/any/binding_property.rs @@ -1,34 +1,16 @@ //! This is a generated file. Don't modify it by hand! Run 'cargo codegen formatter' to re-generate the file. use crate::prelude::*; -use biome_formatter::FormatRuleWithOptions; use biome_html_syntax::AnySvelteBindingProperty; #[derive(Debug, Clone, Default)] -pub(crate) struct FormatAnySvelteBindingProperty { - /// Whether it should be formatted in compact mode. In compact mode, all tokens and children - /// are removed - pub compact: bool, -} +pub(crate) struct FormatAnySvelteBindingProperty; impl FormatRule for FormatAnySvelteBindingProperty { type Context = HtmlFormatContext; fn fmt(&self, node: &AnySvelteBindingProperty, f: &mut HtmlFormatter) -> FormatResult<()> { match node { - AnySvelteBindingProperty::SvelteLiteral(node) => { - node.format().with_options(self.compact).fmt(f) - } + AnySvelteBindingProperty::SvelteLiteral(node) => node.format().fmt(f), AnySvelteBindingProperty::SvelteMemberProperty(node) => node.format().fmt(f), - AnySvelteBindingProperty::SvelteName(node) => { - node.format().with_options(self.compact).fmt(f) - } + AnySvelteBindingProperty::SvelteName(node) => node.format().fmt(f), } } } - -impl FormatRuleWithOptions for FormatAnySvelteBindingProperty { - type Options = bool; - - fn with_options(mut self, options: Self::Options) -> Self { - self.compact = options; - self - } -} diff --git a/crates/biome_html_formatter/src/svelte/value/directive_value.rs b/crates/biome_html_formatter/src/svelte/value/directive_value.rs index d3d816f5d706..6c0e64b58d56 100644 --- a/crates/biome_html_formatter/src/svelte/value/directive_value.rs +++ b/crates/biome_html_formatter/src/svelte/value/directive_value.rs @@ -2,6 +2,7 @@ use crate::html::auxiliary::attribute_initializer_clause::{ CompactKind, FormatHtmlAttributeInitializerClauseOptions, }; use crate::prelude::*; +use crate::shared::FmtAnySvelteBindingProperty; use biome_formatter::{FormatRuleWithOptions, write}; use biome_html_syntax::{ AnySvelteBindingProperty, SvelteDirectiveValue, SvelteDirectiveValueFields, @@ -67,6 +68,10 @@ impl FormatSvelteDirectiveValue { return Ok(false); } + let property = FmtAnySvelteBindingProperty { + node: property, + compact: true, + }; write!( f, [ @@ -79,7 +84,7 @@ impl FormatSvelteDirectiveValue { tag_name: None } ), - property.format().with_options(true), + &property, modifiers.format().with_options(true) ] )?; diff --git a/crates/biome_markdown_formatter/src/markdown/auxiliary/autolink.rs b/crates/biome_markdown_formatter/src/markdown/auxiliary/autolink.rs index b469ccfbe553..01152de992b7 100644 --- a/crates/biome_markdown_formatter/src/markdown/auxiliary/autolink.rs +++ b/crates/biome_markdown_formatter/src/markdown/auxiliary/autolink.rs @@ -22,7 +22,8 @@ impl FormatNodeRule for FormatMdAutolink { .format() .with_options(FormatMdFormatInlineItemListOptions { print_mode: TextPrintMode::Trim(TrimMode::NormalizeWords), - keep_fences_in_italics: false + keep_fences_in_italics: false, + inside_list: false, }), r_angle_token.format() ] diff --git a/crates/biome_markdown_formatter/src/markdown/auxiliary/bullet.rs b/crates/biome_markdown_formatter/src/markdown/auxiliary/bullet.rs index d69e8386c06f..99e142ec3890 100644 --- a/crates/biome_markdown_formatter/src/markdown/auxiliary/bullet.rs +++ b/crates/biome_markdown_formatter/src/markdown/auxiliary/bullet.rs @@ -1,9 +1,12 @@ use crate::markdown::auxiliary::list_marker_prefix::FormatMdListMarkerPrefixOptions; +use crate::markdown::lists::block_list::FormatMdBlockListOptions; use crate::prelude::*; +use crate::shared::TextPrintMode; use biome_formatter::write; use biome_markdown_syntax::{ AnyMdBlock, AnyMdLeafBlock, MarkdownSyntaxKind, MdBullet, MdBulletFields, }; + #[derive(Debug, Clone, Default)] pub(crate) struct FormatMdBullet; impl FormatNodeRule for FormatMdBullet { @@ -19,6 +22,7 @@ impl FormatNodeRule for FormatMdBullet { // but still format content through child formatters. let target_marker = if marker.kind() == MarkdownSyntaxKind::MINUS || first_block_is_dash_thematic_break(&content) + || marker.kind() == MarkdownSyntaxKind::MD_ORDERED_LIST_MARKER { None } else { @@ -31,8 +35,13 @@ impl FormatNodeRule for FormatMdBullet { .format() .with_options(FormatMdListMarkerPrefixOptions { target_marker })] )?; - content.format().fmt(f)?; - Ok(()) + write!( + f, + [content.format().with_options(FormatMdBlockListOptions { + paragraph_print_mode: TextPrintMode::trim_keep_leading_spaces(), + trim: false, + })] + ) } } diff --git a/crates/biome_markdown_formatter/src/markdown/auxiliary/document.rs b/crates/biome_markdown_formatter/src/markdown/auxiliary/document.rs index c618c900fb87..89a450cebe72 100644 --- a/crates/biome_markdown_formatter/src/markdown/auxiliary/document.rs +++ b/crates/biome_markdown_formatter/src/markdown/auxiliary/document.rs @@ -1,5 +1,6 @@ use crate::markdown::lists::block_list::FormatMdBlockListOptions; use crate::prelude::*; +use crate::shared::TextPrintMode; use biome_formatter::write; use biome_markdown_syntax::{MdDocument, MdDocumentFields}; @@ -20,9 +21,10 @@ impl FormatNodeRule for FormatMdDocument { write!( f, [ - value - .format() - .with_options(FormatMdBlockListOptions { trim: true }), + value.format().with_options(FormatMdBlockListOptions { + paragraph_print_mode: TextPrintMode::Pristine, + trim: true + }), format_removed(&eof_token?) ] )?; diff --git a/crates/biome_markdown_formatter/src/markdown/auxiliary/fenced_code_block.rs b/crates/biome_markdown_formatter/src/markdown/auxiliary/fenced_code_block.rs index cc6229fe70ec..132b1fc93c51 100644 --- a/crates/biome_markdown_formatter/src/markdown/auxiliary/fenced_code_block.rs +++ b/crates/biome_markdown_formatter/src/markdown/auxiliary/fenced_code_block.rs @@ -1,12 +1,17 @@ use crate::markdown::lists::inline_item_list::FormatMdFormatInlineItemListOptions; use crate::prelude::*; use crate::shared::TextPrintMode; -use biome_formatter::write; +use biome_formatter::{FormatRuleWithOptions, write}; use biome_markdown_syntax::{MdFencedCodeBlock, MdFencedCodeBlockFields}; use biome_rowan::TextSize; #[derive(Debug, Clone, Default)] -pub(crate) struct FormatMdFencedCodeBlock; +pub(crate) struct FormatMdFencedCodeBlock { + /// Whether the fenced code block is inside a list. + /// When inside a list + inside_list: bool, +} + impl FormatNodeRule for FormatMdFencedCodeBlock { fn fmt_fields(&self, node: &MdFencedCodeBlock, f: &mut MarkdownFormatter) -> FormatResult<()> { let MdFencedCodeBlockFields { @@ -29,10 +34,24 @@ impl FormatNodeRule for FormatMdFencedCodeBlock { let fence_len = (max_inner + 1).max(3); let normalized_fence: String = std::iter::repeat_n('`', fence_len).collect(); + // Spaces to remove in case we're inside a list + let excess = if self.inside_list { indent.len() } else { 0 }; + + if excess > 0 { + for token in indent.iter() { + let char_token = token.md_indent_char_token()?; + f.context() + .comments() + .mark_suppression_checked(token.syntax()); + write!(f, [format_removed(&char_token)])?; + } + } else { + write!(f, [indent.format()])?; + } + write!( f, [ - indent.format(), format_replaced( &l_fence, &text(&normalized_fence, l_fence.text_trimmed_range().start()) @@ -43,13 +62,28 @@ impl FormatNodeRule for FormatMdFencedCodeBlock { .format() .with_options(FormatMdFormatInlineItemListOptions { print_mode: TextPrintMode::Clean, - keep_fences_in_italics: false + keep_fences_in_italics: false, + inside_list: false, }), - hard_line_break(), - r_fence_indent.format(), ] )?; + // The closing fence's indentation is stored entirely in r_fence_indent + // (unlike the opening fence, there is no separate continuation-indent + // node preceding it in the block list). Remove the same number of + // excess spaces that were removed from the opening fence. + let r_fence_tokens: Vec<_> = r_fence_indent.iter().collect(); + for token in r_fence_tokens.iter().take(excess) { + let char_token = token.md_indent_char_token()?; + f.context() + .comments() + .mark_suppression_checked(token.syntax()); + write!(f, [format_removed(&char_token)])?; + } + for token in r_fence_tokens.iter().skip(excess) { + write!(f, [token.format()])?; + } + if let Ok(r_fence) = r_fence { write!( f, @@ -66,6 +100,19 @@ impl FormatNodeRule for FormatMdFencedCodeBlock { } } +pub(crate) struct FormatMdFencedCodeBlockOptions { + pub(crate) inside_list: bool, +} + +impl FormatRuleWithOptions for FormatMdFencedCodeBlock { + type Options = FormatMdFencedCodeBlockOptions; + + fn with_options(mut self, options: Self::Options) -> Self { + self.inside_list = options.inside_list; + self + } +} + /// Find the longest consecutive run of `fence_char` in the code block's content. fn longest_fence_char_sequence(node: &MdFencedCodeBlock, fence_char: char) -> usize { let content = node.content(); diff --git a/crates/biome_markdown_formatter/src/markdown/auxiliary/header.rs b/crates/biome_markdown_formatter/src/markdown/auxiliary/header.rs index 1f9d3fe74b6b..f18f06fef573 100644 --- a/crates/biome_markdown_formatter/src/markdown/auxiliary/header.rs +++ b/crates/biome_markdown_formatter/src/markdown/auxiliary/header.rs @@ -24,7 +24,8 @@ impl FormatNodeRule for FormatMdHeader { [ space(), content.format().with_options(FormatMdParagraphOptions { - trim_mode: TextPrintMode::Trim(TrimMode::Start) + trim_mode: TextPrintMode::Trim(TrimMode::Start), + inside_list: false, }) ] )?; diff --git a/crates/biome_markdown_formatter/src/markdown/auxiliary/inline_code.rs b/crates/biome_markdown_formatter/src/markdown/auxiliary/inline_code.rs index 7b59c4b21842..0926342303bf 100644 --- a/crates/biome_markdown_formatter/src/markdown/auxiliary/inline_code.rs +++ b/crates/biome_markdown_formatter/src/markdown/auxiliary/inline_code.rs @@ -22,7 +22,8 @@ impl FormatNodeRule for FormatMdInlineCode { .format() .with_options(FormatMdFormatInlineItemListOptions { print_mode: TextPrintMode::Pristine, - keep_fences_in_italics: false + keep_fences_in_italics: false, + inside_list: false, }), r_tick_token.format() ] diff --git a/crates/biome_markdown_formatter/src/markdown/auxiliary/inline_image.rs b/crates/biome_markdown_formatter/src/markdown/auxiliary/inline_image.rs index 5d6129f287c6..4133494c103c 100644 --- a/crates/biome_markdown_formatter/src/markdown/auxiliary/inline_image.rs +++ b/crates/biome_markdown_formatter/src/markdown/auxiliary/inline_image.rs @@ -27,7 +27,8 @@ impl FormatNodeRule for FormatMdInlineImage { alt.format() .with_options(FormatMdFormatInlineItemListOptions { print_mode: TextPrintMode::trim_all(), - keep_fences_in_italics: false + keep_fences_in_italics: false, + inside_list: false, }), r_brack_token.format(), l_paren_token.format(), @@ -35,7 +36,8 @@ impl FormatNodeRule for FormatMdInlineImage { .format() .with_options(FormatMdFormatInlineItemListOptions { print_mode: TextPrintMode::Trim(TrimMode::AutoLinkLike), - keep_fences_in_italics: false + keep_fences_in_italics: false, + inside_list: false, }) ] )?; diff --git a/crates/biome_markdown_formatter/src/markdown/auxiliary/inline_link.rs b/crates/biome_markdown_formatter/src/markdown/auxiliary/inline_link.rs index 1c9f0a9e2fad..34977ef51250 100644 --- a/crates/biome_markdown_formatter/src/markdown/auxiliary/inline_link.rs +++ b/crates/biome_markdown_formatter/src/markdown/auxiliary/inline_link.rs @@ -25,7 +25,8 @@ impl FormatNodeRule for FormatMdInlineLink { text.format() .with_options(FormatMdFormatInlineItemListOptions { print_mode: TextPrintMode::trim_all(), - keep_fences_in_italics: false + keep_fences_in_italics: false, + inside_list: false, }), r_brack_token.format(), l_paren_token.format(), @@ -33,7 +34,8 @@ impl FormatNodeRule for FormatMdInlineLink { .format() .with_options(FormatMdFormatInlineItemListOptions { print_mode: TextPrintMode::trim_all(), - keep_fences_in_italics: false + keep_fences_in_italics: false, + inside_list: false, }) ] )?; diff --git a/crates/biome_markdown_formatter/src/markdown/auxiliary/link_destination.rs b/crates/biome_markdown_formatter/src/markdown/auxiliary/link_destination.rs index bc87ff24d340..fbe13f5d0d33 100644 --- a/crates/biome_markdown_formatter/src/markdown/auxiliary/link_destination.rs +++ b/crates/biome_markdown_formatter/src/markdown/auxiliary/link_destination.rs @@ -14,6 +14,7 @@ impl FormatNodeRule for FormatMdLinkDestination { .with_options(FormatMdFormatInlineItemListOptions { print_mode: TextPrintMode::trim_all(), keep_fences_in_italics: false, + inside_list: false, }) .fmt(f) } diff --git a/crates/biome_markdown_formatter/src/markdown/auxiliary/link_label.rs b/crates/biome_markdown_formatter/src/markdown/auxiliary/link_label.rs index ee7f59f03f65..6b9decb5bb12 100644 --- a/crates/biome_markdown_formatter/src/markdown/auxiliary/link_label.rs +++ b/crates/biome_markdown_formatter/src/markdown/auxiliary/link_label.rs @@ -12,6 +12,7 @@ impl FormatNodeRule for FormatMdLinkLabel { .with_options(FormatMdFormatInlineItemListOptions { print_mode: TextPrintMode::Trim(TrimMode::NormalizeWords), keep_fences_in_italics: false, + inside_list: false, }) .fmt(f) } diff --git a/crates/biome_markdown_formatter/src/markdown/auxiliary/link_title.rs b/crates/biome_markdown_formatter/src/markdown/auxiliary/link_title.rs index 699c0d078ee2..d2775de9cd6e 100644 --- a/crates/biome_markdown_formatter/src/markdown/auxiliary/link_title.rs +++ b/crates/biome_markdown_formatter/src/markdown/auxiliary/link_title.rs @@ -16,7 +16,8 @@ impl FormatNodeRule for FormatMdLinkTitle { .format() .with_options(FormatMdFormatInlineItemListOptions { print_mode: TextPrintMode::trim_all(), - keep_fences_in_italics: false + keep_fences_in_italics: false, + inside_list: false, })] ) } diff --git a/crates/biome_markdown_formatter/src/markdown/auxiliary/ordered_list_item.rs b/crates/biome_markdown_formatter/src/markdown/auxiliary/ordered_list_item.rs index 52899831b9a1..6857b3ec3dad 100644 --- a/crates/biome_markdown_formatter/src/markdown/auxiliary/ordered_list_item.rs +++ b/crates/biome_markdown_formatter/src/markdown/auxiliary/ordered_list_item.rs @@ -1,10 +1,11 @@ use crate::prelude::*; -use biome_markdown_syntax::MdOrderedListItem; -use biome_rowan::AstNode; +use biome_markdown_syntax::{MdOrderedListItem, MdOrderedListItemFields}; #[derive(Debug, Clone, Default)] pub(crate) struct FormatMdOrderedListItem; impl FormatNodeRule for FormatMdOrderedListItem { fn fmt_fields(&self, node: &MdOrderedListItem, f: &mut MarkdownFormatter) -> FormatResult<()> { - format_verbatim_node(node.syntax()).fmt(f) + let MdOrderedListItemFields { md_bullet_list } = node.as_fields(); + + f.join().entries(md_bullet_list.iter().formatted()).finish() } } diff --git a/crates/biome_markdown_formatter/src/markdown/auxiliary/paragraph.rs b/crates/biome_markdown_formatter/src/markdown/auxiliary/paragraph.rs index ab8eb8d20fec..e6f6bca4d6da 100644 --- a/crates/biome_markdown_formatter/src/markdown/auxiliary/paragraph.rs +++ b/crates/biome_markdown_formatter/src/markdown/auxiliary/paragraph.rs @@ -7,12 +7,14 @@ use biome_markdown_syntax::{MdParagraph, MdParagraphFields}; #[derive(Debug, Clone)] pub(crate) struct FormatMdParagraph { trim_mode: TextPrintMode, + inside_list: bool, } impl Default for FormatMdParagraph { fn default() -> Self { Self { trim_mode: TextPrintMode::Trim(TrimMode::None), + inside_list: false, } } } @@ -25,7 +27,8 @@ impl FormatNodeRule for FormatMdParagraph { .format() .with_options(FormatMdFormatInlineItemListOptions { print_mode: self.trim_mode, - keep_fences_in_italics: false + keep_fences_in_italics: false, + inside_list: self.inside_list, })] )?; if let Some(hard_line) = hard_line { @@ -38,6 +41,8 @@ impl FormatNodeRule for FormatMdParagraph { pub(crate) struct FormatMdParagraphOptions { /// Whether to trim the start of the paragraph. Usually signaled by the headers pub trim_mode: TextPrintMode, + /// When true, excess leading spaces on continuation lines are removed. + pub inside_list: bool, } impl FormatRuleWithOptions for FormatMdParagraph { @@ -45,6 +50,7 @@ impl FormatRuleWithOptions for FormatMdParagraph { fn with_options(mut self, options: Self::Options) -> Self { self.trim_mode = options.trim_mode; + self.inside_list = options.inside_list; self } } diff --git a/crates/biome_markdown_formatter/src/markdown/auxiliary/quote.rs b/crates/biome_markdown_formatter/src/markdown/auxiliary/quote.rs index 4b37cc3e0860..819472acf1aa 100644 --- a/crates/biome_markdown_formatter/src/markdown/auxiliary/quote.rs +++ b/crates/biome_markdown_formatter/src/markdown/auxiliary/quote.rs @@ -7,7 +7,6 @@ pub(crate) struct FormatMdQuote; impl FormatNodeRule for FormatMdQuote { fn fmt_fields(&self, node: &MdQuote, f: &mut MarkdownFormatter) -> FormatResult<()> { let MdQuoteFields { content, prefix } = node.as_fields(); - write!(f, [prefix.format(), content.format()]) } } diff --git a/crates/biome_markdown_formatter/src/markdown/auxiliary/reference_link.rs b/crates/biome_markdown_formatter/src/markdown/auxiliary/reference_link.rs index 82e487afd6eb..c86a504771b3 100644 --- a/crates/biome_markdown_formatter/src/markdown/auxiliary/reference_link.rs +++ b/crates/biome_markdown_formatter/src/markdown/auxiliary/reference_link.rs @@ -22,7 +22,8 @@ impl FormatNodeRule for FormatMdReferenceLink { text.format() .with_options(FormatMdFormatInlineItemListOptions { print_mode: TextPrintMode::trim_all(), - keep_fences_in_italics: true + keep_fences_in_italics: true, + inside_list: false, }), r_brack_token.format(), ] diff --git a/crates/biome_markdown_formatter/src/markdown/auxiliary/setext_header.rs b/crates/biome_markdown_formatter/src/markdown/auxiliary/setext_header.rs index 103953bb8635..3e6fe52979ea 100644 --- a/crates/biome_markdown_formatter/src/markdown/auxiliary/setext_header.rs +++ b/crates/biome_markdown_formatter/src/markdown/auxiliary/setext_header.rs @@ -32,7 +32,8 @@ impl FormatNodeRule for FormatMdSetextHeader { .format() .with_options(FormatMdFormatInlineItemListOptions { print_mode: TextPrintMode::trim_all(), - keep_fences_in_italics: false + keep_fences_in_italics: false, + inside_list: false, }), format_removed(&underline_token) ] diff --git a/crates/biome_markdown_formatter/src/markdown/auxiliary/textual.rs b/crates/biome_markdown_formatter/src/markdown/auxiliary/textual.rs index 6a139c79ddcf..2f0a0cee7e8b 100644 --- a/crates/biome_markdown_formatter/src/markdown/auxiliary/textual.rs +++ b/crates/biome_markdown_formatter/src/markdown/auxiliary/textual.rs @@ -69,7 +69,7 @@ impl FormatNodeRule for FormatMdTextual { )] ) } - } else if self.print_mode.is_all() { + } else if self.print_mode.is_trim_all() { let trimmed_text = value_token.text().trim_start().trim_end(); write!( f, @@ -78,7 +78,7 @@ impl FormatNodeRule for FormatMdTextual { &text(trimmed_text, value_token.text_trimmed_range().start()) )] ) - } else if self.print_mode.is_start() { + } else if self.print_mode.is_trim_start() { let trimmed_text = value_token.text().trim_start(); write!( f, @@ -87,6 +87,17 @@ impl FormatNodeRule for FormatMdTextual { &text(trimmed_text, value_token.text_trimmed_range().start()) )] ) + } else if self.print_mode.is_keep_leading_spaces() { + // Preserve the leading whitespace trivia as literal text so that + // continuation-line indentation inside list items is kept. + let full_text = value_token.text(); + write!( + f, + [format_replaced( + &value_token, + &text(full_text, value_token.text_range().start()) + )] + ) } else { write!(f, [value_token.format()]) } diff --git a/crates/biome_markdown_formatter/src/markdown/lists/block_list.rs b/crates/biome_markdown_formatter/src/markdown/lists/block_list.rs index 41eea02e6c6a..1bdf3558b463 100644 --- a/crates/biome_markdown_formatter/src/markdown/lists/block_list.rs +++ b/crates/biome_markdown_formatter/src/markdown/lists/block_list.rs @@ -1,11 +1,15 @@ use crate::markdown::auxiliary::newline::FormatMdNewlineOptions; +use crate::markdown::auxiliary::paragraph::FormatMdParagraphOptions; use crate::prelude::*; +use crate::shared::TextPrintMode; use biome_formatter::FormatRuleWithOptions; -use biome_markdown_syntax::{AnyMdBlock, AnyMdLeafBlock, MdBlockList}; +use biome_markdown_syntax::{AnyMdBlock, AnyMdLeafBlock, MdBlockList, MdBullet}; #[derive(Debug, Clone, Default)] pub(crate) struct FormatMdBlockList { /// When true, it removes all leading newlines and trailing newlines + paragraph_print_mode: TextPrintMode, + trim: bool, } impl FormatRule for FormatMdBlockList { @@ -13,8 +17,28 @@ impl FormatRule for FormatMdBlockList { fn fmt(&self, node: &MdBlockList, f: &mut MarkdownFormatter) -> FormatResult<()> { let mut joiner = f.join(); + let inside_list = node + .syntax() + .parent() + .is_some_and(|n| MdBullet::can_cast(n.kind())); + if !self.trim { - return f.join().entries(node.iter().formatted()).finish(); + for node in node.iter() { + match &node { + AnyMdBlock::AnyMdLeafBlock(AnyMdLeafBlock::MdParagraph(paragraph)) => { + joiner.entry(¶graph.format().with_options(FormatMdParagraphOptions { + trim_mode: self.paragraph_print_mode, + inside_list, + })); + } + + _ => { + joiner.entry(&node.format()); + } + } + } + + return joiner.finish(); } let mut iter = node.iter(); @@ -50,6 +74,10 @@ impl FormatRule for FormatMdBlockList { } pub(crate) struct FormatMdBlockListOptions { + /// Signals how [MdParagraph] should be formatted + pub(crate) paragraph_print_mode: TextPrintMode, + + /// When true, leading and trailing newlines are removed pub(crate) trim: bool, } @@ -57,6 +85,7 @@ impl FormatRuleWithOptions for FormatMdBlockList { type Options = FormatMdBlockListOptions; fn with_options(mut self, options: Self::Options) -> Self { + self.paragraph_print_mode = options.paragraph_print_mode; self.trim = options.trim; self } diff --git a/crates/biome_markdown_formatter/src/markdown/lists/inline_item_list.rs b/crates/biome_markdown_formatter/src/markdown/lists/inline_item_list.rs index 09a8c2f73dec..ebfd14d7c029 100644 --- a/crates/biome_markdown_formatter/src/markdown/lists/inline_item_list.rs +++ b/crates/biome_markdown_formatter/src/markdown/lists/inline_item_list.rs @@ -11,16 +11,19 @@ pub(crate) struct FormatMdInlineItemList { print_mode: TextPrintMode, /// When true, and there's a [MdInlineItalic], it instrustructs the formatter to keep the fences keep_fences_in_italics: bool, + inside_list: bool, } impl FormatRule for FormatMdInlineItemList { type Context = MarkdownFormatContext; fn fmt(&self, node: &MdInlineItemList, f: &mut MarkdownFormatter) -> FormatResult<()> { - if self.print_mode.is_auto_link_like() { + if self.inside_list { + return self.fmt_inside_list(node, f); + } else if self.print_mode.is_auto_link_like() { return self.fmt_auto_link_like(node, f); } else if self.print_mode.is_normalize_words() { return self.fmt_normalize_words(node, f); - } else if self.print_mode.is_all() { + } else if self.print_mode.is_trim_all() { return self.fmt_trim_all(node, f); } else if self.print_mode.is_pristine() { return self.fmt_pristine(node, f); @@ -35,16 +38,20 @@ impl FormatRule for FormatMdInlineItemList { match item { AnyMdInline::MdTextual(text) => { if text.is_empty_and_not_newline()? && seen_new_line { - let entry = format_with(|f| { - write!( - f, - [text.format().with_options(FormatMdTextualOptions { - should_remove: true, - ..Default::default() - })] - ) - }); - joiner.entry(&entry); + if self.print_mode.is_keep_leading_spaces() { + joiner.entry(&text.format()); + } else { + let entry = format_with(|f| { + write!( + f, + [text.format().with_options(FormatMdTextualOptions { + should_remove: true, + ..Default::default() + })] + ) + }); + joiner.entry(&entry); + } } else if text.is_newline()? { let entry = format_with(|f| { write!( @@ -63,7 +70,9 @@ impl FormatRule for FormatMdInlineItemList { } else { joiner.entry(&text.format().with_options(FormatMdTextualOptions { should_remove: false, - print_mode: if self.print_mode.is_start() && index == 0 { + print_mode: if (self.print_mode.is_trim_start() && index == 0) + || (self.print_mode.is_keep_leading_spaces() && seen_new_line) + { self.print_mode } else { TextPrintMode::default() @@ -91,6 +100,10 @@ impl FormatRule for FormatMdInlineItemList { })); seen_new_line = false; } + AnyMdInline::MdIndentToken(indent) => { + joiner.entry(&indent.format()); + seen_new_line = false; + } _ => { joiner.entry(&item.format()); seen_new_line = false; @@ -103,6 +116,159 @@ impl FormatRule for FormatMdInlineItemList { } impl FormatMdInlineItemList { + /// Formats inline content inside a list item, normalizing the indentation + /// of continuation lines. + /// + /// In Markdown, each continuation line of a list item must be indented to + /// align with the first character of the item's content. For example, with + /// `1. item`, the content starts at column 3, so continuation lines need + /// exactly 3 spaces. The parser represents those required spaces as + /// `MdIndentToken` nodes in the CST (or, for blank-line-separated "loose" + /// paragraphs, as a preceding `MdContinuationIndent` block). Any extra + /// leading spaces beyond the required indent end up as `MdTextual " "` + /// tokens at the start of the paragraph's `MdInlineItemList`. + /// + /// This function normalizes those extras: + /// + /// - At the very start of the paragraph (loose, blank-line-separated + /// context), ALL leading `" "` textuals are removed — they carry no + /// meaning past the continuation indent. + /// - On a soft-wrap continuation within the same paragraph (after a `\n` + /// textual), a single excess space is stripped (typo) but two or more + /// are preserved (intentional alignment). + /// - Spaces following a hard-line-break (` \n`) are always preserved + /// because those reflect an explicit choice by the author. + fn fmt_inside_list( + &self, + node: &MdInlineItemList, + f: &mut MarkdownFormatter, + ) -> FormatResult<()> { + let items: Vec = node.iter().collect(); + let mut joiner = f.join(); + // True until we have emitted the first non-space content of the + // paragraph. While true, every leading `" "` textual is excess and is + // removed unconditionally. + let mut at_paragraph_start = true; + let mut seen_new_line = false; + let mut after_hard_line = false; + + for (i, item) in items.iter().enumerate() { + match item { + AnyMdInline::MdTextual(text) => { + if text.is_newline()? { + seen_new_line = true; + after_hard_line = false; + // Paragraph content has started — any spaces that come + // after this newline are soft-wrap, not paragraph-start. + at_paragraph_start = false; + joiner.entry(&format_with(|f| { + write!( + f, + [ + text.format().with_options(FormatMdTextualOptions { + should_remove: true, + ..Default::default() + }), + hard_line_break() + ] + ) + })); + } else if at_paragraph_start && text.value_token()?.text() == " " { + // Leading space at the start of a loose paragraph — + // strip unconditionally (these are pure excess past the + // continuation indent). + joiner.entry(&text.format().with_options(FormatMdTextualOptions { + should_remove: true, + ..Default::default() + })); + } else if seen_new_line && !after_hard_line && text.value_token()?.text() == " " + { + // Soft-wrap continuation excess. Single extra space is + // a typo (strip); two or more mean intentional + // alignment (keep). + let next_is_space = matches!( + items.get(i + 1), + Some(AnyMdInline::MdTextual(next)) + if next.value_token().is_ok_and(|t| t.text() == " ") + ); + if next_is_space { + let was_after_newline = seen_new_line; + seen_new_line = false; + after_hard_line = false; + joiner.entry(&text.format().with_options(FormatMdTextualOptions { + should_remove: false, + print_mode: if was_after_newline + && self.print_mode.is_keep_leading_spaces() + { + self.print_mode + } else { + TextPrintMode::default() + }, + })); + } else { + // Single excess space before content — remove it. + seen_new_line = false; + joiner.entry(&text.format().with_options(FormatMdTextualOptions { + should_remove: true, + ..Default::default() + })); + } + } else { + let was_after_newline = seen_new_line; + at_paragraph_start = false; + seen_new_line = false; + after_hard_line = false; + joiner.entry(&text.format().with_options(FormatMdTextualOptions { + should_remove: false, + print_mode: if was_after_newline + && self.print_mode.is_keep_leading_spaces() + { + self.print_mode + } else { + TextPrintMode::default() + }, + })); + } + } + AnyMdInline::MdHardLine(hard_line) => { + seen_new_line = true; + after_hard_line = true; + at_paragraph_start = false; + joiner.entry(&format_with(|f| { + write!( + f, + [hard_line + .format() + .with_options(FormatMdFormatHardLineOptions { + print_mode: self.print_mode, + })] + ) + })); + } + AnyMdInline::MdInlineItalic(italic) => { + seen_new_line = false; + after_hard_line = false; + at_paragraph_start = false; + joiner.entry(&italic.format().with_options(FormatMdInlineItalicOptions { + should_keep_fences: self.keep_fences_in_italics, + })); + } + AnyMdInline::MdIndentToken(indent) => { + // Continuation indent tokens are not content; keep seen_new_line + // so the next space-only token is still detected as excess. + joiner.entry(&indent.format()); + } + _ => { + seen_new_line = false; + at_paragraph_start = false; + joiner.entry(&item.format()); + } + } + } + + joiner.finish() + } + /// If the first and last [MdTextual] are `<` and `>` respectively, /// they are removed. Otherwise falls back to [TrimMode::All]. fn fmt_auto_link_like( @@ -262,7 +428,6 @@ impl FormatMdInlineItemList { fn fmt_clean(&self, node: &MdInlineItemList, f: &mut MarkdownFormatter) -> FormatResult<()> { let mut joiner = f.join(); let mut handled_first = false; - for item in node.iter() { match item { AnyMdInline::MdTextual(text) if !handled_first => { @@ -291,6 +456,10 @@ impl FormatMdInlineItemList { } } } + AnyMdInline::MdTextual(text) if text.is_newline().unwrap_or(false) => { + // After a newline, reset the skip counter for the next line. + joiner.entry(&text.format()); + } AnyMdInline::MdHardLine(hd) => { joiner.entry(&hd.format().with_options(FormatMdFormatHardLineOptions { print_mode: TextPrintMode::Pristine, @@ -338,11 +507,14 @@ impl FormatMdInlineItemList { } } +#[derive(Debug, Default)] pub(crate) struct FormatMdFormatInlineItemListOptions { /// When `true`, and there's a [MdInlineItalic], it instructions the formatter to keep the fences. /// When `false`, it lets the node figure it out. pub(crate) keep_fences_in_italics: bool, pub(crate) print_mode: TextPrintMode, + /// When `true`, excess leading spaces on continuation lines are removed. + pub(crate) inside_list: bool, } impl FormatRuleWithOptions for FormatMdInlineItemList { @@ -351,6 +523,7 @@ impl FormatRuleWithOptions for FormatMdInlineItemList { fn with_options(mut self, options: Self::Options) -> Self { self.print_mode = options.print_mode; self.keep_fences_in_italics = options.keep_fences_in_italics; + self.inside_list = options.inside_list; self } } diff --git a/crates/biome_markdown_formatter/src/shared.rs b/crates/biome_markdown_formatter/src/shared.rs index a08fba6a18bb..fb662e659ab5 100644 --- a/crates/biome_markdown_formatter/src/shared.rs +++ b/crates/biome_markdown_formatter/src/shared.rs @@ -41,17 +41,20 @@ pub(crate) enum TrimMode { /// This mode works similarly to [TrimMode::All], however, text that contains /// words and have more than trailing/leading spaces are normalized to one NormalizeWords, + /// After a newline, keep the whitespace-only tokens that represent + /// continuation-line indentation instead of removing them. + KeepLeadingSpaces, /// Don't trim anything #[default] None, } impl TextPrintMode { - pub(crate) const fn is_start(&self) -> bool { + pub(crate) const fn is_trim_start(&self) -> bool { matches!(self, Self::Trim(TrimMode::Start)) } - pub(crate) const fn is_all(&self) -> bool { + pub(crate) const fn is_trim_all(&self) -> bool { matches!(self, Self::Trim(TrimMode::All)) } @@ -63,6 +66,10 @@ impl TextPrintMode { matches!(self, Self::Trim(TrimMode::AutoLinkLike)) } + pub(crate) const fn is_keep_leading_spaces(&self) -> bool { + matches!(self, Self::Trim(TrimMode::KeepLeadingSpaces)) + } + pub(crate) const fn is_pristine(&self) -> bool { matches!(self, Self::Pristine) } @@ -78,4 +85,8 @@ impl TextPrintMode { pub(crate) const fn trim_all() -> Self { Self::Trim(TrimMode::All) } + + pub(crate) const fn trim_keep_leading_spaces() -> Self { + Self::Trim(TrimMode::KeepLeadingSpaces) + } } diff --git a/crates/biome_markdown_formatter/tests/quick_test.rs b/crates/biome_markdown_formatter/tests/quick_test.rs index 4aeb3001df2a..4b3d47341813 100644 --- a/crates/biome_markdown_formatter/tests/quick_test.rs +++ b/crates/biome_markdown_formatter/tests/quick_test.rs @@ -4,12 +4,17 @@ use biome_markdown_parser::parse_markdown; #[ignore] #[test] fn quick_test() { - let source = "~~~~ -aaa -~~~ -~~~~ + let source = r#"100. Triple-digit marker: required continuation indent is 5. + aligned at 5 spaces + one-extra-space continuation gets stripped + two-extra-space preserved -"; + loose paragraph aligned at 5 + + loose paragraph with single excess + + loose paragraph with multi excess +"#; let parse = parse_markdown(source); // Print CST @@ -24,12 +29,12 @@ aaa false, ); - // Print formatted output let formatted = result.unwrap(); + let first_ir = formatted.document(); let output = formatted.print().unwrap(); - let first_ir = format!("{}", formatted.into_document()); eprintln!("Formatted:\n{}", output.as_code()); + // Idempotency // Now re-parse the formatted output and show its CST let reparse = parse_markdown(output.as_code()); eprintln!("\n--- Re-parsed CST ---"); @@ -38,8 +43,15 @@ aaa let result2 = biome_formatter::format_node(&reparse.syntax(), MdFormatLanguage::new(options), false); let output2 = result2.unwrap(); - eprintln!("Re-formatted:\n{}", output2.print().unwrap().as_code()); - let second_ir = format!("{}", output2.into_document()); - - similar_asserts::assert_eq!(second_ir, first_ir, "left is the re-formatted"); + let second_ir = output2.document(); + similar_asserts::assert_eq!( + output2.print().unwrap().as_code(), + output.as_code(), + "left is the re-formatted" + ); + similar_asserts::assert_eq!( + second_ir.to_string(), + first_ir.to_string(), + "left is the re-formatted" + ); } diff --git a/crates/biome_markdown_formatter/tests/specs/markdown/list_continuation.md b/crates/biome_markdown_formatter/tests/specs/markdown/list_continuation.md new file mode 100644 index 000000000000..a17a991262dc --- /dev/null +++ b/crates/biome_markdown_formatter/tests/specs/markdown/list_continuation.md @@ -0,0 +1,54 @@ +1. Fork the repo and + FF + + FF + +2. Item two. + Soft-wrap continuation at the required indent stays put. + +3. Single extra space on a soft-wrap gets stripped. + aligned line + one-extra-space line + +4. Two or more extra spaces are preserved (intentional alignment). + deeply indented continuation + +- a + b + +- outer + - nested sub item + - another nested sub + +- item with hard line break + normal continuation after hard break + +1. loose list paragraph with single-space excess + + exactly one extra space before this paragraph + +1. loose list paragraph with multi-space excess + + lots of leading spaces here stays aligned + +10. Multi-digit marker: required continuation indent is 4. + aligned at 4 spaces + one-extra-space continuation gets stripped + two-extra-space preserved + + loose paragraph aligned at 4 + + loose paragraph with single excess + + loose paragraph with multi excess + +100. Triple-digit marker: required continuation indent is 5. + aligned at 5 spaces + one-extra-space continuation gets stripped + two-extra-space preserved + + loose paragraph aligned at 5 + + loose paragraph with single excess + + loose paragraph with multi excess diff --git a/crates/biome_markdown_formatter/tests/specs/markdown/list_continuation.md.snap b/crates/biome_markdown_formatter/tests/specs/markdown/list_continuation.md.snap new file mode 100644 index 000000000000..b4bc8d83cf6a --- /dev/null +++ b/crates/biome_markdown_formatter/tests/specs/markdown/list_continuation.md.snap @@ -0,0 +1,125 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: markdown/list_continuation.md +--- + +# Input + +```md +1. Fork the repo and + FF + + FF + +2. Item two. + Soft-wrap continuation at the required indent stays put. + +3. Single extra space on a soft-wrap gets stripped. + aligned line + one-extra-space line + +4. Two or more extra spaces are preserved (intentional alignment). + deeply indented continuation + +- a + b + +- outer + - nested sub item + - another nested sub + +- item with hard line break + normal continuation after hard break + +1. loose list paragraph with single-space excess + + exactly one extra space before this paragraph + +1. loose list paragraph with multi-space excess + + lots of leading spaces here stays aligned + +10. Multi-digit marker: required continuation indent is 4. + aligned at 4 spaces + one-extra-space continuation gets stripped + two-extra-space preserved + + loose paragraph aligned at 4 + + loose paragraph with single excess + + loose paragraph with multi excess + +100. Triple-digit marker: required continuation indent is 5. + aligned at 5 spaces + one-extra-space continuation gets stripped + two-extra-space preserved + + loose paragraph aligned at 5 + + loose paragraph with single excess + + loose paragraph with multi excess + +``` + + +# Formatted + +```md +1. Fork the repo and + FF + + FF + +2. Item two. + Soft-wrap continuation at the required indent stays put. + +3. Single extra space on a soft-wrap gets stripped. + aligned line + one-extra-space line + +4. Two or more extra spaces are preserved (intentional alignment). + deeply indented continuation + +- a + b + +- outer + - nested sub item + - another nested sub + +- item with hard line break + normal continuation after hard break + +1. loose list paragraph with single-space excess + + exactly one extra space before this paragraph + +1. loose list paragraph with multi-space excess + + lots of leading spaces here stays aligned + +10. Multi-digit marker: required continuation indent is 4. + aligned at 4 spaces + one-extra-space continuation gets stripped + two-extra-space preserved + + loose paragraph aligned at 4 + + loose paragraph with single excess + + loose paragraph with multi excess + +100. Triple-digit marker: required continuation indent is 5. + aligned at 5 spaces + one-extra-space continuation gets stripped + two-extra-space preserved + + loose paragraph aligned at 5 + + loose paragraph with single excess + + loose paragraph with multi excess + +``` diff --git a/crates/biome_markdown_formatter/tests/specs/markdown/ordered_lists.md b/crates/biome_markdown_formatter/tests/specs/markdown/ordered_lists.md new file mode 100644 index 000000000000..4bf4eb65e760 --- /dev/null +++ b/crates/biome_markdown_formatter/tests/specs/markdown/ordered_lists.md @@ -0,0 +1,30 @@ +1. Foo +2. Bar + + +1. Foo + Bar + +1. Foo + 1. Bar + 1. Lorem + Ipsum + + +1. Foo + With some + Space + +1. Mixed with sub-bullet + - bullet sub-item + - another sub-item + +1. First +2. Second +3. Third + +10. Ten +11. Eleven + +100. Hundred +101. Hundred-one diff --git a/crates/biome_markdown_formatter/tests/specs/markdown/ordered_lists.md.snap b/crates/biome_markdown_formatter/tests/specs/markdown/ordered_lists.md.snap new file mode 100644 index 000000000000..aaad9426e2b3 --- /dev/null +++ b/crates/biome_markdown_formatter/tests/specs/markdown/ordered_lists.md.snap @@ -0,0 +1,77 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: markdown/ordered_lists.md +--- + +# Input + +```md +1. Foo +2. Bar + + +1. Foo + Bar + +1. Foo + 1. Bar + 1. Lorem + Ipsum + + +1. Foo + With some + Space + +1. Mixed with sub-bullet + - bullet sub-item + - another sub-item + +1. First +2. Second +3. Third + +10. Ten +11. Eleven + +100. Hundred +101. Hundred-one + +``` + + +# Formatted + +```md +1. Foo +2. Bar + + +1. Foo + Bar + +1. Foo + 1. Bar + 1. Lorem + Ipsum + + +1. Foo + With some + Space + +1. Mixed with sub-bullet + - bullet sub-item + - another sub-item + +1. First +2. Second +3. Third + +10. Ten +11. Eleven + +100. Hundred +101. Hundred-one + +``` diff --git a/crates/biome_markdown_formatter/tests/specs/prettier/markdown/blockquote/code.md.snap b/crates/biome_markdown_formatter/tests/specs/prettier/markdown/blockquote/code.md.snap index 327536e6ac42..14f5e16eb4f7 100644 --- a/crates/biome_markdown_formatter/tests/specs/prettier/markdown/blockquote/code.md.snap +++ b/crates/biome_markdown_formatter/tests/specs/prettier/markdown/blockquote/code.md.snap @@ -35,13 +35,6 @@ info: markdown/blockquote/code.md > ```json > { > "extends": [ -@@ -12,4 +11,5 @@ - > "browser": true - > } - > } --> ``` -+> -+``` ``` # Output @@ -60,8 +53,7 @@ info: markdown/blockquote/code.md > "browser": true > } > } -> -``` +> ``` ``` # Lines exceeding max width of 80 characters diff --git a/crates/biome_markdown_formatter/tests/specs/prettier/markdown/blockquote/ignore-code.md.snap b/crates/biome_markdown_formatter/tests/specs/prettier/markdown/blockquote/ignore-code.md.snap index da74f9554e7c..97af06deb15a 100644 --- a/crates/biome_markdown_formatter/tests/specs/prettier/markdown/blockquote/ignore-code.md.snap +++ b/crates/biome_markdown_formatter/tests/specs/prettier/markdown/blockquote/ignore-code.md.snap @@ -67,42 +67,9 @@ info: markdown/blockquote/ignore-code.md ```diff --- Prettier +++ Biome -@@ -3,14 +3,16 @@ - > ```js - > ugly ( code ) ; - > ``` --> ```` -+> -+```` - - > ```md - > - > - This is a long long - > long long long long - > long long paragraph. --> ``` -+> -+``` - - > - test - > ```md -@@ -18,7 +20,8 @@ - > - This is a long long - > long long long long - > long long paragraph. --> ``` -+> -+ ``` - - ````md - > ```md -@@ -36,15 +39,19 @@ - > > long long long long - > > long long paragraph. +@@ -38,10 +38,12 @@ > > ``` --> ```` -+> -+```` + > ```` +> > @@ -113,11 +80,6 @@ info: markdown/blockquote/ignore-code.md > ```js > // prettier-ignore - > const x = 1, - > b = 2 --> ``` -+> -+``` ``` # Output @@ -128,16 +90,14 @@ info: markdown/blockquote/ignore-code.md > ```js > ugly ( code ) ; > ``` -> -```` +> ```` > ```md > > - This is a long long > long long long long > long long paragraph. -> -``` +> ``` > - test > ```md @@ -145,8 +105,7 @@ info: markdown/blockquote/ignore-code.md > - This is a long long > long long long long > long long paragraph. -> - ``` +> ``` ````md > ```md @@ -164,8 +123,7 @@ info: markdown/blockquote/ignore-code.md > > long long long long > > long long paragraph. > > ``` -> -```` +> ```` > > @@ -178,6 +136,5 @@ info: markdown/blockquote/ignore-code.md > // prettier-ignore > const x = 1, > b = 2 -> -``` +> ``` ``` diff --git a/crates/biome_markdown_formatter/tests/specs/prettier/markdown/break/simple.md.snap b/crates/biome_markdown_formatter/tests/specs/prettier/markdown/break/simple.md.snap deleted file mode 100644 index 89c3e2a4942b..000000000000 --- a/crates/biome_markdown_formatter/tests/specs/prettier/markdown/break/simple.md.snap +++ /dev/null @@ -1,45 +0,0 @@ ---- -source: crates/biome_formatter_test/src/snapshot_builder.rs -info: markdown/break/simple.md ---- - -# Input - -```md -123 -456 - -123\ -456 - -- 123 - 123 - -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Biome -@@ -5,4 +5,4 @@ - 456 - - - 123 -- 123 -+123 -``` - -# Output - -```md -123 -456 - -123\ -456 - -- 123 -123 -``` diff --git a/crates/biome_markdown_formatter/tests/specs/prettier/markdown/code/additional-space.md.snap b/crates/biome_markdown_formatter/tests/specs/prettier/markdown/code/additional-space.md.snap index 58811d29151b..908eeb34de12 100644 --- a/crates/biome_markdown_formatter/tests/specs/prettier/markdown/code/additional-space.md.snap +++ b/crates/biome_markdown_formatter/tests/specs/prettier/markdown/code/additional-space.md.snap @@ -59,16 +59,15 @@ info: markdown/code/additional-space.md - yarn install - ``` - -- To check your version of Yarn and ensure it's installed you can type: ++ ```sh ++ yarn install ++ ``` + To check your version of Yarn and ensure it's installed you can type: - - ```sh - yarn --version - ``` + ```sh -+ yarn install -+ ``` -+ To check your version of Yarn and ensure it's installed you can type: -+ ```sh + yarn --version + ``` ``` @@ -94,7 +93,7 @@ info: markdown/code/additional-space.md ```sh yarn install ``` - To check your version of Yarn and ensure it's installed you can type: + To check your version of Yarn and ensure it's installed you can type: ```sh yarn --version ``` diff --git a/crates/biome_markdown_formatter/tests/specs/prettier/markdown/footnoteDefinition/long.md.snap b/crates/biome_markdown_formatter/tests/specs/prettier/markdown/footnoteDefinition/long.md.snap index 57b69c149b54..45ee6dedee71 100644 --- a/crates/biome_markdown_formatter/tests/specs/prettier/markdown/footnoteDefinition/long.md.snap +++ b/crates/biome_markdown_formatter/tests/specs/prettier/markdown/footnoteDefinition/long.md.snap @@ -1,5 +1,6 @@ --- source: crates/biome_formatter_test/src/snapshot_builder.rs +assertion_line: 240 info: markdown/footnoteDefinition/long.md --- diff --git a/crates/biome_markdown_formatter/tests/specs/prettier/markdown/list/indent.md.snap b/crates/biome_markdown_formatter/tests/specs/prettier/markdown/list/indent.md.snap index c7500178c81d..3bddd7501ea1 100644 --- a/crates/biome_markdown_formatter/tests/specs/prettier/markdown/list/indent.md.snap +++ b/crates/biome_markdown_formatter/tests/specs/prettier/markdown/list/indent.md.snap @@ -100,73 +100,74 @@ info: markdown/list/indent.md ```diff --- Prettier +++ Biome -@@ -1,54 +1,63 @@ +@@ -1,73 +1,85 @@ -- [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a - - a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a +- [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a + + - a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a -+ -+ b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b -+ -+ - [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a -+ -+ b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b -- b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b -+ 1. a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a + b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b - - [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a -+ b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b ++ - [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a - b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b - 1. a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a -+ 1. [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a ++ b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b - b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b -+ b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b ++ 1. a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a - 1. [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a -+ 12345678) a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a ++ b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b - b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b - 12345678) a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a -+ b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b ++ 1. [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a - b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b - 12345678. [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a -+ 12345678. [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a ++ b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b ++ ++ 12345678) a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a ++ ++ b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b ++ ++ 12345678. [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a ++ ++ b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b - b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b -+ b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b ++ a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a - a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a -+ a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a ++1. [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a ++ ++ - a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a ++ ++ b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b ++ ++ - [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a - 1. [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a +-1. [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a - - a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a ++ b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b - b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b -+ - a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a ++ 1. a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a - - [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a -+ b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b ++ b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b - b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b - 1. a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a -+ - [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a ++ 1. [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a - b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b +- b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b ++ b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b - 1. [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a -+ 1. a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a -+ -+ b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b -+ -+ 1. [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a -+ -+ b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b -+ + 12345678) a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a - b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b @@ -175,7 +176,7 @@ info: markdown/list/indent.md - b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b - 12345678. [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a -+ 12345678. [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a ++ 12345678. [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a - b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b + b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b @@ -183,33 +184,39 @@ info: markdown/list/indent.md - a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a + a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a - 12345678) [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a +-12345678) [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a ++12345678) [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b + - a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b -@@ -56,6 +65,7 @@ - - [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a + +- - [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a ++ - [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b + 1. a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b -@@ -63,9 +73,11 @@ - 1. [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a + +- 1. [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a ++ 1. [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b + 12345678) a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b -+ - 12345678. [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a +- 12345678. [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a ++ 12345678. [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a ++ b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b + + a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a ``` # Output @@ -219,7 +226,7 @@ info: markdown/list/indent.md - a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a - b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b + b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b - [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a @@ -229,7 +236,7 @@ info: markdown/list/indent.md b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b - 1. [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a + 1. [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b @@ -237,27 +244,27 @@ info: markdown/list/indent.md b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b - 12345678. [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a + 12345678. [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a -1. [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a +1. [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a - a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a - b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b + b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b - - [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a + - [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a - b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b + b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b 1. a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a - b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b + b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b - 1. [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a + 1. [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b @@ -265,13 +272,13 @@ info: markdown/list/indent.md b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b - 12345678. [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a + 12345678. [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a -12345678) [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a +12345678) [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b @@ -279,7 +286,7 @@ info: markdown/list/indent.md b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b - - [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a + - [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b @@ -287,7 +294,7 @@ info: markdown/list/indent.md b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b - 1. [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a + 1. [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b @@ -295,7 +302,7 @@ info: markdown/list/indent.md b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b - 12345678. [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a + 12345678. [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b @@ -306,45 +313,45 @@ info: markdown/list/indent.md ``` 1: - [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a 3: - a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a - 5: b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b + 5: b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b 7: - [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a 9: b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b 11: 1. a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a 13: b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b - 15: 1. [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a + 15: 1. [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a 17: b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b 19: 12345678) a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a 21: b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b - 23: 12345678. [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a + 23: 12345678. [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a 25: b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b 27: a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a - 29: 1. [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a + 29: 1. [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a 31: - a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a - 33: b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b - 35: - [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a - 37: b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b + 33: b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b + 35: - [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a + 37: b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b 39: 1. a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a - 41: b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b - 43: 1. [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a + 41: b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b + 43: 1. [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a 45: b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b 47: 12345678) a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a 49: b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b - 51: 12345678. [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a + 51: 12345678. [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a 53: b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b 55: a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a - 57: 12345678) [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a + 57: 12345678) [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a 59: b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b 61: - a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a 63: b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b - 65: - [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a + 65: - [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a 67: b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b 69: 1. a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a 71: b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b - 73: 1. [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a + 73: 1. [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a 75: b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b 77: 12345678) a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a 79: b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b - 81: 12345678. [ ] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a + 81: 12345678. [] a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a 83: b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b b 85: a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a ``` diff --git a/crates/biome_markdown_formatter/tests/specs/prettier/markdown/list/issue-7846.md.snap b/crates/biome_markdown_formatter/tests/specs/prettier/markdown/list/issue-7846.md.snap index ff119de10239..d10f389b93d1 100644 --- a/crates/biome_markdown_formatter/tests/specs/prettier/markdown/list/issue-7846.md.snap +++ b/crates/biome_markdown_formatter/tests/specs/prettier/markdown/list/issue-7846.md.snap @@ -27,14 +27,11 @@ info: markdown/list/issue-7846.md +++ Biome @@ -1,11 +1,11 @@ - a a -- b b + b b - c c -- d d -- e e -+b b -+c c -+d d -+e e ++ c c + d d + e e 1. a a a - b b b @@ -51,10 +48,10 @@ info: markdown/list/issue-7846.md ```md - a a -b b -c c -d d -e e + b b + c c + d d + e e 1. a a a b b b diff --git a/crates/biome_markdown_formatter/tests/specs/prettier/markdown/list/nested-checkbox.md.snap b/crates/biome_markdown_formatter/tests/specs/prettier/markdown/list/nested-checkbox.md.snap index b156dc2e4921..ea2081b12ffd 100644 --- a/crates/biome_markdown_formatter/tests/specs/prettier/markdown/list/nested-checkbox.md.snap +++ b/crates/biome_markdown_formatter/tests/specs/prettier/markdown/list/nested-checkbox.md.snap @@ -30,18 +30,16 @@ info: markdown/list/nested-checkbox.md - parent list item parent list item parent list item parent list item parent list item parent list item - - child list item child list item child list item child list item child list item child list item -- paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph + - child list item child list item child list item child list item child list item child list item + -+ paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph + paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph - [x] parent task list item parent task list item parent task list item parent task list item - - [x] child task list item child task list item child task list item child task list item -- paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph + - [x] child task list item child task list item child task list item child task list item + -+ paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph + paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph ``` # Output @@ -51,21 +49,21 @@ info: markdown/list/nested-checkbox.md - child list item child list item child list item child list item child list item child list item - paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph + paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph - [x] parent task list item parent task list item parent task list item parent task list item - [x] child task list item child task list item child task list item child task list item - paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph + paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph ``` # Lines exceeding max width of 80 characters ``` 1: - parent list item parent list item parent list item parent list item parent list item parent list item 3: - child list item child list item child list item child list item child list item child list item - 5: paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph + 5: paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph 7: - [x] parent task list item parent task list item parent task list item parent task list item 9: - [x] child task list item child task list item child task list item child task list item - 11: paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph + 11: paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph ``` diff --git a/crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-219.md.snap b/crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-219.md.snap index 25af2e5ab518..43fdd5d6cdcc 100644 --- a/crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-219.md.snap +++ b/crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-219.md.snap @@ -20,17 +20,15 @@ info: markdown/spec/example-219.md +++ Biome @@ -1,3 +1,3 @@ -> > 1. one --> > --> > two + > > 1. one -+>> -+>> two + > > + > > two ``` # Output ```md > > 1. one ->> ->> two +> > +> > two ``` diff --git a/crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-269.md.snap b/crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-269.md.snap index 5ad17729624b..71ed06d56096 100644 --- a/crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-269.md.snap +++ b/crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-269.md.snap @@ -29,13 +29,14 @@ info: markdown/spec/example-269.md -- b -- c -- d -+ - b -+ - c -+ - d - - e +-- e -- f -- g -- h ++ - b ++ - c ++ - d ++ - e + - f + - g + - h @@ -49,7 +50,7 @@ info: markdown/spec/example-269.md - b - c - d -- e + - e - f - g - h diff --git a/xtask/codegen/markdown.ungram b/xtask/codegen/markdown.ungram index 65646de0c296..9f3704269410 100644 --- a/xtask/codegen/markdown.ungram +++ b/xtask/codegen/markdown.ungram @@ -214,8 +214,8 @@ MdBulletListItem = MdBulletList MdOrderedListItem = MdBulletList -AnyMdBulletListMember = MdBullet | MdNewline MdBulletList = AnyMdBulletListMember* +AnyMdBulletListMember = MdBullet | MdNewline // - Hey! // ^^^^^^ // 1. Hey!