fix(formatter/md): fix formatting for MdHardLine#9480
fix(formatter/md): fix formatting for MdHardLine#9480ematipico merged 2 commits intobiomejs:mainfrom
Conversation
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
WalkthroughThe 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
🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
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. Comment |
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (11)
crates/biome_markdown_formatter/tests/specs/prettier/markdown/break/simple.md.snapis excluded by!**/*.snapand included by**crates/biome_markdown_formatter/tests/specs/prettier/markdown/break/wrap.md.snapis excluded by!**/*.snapand included by**crates/biome_markdown_formatter/tests/specs/prettier/markdown/footnoteDefinition/sibling.md.snapis excluded by!**/*.snapand included by**crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-186.md.snapis excluded by!**/*.snapand included by**crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-289.md.snapis excluded by!**/*.snapand included by**crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-597.md.snapis excluded by!**/*.snapand included by**crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-598.md.snapis excluded by!**/*.snapand included by**crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-599.md.snapis excluded by!**/*.snapand included by**crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-600.md.snapis excluded by!**/*.snapand included by**crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-601.md.snapis excluded by!**/*.snapand included by**crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-609.md.snapis excluded by!**/*.snapand included by**
📒 Files selected for processing (2)
crates/biome_markdown_formatter/src/markdown/auxiliary/hard_line.rsjustfile
| 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(), | ||
| ] | ||
| ) | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
@coderabbitai I believe @ematipico moved it inside in #9331 (comment) though. Check the test snapshots and you can see there are no redundant spaces.
There was a problem hiding this comment.
🧩 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 |
There was a problem hiding this comment.
@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.
There was a problem hiding this comment.
@coderabbitai create an issue to track this edge case. Create the issue as a sub issue of #3718
There was a problem hiding this comment.
🧩 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 #9481 — Markdown 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
| f, | ||
| [ | ||
| format_removed(&token), | ||
| text(" ", token.text_range().start()), |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Prettier trims "two or more spaces" to just "two spaces" so this is to match that.
There was a problem hiding this comment.
The comment says the what, not the why. It simply says "here we normalised", but it doesn't explain why
There was a problem hiding this comment.
Got it. On my phone, let me try something ...
@coderabbitai add a code comment based on this thread
There was a problem hiding this comment.
@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 ofspace()(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.
There was a problem hiding this comment.
@ematipico I added the comment about why. PTAL.
| bbb | ||
| aaa | ||
| -bbb | ||
| +bbb |
There was a problem hiding this comment.
@coderabbitai create an issue to track this edge case. Create the issue as a sub issue of #3718
Summary
This PR enhances the formatting for
MdHardLineto fix the gap discussed in this thread. We used to remove\but semantically we should preserve it.Test Plan
Review the changes and removals of snapshot tests.
Docs
Issue #3718
cc @ematipico