Skip to content

fix(formatter/md): fix formatting for MdHardLine#9480

Merged
ematipico merged 2 commits intobiomejs:mainfrom
tidefield:md-formatter
Mar 14, 2026
Merged

fix(formatter/md): fix formatting for MdHardLine#9480
ematipico merged 2 commits intobiomejs:mainfrom
tidefield:md-formatter

Conversation

@tidefield
Copy link
Copy Markdown
Contributor

@tidefield tidefield commented Mar 14, 2026

Summary

This PR enhances the formatting for MdHardLine to fix the gap discussed in this thread. We used to remove \ but semantically we should preserve it.

Test Plan

$ cargo test -p biome_markdown_formatter

Review the changes and removals of snapshot tests.

Docs

Issue #3718

cc @ematipico

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 14, 2026

⚠️ No Changeset found

Latest commit: cfa2986

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@tidefield tidefield marked this pull request as ready for review March 14, 2026 11:16
@github-actions github-actions bot added A-Formatter Area: formatter L-Markdown Language: Markdown labels Mar 14, 2026
@tidefield tidefield changed the title For formatting for MdHardLine Fix formatting for MdHardLine Mar 14, 2026
@tidefield tidefield changed the title Fix formatting for MdHardLine fix(formatter/md): Fix formatting for MdHardLine Mar 14, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 14, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3204b254-5766-4f0d-9ac6-524abc3d7572

📥 Commits

Reviewing files that changed from the base of the PR and between 281f090 and cfa2986.

📒 Files selected for processing (1)
  • crates/biome_markdown_formatter/src/markdown/auxiliary/hard_line.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/biome_markdown_formatter/src/markdown/auxiliary/hard_line.rs

Walkthrough

The MdHardLine formatter now inspects the removed token's text and branches: if the trimmed text ends with a backslash it emits the token, a literal backslash at the token start, then a hard line break; otherwise it emits the token, two spaces at the token start, then a hard line break. Error propagation behaviour is unchanged. Separately, a justfile comment was corrected from "just test-create" to "just test-crate".

Suggested reviewers

  • ematipico
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: fixing MdHardLine formatting in the Markdown formatter.
Description check ✅ Passed The description is directly related to the changeset, explaining the motivation for preserving backslashes in MdHardLine and providing testing instructions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/biome_markdown_formatter/src/markdown/auxiliary/hard_line.rs`:
- Around line 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.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6f4857ae-a401-415f-984e-ecd385e1d0b6

📥 Commits

Reviewing files that changed from the base of the PR and between 1f30838 and 6df8189.

⛔ Files ignored due to path filters (11)
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/break/simple.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/break/wrap.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/footnoteDefinition/sibling.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-186.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-289.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-597.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-598.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-599.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-600.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-601.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-609.md.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (2)
  • crates/biome_markdown_formatter/src/markdown/auxiliary/hard_line.rs
  • justfile

Comment on lines +11 to +31
if text_content.trim_end().ends_with('\\') {
// Preserve backslash form
write!(
f,
[
format_removed(&token),
text("\\", token.text_range().start()),
hard_line_break()
]
)
} else {
// Normalize spaces to 2 spaces
write!(
f,
[
format_removed(&token),
text(" ", token.text_range().start()),
hard_line_break(),
]
)
}
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.

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

@tidefield tidefield changed the title fix(formatter/md): Fix formatting for MdHardLine fix(formatter/md): fix formatting for MdHardLine Mar 14, 2026
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.

bbb
aaa
-bbb
+bbb
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

@ematipico ematipico merged commit fe9ff6b into biomejs:main Mar 14, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Formatter Area: formatter L-Markdown Language: Markdown

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants