append_single_item.ex (1512B)
1 defmodule Credo.Check.Refactor.AppendSingleItem do 2 use Credo.Check, 3 base_priority: :low, 4 tags: [:controversial], 5 explanations: [ 6 check: """ 7 When building up large lists, it is faster to prepend than 8 append. Therefore: It is sometimes best to prepend to the list 9 during iteration and call Enum.reverse/1 at the end, as it is quite 10 fast. 11 12 Example: 13 14 list = list_so_far ++ [new_item] 15 16 # refactoring it like this can make the code faster: 17 18 list = [new_item] ++ list_so_far 19 # ... 20 Enum.reverse(list) 21 22 """ 23 ] 24 25 @doc false 26 @impl true 27 def run(%SourceFile{} = source_file, params) do 28 issue_meta = IssueMeta.for(source_file, params) 29 30 Credo.Code.prewalk(source_file, &traverse(&1, &2, issue_meta)) 31 end 32 33 # [a] ++ b is OK 34 # TODO: consider for experimental check front-loader (ast) 35 defp traverse({:++, _, [[_], _]} = ast, issues, _issue_meta) do 36 {ast, issues} 37 end 38 39 # a ++ [b] is not 40 defp traverse({:++, meta, [_, [_]]} = ast, issues, issue_meta) do 41 {ast, [issue_for(issue_meta, meta[:line], :++) | issues]} 42 end 43 44 defp traverse(ast, issues, _issue_meta) do 45 {ast, issues} 46 end 47 48 defp issue_for(issue_meta, line_no, trigger) do 49 format_issue( 50 issue_meta, 51 message: "Appending a single item to a list is inefficient, use [head | tail] 52 notation (and Enum.reverse/1 when order matters)", 53 trigger: trigger, 54 line_no: line_no 55 ) 56 end 57 end