From d0f1a8f231151d79ca7d447e94013be2960e45e0 Mon Sep 17 00:00:00 2001 From: Adam Olsen Date: Wed, 17 Jun 2026 14:29:50 -0600 Subject: [PATCH] Ensure we aren't adding extra newlines to failure output --- lib/credo/cli/command/diff/output/default.ex | 12 +----- .../cli/command/suggest/output/default.ex | 7 +-- lib/credo/cli/output/shell.ex | 35 +++++++++++++++ lib/credo/cli/output/ui.ex | 31 +++++++++++++ test/credo/cli/output/ui_test.exs | 43 +++++++++++++++++++ 5 files changed, 113 insertions(+), 15 deletions(-) diff --git a/lib/credo/cli/command/diff/output/default.ex b/lib/credo/cli/command/diff/output/default.ex index cb0d84158..d35cd5b1d 100644 --- a/lib/credo/cli/command/diff/output/default.ex +++ b/lib/credo/cli/command/diff/output/default.ex @@ -328,20 +328,12 @@ defmodule Credo.CLI.Command.Diff.Output.Default do " ", first_line ] - |> UI.puts() + |> UI.write_line() other_lines |> Enum.each(&print_issue_message(&1, issue, outer_color, message_color)) end - defp print_issue_message( - "", - _issue, - _outer_color, - _message_color - ) do - end - defp print_issue_message( message, issue, @@ -358,7 +350,7 @@ defmodule Credo.CLI.Command.Diff.Output.Default do " ", message ] - |> UI.puts() + |> UI.write_line() end defp print_issue_line( diff --git a/lib/credo/cli/command/suggest/output/default.ex b/lib/credo/cli/command/suggest/output/default.ex index c71b95a14..39c7e8f28 100644 --- a/lib/credo/cli/command/suggest/output/default.ex +++ b/lib/credo/cli/command/suggest/output/default.ex @@ -263,15 +263,12 @@ defmodule Credo.CLI.Command.Suggest.Output.Default do " ", first_line ] - |> UI.puts() + |> UI.write_line() other_lines |> Enum.each(&print_issue_message(&1, outer_color, message_color)) end - defp print_issue_message("", _outer_color, _message_color) do - end - defp print_issue_message(message, outer_color, message_color) do [ UI.edge(outer_color), @@ -282,7 +279,7 @@ defmodule Credo.CLI.Command.Suggest.Output.Default do " ", message ] - |> UI.puts() + |> UI.write_line() end defp print_issue_line( diff --git a/lib/credo/cli/output/shell.ex b/lib/credo/cli/output/shell.ex index 98575f2f7..b6a9c340c 100644 --- a/lib/credo/cli/output/shell.ex +++ b/lib/credo/cli/output/shell.ex @@ -28,6 +28,11 @@ defmodule Credo.CLI.Output.Shell do GenServer.call(__MODULE__, {:suppress_output, false}) end + @doc "Like `puts/1`, but does not append a trailing newline." + def write(value) do + GenServer.call(__MODULE__, {:write, value}) + end + @doc "Like `puts/1`, but writes to `:stderr`." def warn(value) do GenServer.call(__MODULE__, {:warn, value}) @@ -77,6 +82,32 @@ defmodule Credo.CLI.Output.Shell do {:reply, nil, current_state} end + def handle_call({:write, _value}, _from, %{suppress_output: true} = current_state) do + {:reply, nil, current_state} + end + + def handle_call( + {:write, value}, + _from, + %{use_colors: true, suppress_output: false} = current_state + ) do + do_write(value) + + {:reply, nil, current_state} + end + + def handle_call( + {:write, value}, + _from, + %{use_colors: false, suppress_output: false} = current_state + ) do + value + |> remove_colors() + |> do_write() + + {:reply, nil, current_state} + end + def handle_call({:warn, _value}, _from, %{suppress_output: true} = current_state) do {:reply, nil, current_state} end @@ -114,6 +145,10 @@ defmodule Credo.CLI.Output.Shell do Bunt.puts(value) end + defp do_write(value) do + Bunt.write(value) + end + defp do_warn(value) do Bunt.warn(value) end diff --git a/lib/credo/cli/output/ui.ex b/lib/credo/cli/output/ui.ex index dfd401846..fda5a0c8d 100644 --- a/lib/credo/cli/output/ui.ex +++ b/lib/credo/cli/output/ui.ex @@ -23,6 +23,8 @@ defmodule Credo.CLI.Output.UI do def puts(_), do: nil def puts(_, color) when is_atom(color), do: nil + def write(_), do: nil + def warn(_), do: nil else defdelegate puts, to: @shell_service @@ -32,9 +34,26 @@ defmodule Credo.CLI.Output.UI do @shell_service.puts([color, v]) end + defdelegate write(v), to: @shell_service + defdelegate warn(v), to: @shell_service end + def write_line(value) do + value + |> with_trailing_newline() + |> write() + end + + @doc false + def with_trailing_newline(value) do + if ends_with_newline?(value) do + value + else + List.wrap(value) ++ ["\n"] + end + end + def edge(color, indent \\ 2) when is_integer(indent) do [:reset, color, @edge |> String.pad_trailing(indent)] end @@ -97,4 +116,16 @@ defmodule Credo.CLI.Output.UI do String.slice(line, 0, chars_to_display) <> ellipsis end end + + defp ends_with_newline?(value) do + value + |> List.wrap() + |> List.flatten() + |> Enum.reject(&is_atom/1) + |> List.last() + |> case do + nil -> false + value -> value |> to_string() |> String.ends_with?("\n") + end + end end diff --git a/test/credo/cli/output/ui_test.exs b/test/credo/cli/output/ui_test.exs index 9a9f5ad3d..e0271c59d 100644 --- a/test/credo/cli/output/ui_test.exs +++ b/test/credo/cli/output/ui_test.exs @@ -33,6 +33,49 @@ defmodule Credo.CLI.Output.UITest do assert expected == lines end + test "it should keep each line break on its own element so callers can write them" do + lines = + "First line of the message.\nSecond line of the message." + |> UI.wrap_at(80) + + expected = [ + "First line of the message.\n", + "Second line of the message." + ] + + assert expected == lines + end + + test "it should keep a blank line as its own element" do + lines = + "First paragraph.\n\nSecond paragraph." + |> UI.wrap_at(80) + + expected = [ + "First paragraph.\n", + "\n", + "Second paragraph." + ] + + assert expected == lines + end + + test "with_trailing_newline should append a newline to plain content" do + assert ["Plain wrapped line", "\n"] == UI.with_trailing_newline("Plain wrapped line") + end + + test "with_trailing_newline should not double a newline already present in the final chunk" do + line = ["First line of the message.\n"] + + assert line == UI.with_trailing_newline(line) + end + + test "with_trailing_newline should keep a blank line as a single line break" do + line = ["\n"] + + assert line == UI.with_trailing_newline(line) + end + test "it should be able to break up a line including unicode characters" do lines = "あいうえ"