unless_with_else.ex (1934B)
1 defmodule Credo.Check.Refactor.UnlessWithElse do 2 use Credo.Check, 3 base_priority: :high, 4 explanations: [ 5 check: """ 6 An `unless` block should not contain an else block. 7 8 So while this is fine: 9 10 unless allowed? do 11 raise "Not allowed!" 12 end 13 14 This should be refactored: 15 16 unless allowed? do 17 raise "Not allowed!" 18 else 19 proceed_as_planned() 20 end 21 22 to look like this: 23 24 if allowed? do 25 proceed_as_planned() 26 else 27 raise "Not allowed!" 28 end 29 30 The reason for this is not a technical but a human one. The `else` in this 31 case will be executed when the condition is met, which is the opposite of 32 what the wording seems to imply. 33 """ 34 ] 35 36 @doc false 37 @impl true 38 def run(%SourceFile{} = source_file, params) do 39 issue_meta = IssueMeta.for(source_file, params) 40 41 Credo.Code.prewalk(source_file, &traverse(&1, &2, issue_meta)) 42 end 43 44 defp traverse({:@, _, [{:unless, _, _}]}, issues, _issue_meta) do 45 {nil, issues} 46 end 47 48 # TODO: consider for experimental check front-loader (ast) 49 # NOTE: we have to exclude the cases matching the above clause! 50 defp traverse({:unless, meta, _arguments} = ast, issues, issue_meta) do 51 new_issue = issue_for_else_block(Credo.Code.Block.else_block_for!(ast), meta, issue_meta) 52 53 {ast, issues ++ List.wrap(new_issue)} 54 end 55 56 defp traverse(ast, issues, _issue_meta) do 57 {ast, issues} 58 end 59 60 defp issue_for_else_block(nil, _meta, _issue_meta), do: nil 61 62 defp issue_for_else_block(_else_block, meta, issue_meta) do 63 issue_for(issue_meta, meta[:line], "unless") 64 end 65 66 defp issue_for(issue_meta, line_no, trigger) do 67 format_issue( 68 issue_meta, 69 message: "Unless conditions should avoid having an `else` block.", 70 trigger: trigger, 71 line_no: line_no 72 ) 73 end 74 end