with_custom_tagged_tuple.ex (2577B)
1 defmodule Credo.Check.Readability.WithCustomTaggedTuple do 2 use Credo.Check, 3 category: :warning, 4 base_priority: :low, 5 explanations: [ 6 check: """ 7 Avoid using custom tags for error reporting from `with` macros. 8 9 This code injects placeholder tags such as `:resource` and `:authz` for the purpose of error 10 reporting. 11 12 with {:resource, {:ok, resource}} <- {:resource, Resource.fetch(user)}, 13 {:authz, :ok} <- {:authz, Resource.authorize(resource, user)} do 14 do_something_with(resource) 15 else 16 {:resource, _} -> {:error, :not_found} 17 {:authz, _} -> {:error, :unauthorized} 18 end 19 20 Instead, extract each validation into a separate helper function which returns error 21 information immediately: 22 23 defp find_resource(user) do 24 with :error <- Resource.fetch(user), do: {:error, :not_found} 25 end 26 27 defp authorize(resource, user) do 28 with :error <- Resource.authorize(resource, user), do: {:error, :unauthorized} 29 end 30 31 At this point, the validation chain in `with` becomes clearer and easier to understand: 32 33 with {:ok, resource} <- find_resource(user), 34 :ok <- authorize(resource, user), 35 do: do_something(user) 36 37 Like all `Readability` issues, this one is not a technical concern. 38 But you can improve the odds of others reading and liking your code by making 39 it easier to follow. 40 """ 41 ] 42 43 @doc false 44 @impl true 45 def run(%SourceFile{} = source_file, params \\ []) do 46 source_file 47 |> errors() 48 |> Enum.map(&credo_error(&1, IssueMeta.for(source_file, params))) 49 end 50 51 defp errors(source_file) do 52 {_ast, errors} = Macro.prewalk(Credo.Code.ast(source_file), MapSet.new(), &traverse/2) 53 Enum.sort_by(errors, &{&1.line, &1.column}) 54 end 55 56 defp traverse({:with, _meta, args}, errors) do 57 errors = 58 args 59 |> Stream.map(&placeholder/1) 60 |> Enum.reject(&is_nil/1) 61 |> Enum.into(errors) 62 63 {args, errors} 64 end 65 66 defp traverse(ast, state), do: {ast, state} 67 68 defp placeholder({:<-, meta, [{placeholder, _}, {placeholder, _}]}) when is_atom(placeholder), 69 do: %{placeholder: placeholder, line: meta[:line], column: meta[:column]} 70 71 defp placeholder(_), do: nil 72 73 defp credo_error(error, issue_meta) do 74 format_issue( 75 issue_meta, 76 message: "Invalid usage of placeholder `#{inspect(error.placeholder)}` in with", 77 line_no: error.line, 78 column: error.column 79 ) 80 end 81 end