Skip to content

feat(formatter): trailing newline#8334

Merged
ematipico merged 9 commits intonextfrom
feat/ending-newline
Jan 6, 2026
Merged

feat(formatter): trailing newline#8334
ematipico merged 9 commits intonextfrom
feat/ending-newline

Conversation

@ematipico
Copy link
Member

@ematipico ematipico commented Dec 2, 2025

Summary

This is a proposal to add trailingNewline to the Biome formatter.

I would like to know what @biomejs/core-contributors and @biomejs/maintainers think about it.

I would plan to add this option as long as we document the unsafety of removing the trailing newline from the files. Hence, this will never be the default.

For example, there are various historical issues about files that don't have a newline:

A user also raised a concern regarding POSIX. However, this seems to be more nounced (I asked Claude Code about this)

A file without a trailing newline doesn't strictly conform to the POSIX definition of a "text file," because it contains a final sequence of characters that doesn't meet the definition of a "line." So in a narrow, pedantic reading of the standard, such a file isn't a well-formed text file.
What's Misleading
However, this doesn't mean the file is therefore classified as a "binary file." That's a false dichotomy. POSIX doesn't define binary files as "anything that isn't a text file." In practice:

Binary files typically contain non-printable characters, null bytes, or data not intended to be interpreted as character-encoded text
A file missing a trailing newline is still character data, still human-readable, still processed as text by virtually every tool—it's just non-conforming text

Still, it seems to be a concern to acknowledge, and maybe document.

Here's what I was thinking:

  • if we accept it, we add some resources to the docs, so users are aware of the possible issues
  • if we don't, we can document on our website why we decided against this option

Note

The majority of the code was created with Claude Code, but I to intervene too because at some point the AI didn't know how to connect the dots. The AI updated all the configs and tests.

I updated the snapshots to make the newline more evident, since it wasn't before. I wrote some gluecode to make the feature working via Workspace, and edited the changeset

Test Plan

New tests added.

Docs

biomejs/website#3771

@changeset-bot
Copy link

changeset-bot bot commented Dec 2, 2025

🦋 Changeset detected

Latest commit: 6fe0aff

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 14 packages
Name Type
@biomejs/biome Minor
@biomejs/cli-win32-x64 Minor
@biomejs/cli-win32-arm64 Minor
@biomejs/cli-darwin-x64 Minor
@biomejs/cli-darwin-arm64 Minor
@biomejs/cli-linux-x64 Minor
@biomejs/cli-linux-arm64 Minor
@biomejs/cli-linux-x64-musl Minor
@biomejs/cli-linux-arm64-musl Minor
@biomejs/wasm-web Minor
@biomejs/wasm-bundler Minor
@biomejs/wasm-nodejs Minor
@biomejs/backend-jsonrpc Patch
@biomejs/js-api Major

Not sure what this means? Click here to learn what changesets are.

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

