/
double_boolean_negation.ex
76 lines (60 loc) · 2.17 KB
/
double_boolean_negation.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
defmodule Credo.Check.Refactor.DoubleBooleanNegation do
use Credo.Check,
base_priority: :low,
tags: [:controversial],
explanations: [
check: """
Having double negations in your code can obscure the parameter's original value.
# NOT preferred
!!var
This will return `false` for `false` and `nil`, and `true` for anything else.
At first this seems like an extra clever shorthand to cast anything truthy to
`true` and anything non-truthy to `false`. But in most scenarios you want to
be explicit about your input parameters (because it is easier to reason about
edge-cases, code-paths and tests).
Also: `nil` and `false` do mean two different things.
A scenario where you want this kind of flexibility, however, is parsing
external data, e.g. a third party JSON-API where a value is sometimes `null`
and sometimes `false` and you want to normalize that before handing it down
in your program.
In these case, you would be better off making the cast explicit by introducing
a helper function:
# preferred
defp present?(nil), do: false
defp present?(false), do: false
defp present?(_), do: true
This makes your code more explicit than relying on the implications of `!!`.
"""
]
@doc false
@impl true
def run(%SourceFile{} = source_file, params) do
issue_meta = IssueMeta.for(source_file, params)
Credo.Code.prewalk(source_file, &traverse(&1, &2, issue_meta))
end
# Checking for `!!`
defp traverse({:!, meta, [{:!, _, ast}]}, issues, issue_meta) do
issue =
format_issue(
issue_meta,
message: "Double boolean negation found.",
trigger: "!!",
line_no: meta[:line]
)
{ast, [issue | issues]}
end
# Checking for `not not`
defp traverse({:not, meta, [{:not, _, ast}]}, issues, issue_meta) do
issue =
format_issue(
issue_meta,
message: "Double boolean negation found.",
trigger: "not not",
line_no: meta[:line]
)
{ast, [issue | issues]}
end
defp traverse(ast, issues, _issue_meta) do
{ast, issues}
end
end