priority.ex (3756B)
1 defmodule Credo.Priority do 2 @moduledoc false 3 4 # In Credo each Issue is given a priority to differentiate issues by a second 5 # dimension next to their Category. 6 7 alias Credo.Code.Module 8 alias Credo.Code.Parameters 9 alias Credo.Code.Scope 10 alias Credo.SourceFile 11 12 @def_ops [:def, :defp, :defmacro] 13 @many_functions_count 5 14 15 @priority_names_map %{ 16 "ignore" => -100, 17 "low" => -10, 18 "normal" => 1, 19 "high" => +10, 20 "higher" => +20 21 } 22 23 @doc "Converts a given priority name to a numerical priority" 24 def to_integer(nil), do: 0 25 26 def to_integer(value) when is_number(value), do: value 27 28 def to_integer(string) when is_binary(string) do 29 case Integer.parse(string) do 30 :error -> string |> String.to_atom() |> to_integer() 31 {value, ""} -> value 32 {_value, _rest} -> raise "Got an invalid priority: #{inspect(string)}" 33 end 34 end 35 36 def to_integer(key) when is_atom(key) do 37 @priority_names_map[to_string(key)] || raise "Got an invalid priority: #{inspect(key)}" 38 end 39 40 def to_atom(priority) 41 42 def to_atom(priority) when is_number(priority) do 43 cond do 44 priority > 19 -> :higher 45 priority in 10..19 -> :high 46 priority in 0..9 -> :normal 47 priority in -10..-1 -> :low 48 priority < -10 -> :ignore 49 end 50 end 51 52 def to_atom(%{priority: priority}), do: to_atom(priority) 53 54 def to_atom(priority) when is_atom(priority), do: priority 55 56 def to_atom(_), do: nil 57 58 def scope_priorities(%SourceFile{} = source_file) do 59 line_count = 60 source_file 61 |> SourceFile.lines() 62 |> length() 63 64 empty_priorities = Enum.map(1..line_count, fn _ -> [] end) 65 66 priority_list = Credo.Code.prewalk(source_file, &traverse/2, empty_priorities) 67 68 base_map = make_base_map(priority_list, source_file) 69 70 lookup = Enum.into(base_map, %{}) 71 72 Enum.into(base_map, %{}, fn {scope_name, prio} -> 73 names = String.split(scope_name, ".") 74 75 if names |> List.last() |> String.match?(~r/^[a-z]/) do 76 mod_name = 77 names 78 |> Enum.slice(0..(length(names) - 2)) 79 |> Enum.join(".") 80 81 mod_prio = lookup[mod_name] || 0 82 83 {scope_name, prio + mod_prio} 84 else 85 {scope_name, prio} 86 end 87 end) 88 end 89 90 defp make_base_map(priority_list, %SourceFile{} = source_file) do 91 ast = SourceFile.ast(source_file) 92 scope_info_list = Scope.scope_info_list(ast) 93 94 priority_list 95 |> Enum.with_index() 96 |> Enum.map(fn {list, index} -> 97 case list do 98 [] -> 99 nil 100 101 _ -> 102 {_, scope_name} = Scope.name_from_scope_info_list(scope_info_list, index + 1) 103 {scope_name, Enum.sum(list)} 104 end 105 end) 106 |> Enum.reject(&is_nil/1) 107 end 108 109 defp traverse({:defmodule, meta, _} = ast, acc) do 110 added_prio = priority_for(ast) 111 112 {ast, List.update_at(acc, meta[:line] - 1, &(&1 ++ [added_prio]))} 113 end 114 115 for op <- @def_ops do 116 defp traverse({unquote(op), meta, arguments} = ast, acc) 117 when is_list(arguments) do 118 added_prio = priority_for(ast) 119 120 case arguments do 121 [{_func_name, _meta, _func_arguments}, _do_block] -> 122 {ast, List.update_at(acc, meta[:line] - 1, &(&1 ++ [added_prio]))} 123 124 _ -> 125 {ast, acc} 126 end 127 end 128 end 129 130 defp traverse(ast, acc) do 131 {ast, acc} 132 end 133 134 defp priority_for({:defmodule, _, _} = ast) do 135 if Module.def_count(ast) >= @many_functions_count do 136 2 137 else 138 1 139 end 140 end 141 142 for op <- @def_ops do 143 defp priority_for({unquote(op), _, arguments} = ast) 144 when is_list(arguments) do 145 count = Parameters.count(ast) 146 147 cond do 148 count == 0 -> 0 149 count in 1..2 -> 1 150 count in 3..4 -> 2 151 true -> 3 152 end 153 end 154 end 155 end