Skip to content

Commit d066fc0

Browse files
authored
Merge pull request #1121 from quangngd/ignore_predicate_name_when_impl
2 parents 3fb97c5 + 1b905d5 commit d066fc0

File tree

2 files changed

+170
-23
lines changed

2 files changed

+170
-23
lines changed

lib/credo/check/readability/predicate_function_names.ex

+85-23
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,63 @@ defmodule Credo.Check.Readability.PredicateFunctionNames do
5252
def run(%SourceFile{} = source_file, params) do
5353
issue_meta = IssueMeta.for(source_file, params)
5454

55-
Credo.Code.prewalk(source_file, &traverse(&1, &2, issue_meta))
55+
issue_candidates = Credo.Code.prewalk(source_file, &traverse(&1, &2, issue_meta))
56+
57+
if issue_candidates == [] do
58+
[]
59+
else
60+
impl_list = Credo.Code.prewalk(source_file, &find_impls(&1, &2))
61+
62+
issue_candidates
63+
|> Enum.reject(fn {_, signature} -> signature in impl_list end)
64+
|> Enum.map(fn {issue, _} -> issue end)
65+
end
66+
end
67+
68+
defp find_impls({:__block__, _meta, args} = ast, impls) do
69+
block_impls = find_impls_in_block(args)
70+
{ast, block_impls ++ impls}
71+
end
72+
73+
defp find_impls(ast, impls) do
74+
{ast, impls}
75+
end
76+
77+
defp find_impls_in_block(block_args) when is_list(block_args) do
78+
block_args
79+
|> Enum.reduce([], &do_find_impls_in_block/2)
80+
end
81+
82+
defp do_find_impls_in_block({:@, _, [{:impl, _, [impl]}]}, acc) when impl != false do
83+
[:record_next_definition | acc]
84+
end
85+
86+
# def when
87+
defp do_find_impls_in_block({keyword, meta, [{:when, _, def_ast} | _]}, [
88+
:record_next_definition | impls
89+
])
90+
when keyword in @def_ops do
91+
do_find_impls_in_block({keyword, meta, def_ast}, [:record_next_definition | impls])
92+
end
93+
94+
# def 0 arity
95+
defp do_find_impls_in_block({keyword, _meta, [{name, _, nil} | _]}, [
96+
:record_next_definition | impls
97+
])
98+
when keyword in @def_ops do
99+
[{to_string(name), 0} | impls]
100+
end
101+
102+
# def n arity
103+
defp do_find_impls_in_block({keyword, _meta, [{name, _, args} | _]}, [
104+
:record_next_definition | impls
105+
])
106+
when keyword in @def_ops do
107+
[{to_string(name), length(args)} | impls]
108+
end
109+
110+
defp do_find_impls_in_block(_, acc) do
111+
acc
56112
end
57113

58114
for op <- @def_ops do
@@ -66,56 +122,62 @@ defmodule Credo.Check.Readability.PredicateFunctionNames do
66122
issues,
67123
issue_meta
68124
) do
69-
{ast, issues_for_definition(op, arguments, issues, issue_meta)}
125+
{ast, issues_candidate_for_definition(op, arguments, issues, issue_meta)}
70126
end
71127
end
72128

73129
defp traverse(ast, issues, _issue_meta) do
74130
{ast, issues}
75131
end
76132

77-
defp issues_for_definition(op, body, issues, issue_meta) do
78-
case Enum.at(body, 0) do
79-
{name, meta, nil} ->
80-
issues_for_name(op, name, meta, issues, issue_meta)
133+
defp issues_candidate_for_definition(op, [{name, meta, nil} | _], issues, issue_meta) do
134+
issues_candidate_for_definition(op, [{name, meta, []}], issues, issue_meta)
135+
end
81136

82-
{name, meta, [_ | _]} ->
83-
issues_for_name(op, name, meta, issues, issue_meta)
137+
defp issues_candidate_for_definition(op, [{name, meta, args} | _], issues, issue_meta) do
138+
issues_candidate_for_name(op, name, meta, issues, issue_meta, args)
139+
end
84140

85-
_ ->
86-
issues
87-
end
141+
defp issues_candidate_for_definition(_op, _, issues, _issue_meta) do
142+
issues
88143
end
89144

