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