@ematipico ematipico changed the title feat(lsp): report progress while scanning the project (#7961) feat(formatter): trailing newline Dec 2, 2025
@github-actions github-actions bot added A-CLI Area: CLI A-Project Area: project A-Formatter Area: formatter L-JavaScript Language: JavaScript and super languages L-CSS Language: CSS L-JSON Language: JSON and super languages L-HTML Language: HTML and super languages L-Grit Language: GritQL A-Type-Inference Area: type inference labels Dec 2, 2025
@ematipico ematipico force-pushed the feat/ending-newline branch from 8b279bb to f2e5e48 Compare December 2, 2025 13:30
@codspeed-hq
Copy link

codspeed-hq bot commented Dec 2, 2025

CodSpeed Performance Report

Merging #8334 will not alter performance

Comparing feat/ending-newline (6fe0aff) with next (fe3d424)

Summary

✅ 58 untouched
⏩ 95 skipped1

Footnotes

  1. 95 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@arendjr
Copy link
Contributor

arendjr commented Dec 2, 2025

Not a fan of the option, but if there is user demand and proper documentation, then I don’t mind indeed.

@ematipico
Copy link
Member Author

Also considered naming the option unsafeTrailingNewline, but I thought it would be too much of an opinion

Copy link
Contributor

dyc3 commented Dec 2, 2025

I share @arendjr 's sentiment. The logic is simple enough, so it's not a huge deal I suppose.

@ematipico ematipico force-pushed the feat/ending-newline branch from 1b63a75 to aee8160 Compare January 2, 2026 09:43
@ematipico ematipico marked this pull request as ready for review January 2, 2026 09:43
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 2, 2026

Walkthrough

Adds a configurable trailing-newline feature throughout the formatter stack: new public type TrailingNewline (default true), optional trailing_newline fields on global, per-language and override formatter configurations, and CLI flags for each formatter. Formatter contexts, builder APIs and printers were extended to expose and honour the option (emit or strip trailing newlines). EditorConfig mapping updated to use trailing-newline semantics. CLI help visibility metadata had several hide attributes removed. A skip_ignore_check flag was threaded through stdin/file processing payloads and feature-resolution calls.

Possibly related PRs

Suggested reviewers

  • dyc3
  • denbezrukov

Pre-merge checks

✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main feature being added: a trailing newline option for the formatter.
Description check ✅ Passed The PR description clearly outlines the proposal to add a trailingNewline option to the Biome formatter, addressing specific motivations and concerns regarding file trailing newlines.

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

Copy link
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: 0

🧹 Nitpick comments (4)
crates/biome_formatter_test/src/snapshot_builder.rs (1)

102-102: Inconsistent handling between with_output_and_options and with_output.

The extra newline added here makes trailing newlines evident in snapshots, but the with_output method (line 115) doesn't include this same newline before its closing backticks. Should both methods format output consistently, or is there a specific reason for the difference?

🔎 Suggested change for consistency

If both methods should format output the same way, apply this change to with_output:

 self.write_extension();
 self.snapshot.push_str(output.content);
+writeln!(self.snapshot).unwrap();
 writeln!(self.snapshot, "```").unwrap();
crates/biome_cli/tests/cases/editorconfig.rs (1)

702-747: Test structure looks good.

The test follows the established pattern and properly sets up editorconfig integration. Consider adding a comment explaining the expected behaviour (why is_err() is expected when insert_final_newline = false and the file has no trailing newline).

crates/biome_js_formatter/src/js/auxiliary/module.rs (1)

32-40: Order inconsistency with HTML formatter.

This implementation emits format_removed(eof_token) before the conditional hard_line_break, whilst the HTML formatter (in crates/biome_html_formatter/src/html/auxiliary/root.rs lines 30-34) emits the conditional hard_line_break first, then format_removed(eof_token). Consider standardising the order for consistency across formatters, though functionally this may not matter for EOF handling.

crates/biome_formatter/src/lib.rs (1)

1207-1215: Implementation is correct and handles all line ending types.

The while loop approach is simple and works well for the typical case of 1-2 trailing newlines.

Optional: Minor optimization for edge cases

If you expect files with many trailing newlines, you could optimize by finding the last non-newline position and truncating once:

 pub fn strip_trailing_newlines(mut self) -> Self {
-    // Strip all trailing line ending characters
-    while self.code.ends_with('\n') || self.code.ends_with('\r') {
-        self.code.pop();
-    }
+    // Find the last non-newline position
+    let trim_pos = self.code
+        .trim_end_matches(|c| c == '\n' || c == '\r')
+        .len();
+    self.code.truncate(trim_pos);
     self
 }

However, the current implementation is perfectly fine for the common case.

@ematipico ematipico added this to the Biome v2.4 milestone Jan 2, 2026
@ematipico
Copy link
Member Author

It seems we're pretty aligned with the option. We don't love it, but its maintenance is trivial.

@ematipico ematipico requested a review from a team January 2, 2026 10:22
@ematipico ematipico requested a review from a team January 2, 2026 10:22
@ematipico ematipico merged commit ae8ac8e into next Jan 6, 2026
25 checks passed
@ematipico ematipico deleted the feat/ending-newline branch January 6, 2026 09:36
@github-actions github-actions bot mentioned this pull request Feb 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-CLI Area: CLI A-Formatter Area: formatter A-Project Area: project A-Type-Inference Area: type inference L-CSS Language: CSS L-Grit Language: GritQL L-HTML Language: HTML and super languages L-JavaScript Language: JavaScript and super languages L-JSON Language: JSON and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants