Skip to content

Commit 6a27152

Browse files
committed
Ensure single trailing newline in generated files
And no unintentional consecutive blank lines in the generated content. Those two issues are rather aesthetic, but downstream users notice and they are a common source of code churn and maintenance overheard that we can prevent from now on. The single trailing newline is a common convention in Unix and POSIX systems, and it is also what the Elixir formatter does (`Code.format_file!` always appends a trailing newline [1]). The consecutive blank lines in Elixir code are also automatically removed by the Elixir formatter. It is not a strict rule for other files, but generally they are not expected and most of the time added unintentionally, e.g. when using conditional EEx templates or concatenating strings (AGENTS.md / usage rules). Except for a handful of generated files (favicon.ico, phoenix.png and *.pem certificates), all generated files are candidates for those two rules. Instead of updating lots of tests (which would cause a lot of churn and be a future maintenance burden, easy to miss in new tests), we update `MixHelper.assert_file/1` to check for those two rules in all current and future generated files. If we need more exceptions in the future, it is easy to change `assert_file/1` to add them. [1]: https://github.com/elixir-lang/elixir/blob/545dddf138e4cb1ee874e6f2c26882c9b438f551/lib/elixir/lib/code.ex#L1137-L1141
1 parent 479ef74 commit 6a27152

15 files changed

Lines changed: 52 additions & 51 deletions

File tree

installer/lib/phx_new/generator.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ defmodule Phx.New.Generator do
128128
project.binding[:javascript] && project.binding[:css] &&
129129
@new_project_rules_files["assets.md"],
130130
# generic usage rules
131-
"\n<!-- usage-rules-start -->",
131+
"<!-- usage-rules-start -->",
132132
[
133133
"<!-- phoenix:elixir-start -->\n",
134134
@rules_files["elixir.md"],
@@ -157,7 +157,7 @@ defmodule Phx.New.Generator do
157157
@rules_files["liveview.md"],
158158
"\n<!-- phoenix:liveview-end -->"
159159
],
160-
"<!-- usage-rules-end -->"
160+
"<!-- usage-rules-end -->\n"
161161
]
162162
|> Enum.reject(fn part -> part == nil or part == false end)
163163
|> Enum.intersperse("\n\n")

installer/templates/phx_assets/app.js.eex

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
// import "some-package"
1616
//
1717
// If you have dependencies that try to import CSS, esbuild will generate a separate `app.css` file.
18-
// To load it, simply add a second `<link>` to your `root.html.heex` file.
19-
<%= if @html do %>
18+
// To load it, simply add a second `<link>` to your `root.html.heex` file.<%= if @html do %>
19+
2020
// Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
2121
import "phoenix_html"
2222
// Establish Phoenix Socket and LiveView configuration.
@@ -79,12 +79,11 @@ import "phoenix_html"
7979
<%= @live_comment %>
8080
<%= @live_comment %> window.liveReloader = reloader
8181
<%= @live_comment %> })
82-
<%= @live_comment %>}
82+
<%= @live_comment %>}<%= if not @live do %>
8383

84-
<%= if not @live do %>
8584
// Handle flash close
8685
document.querySelectorAll("[role=alert][data-flash]").forEach((el) => {
8786
el.addEventListener("click", () => {
8887
el.setAttribute("hidden", "")
8988
})
90-
})<% end %><% end %>
89+
})<% end %><% end %>

installer/templates/phx_gettext/errors.pot.eex

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
##
77
## Run `mix gettext.extract` to bring this file up to
88
## date. Leave `msgstr`s empty as changing them here has no
9-
## effect: edit them in PO (`.po`) files instead.
10-
<%= if @ecto do %>## From Ecto.Changeset.cast/4
9+
## effect: edit them in PO (`.po`) files instead.<%= if @ecto do %>
10+
11+
## From Ecto.Changeset.cast/4
1112
msgid "can't be blank"
1213
msgstr ""
1314

installer/templates/phx_single/gitignore.eex

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ erl_crash.dump
2323
/tmp/
2424

2525
# Ignore package tarball (built via "mix hex.build").
26-
<%= @app_name %>-*.tar
27-
<%= if @javascript or @css do %>
26+
<%= @app_name %>-*.tar<%= if @javascript or @css do %>
27+
2828
# Ignore assets that are produced by build tools.
2929
/priv/static/assets/
3030

@@ -33,9 +33,8 @@ erl_crash.dump
3333

3434
# In case you use Node.js/npm, you want to ignore these.
3535
npm-debug.log
36-
/assets/node_modules/
37-
<% end %><%= if @adapter_app == :ecto_sqlite3 do %>
36+
/assets/node_modules/<% end %><%= if @adapter_app == :ecto_sqlite3 do %>
37+
3838
# Database files
3939
*.db
40-
*.db-*
41-
<% end %>
40+
*.db-*<% end %>

installer/templates/phx_static/default.css

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2587,4 +2587,3 @@
25872587
transform: rotate(360deg);
25882588
}
25892589
}
2590-

installer/templates/phx_umbrella/apps/app_name_web/gitignore.eex

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ erl_crash.dump
2323
/tmp/
2424

2525
# Ignore package tarball (built via "mix hex.build").
26-
<%= @web_app_name %>-*.tar
27-
<%= if @javascript or @css do %>
26+
<%= @web_app_name %>-*.tar<%= if @javascript or @css do %>
27+
2828
# Ignore assets that are produced by build tools.
2929
/priv/static/assets/
3030

@@ -33,9 +33,8 @@ erl_crash.dump
3333

3434
# In case you use Node.js/npm, you want to ignore these.
3535
npm-debug.log
36-
/assets/node_modules/
37-
<% end %><%= if @adapter_app == :ecto_sqlite3 do %>
36+
/assets/node_modules/<% end %><%= if @adapter_app == :ecto_sqlite3 do %>
37+
3838
# Database files
3939
*.db
40-
*.db-*
41-
<% end %>
40+
*.db-*<% end %>

installer/templates/phx_umbrella/config/test.exs.eex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ config :phoenix, :plug_init_mode, :runtime<%= if @html do %>
1616
# Enable helpful, but potentially expensive runtime checks
1717
config :phoenix_live_view,
1818
enable_expensive_runtime_checks: true<% end %>
19-
19+
2020
# Sort query params output of verified routes for robust url comparisons
2121
config :phoenix,
22-
sort_verified_routes_query_params: true
22+
sort_verified_routes_query_params: true

installer/templates/phx_umbrella/gitignore.eex

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,8 @@ erl_crash.dump
2020
*.ez
2121

2222
# Temporary files, for example, from tests.
23-
/tmp/
23+
/tmp/<%= if @adapter_app == :ecto_sqlite3 do %>
2424

25-
<%= if @adapter_app == :ecto_sqlite3 do %>
2625
# Database files
2726
*.db
28-
*.db-*
29-
<% end %>
27+
*.db-*<% end %>

installer/test/mix_helper.exs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,21 @@ defmodule MixHelper do
9696

9797
def assert_file(file) do
9898
assert File.regular?(file), "Expected #{file} to exist, but does not"
99+
content = File.read!(file)
100+
101+
if not binary_file?(file) do
102+
# \S\n\z matches non-whitespace followed by a single newline at EOF
103+
assert content == "" or content =~ ~r/\S\n\z/,
104+
"Expected #{file} to end with a single trailing newline"
105+
106+
refute content =~ "\n\n\n", "Expected #{file} to not contain consecutive blank lines"
107+
end
108+
109+
content
99110
end
100111

112+
defp binary_file?(file), do: Path.extname(file) in ~w(.ico .pem .png)
113+
101114
def refute_file(file) do
102115
refute File.regular?(file), "Expected #{file} to not exist, but it does"
103116
end
@@ -111,8 +124,8 @@ defmodule MixHelper do
111124
assert_file(file, &assert(&1 =~ match))
112125

113126
is_function(match, 1) ->
114-
assert_file(file)
115-
match.(File.read!(file))
127+
content = assert_file(file)
128+
match.(content)
116129

117130
true ->
118131
raise inspect({file, match})

installer/test/phx_new_test.exs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,6 @@ defmodule Mix.Tasks.Phx.NewTest do
147147
assert_file("phx_blog/.gitignore", fn file ->
148148
assert file =~ "/priv/static/assets/"
149149
assert file =~ "phx_blog-*.tar"
150-
assert file =~ ~r/\n$/
151150
end)
152151

153152
assert_file("phx_blog/config/dev.exs", fn file ->
@@ -324,7 +323,6 @@ defmodule Mix.Tasks.Phx.NewTest do
324323
# No assets
325324
assert_file("phx_blog/.gitignore", fn file ->
326325
refute file =~ "/priv/static/assets/"
327-
assert file =~ ~r/\n$/
328326
end)
329327

330328
refute File.exists?("phx_blog/priv/static/images/logo.svg")
@@ -531,8 +529,6 @@ defmodule Mix.Tasks.Phx.NewTest do
531529
refute file =~ "/priv/static/assets/"
532530
end)
533531

534-
assert_file("phx_blog/.gitignore")
535-
assert_file("phx_blog/.gitignore", ~r/\n$/)
536532
assert_file("phx_blog/priv/static/assets/css/app.css")
537533
assert_file("phx_blog/priv/static/assets/js/app.js")
538534
assert_file("phx_blog/priv/static/favicon.ico")
@@ -592,7 +588,6 @@ defmodule Mix.Tasks.Phx.NewTest do
592588
Mix.Tasks.Phx.New.run([project_path, "--app", @app_name, "--module", "PhoteuxBlog"])
593589

594590
assert_file("custom_path/.gitignore")
595-
assert_file("custom_path/.gitignore", ~r/\n$/)
596591
assert_file("custom_path/mix.exs", ~r/app: :phx_blog/)
597592
assert_file("custom_path/lib/phx_blog_web/endpoint.ex", ~r/app: :phx_blog/)
598593
assert_file("custom_path/config/config.exs", ~r/namespace: PhoteuxBlog/)

0 commit comments

Comments
 (0)