Skip to content

fix(builtins): strip extra trailing newlines in end-of-file-fixer#708

Merged
jdx merged 4 commits intomainfrom
fix/end-of-file-fixer-extra-newlines
Feb 28, 2026
Merged

fix(builtins): strip extra trailing newlines in end-of-file-fixer#708
jdx merged 4 commits intomainfrom
fix/end-of-file-fixer-extra-newlines

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented Feb 28, 2026

Summary

  • end-of-file-fixer now strips extra trailing newlines to match pre-commit-hooks behavior ("files end in a newline and only a newline")
  • Previously only missing newlines were added; files with multiple trailing newlines were left unchanged
  • Check, fix, and diff modes all handle the extra-newlines case

Closes #707

Test plan

  • Unit tests: added tests for extra trailing newlines detection and fixing, plus single-newline file edge case (10 tests pass)
  • Integration tests: added bats tests for detecting and fixing extra trailing newlines (8 tests pass)
  • Existing tests continue to pass (missing newline, already correct, empty file, binary skip, diff mode)

🤖 Generated with Claude Code


Note

Medium Risk
Changes end-of-file-fixer semantics from “ensure a final newline” to “ensure exactly one trailing newline” and switches fix behavior to rewrite file contents, which could affect edge cases around file encoding/IO but is scoped to this utility.

Overview
Updates hk util end-of-file-fixer to enforce the pre-commit-hooks behavior that text files must end with exactly one newline (detecting and stripping extra trailing newlines), in addition to adding a missing one.

--diff, check, and --fix modes now share a normalization path (normalize_ending/has_proper_ending), with additional unit and Bats tests covering extra-newline and single-byte ("\n") edge cases. Also cleans up several docs/examples and test files that had stray trailing list markers/blank lines.

Written by Cursor Bugbot for commit 1669aae. This will update automatically on new commits. Configure here.

Match pre-commit-hooks behavior where files end in "a newline and only
a newline". Previously only missing newlines were added; now extra
trailing newlines are also removed.

Closes #707

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the end-of-file-fixer utility to enforce a consistent file ending policy, ensuring that all text files conclude with precisely one newline character. This change addresses cases where files previously had multiple trailing newlines, bringing the utility's behavior in line with common standards like those found in pre-commit-hooks. The update improves code consistency and prevents issues related to varying file ending formats.

Highlights

  • Enhanced Newline Handling: The end-of-file-fixer utility now correctly handles and strips extra trailing newlines, aligning its behavior with common standards like pre-commit-hooks.
  • Refactored Logic: The internal logic for detecting and fixing file endings has been refactored to ensure all files conclude with exactly one newline character.
  • Comprehensive Testing: New unit and integration tests were added to validate the detection and fixing of extra trailing newlines, alongside existing test coverage.
Changelog
  • src/cli/util/end_of_file_fixer.rs
    • Removed unused Write trait import.
    • Renamed has_final_newline to has_proper_ending and updated its implementation to check for exactly one trailing newline, including a special case for single-byte files.
    • Modified generate_diff to use the new has_proper_ending function and to trim all existing trailing newlines before adding a single one for the fix.
    • Refactored fix_end_of_file to read the entire file content, trim_end_matches('\n'), and then append a single newline, ensuring idempotency and correctness for all cases (missing, extra, or correct newlines).
    • Added new unit tests (test_has_proper_ending_extra_newlines, test_has_proper_ending_single_newline_file, test_fix_extra_trailing_newlines) to cover the new functionality.
    • Updated existing unit tests to reflect the new function names and logic.
  • test/util_end_of_file_fixer.bats
    • Added two new integration tests to verify that the end-of-file-fixer utility correctly detects and fixes files with extra trailing newlines.
Activity
  • Unit tests were added for extra trailing newlines detection and fixing, including an edge case for single-newline files (10 tests pass).
  • Integration tests were added using bats for detecting and fixing extra trailing newlines (8 tests pass).
  • All existing tests continue to pass, covering cases like missing newlines, already correct files, empty files, binary skips, and diff mode.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request enhances the end-of-file-fixer to strip extra trailing newlines, aligning its behavior with the popular pre-commit-hooks tool. The changes are logical and well-implemented, covering check, fix, and diff modes. The addition of comprehensive unit and integration tests for the new functionality and edge cases is commendable. I have one minor suggestion regarding code duplication to improve long-term maintainability.

Comment thread src/cli/util/end_of_file_fixer.rs Outdated
Comment on lines +140 to +141
let trimmed = content.trim_end_matches('\n');
let fixed = format!("{trimmed}\n");
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.

