diff_summary.ex (5453B)
1 defmodule Credo.CLI.Command.Diff.DiffSummary do 2 @moduledoc false 3 4 # This module is responsible for printing the summary at the end of the analysis. 5 6 @category_wording [ 7 {:consistency, "consistency issue", "consistency issues"}, 8 {:warning, "warning", "warnings"}, 9 {:refactor, "refactoring opportunity", "refactoring opportunities"}, 10 {:readability, "code readability issue", "code readability issues"}, 11 {:design, "software design suggestion", "software design suggestions"} 12 ] 13 @cry_for_help "Please report incorrect results: https://github.com/rrrene/credo/issues" 14 15 alias Credo.CLI.Output 16 alias Credo.CLI.Output.UI 17 alias Credo.Execution 18 19 def print( 20 _source_files, 21 %Execution{format: "flycheck"}, 22 _time_load, 23 _time_run 24 ) do 25 nil 26 end 27 28 def print(_source_files, %Execution{format: "oneline"}, _time_load, _time_run) do 29 nil 30 end 31 32 def print(source_files, exec, time_load, time_run) do 33 issues = Execution.get_issues(exec) 34 source_file_count = exec |> Execution.get_source_files() |> Enum.count() 35 checks_count = count_checks(exec) 36 37 git_ref = Execution.get_assign(exec, "credo.diff.previous_git_ref") 38 39 UI.puts() 40 UI.puts([:faint, @cry_for_help]) 41 UI.puts() 42 43 UI.puts(format_time_spent(checks_count, source_file_count, time_load, time_run)) 44 UI.puts() 45 46 new_issues = Enum.filter(issues, &(&1.diff_marker == :new)) 47 fixed_issues = Enum.filter(issues, &(&1.diff_marker == :fixed)) 48 old_issues = Enum.filter(issues, &(&1.diff_marker == :old)) 49 50 UI.puts([ 51 "Changes between ", 52 :faint, 53 :cyan, 54 git_ref, 55 :reset, 56 " and working dir:" 57 ]) 58 59 UI.puts() 60 UI.puts(summary_parts_new(source_files, new_issues)) 61 UI.puts(summary_parts_fixed(source_files, fixed_issues)) 62 UI.puts(summary_parts_old(source_files, old_issues)) 63 UI.puts() 64 65 print_priority_hint(exec) 66 end 67 68 defp count_checks(exec) do 69 {result, _only_matching, _ignore_matching} = Execution.checks(exec) 70 71 Enum.count(result) 72 end 73 74 defp print_priority_hint(%Execution{min_priority: min_priority}) 75 when min_priority >= 0 do 76 UI.puts([ 77 :faint, 78 "Showing priority issues: ↑ ↗ → (use `mix credo explain` to explain issues, `mix credo diff --help` for options)." 79 ]) 80 end 81 82 defp print_priority_hint(_) do 83 UI.puts([ 84 :faint, 85 "Use `mix credo explain` to explain issues, `mix credo diff --help` for options." 86 ]) 87 end 88 89 defp format_time_spent(check_count, source_file_count, time_load, time_run) do 90 time_run = time_run |> div(10_000) 91 time_load = time_load |> div(10_000) 92 93 formatted_total = format_in_seconds(time_run + time_load) 94 95 time_to_load = format_in_seconds(time_load) 96 time_to_run = format_in_seconds(time_run) 97 98 total_in_seconds = 99 case formatted_total do 100 "1.0" -> "1 second" 101 value -> "#{value} seconds" 102 end 103 104 checks = 105 if check_count == 1 do 106 "1 check" 107 else 108 "#{check_count} checks" 109 end 110 111 source_files = 112 if source_file_count == 1 do 113 "1 file" 114 else 115 "#{source_file_count} files" 116 end 117 118 [ 119 :faint, 120 "Analysis took #{total_in_seconds} ", 121 "(#{time_to_load}s to load, #{time_to_run}s running #{checks} on #{source_files})" 122 ] 123 end 124 125 defp format_in_seconds(t) do 126 if t < 10 do 127 "0.0#{t}" 128 else 129 t = div(t, 10) 130 "#{div(t, 10)}.#{rem(t, 10)}" 131 end 132 end 133 134 defp category_count(issues, category) do 135 issues 136 |> Enum.filter(&(&1.category == category)) 137 |> Enum.count() 138 end 139 140 defp summary_parts_new(_source_files, issues) do 141 parts = 142 @category_wording 143 |> Enum.flat_map(&summary_part(&1, issues, "new ")) 144 145 parts = 146 parts 147 |> List.update_at(Enum.count(parts) - 1, fn last_part -> 148 String.replace(last_part, ", ", "") 149 end) 150 151 parts = 152 if Enum.empty?(parts) do 153 "no issues" 154 else 155 parts 156 end 157 158 [ 159 :green, 160 :bright, 161 "+ ", 162 :reset, 163 :green, 164 " added ", 165 :reset, 166 parts, 167 "," 168 ] 169 end 170 171 defp summary_parts_fixed(_source_files, issues) do 172 parts = 173 @category_wording 174 |> Enum.flat_map(&summary_part(&1, issues)) 175 176 parts = 177 parts 178 |> List.update_at(Enum.count(parts) - 1, fn last_part -> 179 String.replace(last_part, ", ", "") 180 end) 181 182 parts = 183 if Enum.empty?(parts) do 184 "no issues" 185 else 186 parts 187 end 188 189 [ 190 :faint, 191 "✔ ", 192 :reset, 193 " fixed ", 194 :faint, 195 parts, 196 ", and" 197 ] 198 end 199 200 defp summary_parts_old(_source_files, issues) do 201 parts = 202 @category_wording 203 |> Enum.flat_map(&summary_part(&1, issues)) 204 |> Enum.reject(&is_atom(&1)) 205 206 parts = 207 parts 208 |> List.update_at(Enum.count(parts) - 1, fn last_part -> 209 String.replace(last_part, ", ", "") 210 end) 211 212 parts = 213 if Enum.empty?(parts) do 214 "no issues" 215 else 216 parts 217 end 218 219 [ 220 :faint, 221 "~ ", 222 " kept ", 223 parts, 224 "." 225 ] 226 end 227 228 defp summary_part({category, singular, plural}, issues, qualifier \\ "") do 229 color = Output.check_color(category) 230 231 case category_count(issues, category) do 232 0 -> [] 233 1 -> [color, "1 #{qualifier}#{singular}, "] 234 x -> [color, "#{x} #{qualifier}#{plural}, "] 235 end 236 end 237 end