/
alias_order.ex
171 lines (132 loc) · 4.28 KB
/
alias_order.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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
defmodule Credo.Check.Readability.AliasOrder do
@moduledoc """
Alphabetically ordered lists are more easily scannable by the read.
# preferred
alias Module1
alias Module2
alias Module3
# NOT preferred
alias Module1
alias Module3
alias Module2
Alias should be alphabetically ordered among their group:
# preferred
alias Module3
alias Module4
alias Module1
alias Module2
# NOT preferred
alias Module3
alias Module4
alias Module2
alias Module1
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.
"""
@explanation [check: @moduledoc]
alias Credo.Code
alias Credo.Code.Name
use Credo.Check, base_priority: :high
@doc false
def run(source_file, params \\ []) do
issue_meta = IssueMeta.for(source_file, params)
Code.prewalk(source_file, &traverse(&1, &2, issue_meta))
end
defp traverse({:defmodule, _, _} = ast, issues, issue_meta) do
new_issues =
ast
|> extract_alias_groups()
|> Enum.reduce([], &traverse_groups(&1, &2, issue_meta))
{ast, issues ++ new_issues}
end
defp traverse(ast, issues, _issue_meta), do: {ast, issues}
defp traverse_groups(group, acc, issue_meta) do
chunk_method =
if function_exported?(Enum, :chunk_every, 2) do
:chunk_every
else
:chunk
end
group
|> (&apply(Enum, chunk_method, [&1, 2, 1])).()
|> Enum.reduce_while(nil, &process_group/2)
|> case do
nil ->
acc
{line_no, trigger} ->
acc ++ [issue_for(issue_meta, line_no, trigger)]
end
end
defp process_group([{_, first_line_content}, {_, second_line_content} = second_line], _)
when first_line_content > second_line_content,
do: {:halt, second_line}
defp process_group(_, _), do: {:cont, nil}
defp extract_alias_groups({:defmodule, _, _arguments} = ast) do
ast
|> Code.postwalk(&find_alias_groups/2)
|> Enum.reverse()
|> Enum.reduce([[]], fn definition, acc ->
case definition do
nil ->
[[]] ++ acc
definition ->
[group | groups] = acc
[group ++ [definition]] ++ groups
end
end)
|> Enum.reverse()
end
defp acculumate_alias_into_group(ast, modules, line, [{line_no, _} | _] = aliases) when line_no != 0 and line_no != line - 1 do
{ast, modules ++ [nil] ++ aliases}
end
defp acculumate_alias_into_group(ast, modules, _, aliases) do
{ast, modules ++ aliases}
end
defp find_alias_groups(
{:alias, _, [{:__aliases__, [line: line, column: _], mod_list}]} = ast,
aliases
),
do: process_single_alias(ast, line, mod_list, aliases)
defp find_alias_groups({:alias, _, [{:__aliases__, [line: line], mod_list}]} = ast, aliases),
do: process_single_alias(ast, line, mod_list, aliases)
defp find_alias_groups(
{:alias, _,
[
{{:., _, [{:__aliases__, [line: line, column: _], mod_list}, :{}]}, _, multi_mod_list}
]} = ast,
aliases
), do: process_multi_alias(ast, line, mod_list, multi_mod_list, aliases)
defp find_alias_groups(
{:alias, _,
[
{{:., _, [{:__aliases__, [line: line], mod_list}, :{}]}, _, multi_mod_list}
]} = ast,
aliases
), do: process_multi_alias(ast, line, mod_list, multi_mod_list, aliases)
defp find_alias_groups(ast, aliases), do: {ast, aliases}
defp process_multi_alias(ast, line, mod_list, multi_mod_list, aliases) do
modules =
multi_mod_list
|> Enum.map(& {line, Name.full([Name.full(mod_list), Name.full(&1)])})
|> Enum.reverse()
acculumate_alias_into_group(ast, modules, line, aliases)
end
defp process_single_alias(ast, line, mod_list, aliases) do
modules =
mod_list
|> Name.full()
|> List.wrap()
|> Enum.join()
|> (& [{line, &1}]).()
acculumate_alias_into_group(ast, modules, line, aliases)
end
defp issue_for(issue_meta, line_no, trigger) do
format_issue(
issue_meta,
message: "The alias `#{trigger}` is not alphabetically ordered among its group.",
trigger: trigger,
line_no: line_no
)
end
end