operation_on_same_values.ex (2770B)
1 defmodule Credo.Check.Warning.OperationOnSameValues do 2 use Credo.Check, 3 base_priority: :high, 4 explanations: [ 5 check: """ 6 Operations on the same values always yield the same result and therefore make 7 little sense in production code. 8 9 Examples: 10 11 x == x # always returns true 12 x <= x # always returns true 13 x >= x # always returns true 14 x != x # always returns false 15 x > x # always returns false 16 y / y # always returns 1 17 y - y # always returns 0 18 19 In practice they are likely the result of a debugging session or were made by 20 mistake. 21 """ 22 ] 23 24 @def_ops [:def, :defp, :defmacro] 25 @ops ~w(== >= <= != > < / -)a 26 @ops_and_constant_results [ 27 {:==, "Comparison", true}, 28 {:>=, "Comparison", true}, 29 {:<=, "Comparison", true}, 30 {:!=, "Comparison", false}, 31 {:>, "Comparison", false}, 32 {:<, "Comparison", false}, 33 {:/, "Operation", 1}, 34 {:-, "Operation", 0} 35 ] 36 37 @doc false 38 @impl true 39 def run(%SourceFile{} = source_file, params) do 40 issue_meta = IssueMeta.for(source_file, params) 41 42 Credo.Code.prewalk(source_file, &traverse(&1, &2, issue_meta)) 43 end 44 45 # TODO: consider for experimental check front-loader (ast) 46 for op <- @def_ops do 47 # exclude def arguments for operators 48 defp traverse( 49 {unquote(op), _meta, [{op, _, _} | rest]}, 50 issues, 51 _issue_meta 52 ) 53 when op in @ops do 54 {rest, issues} 55 end 56 end 57 58 for {op, operation_name, constant_result} <- @ops_and_constant_results do 59 defp traverse({unquote(op), meta, [lhs, rhs]} = ast, issues, issue_meta) do 60 if variable_or_mod_attribute?(lhs) && 61 Credo.Code.remove_metadata(lhs) == Credo.Code.remove_metadata(rhs) do 62 new_issue = 63 issue_for( 64 issue_meta, 65 meta[:line], 66 unquote(op), 67 unquote(operation_name), 68 unquote(constant_result) 69 ) 70 71 {ast, issues ++ [new_issue]} 72 else 73 {ast, issues} 74 end 75 end 76 end 77 78 defp variable_or_mod_attribute?({atom, _meta, nil}) when is_atom(atom), do: true 79 defp variable_or_mod_attribute?({:@, _meta, list}) when is_list(list), do: true 80 defp variable_or_mod_attribute?(_), do: false 81 82 # exclude @spec definitions 83 defp traverse({:@, _meta, [{:spec, _, _} | _]}, issues, _issue_meta) do 84 {nil, issues} 85 end 86 87 defp traverse(ast, issues, _issue_meta) do 88 {ast, issues} 89 end 90 91 defp issue_for(issue_meta, line_no, trigger, operation, constant_result) do 92 format_issue( 93 issue_meta, 94 message: "#{operation} will always return #{constant_result}.", 95 trigger: trigger, 96 line_no: line_no 97 ) 98 end 99 end