first_run_hint.ex (5522B)
1 defmodule Credo.CLI.Output.FirstRunHint do 2 alias Credo.CLI.Output 3 alias Credo.CLI.Output.UI 4 alias Credo.Execution 5 6 @lots_of_issue_threshold 30 7 @command_padding 40 8 @category_count 5 9 10 def call(exec) do 11 term_width = Output.term_columns() 12 issues = Execution.get_issues(exec) 13 14 headline = " 8< " 15 bar = String.pad_leading("", div(term_width - String.length(headline), 2), "-") 16 17 UI.puts() 18 UI.puts() 19 UI.puts([:magenta, :bright, "#{bar} 8< #{bar}"]) 20 UI.puts() 21 UI.puts() 22 23 issue_count = Enum.count(issues) 24 25 readability_issue_count = 26 issues 27 |> Enum.filter(&(&1.category == :readability)) 28 |> Enum.count() 29 30 relative_issue_count_per_category = div(issue_count, @category_count) 31 32 mostly_readability_issues = 33 readability_issue_count >= div(@lots_of_issue_threshold, 2) && 34 readability_issue_count > relative_issue_count_per_category * 2 35 36 readability_hint = 37 if mostly_readability_issues do 38 [ 39 """ 40 41 While not recommended, you could simply start ignoring issues for the time being: 42 43 """, 44 :cyan, 45 String.pad_trailing(" mix credo --ignore readability", @command_padding), 46 :faint, 47 "# exclude checks matching a given phrase", 48 "\n", 49 :reset 50 ] 51 else 52 [] 53 end 54 55 if issue_count >= @lots_of_issue_threshold do 56 UI.puts([ 57 :reset, 58 :orange, 59 """ 60 # Where to start? 61 """, 62 :reset, 63 """ 64 65 That's a lot of issues to deal with at once. 66 """, 67 readability_hint 68 ]) 69 else 70 UI.puts([ 71 :reset, 72 :orange, 73 """ 74 # How to introduce Credo 75 """, 76 :reset, 77 """ 78 79 This is looking pretty already! You can probably just fix the issues above in one go. 80 81 """, 82 readability_hint 83 ]) 84 end 85 86 print_lots_of_issues(exec) 87 88 UI.puts([ 89 :reset, 90 :orange, 91 """ 92 93 ## Every project is different 94 """, 95 :reset, 96 """ 97 98 Introducing code analysis to an existing codebase should not be about following any 99 "best practice" in particular, it should be about helping you to get to know the ropes 100 and make the changes you want. 101 102 Try the options outlined above to see which one is working for this project! 103 """ 104 ]) 105 end 106 107 defp print_lots_of_issues(exec) do 108 working_dir = Execution.working_dir(exec) 109 now = now() 110 default_branch = default_branch(working_dir) 111 latest_commit_on_default_branch = latest_commit_on_default_branch(working_dir) 112 latest_tag = latest_tag(working_dir) 113 114 current_branch = current_branch(working_dir) 115 116 if current_branch != default_branch do 117 UI.puts([ 118 :reset, 119 """ 120 You can use `diff` to only show the issues that were introduced on this branch: 121 """, 122 :cyan, 123 """ 124 125 mix credo diff #{default_branch} 126 127 """ 128 ]) 129 end 130 131 UI.puts([ 132 :reset, 133 :orange, 134 """ 135 ## Compare to a point in history 136 """, 137 :reset, 138 """ 139 140 Alternatively, you can use `diff` to only show the issues that were introduced after a certain tag or commit: 141 """ 142 ]) 143 144 if latest_tag do 145 UI.puts([ 146 :cyan, 147 String.pad_trailing(" mix credo diff #{latest_tag} ", @command_padding), 148 :faint, 149 "# use the latest tag", 150 "\n" 151 ]) 152 end 153 154 UI.puts([ 155 :reset, 156 :cyan, 157 String.pad_trailing( 158 " mix credo diff #{latest_commit_on_default_branch}", 159 @command_padding 160 ), 161 :faint, 162 "# use the current HEAD of #{default_branch}", 163 "\n\n", 164 :reset, 165 """ 166 Lastly, you can compare your working dir against this point in time: 167 168 """, 169 :cyan, 170 String.pad_trailing(" mix credo diff --since #{now}", @command_padding), 171 :faint, 172 "# use the current date", 173 "\n" 174 ]) 175 end 176 177 defp latest_tag(working_dir) do 178 case System.cmd("git", ~w"rev-list --tags --max-count=1", cd: working_dir) do 179 {"", 0} -> 180 nil 181 182 {latest_tag_sha1, 0} -> 183 case System.cmd("git", ~w"describe --tags #{latest_tag_sha1}", cd: working_dir) do 184 {tagname, 0} -> String.trim(tagname) 185 _ -> nil 186 end 187 188 _ -> 189 nil 190 end 191 end 192 193 defp current_branch(working_dir) do 194 case System.cmd("git", ~w"rev-parse --abbrev-ref HEAD", cd: working_dir) do 195 {output, 0} -> String.trim(output) 196 _ -> nil 197 end 198 end 199 200 defp default_branch(working_dir) do 201 remote_name = default_remote_name(working_dir) 202 203 case System.cmd("git", ~w"symbolic-ref refs/remotes/#{remote_name}/HEAD", cd: working_dir) do 204 {output, 0} -> ~r"refs/remotes/#{remote_name}/(.+)$" |> Regex.run(output) |> Enum.at(1) 205 _ -> nil 206 end 207 end 208 209 defp default_remote_name(_working_dir) do 210 "origin" 211 end 212 213 defp latest_commit_on_default_branch(working_dir) do 214 case System.cmd( 215 "git", 216 ~w"rev-parse --short #{default_remote_name(working_dir)}/#{default_branch(working_dir)}", 217 cd: working_dir 218 ) do 219 {output, 0} -> String.trim(output) 220 _ -> nil 221 end 222 end 223 224 defp now do 225 %{year: year, month: month, day: day} = DateTime.utc_now() 226 227 "#{year}-#{pad(month)}-#{pad(day)}" 228 end 229 230 defp pad(number) when number < 10, do: "0#{number}" 231 defp pad(number), do: to_string(number) 232 end