Skip to content

Commit

Permalink
Add new AliasOrder check
Browse files Browse the repository at this point in the history
  • Loading branch information
remi committed Jun 3, 2018
1 parent d6b7c99 commit f2ed576
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 2 deletions.
1 change: 1 addition & 0 deletions .credo.exs
Expand Up @@ -82,6 +82,7 @@
#
## Readability Checks
#
{Credo.Check.Readability.AliasOrder},
{Credo.Check.Readability.FunctionNames},
{Credo.Check.Readability.LargeNumbers},
{Credo.Check.Readability.MaxLineLength, priority: :low, max_length: 80},
Expand Down
130 changes: 130 additions & 0 deletions lib/credo/check/readability/alias_order.ex
@@ -0,0 +1,130 @@
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
group
|> Enum.chunk_every(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 find_alias_groups(
{:alias, _, [{:__aliases__, [line: line, column: _], mod_list}]} = ast,
aliases
) do
module_names =
mod_list
|> Name.full()
|> List.wrap()
|> Enum.join()

case aliases do
[{line_no, _} | _] when line_no != 0 and line_no != line - 1 ->
{ast, [{line, module_names}, nil] ++ aliases}

_ ->
{ast, [{line, module_names}] ++ aliases}
end
end

defp find_alias_groups(ast, aliases), do: {ast, aliases}

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
2 changes: 0 additions & 2 deletions test/credo/check/readability/alias_order_test.exs
Expand Up @@ -3,8 +3,6 @@ defmodule Credo.Check.Readability.AliasOrderTest do

@described_check Credo.Check.Readability.AliasOrder

@moduletag :to_be_implemented

#
# cases NOT raising issues
#
Expand Down

0 comments on commit f2ed576

Please sign in to comment.