collector.ex (2603B)
1 defmodule Credo.Check.Consistency.MultiAliasImportRequireUse.Collector do 2 @moduledoc false 3 4 use Credo.Check.Consistency.Collector 5 6 @directives [:alias, :import, :require, :use] 7 8 def collect_matches(source_file, _params) do 9 source_file 10 |> Credo.Code.prewalk(&traverse/2, []) 11 |> group_usages 12 |> count_occurrences 13 end 14 15 def find_locations_not_matching(expected, source_file) do 16 source_file 17 |> Credo.Code.prewalk(&traverse/2, []) 18 |> group_usages 19 |> drop_locations(expected) 20 end 21 22 defp traverse({directive, meta, arguments} = ast, acc) 23 when directive in @directives do 24 aliases = 25 case arguments do 26 [{:__aliases__, _, nested_modules}] when length(nested_modules) > 1 -> 27 base_name = Enum.slice(nested_modules, 0..-2) 28 {:single, base_name} 29 30 [{{:., _, [{:__aliases__, _, _namespaces}, :{}]}, _, _nested_aliases}] -> 31 :multi 32 33 _ -> 34 nil 35 end 36 37 if aliases do 38 {ast, [{directive, aliases, meta[:line]} | acc]} 39 else 40 {ast, acc} 41 end 42 end 43 44 defp traverse(ast, acc), do: {ast, acc} 45 46 defp group_usages(usages) do 47 split_with(usages, fn 48 {_directive, :multi, _line_no} -> true 49 _ -> false 50 end) 51 end 52 53 defp count_occurrences({multi, single}) do 54 stats = [ 55 multi: Enum.count(multi), 56 single: single |> multiple_single_locations |> Enum.count() 57 ] 58 59 stats 60 |> Enum.filter(fn {_, count} -> count > 0 end) 61 |> Enum.into(%{}) 62 end 63 64 defp drop_locations({_, single}, :multi), do: multiple_single_locations(single) 65 66 defp drop_locations({multi, _}, :single), do: multi_locations(multi) 67 68 defp multi_locations(multi_usages) do 69 Enum.map(multi_usages, fn {_directive, :multi, line_no} -> line_no end) 70 end 71 72 defp multiple_single_locations(single_usages) do 73 single_usages 74 |> Enum.group_by(fn {directive, base_name, _line_no} -> 75 {directive, base_name} 76 end) 77 |> Enum.filter(fn {_grouped_by, occurrences} -> 78 Enum.count(occurrences) > 1 79 end) 80 |> Enum.map(fn {_grouped_by, [{_, _, line_no} | _]} -> line_no end) 81 end 82 83 # Enum.split_with/2 is not available on Elixir < 1.4 84 # see https://github.com/elixir-lang/elixir/blob/v1.4.4/lib/elixir/lib/enum.ex#L1620 85 defp split_with(enumerable, fun) when is_function(fun, 1) do 86 {acc1, acc2} = 87 Enum.reduce(enumerable, {[], []}, fn entry, {acc1, acc2} -> 88 if fun.(entry) do 89 {[entry | acc1], acc2} 90 else 91 {acc1, [entry | acc2]} 92 end 93 end) 94 95 {:lists.reverse(acc1), :lists.reverse(acc2)} 96 end 97 end