zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

alias_order.ex (5751B)


      1 defmodule Credo.Check.Readability.AliasOrder do
      2   use Credo.Check,
      3     base_priority: :low,
      4     explanations: [
      5       check: """
      6       Alphabetically ordered lists are more easily scannable by the read.
      7 
      8           # preferred
      9 
     10           alias ModuleA
     11           alias ModuleB
     12           alias ModuleC
     13 
     14           # NOT preferred
     15 
     16           alias ModuleA
     17           alias ModuleC
     18           alias ModuleB
     19 
     20       Alias should be alphabetically ordered among their group:
     21 
     22           # preferred
     23 
     24           alias ModuleC
     25           alias ModuleD
     26 
     27           alias ModuleA
     28           alias ModuleB
     29 
     30           # NOT preferred
     31 
     32           alias ModuleC
     33           alias ModuleD
     34 
     35           alias ModuleB
     36           alias ModuleA
     37 
     38       Like all `Readability` issues, this one is not a technical concern.
     39       But you can improve the odds of others reading and liking your code by making
     40       it easier to follow.
     41       """
     42     ]
     43 
     44   alias Credo.Code.Name
     45 
     46   @doc false
     47   @impl true
     48   def run(%SourceFile{} = source_file, params) do
     49     issue_meta = IssueMeta.for(source_file, params)
     50 
     51     Credo.Code.prewalk(source_file, &traverse(&1, &2, issue_meta))
     52   end
     53 
     54   defp traverse({:defmodule, _, _} = ast, issues, issue_meta) do
     55     new_issues =
     56       ast
     57       |> extract_alias_groups()
     58       |> Enum.reduce([], &traverse_groups(&1, &2, issue_meta))
     59 
     60     {ast, issues ++ new_issues}
     61   end
     62 
     63   defp traverse(ast, issues, _), do: {ast, issues}
     64 
     65   defp traverse_groups(group, acc, issue_meta) do
     66     group
     67     |> Enum.chunk_every(2, 1)
     68     |> Enum.reduce_while(nil, &process_group/2)
     69     |> case do
     70       nil ->
     71         acc
     72 
     73       line ->
     74         acc ++ [issue_for(issue_meta, line)]
     75     end
     76   end
     77 
     78   defp process_group([{line_no, mod_list_second, a}, {_line_no, _mod_list_second, b}], _)
     79        when a > b do
     80     module =
     81       case mod_list_second do
     82         {base, _} -> base
     83         value -> value
     84       end
     85 
     86     issue_opts = issue_opts(line_no, module, module)
     87 
     88     {:halt, issue_opts}
     89   end
     90 
     91   defp process_group([{line_no1, mod_list_first, _}, {line_no2, mod_list_second, _}], _) do
     92     issue_opts =
     93       cond do
     94         issue = inner_group_order_issue(line_no1, mod_list_first) ->
     95           issue
     96 
     97         issue = inner_group_order_issue(line_no2, mod_list_second) ->
     98           issue
     99 
    100         true ->
    101           nil
    102       end
    103 
    104     if issue_opts do
    105       {:halt, issue_opts}
    106     else
    107       {:cont, nil}
    108     end
    109   end
    110 
    111   defp process_group([{line_no1, mod_list_first, _}], _) do
    112     if issue_opts = inner_group_order_issue(line_no1, mod_list_first) do
    113       {:halt, issue_opts}
    114     else
    115       {:cont, nil}
    116     end
    117   end
    118 
    119   defp process_group(_, _), do: {:cont, nil}
    120 
    121   defp inner_group_order_issue(_line_no, {_base, []}), do: nil
    122 
    123   defp inner_group_order_issue(line_no, {base, mod_list}) do
    124     downcased_mod_list = Enum.map(mod_list, &String.downcase(to_string(&1)))
    125     sorted_downcased_mod_list = Enum.sort(downcased_mod_list)
    126 
    127     if downcased_mod_list != sorted_downcased_mod_list do
    128       issue_opts(line_no, base, mod_list, downcased_mod_list, sorted_downcased_mod_list)
    129     end
    130   end
    131 
    132   defp issue_opts(line_no, base, mod_list, downcased_mod_list, sorted_downcased_mod_list) do
    133     trigger =
    134       downcased_mod_list
    135       |> Enum.with_index()
    136       |> Enum.find_value(fn {downcased_mod_entry, index} ->
    137         if downcased_mod_entry != Enum.at(sorted_downcased_mod_list, index) do
    138           Enum.at(mod_list, index)
    139         end
    140       end)
    141 
    142     issue_opts(line_no, [base, trigger], trigger)
    143   end
    144 
    145   defp issue_opts(line_no, module, trigger) do
    146     %{
    147       line_no: line_no,
    148       trigger: trigger,
    149       module: module
    150     }
    151   end
    152 
    153   defp extract_alias_groups({:defmodule, _, _} = ast) do
    154     ast
    155     |> Credo.Code.postwalk(&find_alias_groups/2)
    156     |> Enum.reverse()
    157     |> Enum.reduce([[]], fn definition, acc ->
    158       case definition do
    159         nil ->
    160           [[]] ++ acc
    161 
    162         definition ->
    163           [group | groups] = acc
    164           [group ++ [definition]] ++ groups
    165       end
    166     end)
    167     |> Enum.reverse()
    168   end
    169 
    170   defp find_alias_groups(
    171          {:alias, _, [{:__aliases__, meta, mod_list} | _]} = ast,
    172          aliases
    173        ) do
    174     compare_name = compare_name(ast)
    175     modules = [{meta[:line], {Name.full(mod_list), []}, compare_name}]
    176 
    177     accumulate_alias_into_group(ast, modules, meta[:line], aliases)
    178   end
    179 
    180   defp find_alias_groups(
    181          {:alias, _,
    182           [
    183             {{:., _, [{:__aliases__, meta, mod_list}, :{}]}, _, multi_mod_list}
    184           ]} = ast,
    185          aliases
    186        ) do
    187     multi_mod_list =
    188       multi_mod_list
    189       |> Enum.map(fn {:__aliases__, _, mod_list} -> mod_name(mod_list) end)
    190 
    191     compare_name = compare_name(ast)
    192     modules = [{meta[:line], {Name.full(mod_list), multi_mod_list}, compare_name}]
    193 
    194     nested_mod_line = meta[:line] + 1
    195     accumulate_alias_into_group(ast, modules, nested_mod_line, aliases)
    196   end
    197 
    198   defp find_alias_groups(ast, aliases), do: {ast, aliases}
    199 
    200   defp mod_name(mod_list) do
    201     Enum.map_join(mod_list, ".", &to_string/1)
    202   end
    203 
    204   defp compare_name(value) do
    205     value
    206     |> Macro.to_string()
    207     |> String.downcase()
    208     |> String.replace(~r/[\{\}]/, "")
    209     |> String.replace(~r/,.+/, "")
    210   end
    211 
    212   defp accumulate_alias_into_group(ast, modules, line, [{line_no, _, _} | _] = aliases)
    213        when line_no != 0 and line_no != line - 1 do
    214     {ast, modules ++ [nil] ++ aliases}
    215   end
    216 
    217   defp accumulate_alias_into_group(ast, modules, _, aliases) do
    218     {ast, modules ++ aliases}
    219   end
    220 
    221   defp issue_for(issue_meta, %{line_no: line_no, trigger: trigger, module: module}) do
    222     format_issue(
    223       issue_meta,
    224       message: "The alias `#{Name.full(module)}` is not alphabetically ordered among its group.",
    225       trigger: trigger,
    226       line_no: line_no
    227     )
    228   end
    229 end