From ddc61a28d7850b1b633c56d36ee975d01ce41838 Mon Sep 17 00:00:00 2001 From: Rodolfo Carvalho Date: Sun, 2 Aug 2026 10:26:59 +0200 Subject: [PATCH] Fix trailing whitespace in files generated by "mix new" When creating a new library with `mix new`, I noticed it generated `README.md` and `.gitignore` files with an extra trailing newline. Depending on Git configuration, e.g. `core.whitespace = blank-at-eof`, Git flags these extra blank lines at the end of files as whitespace errors. Managing whitespace in EEx templates can be tricky when control tags like `<%= if ... do %>` and `<% end %>` sit on dedicated lines, as line breaks around tags accumulate in the rendered output. This whitespace behavior has been present for many years, dating back to 2015 for `README.md` when conditional installation instructions were added, and for `.gitignore` when entries were alphabetized. This commit formats the conditional tags in both templates so EEx naturally produces exactly one trailing newline for both standard and umbrella projects, and adds test assertions to prevent regressions. Other embedded templates across Mix tasks were also reviewed (with AI assistance) for middle-of-template and trailing whitespace issues, and no further issues were found. --- lib/mix/lib/mix/tasks/new.ex | 14 ++++++-------- lib/mix/test/mix/tasks/new_test.exs | 24 ++++++++++++++++++++---- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/lib/mix/lib/mix/tasks/new.ex b/lib/mix/lib/mix/tasks/new.ex index a8dbc3c101a..8290e163291 100644 --- a/lib/mix/lib/mix/tasks/new.ex +++ b/lib/mix/lib/mix/tasks/new.ex @@ -269,8 +269,8 @@ defmodule Mix.Tasks.New do embed_template(:readme, """ # <%= @mod %> - **TODO: Add description** - <%= if @app do %> + **TODO: Add description**<%= if @app do %> + ## Installation If [available in Hex](https://hex.pm/docs/publish), the package can be installed @@ -286,8 +286,7 @@ defmodule Mix.Tasks.New do Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) and published on [HexDocs](https://hexdocs.pm). Once published, the docs can - be found at .hexdocs.pm>. - <% end %> + be found at .hexdocs.pm>.<% end %> """) embed_template(:formatter, """ @@ -325,11 +324,10 @@ defmodule Mix.Tasks.New do erl_crash.dump # Also ignore archive artifacts (built via "mix archive.build"). - *.ez - <%= if @app do %> + *.ez<%= if @app do %> + # Ignore package tarball (built via "mix hex.build"). - <%= @app %>-*.tar - <% end %> + <%= @app %>-*.tar<% end %> """) embed_template(:mix_exs, """ diff --git a/lib/mix/test/mix/tasks/new_test.exs b/lib/mix/test/mix/tasks/new_test.exs index bd0a9e4bde4..5677735b73e 100644 --- a/lib/mix/test/mix/tasks/new_test.exs +++ b/lib/mix/test/mix/tasks/new_test.exs @@ -16,8 +16,16 @@ defmodule Mix.Tasks.NewTest do assert file =~ "version: \"0.1.0\"" end) - assert_file("hello_world/README.md", ~r/# HelloWorld\n/) - assert_file("hello_world/.gitignore") + assert_file("hello_world/README.md", fn file -> + assert file =~ "# HelloWorld\n" + assert String.ends_with?(file, "\n") + refute String.ends_with?(file, "\n\n") + end) + + assert_file("hello_world/.gitignore", fn file -> + assert String.ends_with?(file, "\n") + refute String.ends_with?(file, "\n\n") + end) assert_file("hello_world/lib/hello_world.ex", ~r/defmodule HelloWorld do/) assert_file("hello_world/test/test_helper.exs", ~r/ExUnit.start()/) @@ -118,8 +126,16 @@ defmodule Mix.Tasks.NewTest do assert file =~ "apps_path: \"apps\"" end) - assert_file("hello_world/README.md", ~r/# HelloWorld\n/) - assert_file("hello_world/.gitignore") + assert_file("hello_world/README.md", fn file -> + assert file =~ "# HelloWorld\n" + assert String.ends_with?(file, "\n") + refute String.ends_with?(file, "\n\n") + end) + + assert_file("hello_world/.gitignore", fn file -> + assert String.ends_with?(file, "\n") + refute String.ends_with?(file, "\n\n") + end) assert_received {:mix_shell, :info, ["* creating mix.exs"]}