medium

This logic to trim trailing newlines and append a single newline is also present in the generate_diff function (lines 90-91). To improve maintainability and reduce code duplication, consider extracting this logic into a shared helper function. This would ensure that any future modifications to how file endings are fixed only need to be made in one place.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Feb 28, 2026

Greptile Summary

Extended end-of-file-fixer to enforce exactly one trailing newline by detecting and stripping extra newlines, matching pre-commit-hooks behavior.

Key changes:

  • Renamed has_final_newline to has_proper_ending and enhanced it to check for exactly one trailing newline (reads last 2 bytes instead of 1)
  • Updated fix_end_of_file from append-mode to read-trim-write strategy
  • Added special handling for 1-byte files edge case
  • Comprehensive test coverage: 3 new unit tests + 2 new integration tests

Issues found:

  • Two doc comments still reference the old behavior (adding vs. ensuring exactly one newline)

Confidence Score: 4/5

  • Safe to merge after fixing the two outdated doc comments
  • Logic is well-designed with proper edge case handling and comprehensive test coverage, but doc comments need updating to reflect the new behavior
  • src/cli/util/end_of_file_fixer.rs needs doc comment updates on lines 6 and 13

Important Files Changed

Filename Overview
src/cli/util/end_of_file_fixer.rs Enhanced to strip extra trailing newlines; logic is sound but has outdated doc comments
test/util_end_of_file_fixer.bats Added comprehensive tests for detecting and fixing extra trailing newlines

Fix All in Claude Code

Last reviewed commit: 3c5d0d7

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment thread src/cli/util/end_of_file_fixer.rs Outdated
use std::io::Read;
use std::path::PathBuf;

/// Check for and optionally fix missing final newlines in files
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

module doc says "missing" but now also handles extra trailing newlines

Suggested change
/// Check for and optionally fix missing final newlines in files
/// Check for and optionally fix files to end with exactly one newline

Fix in Claude Code

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Feb 28, 2026

Additional Comments (1)

src/cli/util/end_of_file_fixer.rs
doc comment still says "adding final newline" but now also strips extra newlines

    /// Fix files to end with exactly one newline

Fix in Claude Code

jdx and others added 2 commits February 28, 2026 11:27
- Update doc comments to reflect new behavior (exactly one newline)
- Extract shared trim+append logic into normalize_ending() helper

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jdx jdx enabled auto-merge (squash) February 28, 2026 11:42
@jdx jdx merged commit 17cbd4e into main Feb 28, 2026
17 checks passed
@jdx jdx deleted the fix/end-of-file-fixer-extra-newlines branch February 28, 2026 12:08
@jdx jdx mentioned this pull request Feb 28, 2026
tmeijn pushed a commit to tmeijn/dotfiles that referenced this pull request Mar 11, 2026
This MR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [hk](https://github.com/jdx/hk) | minor | `1.36.0` → `1.38.0` |

MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot).

**Proposed changes to behavior should be submitted there as MRs.**

---

### Release Notes

<details>
<summary>jdx/hk (hk)</summary>

### [`v1.38.0`](https://github.com/jdx/hk/blob/HEAD/CHANGELOG.md#1380---2026-03-06)

[Compare Source](jdx/hk@v1.37.0...v1.38.0)

##### 🚀 Features

