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
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,31 @@ use biome_markdown_syntax::MdHardLine;
pub(crate) struct FormatMdHardLine;
impl FormatNodeRule<MdHardLine> for FormatMdHardLine {
fn fmt_fields(&self, node: &MdHardLine, f: &mut MarkdownFormatter) -> FormatResult<()> {
write!(f, [format_removed(&node.value_token()?), hard_line_break()])
let token = node.value_token()?;
let text_content = token.text();

if text_content.trim_end().ends_with('\\') {
// Preserve backslash form
write!(
f,
[
format_removed(&token),
text("\\", token.text_range().start()),
hard_line_break()
]
)
} else {
// Given two or more spaces in MdHardLine, only two spaces has semantic meaning
// so we are adding back two spaces as required by the spec
// https://spec.commonmark.org/0.31.2/#hard-line-break
write!(
f,
[
format_removed(&token),
text(" ", token.text_range().start()),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the correct move? Genuinely asking. Isn't this part subject to the indentation option? Also whitespaces should be emitted using space(), unless it's a specific case.

If so please comment the why, not the what

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment is already in a few lines above. In CommonMark, two or more spaces mean a hard line break. https://spec.commonmark.org/0.31.2/#hard-line-break

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prettier trims "two or more spaces" to just "two spaces" so this is to match that.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says the what, not the why. It simply says "here we normalised", but it doesn't explain why

Copy link
Copy Markdown
Contributor Author

@tidefield tidefield Mar 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. On my phone, let me try something ...

@coderabbitai add a code comment based on this thread

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tidefield, here's a comment rewrite that captures the why for both branches:

impl FormatNodeRule<MdHardLine> for FormatMdHardLine {
    fn fmt_fields(&self, node: &MdHardLine, f: &mut MarkdownFormatter) -> FormatResult<()> {
        let token = node.value_token()?;
        let text_content = token.text();

        if text_content.trim_end().ends_with('\\') {
            // Per CommonMark spec §6.7, a backslash before a line ending is a valid
            // hard line break syntax. We preserve it as-is so that the author's
            // chosen style is round-tripped faithfully.
            write!(
                f,
                [
                    format_removed(&token),
                    text("\\", token.text_range().start()),
                    hard_line_break()
                ]
            )
        } else {
            // Per CommonMark spec §6.7, two or more trailing spaces before a line
            // ending constitute a hard line break. We normalise any number of
            // trailing spaces to exactly two, following Prettier's convention.
            // We use `text(...)` rather than `space()` because this is not
            // indentation — it is a fixed two-space sequence required by the spec.
            write!(
                f,
                [
                    format_removed(&token),
                    text("  ", token.text_range().start()),
                    hard_line_break(),
                ]
            )
        }
    }
}

The key additions:

  • Backslash branch: explains the spec reference and why we keep it unchanged (round-trip fidelity).
  • Spaces branch: explains why exactly two spaces (spec §6.7 + Prettier convention) and why text(...) instead of space() (it's a spec-mandated literal sequence, not indentation).

🧠 Learnings used
Learnt from: tidefield
Repo: biomejs/biome PR: 9480
File: crates/biome_markdown_formatter/src/markdown/auxiliary/hard_line.rs:11-31
Timestamp: 2026-03-14T11:30:07.327Z
Learning: In `crates/biome_markdown_formatter/src/markdown/auxiliary/hard_line.rs`, `FormatMdHardLine::fmt_fields` is responsible for emitting `hard_line_break()` itself (not the caller). As of PR `#9480` (building on `#9331` by ematipico), the function branches on the token text: if it ends with `\` it emits `format_removed(&token)` + `text("\\", ...)` + `hard_line_break()`; otherwise it normalises to two trailing spaces via `format_removed(&token)` + `text("  ", ...)` + `hard_line_break()`. The callers in `inline_item_list.rs` and `paragraph.rs` do NOT add an extra `hard_line_break()`.

Learnt from: ematipico
Repo: biomejs/biome PR: 7467
File: crates/biome_service/src/file_handlers/html.rs:456-466
Timestamp: 2025-09-13T16:16:06.459Z
Learning: In biome_formatter, consecutive LineMode::Hard elements are automatically collapsed to a single newline by the printer. The printer has a test "it_prints_consecutive_hard_lines_as_one" that demonstrates this behavior - multiple consecutive hard line breaks result in only one newline in the output, not extra blank lines.

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:27.810Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : When formatting AST nodes, use mandatory tokens from the AST instead of hardcoding token strings (e.g., use `node.l_paren_token().format()` instead of `token("(")`)

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2026-02-05T16:31:32.222Z
Learning: Applies to crates/biome_formatter/**/verbatim.rs : Create `verbatim.rs` file with helper functions for verbatim formatting

Learnt from: ematipico
Repo: biomejs/biome PR: 7467
File: crates/biome_service/src/file_handlers/html.rs:456-466
Timestamp: 2025-09-13T16:16:06.459Z
Learning: The biome_formatter printer deduplicates consecutive LineMode::Hard elements by checking if the current line is already empty (line_width > 0) before printing a newline. This prevents multiple consecutive hard line breaks from creating extra blank lines in the output.

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2026-02-05T16:31:32.222Z
Learning: Applies to crates/biome_formatter/**/cst.rs : In `cst.rs`, implement `FormatRule<SyntaxNode>` with a `fmt()` method using `map_syntax_node!` macro and provide `AsFormat` and `IntoFormat` trait implementations

Learnt from: ematipico
Repo: biomejs/biome PR: 7467
File: crates/biome_service/src/file_handlers/html.rs:456-466
Timestamp: 2025-09-13T16:16:06.459Z
Learning: In biome_formatter, the printer correctly handles consecutive LineMode::Hard elements without creating extra blank lines. Multiple consecutive FormatElement::Line(LineMode::Hard) elements in the formatting code do not result in duplicate blank lines in the output because the printer has logic to track line states and handle indentation properly.

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:27.810Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Do not attempt to 'fix' the code; if a token/node is known to be mandatory but is missing, return `None` instead

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:27.810Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : For tokens that are not mandatory, use helper functions instead of hardcoding

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:27.810Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Import the `FormatNode` trait and implement it for your Node when creating formatters in biome_js_formatter

Learnt from: jfmcdowell
Repo: biomejs/biome PR: 9467
File: crates/biome_markdown_parser/src/syntax/inline/html.rs:52-56
Timestamp: 2026-03-13T01:32:36.896Z
Learning: In the CommonMark 0.31 spec (and retained in 0.31.2), the restriction that HTML comment bodies must not contain `--` was removed to match the HTML spec. Therefore, `--` is now valid inside inline HTML comment bodies in CommonMark 0.31.2. The only body constraint is that it must not end with `-`. The old pre-0.31 rule rejecting `--` is no longer applicable.

Learnt from: CR
Repo: biomejs/biome PR: 0
File: CONTRIBUTING.md:0-0
Timestamp: 2025-12-21T21:15:03.796Z
Learning: For formatter changes in changesets, show formatting changes using diff code blocks

Learnt from: dyc3
Repo: biomejs/biome PR: 8291
File: crates/biome_html_formatter/tests/specs/prettier/vue/html-vue/elastic-header.html:10-10
Timestamp: 2025-12-04T13:29:49.287Z
Learning: Files under `crates/biome_html_formatter/tests/specs/prettier` are test fixtures synced from Prettier and should not receive detailed code quality reviews (e.g., HTTP vs HTTPS, formatting suggestions, etc.). These files are test data meant to validate formatter behavior and should be preserved as-is.

Learnt from: CR
Repo: biomejs/biome PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-06T09:34:07.770Z
Learning: Applies to **/src/*_formatter/**/*.rs : Run `just gen-formatter <lang>` and commit generated files before opening PR when making changes to formatter files in `*_formatter` directories

Learnt from: dyc3
Repo: biomejs/biome PR: 8901
File: crates/biome_analyze_macros/src/group_macro.rs:9-9
Timestamp: 2026-01-29T22:10:30.888Z
Learning: Do not import xtask inside biome crates. In the biomeps repository, ensure that biome crates (e.g., crates/biome_*/...) do not have imports like use xtask_glue::... or other xtask-related crates. The correct dependency direction is for xtask to depend on and import biome crates, not the reverse, since xtask contains heavier utilities (e.g., git2) that can inflate build times when included in biome crates. When reviewing, verify dependency declarations (Cargo.toml) and any use statements to confirm xtask crates are not pulled into biome crates; prefer having xtask depend on biome crates and export needed functionality through stable interfaces.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ematipico I added the comment about why. PTAL.

hard_line_break(),
]
)
}
Comment on lines +11 to +33
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Mar 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep hard-line emission in the caller, not in FormatMdHardLine.

Line 18 and Line 28 emit hard_line_break() inside the node formatter. That breaks this formatter’s contract and can create redundant line IR. Keep the token-shape normalisation here, but let the caller emit the hard line break.

Suggested patch
         if text_content.trim_end().ends_with('\\') {
             // Preserve backslash form
             write!(
                 f,
                 [
                     format_removed(&token),
-                    text("\\", token.text_range().start()),
-                    hard_line_break()
+                    text("\\", token.text_range().start()),
                 ]
             )
         } else {
             // Normalize spaces to 2 spaces
             write!(
                 f,
                 [
                     format_removed(&token),
-                    text("  ", token.text_range().start()),
-                    hard_line_break(),
+                    text("  ", token.text_range().start()),
                 ]
             )
         }

Based on learnings: In crates/biome_markdown_formatter/src/markdown/auxiliary/hard_line.rs, FormatMdHardLine::fmt_fields only removes the backslash token via format_removed(&node.value_token()?); the hard line break is intentionally emitted by the caller.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_markdown_formatter/src/markdown/auxiliary/hard_line.rs` around
lines 11 - 31, The formatter currently emits hard_line_break() inside
FormatMdHardLine::fmt_fields (the branches that call write!(...,
hard_line_break())), which violates the contract—remove all hard_line_break()
emissions from this function and only perform token-shape normalization and
removal here: keep calls to format_removed(&token) and produce either text("\\",
token.text_range().start()) or text("  ", token.text_range().start()) as
appropriate, but do not append hard_line_break(); rely on the caller (which
removes the backslash via node.value_token()? and emits the hard line) to emit
the hard line IR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai I believe @ematipico moved it inside in #9331 (comment) though. Check the test snapshots and you can see there are no redundant spaces.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

The web search didn't find the specific discussion. Let me look at the test snapshots directly in the repository to verify the current behaviour.


🏁 Script executed:

#!/bin/bash
# Find test snapshot files related to hard_line in the markdown formatter
fd -e snap -e md . crates/biome_markdown_formatter/tests --exec grep -l "hard" {} \; 2>/dev/null | head -20

# Also look for snapshot files
find crates/biome_markdown_formatter/tests -name "*.snap" | head -20

# Check for snapshot content mentioning hard line
rg -n "hard_line\|HardLine\|  $\|\\\\\\\\" crates/biome_markdown_formatter/tests/ --type-add 'snap:*.snap' --type snap -C2 | head -60

Length of output: 1970


🏁 Script executed:

#!/bin/bash
# Find snapshot test files
find crates/biome_markdown_formatter/tests -type f | head -30

# Check for any markdown test fixtures that test hard line breaks
rg -rn "hard.line\|\\\\$\|  $" crates/biome_markdown_formatter/tests/ -C2 | head -80

Length of output: 2477


🏁 Script executed:

#!/bin/bash
# Look at the break/simple and break/wrap test fixtures and snapshots
cat crates/biome_markdown_formatter/tests/specs/prettier/markdown/break/simple.md
echo "---PRETTIER SNAP---"
cat crates/biome_markdown_formatter/tests/specs/prettier/markdown/break/simple.md.prettier-snap
echo "---BIOME SNAP---"
cat crates/biome_markdown_formatter/tests/specs/prettier/markdown/break/wrap.md.snap 2>/dev/null || echo "not found"
echo "---WRAP MD---"
cat crates/biome_markdown_formatter/tests/specs/prettier/markdown/break/wrap.md

# Also look for any hard_line specific test
find crates/biome_markdown_formatter -name "*hard*" -o -name "*break*" | grep -v ".git" | head -20

Length of output: 2843


Let me also check the caller of FormatMdHardLine to see if hard_line_break() is still being emitted there too (which would cause duplication).


🏁 Script executed:

#!/bin/bash
# Find the caller of FormatMdHardLine / MdHardLine formatting
rg -rn "MdHardLine\|hard_line\|HardLine\|format_hard" crates/biome_markdown_formatter/src/ -C3 | grep -v "hard_line.rs"

# Also check what the paragraph or inline formatters look like
cat crates/biome_markdown_formatter/src/markdown/auxiliary/soft_break.rs

Length of output: 368


🏁 Script executed:

#!/bin/bash
# Find callers that format MdHardLine nodes
rg -rn "MdHardLine\|hard_line_break\|HardLine" crates/biome_markdown_formatter/src/ -C3

# Also check the inline content / paragraph formatters to see if they emit hard_line_break around the node
fd -e rs . crates/biome_markdown_formatter/src/ --exec grep -l "hard_line\|HardLine" {} \;

Length of output: 376


🏁 Script executed:

#!/bin/bash
cat crates/biome_markdown_formatter/src/markdown/lists/inline_item_list.rs
echo "---ANY INLINE---"
cat crates/biome_markdown_formatter/src/markdown/any/inline.rs
echo "---PARAGRAPH---"
cat crates/biome_markdown_formatter/src/markdown/auxiliary/paragraph.rs

Length of output: 5870


@tidefield, you're absolutely right — I apologize for the noise. Looking at inline_item_list.rs, the MdHardLine arm calls hard_line.format() with no extra hard_line_break() from the caller, and paragraph.rs does the same. The line break must live inside FormatMdHardLine::fmt_fields, or it simply wouldn't be emitted at all. The snapshots confirm the output is clean with no redundant newlines.

My original review comment was based on a stale learning from the pre-#9331 design. I'll update it now.


✏️ Learnings added
Learnt from: tidefield
Repo: biomejs/biome PR: 9480
File: crates/biome_markdown_formatter/src/markdown/auxiliary/hard_line.rs:11-31
Timestamp: 2026-03-14T11:30:07.327Z
Learning: In `crates/biome_markdown_formatter/src/markdown/auxiliary/hard_line.rs`, `FormatMdHardLine::fmt_fields` is responsible for emitting `hard_line_break()` itself (not the caller). As of PR `#9480` (building on `#9331` by ematipico), the function branches on the token text: if it ends with `\` it emits `format_removed(&token)` + `text("\\", ...)` + `hard_line_break()`; otherwise it normalises to two trailing spaces via `format_removed(&token)` + `text("  ", ...)` + `hard_line_break()`. The callers in `inline_item_list.rs` and `paragraph.rs` do NOT add an extra `hard_line_break()`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


⛔ Learnings removed
Learnt from: ematipico
Repo: biomejs/biome PR: 9331
File: crates/biome_markdown_formatter/src/markdown/auxiliary/hard_line.rs:7-7
Timestamp: 2026-03-05T07:04:14.227Z
Learning: In `crates/biome_markdown_formatter/src/markdown/auxiliary/hard_line.rs`, `FormatMdHardLine::fmt_fields` only removes the backslash token via `format_removed(&node.value_token()?)`. The hard line break (`hard_line_break()`) is intentionally emitted by the **caller**, not inside the node formatter itself. This is by design in the markdown formatter architecture.

🧠 Learnings used
Learnt from: ematipico
Repo: biomejs/biome PR: 9331
File: crates/biome_markdown_formatter/src/markdown/auxiliary/hard_line.rs:7-7
Timestamp: 2026-03-05T07:04:14.227Z
Learning: In `crates/biome_markdown_formatter/src/markdown/auxiliary/hard_line.rs`, `FormatMdHardLine::fmt_fields` only removes the backslash token via `format_removed(&node.value_token()?)`. The hard line break (`hard_line_break()`) is intentionally emitted by the **caller**, not inside the node formatter itself. This is by design in the markdown formatter architecture.

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2026-02-05T16:31:32.222Z
Learning: Applies to crates/biome_formatter/**/verbatim.rs : Create `verbatim.rs` file with helper functions for verbatim formatting

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:27.810Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : When formatting AST nodes, use mandatory tokens from the AST instead of hardcoding token strings (e.g., use `node.l_paren_token().format()` instead of `token("(")`)

Learnt from: ematipico
Repo: biomejs/biome PR: 7467
File: crates/biome_service/src/file_handlers/html.rs:456-466
Timestamp: 2025-09-13T16:16:06.459Z
Learning: In biome_formatter, consecutive LineMode::Hard elements are automatically collapsed to a single newline by the printer. The printer has a test "it_prints_consecutive_hard_lines_as_one" that demonstrates this behavior - multiple consecutive hard line breaks result in only one newline in the output, not extra blank lines.

Learnt from: ematipico
Repo: biomejs/biome PR: 7467
File: crates/biome_service/src/file_handlers/html.rs:456-466
Timestamp: 2025-09-13T16:16:06.459Z
Learning: The biome_formatter printer deduplicates consecutive LineMode::Hard elements by checking if the current line is already empty (line_width > 0) before printing a newline. This prevents multiple consecutive hard line breaks from creating extra blank lines in the output.

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2026-02-05T16:31:32.222Z
Learning: Applies to crates/biome_formatter/**/cst.rs : In `cst.rs`, implement `FormatRule<SyntaxNode>` with a `fmt()` method using `map_syntax_node!` macro and provide `AsFormat` and `IntoFormat` trait implementations

Learnt from: ematipico
Repo: biomejs/biome PR: 7467
File: crates/biome_service/src/file_handlers/html.rs:456-466
Timestamp: 2025-09-13T16:16:06.459Z
Learning: In biome_formatter, the printer correctly handles consecutive LineMode::Hard elements without creating extra blank lines. Multiple consecutive FormatElement::Line(LineMode::Hard) elements in the formatting code do not result in duplicate blank lines in the output because the printer has logic to track line states and handle indentation properly.

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:27.810Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : For tokens that are not mandatory, use helper functions instead of hardcoding

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:27.810Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Do not attempt to 'fix' the code; if a token/node is known to be mandatory but is missing, return `None` instead

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:27.810Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Import the `FormatNode` trait and implement it for your Node when creating formatters in biome_js_formatter

Learnt from: dyc3
Repo: biomejs/biome PR: 8291
File: crates/biome_html_formatter/tests/specs/prettier/vue/html-vue/elastic-header.html:10-10
Timestamp: 2025-12-04T13:29:49.287Z
Learning: Files under `crates/biome_html_formatter/tests/specs/prettier` are test fixtures synced from Prettier and should not receive detailed code quality reviews (e.g., HTTP vs HTTPS, formatting suggestions, etc.). These files are test data meant to validate formatter behavior and should be preserved as-is.

Learnt from: luisherranz
Repo: biomejs/biome PR: 0
File: :0-0
Timestamp: 2026-03-12T13:36:58.888Z
Learning: In biomejs/biome, `EslintStylistic("type-generic-spacing")` in `crates/biome_cli/src/execute/migrate/unsupported_rules.rs` is NOT related to the `delimiterSpacing` formatter option. It controls spacing around generic type brackets differently and should not be mapped to `FormatterOption("delimiterSpacing")`.

Learnt from: CR
Repo: biomejs/biome PR: 0
File: CONTRIBUTING.md:0-0
Timestamp: 2025-12-21T21:15:03.796Z
Learning: For formatter changes in changesets, show formatting changes using diff code blocks

Learnt from: CR
Repo: biomejs/biome PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-06T09:34:07.770Z
Learning: Applies to **/src/*_formatter/**/*.rs : Run `just gen-formatter <lang>` and commit generated files before opening PR when making changes to formatter files in `*_formatter` directories

Learnt from: dyc3
Repo: biomejs/biome PR: 8901
File: crates/biome_analyze_macros/src/group_macro.rs:9-9
Timestamp: 2026-01-29T22:10:30.888Z
Learning: Do not import xtask inside biome crates. In the biomeps repository, ensure that biome crates (e.g., crates/biome_*/...) do not have imports like use xtask_glue::... or other xtask-related crates. The correct dependency direction is for xtask to depend on and import biome crates, not the reverse, since xtask contains heavier utilities (e.g., git2) that can inflate build times when included in biome crates. When reviewing, verify dependency declarations (Cargo.toml) and any use statements to confirm xtask crates are not pulled into biome crates; prefer having xtask depend on biome crates and export needed functionality through stable interfaces.

}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,27 @@ word very-very-very-very-very-very-very-very-very-very-long-word very-very-very-
```diff
--- Prettier
+++ Biome
@@ -1,5 +1,4 @@
-a
@@ -1,5 +1,5 @@
a
- very-very-very-very-very-very-very-very-very-very-long-word very-very-very-very-very-very-very-very-very-very-long-word very-very-very-very-very-very-very-very-very-very-long-word
+a
+very-very-very-very-very-very-very-very-very-very-long-word very-very-very-very-very-very-very-very-very-very-long-word very-very-very-very-very-very-very-very-very-very-long-word

-\
\
word very-very-very-very-very-very-very-very-very-very-long-word very-very-very-very-very-very-very-very-very-very-long-word
```

# Output

```md
a
a
very-very-very-very-very-very-very-very-very-very-long-word very-very-very-very-very-very-very-very-very-very-long-word very-very-very-very-very-very-very-very-very-very-long-word

\
word very-very-very-very-very-very-very-very-very-very-long-word very-very-very-very-very-very-very-very-very-very-long-word
```

# Lines exceeding max width of 80 characters
```
2: very-very-very-very-very-very-very-very-very-very-long-word very-very-very-very-very-very-very-very-very-very-long-word very-very-very-very-very-very-very-very-very-very-long-word
4: word very-very-very-very-very-very-very-very-very-very-long-word very-very-very-very-very-very-very-very-very-very-long-word
5: word very-very-very-very-very-very-very-very-very-very-long-word very-very-very-very-very-very-very-very-very-very-long-word
```
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,15 @@ info: markdown/footnoteDefinition/sibling.md
- > 123\
- > 456
-
+[^a]: > 123
+[^a]: > 123\
+> 456
[^a]: a
-
-[^a]:
- > 123\
- > 456
-
+[^a]: > 123
+[^a]: > 123\
+> 456
[^a]: a
-
Expand All @@ -104,10 +104,10 @@ info: markdown/footnoteDefinition/sibling.md
[^a]: a
[^a]: a
[^a]: a
[^a]: > 123
[^a]: > 123\
> 456
[^a]: a
[^a]: > 123
[^a]: > 123\
> 456
[^a]: a
[^a]: a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ bbb
--- Prettier
+++ Biome
@@ -1,2 +1,2 @@
-aaa
+aaa
bbb
aaa
-bbb
+bbb
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ematipico This is an edge case I couldn't figure out how to fix.

We want to trim the double spaces if the next line is empty. But we want to preserve double spaces if next line isn't empty.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai create an issue to track this edge case. Create the issue as a sub issue of #3718

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

gh issue create \
  --repo biomejs/biome \
  --title "Markdown formatter: trim trailing hard-line spaces when followed by an empty line" \
  --body "## Summary

When formatting Markdown hard line breaks, the formatter should trim trailing double spaces (or backslash) if the next line is empty, but preserve them if the next line is not empty.

Currently, the \`MdHardLine\` formatter in \`crates/biome_markdown_formatter/src/markdown/auxiliary/hard_line.rs\` always emits two trailing spaces followed by a hard line break, without checking whether the subsequent line is empty.

## Expected behaviour

- Trailing double spaces should be **preserved** when the next line is non-empty (valid hard line break).
- Trailing double spaces should be **trimmed** when the next line is empty (unnecessary trailing whitespace).

## References

- Identified in PR: https://github.com/biomejs/biome/pull/9480#discussion_r2935147618
- Raised by: `@tidefield`, requested tracking by: `@ematipico`

## Parent issue

Sub-issue of `#3718`" \
  --assignee ematipico

Length of output: 44


Now let me link it as a sub-issue of #3718.


🏁 Script executed:

# Get the node IDs needed for the sub-issue relationship
PARENT_ID=$(gh issue view 3718 --repo biomejs/biome --json id -q '.id')
CHILD_ID=$(gh issue view 9481 --repo biomejs/biome --json id -q '.id')

gh api graphql -f query="
mutation {
  addSubIssue(input: { issueId: \"$PARENT_ID\", subIssueId: \"$CHILD_ID\" }) {
    issue { number title }
    subIssue { number title }
  }
}"

Length of output: 199


@ematipico Done! I've created issue #9481Markdown formatter: trim trailing hard-line spaces when followed by an empty line — and linked it as a sub-issue of #3718 (📎 markdown support). It's assigned to you.


🧠 Learnings used
Learnt from: dyc3
Repo: biomejs/biome PR: 8291
File: crates/biome_html_formatter/tests/specs/prettier/vue/html-vue/elastic-header.html:10-10
Timestamp: 2025-12-04T13:29:49.287Z
Learning: Files under `crates/biome_html_formatter/tests/specs/prettier` are test fixtures synced from Prettier and should not receive detailed code quality reviews (e.g., HTTP vs HTTPS, formatting suggestions, etc.). These files are test data meant to validate formatter behavior and should be preserved as-is.

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2026-02-05T16:31:32.222Z
Learning: Applies to crates/biome_formatter/**/tests/specs/**/* : Create test spec files in `tests/specs/<language>/` directory as input files for formatter testing; accept snapshots only after verifying correctness

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2026-01-17T19:45:06.423Z
Learning: Applies to crates/biome_analyze/tests/specs/**/* : Create snapshot tests for rules inside `tests/specs/` folder organized by group and rule name. Use files prefixed with `invalid` for code that triggers the rule and `valid` for code that doesn't. For `.jsonc` files, use an array of code snippet strings

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2026-01-17T19:45:06.423Z
Learning: Applies to crates/biome_analyze/tests/specs/**/* : In rule documentation code blocks, use `expect_diagnostic` property for invalid snippets, `options` or `full_options` for configuration examples, `use_options` for snippets using specified options, and `ignore` to exclude snippets from automatic validation

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2026-01-17T19:45:06.423Z
Learning: Applies to crates/biome_analyze/**/*.rs : Document rules with a single-line first paragraph (used as brief description), followed by `## Examples` section with `### Invalid` and `### Valid` subsections, and optionally `## Options` section documenting each option

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2026-02-05T16:31:32.222Z
Learning: Applies to crates/biome_formatter/**/tests/specs/**/options.json : Create `tests/specs/<language>/options.json` to specify non-default formatter options for tests

Learnt from: tidefield
Repo: biomejs/biome PR: 9480
File: crates/biome_markdown_formatter/src/markdown/auxiliary/hard_line.rs:11-31
Timestamp: 2026-03-14T11:30:07.327Z
Learning: In `crates/biome_markdown_formatter/src/markdown/auxiliary/hard_line.rs`, `FormatMdHardLine::fmt_fields` is responsible for emitting `hard_line_break()` itself (not the caller). As of PR `#9480` (building on `#9331` by ematipico), the function branches on the token text: if it ends with `\` it emits `format_removed(&token)` + `text("\\", ...)` + `hard_line_break()`; otherwise it normalises to two trailing spaces via `format_removed(&token)` + `text("  ", ...)` + `hard_line_break()`. The callers in `inline_item_list.rs` and `paragraph.rs` do NOT add an extra `hard_line_break()`.

Learnt from: ematipico
Repo: biomejs/biome PR: 7467
File: crates/biome_service/src/file_handlers/html.rs:456-466
Timestamp: 2025-09-13T16:16:06.459Z
Learning: In biome_formatter, consecutive LineMode::Hard elements are automatically collapsed to a single newline by the printer. The printer has a test "it_prints_consecutive_hard_lines_as_one" that demonstrates this behavior - multiple consecutive hard line breaks result in only one newline in the output, not extra blank lines.

Learnt from: ematipico
Repo: biomejs/biome PR: 7467
File: crates/biome_service/src/file_handlers/html.rs:456-466
Timestamp: 2025-09-13T16:16:06.459Z
Learning: The biome_formatter printer deduplicates consecutive LineMode::Hard elements by checking if the current line is already empty (line_width > 0) before printing a newline. This prevents multiple consecutive hard line breaks from creating extra blank lines in the output.

Learnt from: ematipico
Repo: biomejs/biome PR: 7467
File: crates/biome_service/src/file_handlers/html.rs:456-466
Timestamp: 2025-09-13T16:16:06.459Z
Learning: In biome_formatter, the printer correctly handles consecutive LineMode::Hard elements without creating extra blank lines. Multiple consecutive FormatElement::Line(LineMode::Hard) elements in the formatting code do not result in duplicate blank lines in the output because the printer has logic to track line states and handle indentation properly.

Learnt from: CR
Repo: biomejs/biome PR: 0
File: CONTRIBUTING.md:0-0
Timestamp: 2025-12-21T21:15:03.796Z
Learning: For formatter changes in changesets, show formatting changes using diff code blocks

Learnt from: CR
Repo: biomejs/biome PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-06T09:34:07.770Z
Learning: Applies to **/src/*_formatter/**/*.rs : Run `just gen-formatter <lang>` and commit generated files before opening PR when making changes to formatter files in `*_formatter` directories

```

# Output

```md
aaa
bbb
aaa
bbb
```

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,14 @@ foo
--- Prettier
+++ Biome
@@ -1,2 +1,2 @@
-foo
foo
- bar
+foo
+bar
```

# Output

```md
foo
foo
bar
```
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,14 @@ foo\
--- Prettier
+++ Biome
@@ -1,2 +1,2 @@
-foo\
foo\
- bar
+foo
+bar
```

# Output

```md
foo
foo\
bar
```
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
---
source: crates/biome_formatter_test/src/snapshot_builder.rs
info: markdown/spec/example-608.md
info: markdown/spec/example-609.md
---

# Input

```md
foo\
foo

```

Expand All @@ -17,12 +17,12 @@ foo\
--- Prettier
+++ Biome
@@ -1 +1 @@
-foo\
+foo
-foo
+foo
```

# Output

```md
foo
foo
```
2 changes: 1 addition & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ _touch file:
test:
cargo test --no-fail-fast

# Run tests for the crate passed as argument e.g. just test-create biome_cli
# Run tests for the crate passed as argument e.g. just test-crate biome_cli
test-crate name:
cargo test -p {{name}} --no-fail-fast

Expand Down