Skip to content

Commit 772ede8

Browse files
committed
Extract valid_chain_start?/3 pipe helper function into helper module
1 parent ca8fb52 commit 772ede8

File tree

2 files changed

+234
-231
lines changed

2 files changed

+234
-231
lines changed

lib/credo/check/pipe_helpers.ex

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
defmodule Credo.Check.PipeHelpers do
2+
@elixir_custom_operators [
3+
:<-,
4+
:|||,
5+
:&&&,
6+
:<<<,
7+
:>>>,
8+
:<<~,
9+
:~>>,
10+
:<~,
11+
:~>,
12+
:<~>,
13+
:"<|>",
14+
:"^^^",
15+
:"~~~",
16+
:"..//"
17+
]
18+
19+
def valid_chain_start?(
20+
{:__block__, _, [single_ast_node]},
21+
excluded_functions,
22+
excluded_argument_types
23+
) do
24+
valid_chain_start?(
25+
single_ast_node,
26+
excluded_functions,
27+
excluded_argument_types
28+
)
29+
end
30+
31+
for atom <- [
32+
:%,
33+
:%{},
34+
:..,
35+
:<<>>,
36+
:@,
37+
:__aliases__,
38+
:unquote,
39+
:{},
40+
:&,
41+
:<>,
42+
:++,
43+
:--,
44+
:&&,
45+
:||,
46+
:+,
47+
:-,
48+
:*,
49+
:/,
50+
:>,
51+
:>=,
52+
:<,
53+
:<=,
54+
:==,
55+
:for,
56+
:with,
57+
:not,
58+
:and,
59+
:or
60+
] do
61+
def valid_chain_start?(
62+
{unquote(atom), _meta, _arguments},
63+
_excluded_functions,
64+
_excluded_argument_types
65+
) do
66+
true
67+
end
68+
end
69+
70+
for operator <- @elixir_custom_operators do
71+
def valid_chain_start?(
72+
{unquote(operator), _meta, _arguments},
73+
_excluded_functions,
74+
_excluded_argument_types
75+
) do
76+
true
77+
end
78+
end
79+
80+
# anonymous function
81+
def valid_chain_start?(
82+
{:fn, _, [{:->, _, [_args, _body]}]},
83+
_excluded_functions,
84+
_excluded_argument_types
85+
) do
86+
true
87+
end
88+
89+
# function_call()
90+
def valid_chain_start?(
91+
{atom, _, []},
92+
_excluded_functions,
93+
_excluded_argument_types
94+
)
95+
when is_atom(atom) do
96+
true
97+
end
98+
99+
# function_call(with, args) and sigils
100+
def valid_chain_start?(
101+
{atom, _, arguments} = ast,
102+
excluded_functions,
103+
excluded_argument_types
104+
)
105+
when is_atom(atom) and is_list(arguments) do
106+
sigil?(atom) ||
107+
valid_chain_start_function_call?(
108+
ast,
109+
excluded_functions,
110+
excluded_argument_types
111+
)
112+
end
113+
114+
# map[:access]
115+
def valid_chain_start?(
116+
{{:., _, [Access, :get]}, _, _},
117+
_excluded_functions,
118+
_excluded_argument_types
119+
) do
120+
true
121+
end
122+
123+
# Module.function_call()
124+
def valid_chain_start?(
125+
{{:., _, _}, _, []},
126+
_excluded_functions,
127+
_excluded_argument_types
128+
),
129+
do: true
130+
131+
# Elixir <= 1.8.0
132+
# '__#{val}__' are compiled to String.to_charlist("__#{val}__")
133+
# we want to consider these charlists a valid pipe chain start
134+
def valid_chain_start?(
135+
{{:., _, [String, :to_charlist]}, _, [{:<<>>, _, _}]},
136+
_excluded_functions,
137+
_excluded_argument_types
138+
),
139+
do: true
140+
141+
# Elixir >= 1.8.0
142+
# '__#{val}__' are compiled to String.to_charlist("__#{val}__")
143+
# we want to consider these charlists a valid pipe chain start
144+
def valid_chain_start?(
145+
{{:., _, [List, :to_charlist]}, _, [[_ | _]]},
146+
_excluded_functions,
147+
_excluded_argument_types
148+
),
149+
do: true
150+
151+
# Module.function_call(with, parameters)
152+
def valid_chain_start?(
153+
{{:., _, _}, _, _} = ast,
154+
excluded_functions,
155+
excluded_argument_types
156+
) do
157+
valid_chain_start_function_call?(
158+
ast,
159+
excluded_functions,
160+
excluded_argument_types
161+
)
162+
end
163+
164+
def valid_chain_start?(_, _excluded_functions, _excluded_argument_types), do: true
165+
166+
def valid_chain_start_function_call?(
167+
{_atom, _, arguments} = ast,
168+
excluded_functions,
169+
excluded_argument_types
170+
) do
171+
function_name = to_function_call_name(ast)
172+
173+
found_argument_types =
174+
case arguments do
175+
[nil | _] -> [:atom]
176+
x -> x |> List.first() |> argument_type()
177+
end
178+
179+
Enum.member?(excluded_functions, function_name) ||
180+
Enum.any?(
181+
found_argument_types,
182+
&Enum.member?(excluded_argument_types, &1)
183+
)
184+
end
185+
186+
defp sigil?(atom) do
187+
atom
188+
|> to_string
189+
|> String.match?(~r/^sigil_[a-zA-Z]$/)
190+
end
191+
192+
defp to_function_call_name({_, _, _} = ast) do
193+
{ast, [], []}
194+
|> Macro.to_string()
195+
|> String.replace(~r/\.?\(.*\)$/s, "")
196+
end
197+
198+
@alphabet_wo_r ~w(a b c d e f g h i j k l m n o p q s t u v w x y z)
199+
@all_sigil_chars Enum.flat_map(@alphabet_wo_r, &[&1, String.upcase(&1)])
200+
@matchable_sigils Enum.map(@all_sigil_chars, &:"sigil_#{&1}")
201+
202+
for sigil_atom <- @matchable_sigils do
203+
defp argument_type({unquote(sigil_atom), _, _}) do
204+
[unquote(sigil_atom)]
205+
end
206+
end
207+
208+
defp argument_type({:sigil_r, _, _}), do: [:sigil_r, :regex]
209+
defp argument_type({:sigil_R, _, _}), do: [:sigil_R, :regex]
210+
211+
defp argument_type({:fn, _, _}), do: [:fn]
212+
defp argument_type({:%{}, _, _}), do: [:map]
213+
defp argument_type({:{}, _, _}), do: [:tuple]
214+
defp argument_type(nil), do: []
215+
216+
defp argument_type(v) when is_atom(v), do: [:atom]
217+
defp argument_type(v) when is_binary(v), do: [:binary]
218+
defp argument_type(v) when is_bitstring(v), do: [:bitstring]
219+
defp argument_type(v) when is_boolean(v), do: [:boolean]
220+
221+
defp argument_type(v) when is_list(v) do
222+
if Keyword.keyword?(v) do
223+
[:keyword, :list]
224+
else
225+
[:list]
226+
end
227+
end
228+
229+
defp argument_type(v) when is_number(v), do: [:number]
230+
231+
defp argument_type(v), do: [:credo_type_error, v]
232+
end

0 commit comments

Comments
 (0)