- **(hook)** add `fail_on_fix` option by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;725](jdx/hk#725)

##### 🐛 Bug Fixes

- **(builtins)** remove redundant check/check\_diff from builtins by [@&#8203;nkakouros](https://github.com/nkakouros) in [#&#8203;726](jdx/hk#726)

##### 📦️ Dependency Updates

- update anthropics/claude-code-action digest to [`26ec041`](jdx/hk@26ec041) by [@&#8203;renovate\[bot\]](https://github.com/renovate\[bot]) in [#&#8203;720](jdx/hk#720)
- update jdx/mise-action digest to [`e79ddf6`](jdx/hk@e79ddf6) by [@&#8203;renovate\[bot\]](https://github.com/renovate\[bot]) in [#&#8203;721](jdx/hk#721)
- update actions-rust-lang/setup-rust-toolchain digest to [`a0b538f`](jdx/hk@a0b538f) by [@&#8203;renovate\[bot\]](https://github.com/renovate\[bot]) in [#&#8203;719](jdx/hk#719)
- update rust crate tokio to v1.50.0 by [@&#8203;renovate\[bot\]](https://github.com/renovate\[bot]) in [#&#8203;722](jdx/hk#722)

### [`v1.37.0`](https://github.com/jdx/hk/blob/HEAD/CHANGELOG.md#1370---2026-03-03)

[Compare Source](jdx/hk@v1.36.0...v1.37.0)

##### 🚀 Features

- **(hook)** add env support to hooks by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;709](jdx/hk#709)
- parse Go-style diffs by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;704](jdx/hk#704)

##### 🐛 Bug Fixes

- **(builtins)** strip extra trailing newlines in end-of-file-fixer by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;708](jdx/hk#708)
- **(docs)** correctly document what --all is about by [@&#8203;nkakouros](https://github.com/nkakouros) in [#&#8203;715](jdx/hk#715)
- **(git)** exclude untracked files from unstaged\_files set by [@&#8203;nkakouros](https://github.com/nkakouros) in [#&#8203;716](jdx/hk#716)
- **(hkrc)** config format and load order by [@&#8203;ivy](https://github.com/ivy) in [#&#8203;710](jdx/hk#710)
- **(release)** write release notes to file instead of capturing stdout by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;688](jdx/hk#688)
- **(release)** make release notes editorialization non-blocking by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;690](jdx/hk#690)
- **(step)** gate check\_diff forced check\_first on Fix mode only by [@&#8203;nkakouros](https://github.com/nkakouros) in [#&#8203;717](jdx/hk#717)

##### 📚 Documentation

- **(shanty)** add audio player with sea shanty recording by [@&#8203;jdx](https://github.com/jdx) in [67a25ad](jdx/hk@67a25ad)
- document config file search paths by [@&#8203;ivy](https://github.com/ivy) in [#&#8203;701](jdx/hk#701)
- require AI disclosure on GitHub comments by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;703](jdx/hk#703)

##### 🔍 Other Changes

- replace gen-release-notes script with communique by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;700](jdx/hk#700)
- add autofix.ci workflow by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;705](jdx/hk#705)

##### 📦️ Dependency Updates

- lock file maintenance by [@&#8203;renovate\[bot\]](https://github.com/renovate\[bot]) in [#&#8203;686](jdx/hk#686)
- update taiki-e/upload-rust-binary-action digest to [`f391289`](jdx/hk@f391289) by [@&#8203;renovate\[bot\]](https://github.com/renovate\[bot]) in [#&#8203;692](jdx/hk#692)
- update anthropics/claude-code-action digest to [`c22f7c3`](jdx/hk@c22f7c3) by [@&#8203;renovate\[bot\]](https://github.com/renovate\[bot]) in [#&#8203;691](jdx/hk#691)
- update rust crate libc to v0.2.181 by [@&#8203;renovate\[bot\]](https://github.com/renovate\[bot]) in [#&#8203;694](jdx/hk#694)
- update rust crate clap to v4.5.58 by [@&#8203;renovate\[bot\]](https://github.com/renovate\[bot]) in [#&#8203;693](jdx/hk#693)
- lock file maintenance by [@&#8203;renovate\[bot\]](https://github.com/renovate\[bot]) in [#&#8203;695](jdx/hk#695)
- update anthropics/claude-code-action digest to [`edd85d6`](jdx/hk@edd85d6) by [@&#8203;renovate\[bot\]](https://github.com/renovate\[bot]) in [#&#8203;698](jdx/hk#698)
- update rust crate clap to v4.5.60 by [@&#8203;renovate\[bot\]](https://github.com/renovate\[bot]) in [#&#8203;699](jdx/hk#699)
- lock file maintenance by [@&#8203;renovate\[bot\]](https://github.com/renovate\[bot]) in [#&#8203;702](jdx/hk#702)
- lock file maintenance by [@&#8203;renovate\[bot\]](https://github.com/renovate\[bot]) in [#&#8203;711](jdx/hk#711)

##### New Contributors

- [@&#8203;ivy](https://github.com/ivy) made their first contribution in [#&#8203;710](jdx/hk#710)
- [@&#8203;nkakouros](https://github.com/nkakouros) made their first contribution in [#&#8203;715](jdx/hk#715)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this MR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box

---

This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My40OS4wIiwidXBkYXRlZEluVmVyIjoiNDMuNTcuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiUmVub3ZhdGUgQm90IiwiYXV0b21hdGlvbjpib3QtYXV0aG9yZWQiLCJkZXBlbmRlbmN5LXR5cGU6Om1pbm9yIl19-->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant