map_into.ex (2940B)
1 defmodule Credo.Check.Refactor.MapInto do 2 # only available in Elixir < 1.8 since performance improvements have since made this check obsolete 3 use Credo.Check, 4 base_priority: :high, 5 elixir_version: "< 1.8.0", 6 explanations: [ 7 check: """ 8 `Enum.into/3` is more efficient than `Enum.map/2 |> Enum.into/2`. 9 10 This should be refactored: 11 12 [:apple, :banana, :carrot] 13 |> Enum.map(&({&1, to_string(&1)})) 14 |> Enum.into(%{}) 15 16 to look like this: 17 18 Enum.into([:apple, :banana, :carrot], %{}, &({&1, to_string(&1)})) 19 20 The reason for this is performance, because the separate calls to 21 `Enum.map/2` and `Enum.into/2` require two iterations whereas 22 `Enum.into/3` only requires one. 23 24 **NOTE**: This check is only available in Elixir < 1.8 since performance 25 improvements have since made this check obsolete. 26 """ 27 ] 28 29 @doc false 30 @impl true 31 def run(%SourceFile{} = source_file, params) do 32 issue_meta = IssueMeta.for(source_file, params) 33 34 Credo.Code.prewalk(source_file, &traverse(&1, &2, issue_meta)) 35 end 36 37 defp traverse( 38 {{:., _, [{:__aliases__, meta, [:Enum]}, :into]}, _, 39 [{{:., _, [{:__aliases__, _, [:Enum]}, :map]}, _, _}, _]} = ast, 40 issues, 41 issue_meta 42 ) do 43 new_issue = issue_for(issue_meta, meta[:line], "map_into") 44 45 {ast, issues ++ List.wrap(new_issue)} 46 end 47 48 defp traverse( 49 {:|>, meta, 50 [ 51 {{:., _, [{:__aliases__, _, [:Enum]}, :map]}, _, _}, 52 {{:., _, [{:__aliases__, _, [:Enum]}, :into]}, _, _} 53 ]} = ast, 54 issues, 55 issue_meta 56 ) do 57 new_issue = issue_for(issue_meta, meta[:line], "map_into") 58 59 {ast, issues ++ List.wrap(new_issue)} 60 end 61 62 defp traverse( 63 {{:., meta, [{:__aliases__, _, [:Enum]}, :into]}, _, 64 [ 65 {:|>, _, [_, {{:., _, [{:__aliases__, _, [:Enum]}, :map]}, _, _}]}, 66 _ 67 ]} = ast, 68 issues, 69 issue_meta 70 ) do 71 new_issue = issue_for(issue_meta, meta[:line], "map_into") 72 73 {ast, issues ++ List.wrap(new_issue)} 74 end 75 76 defp traverse( 77 {:|>, meta, 78 [ 79 {:|>, _, 80 [ 81 _, 82 {{:., _, [{:__aliases__, _, [:Enum]}, :map]}, _, _} 83 ]}, 84 {{:., _, [{:__aliases__, _, [:Enum]}, :into]}, _, into_args} 85 ]} = ast, 86 issues, 87 issue_meta 88 ) 89 when length(into_args) == 1 do 90 new_issue = issue_for(issue_meta, meta[:line], "map_into") 91 92 {ast, issues ++ List.wrap(new_issue)} 93 end 94 95 defp traverse(ast, issues, _issue_meta) do 96 {ast, issues} 97 end 98 99 defp issue_for(issue_meta, line_no, trigger) do 100 format_issue( 101 issue_meta, 102 message: "`Enum.into/3` is more efficient than `Enum.map/2 |> Enum.into/2`", 103 trigger: trigger, 104 line_no: line_no 105 ) 106 end 107 end