90-
defp issues_for_name(_op, {:unquote, _, [_ | _]} = _name, _meta, issues, _issue_meta) do
145+
defp issues_candidate_for_name(
146+
_op,
147+
{:unquote, _, [_ | _]} = _name,
148+
_meta,
149+
issues,
150+
_issue_meta,
151+
_args
152+
) do
91153
issues
92154
end
93155

94-
defp issues_for_name(op, name, meta, issues, issue_meta) do
156+
defp issues_candidate_for_name(op, name, meta, issues, issue_meta, args) do
95157
name = to_string(name)
96158

97159
cond do
98160
String.starts_with?(name, "is_") && String.ends_with?(name, "?") ->
99161
[
100-
issue_for(issue_meta, meta[:line], name, :predicate_and_question_mark)
162+
issue_candidate_for(issue_meta, meta[:line], name, args, :predicate_and_question_mark)
101163
| issues
102164
]
103165

104166
String.starts_with?(name, "is_") && op != :defmacro ->
105-
[issue_for(issue_meta, meta[:line], name, :only_predicate) | issues]
167+
[issue_candidate_for(issue_meta, meta[:line], name, args, :only_predicate) | issues]
106168

107169
true ->
108170
issues
109171
end
110172
end
111173

112-
defp issue_for(issue_meta, line_no, trigger, _) do
113-
format_issue(
114-
issue_meta,
115-
message:
116-
"Predicate function names should not start with 'is', and should end in a question mark.",
117-
trigger: trigger,
118-
line_no: line_no
119-
)
174+
defp issue_candidate_for(issue_meta, line_no, trigger, args, _) do
175+
{format_issue(
176+
issue_meta,
177+
message:
178+
"Predicate function names should not start with 'is', and should end in a question mark.",
179+
trigger: trigger,
180+
line_no: line_no
181+
), {trigger, length(args)}}
120182
end
121183
end

test/credo/check/readability/predicate_function_names_test.exs

+85
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,91 @@ defmodule Credo.Check.Readability.PredicateFunctionNamesTest do
8181
|> refute_issues()
8282
end
8383

84+
test "it should NOT report a violation with callback" do
85+
"""
86+
defmodule Foo do
87+
@callback is_bar
88+
@callback is_bar(a)
89+
end
90+
91+
defmodule FooImpl do
92+
@behaviour Foo
93+
94+
@impl Foo
95+
def is_bar do
96+
end
97+
98+
@impl Foo
99+
def is_bar(a) when is_binary(a) do
100+
end
101+
102+
@impl Foo
103+
def is_bar(a) do
104+
end
105+
end
106+
"""
107+
|> to_source_file
108+
|> run_check(@described_check)
109+
|> refute_issues()
110+
end
111+
112+
test "it should report a violation with false negatives" do
113+
"""
114+
defmodule FooImpl do
115+
def impl(false), do: false
116+
def impl(true), do: true
117+
def is_bar do
118+
end
119+
end
120+
"""
121+
|> to_source_file
122+
|> run_check(@described_check)
123+
|> assert_issue()
124+
end
125+
126+
test "it should report a violation with false negatives /2" do
127+
"""
128+
defmodule FooImpl do
129+
impl(true)
130+
def is_bar do
131+
end
132+
end
133+
"""
134+
|> to_source_file
135+
|> run_check(@described_check)
136+
|> assert_issue()
137+
end
138+
139+
test "it should report a violation with false negatives /3" do
140+
"""
141+
defmodule Foo do
142+
@impl is_bar(a)
143+
end
144+
defmodule FooImpl do
145+
def is_bar do
146+
end
147+
end
148+
"""
149+
|> to_source_file
150+
|> run_check(@described_check)
151+
|> assert_issue()
152+
end
153+
154+
test "it should report a violation with false negatives /4" do
155+
"""
156+
defmodule Foo do
157+
@impl true
158+
end
159+
defmodule FooImpl do
160+
def is_bar do
161+
end
162+
end
163+
"""
164+
|> to_source_file
165+
|> run_check(@described_check)
166+
|> assert_issue()
167+
end
168+
84169
#
85170
# cases raising issues
86171
#

0 commit comments

Comments
 (0)