summary.ex (4694B)
1 defmodule Credo.CLI.Output.Summary 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.FirstRunHint 17 alias Credo.CLI.Output.UI 18 alias Credo.Execution 19 alias Credo.SourceFile 20 21 def print( 22 _source_files, 23 %Execution{format: "flycheck"}, 24 _time_load, 25 _time_run 26 ) do 27 nil 28 end 29 30 def print(_source_files, %Execution{format: "oneline"}, _time_load, _time_run) do 31 nil 32 end 33 34 def print(source_files, exec, time_load, time_run) do 35 issues = Execution.get_issues(exec) 36 source_file_count = exec |> Execution.get_source_files() |> Enum.count() 37 checks_count = count_checks(exec) 38 39 UI.puts() 40 UI.puts([:faint, @cry_for_help]) 41 UI.puts() 42 UI.puts([:faint, format_time_spent(checks_count, source_file_count, time_load, time_run)]) 43 44 UI.puts(summary_parts(source_files, issues)) 45 UI.puts() 46 47 print_priority_hint(exec) 48 print_first_run_hint(exec) 49 end 50 51 defp print_first_run_hint(%Execution{cli_options: %{switches: %{first_run: true}}} = exec) do 52 FirstRunHint.call(exec) 53 end 54 55 defp print_first_run_hint(exec), do: exec 56 57 defp count_checks(exec) do 58 {result, _only_matching, _ignore_matching} = Execution.checks(exec) 59 60 Enum.count(result) 61 end 62 63 defp print_priority_hint(%Execution{min_priority: min_priority}) 64 when min_priority >= 0 do 65 UI.puts([ 66 :faint, 67 "Showing priority issues: ↑ ↗ → (use `mix credo explain` to explain issues, `mix credo --help` for options)." 68 ]) 69 end 70 71 defp print_priority_hint(_) do 72 UI.puts([ 73 :faint, 74 "Use `mix credo explain` to explain issues, `mix credo --help` for options." 75 ]) 76 end 77 78 defp format_time_spent(check_count, source_file_count, time_load, time_run) do 79 time_run = time_run |> div(10_000) 80 time_load = time_load |> div(10_000) 81 82 formatted_total = format_in_seconds(time_run + time_load) 83 84 time_to_load = format_in_seconds(time_load) 85 time_to_run = format_in_seconds(time_run) 86 87 total_in_seconds = 88 case formatted_total do 89 "1.0" -> "1 second" 90 value -> "#{value} seconds" 91 end 92 93 checks = 94 if check_count == 1 do 95 "1 check" 96 else 97 "#{check_count} checks" 98 end 99 100 source_files = 101 if source_file_count == 1 do 102 "1 file" 103 else 104 "#{source_file_count} files" 105 end 106 107 breakdown = "#{time_to_load}s to load, #{time_to_run}s running #{checks} on #{source_files}" 108 109 "Analysis took #{total_in_seconds} (#{breakdown})" 110 end 111 112 defp format_in_seconds(t) do 113 if t < 10 do 114 "0.0#{t}" 115 else 116 t = div(t, 10) 117 "#{div(t, 10)}.#{rem(t, 10)}" 118 end 119 end 120 121 defp category_count(issues, category) do 122 issues 123 |> Enum.filter(&(&1.category == category)) 124 |> Enum.count() 125 end 126 127 defp summary_parts(source_files, issues) do 128 parts = 129 @category_wording 130 |> Enum.flat_map(&summary_part(&1, issues)) 131 132 parts = 133 parts 134 |> List.update_at(Enum.count(parts) - 1, fn last_part -> 135 String.replace(last_part, ", ", "") 136 end) 137 138 parts = 139 if Enum.empty?(parts) do 140 "no issues" 141 else 142 parts 143 end 144 145 [ 146 :green, 147 "#{scope_count(source_files)} mods/funs, ", 148 :reset, 149 "found ", 150 parts, 151 "." 152 ] 153 end 154 155 defp summary_part({category, singular, plural}, issues) do 156 color = Output.check_color(category) 157 158 case category_count(issues, category) do 159 0 -> [] 160 1 -> [color, "1 #{singular}, "] 161 x -> [color, "#{x} #{plural}, "] 162 end 163 end 164 165 defp scope_count(%SourceFile{} = source_file) do 166 Credo.Code.prewalk(source_file, &scope_count_traverse/2, 0) 167 end 168 169 defp scope_count([]), do: 0 170 171 defp scope_count(source_files) when is_list(source_files) do 172 source_files 173 |> Enum.map(&Task.async(fn -> scope_count(&1) end)) 174 |> Enum.map(&Task.await/1) 175 |> Enum.reduce(&(&1 + &2)) 176 end 177 178 @def_ops [:defmodule, :def, :defp, :defmacro] 179 for op <- @def_ops do 180 defp scope_count_traverse({unquote(op), _, _} = ast, count) do 181 {ast, count + 1} 182 end 183 end 184 185 defp scope_count_traverse(ast, count) do 186 {ast, count} 187 end 188 end