cond_statements.ex (2039B)
1 defmodule Credo.Check.Refactor.CondStatements do 2 use Credo.Check, 3 explanations: [ 4 check: """ 5 Each cond statement should have 3 or more statements including the 6 "always true" statement. 7 8 Consider an `if`/`else` construct if there is only one condition and the 9 "always true" statement, since it will more accessible to programmers 10 new to the codebase (and possibly new to Elixir). 11 12 Example: 13 14 cond do 15 x == y -> 0 16 true -> 1 17 end 18 19 # should be written as 20 21 if x == y do 22 0 23 else 24 1 25 end 26 27 """ 28 ] 29 30 @doc false 31 @impl true 32 def run(%SourceFile{} = source_file, params) do 33 issue_meta = IssueMeta.for(source_file, params) 34 35 Credo.Code.prewalk(source_file, &traverse(&1, &2, issue_meta)) 36 end 37 38 # TODO: consider for experimental check front-loader (ast) 39 defp traverse({:cond, meta, arguments} = ast, issues, issue_meta) do 40 conditions = 41 arguments 42 |> Credo.Code.Block.do_block_for!() 43 |> List.wrap() 44 45 count = Enum.count(conditions) 46 47 should_be_written_as_if_else_block? = 48 count <= 2 && contains_always_matching_condition?(conditions) 49 50 if should_be_written_as_if_else_block? do 51 {ast, issues ++ [issue_for(issue_meta, meta[:line], :cond)]} 52 else 53 {ast, issues} 54 end 55 end 56 57 defp traverse(ast, issues, _issue_meta) do 58 {ast, issues} 59 end 60 61 defp contains_always_matching_condition?(conditions) do 62 Enum.any?(conditions, fn 63 {:->, _meta, [[{name, _meta2, nil}], _args]} when is_atom(name) -> 64 name |> to_string |> String.starts_with?("_") 65 66 {:->, _meta, [[true], _args]} -> 67 true 68 69 _ -> 70 false 71 end) 72 end 73 74 defp issue_for(issue_meta, line_no, trigger) do 75 format_issue( 76 issue_meta, 77 message: 78 "Cond statements should contain at least two conditions besides `true`, consider using `if` instead.", 79 trigger: trigger, 80 line_no: line_no 81 ) 82 end 83 end