redundant_with_clause_result.ex (2459B)
1 defmodule Credo.Check.Refactor.RedundantWithClauseResult do 2 use Credo.Check, 3 base_priority: :high, 4 explanations: [ 5 check: ~S""" 6 `with` statements are useful when you need to chain a sequence 7 of pattern matches, stopping at the first one that fails. 8 9 If the match of the last clause in a `with` statement is identical to the expression in the 10 in its body, the code should be refactored to remove the redundant expression. 11 12 This should be refactored: 13 14 with {:ok, map} <- check(input), 15 {:ok, result} <- something(map) do 16 {:ok, result} 17 end 18 19 to look like this: 20 21 with {:ok, map} <- check(input) do 22 something(map) 23 end 24 """ 25 ] 26 27 alias Credo.Code.Block 28 29 require Logger 30 31 @redundant_with "the `with` statement is redundant" 32 @redundant_clause "the last clause in `with` is redundant" 33 34 @doc false 35 @impl true 36 def run(%SourceFile{} = source_file, params) do 37 issue_meta = IssueMeta.for(source_file, params) 38 Credo.Code.prewalk(source_file, &traverse(&1, &2, issue_meta)) 39 end 40 41 defp traverse({:with, meta, clauses_and_body} = ast, issues, issue_meta) do 42 case split(clauses_and_body) do 43 {:ok, clauses, body} -> 44 case issue_for({clauses, body}, meta, issue_meta) do 45 nil -> {ast, issues} 46 issue -> {ast, [issue | issues]} 47 end 48 49 :error -> 50 {ast, issues} 51 end 52 end 53 54 defp traverse(ast, issues, _issue_meta) do 55 {ast, issues} 56 end 57 58 defp split(clauses_and_body) do 59 case Block.do_block?(clauses_and_body) and not Block.else_block?(clauses_and_body) do 60 false -> 61 :error 62 63 true -> 64 {clauses, [body]} = Enum.split(clauses_and_body, -1) 65 {:ok, clauses, Keyword.get(body, :do)} 66 end 67 end 68 69 defp issue_for({clauses, body}, meta, issue_meta) do 70 case {redundant?(List.last(clauses), body), length(clauses)} do 71 {true, 1} -> 72 format_issue(issue_meta, message: @redundant_with, line_no: meta[:line]) 73 74 {true, _length} -> 75 format_issue(issue_meta, message: @redundant_clause, line_no: meta[:line]) 76 77 _else -> 78 nil 79 end 80 end 81 82 defp redundant?({:<-, _meta, [body, _expr]}, body), do: true 83 84 defp redundant?({:<-, _meta, [match, _expr]}, body) do 85 Credo.Code.remove_metadata(match) == Credo.Code.remove_metadata(body) 86 end 87 88 defp redundant?(_last_clause, _body), do: false 89 end