Skip to content

Instantly share code, notes, and snippets.

@BrooklinJazz
Forked from rodrigues/function_order.ex
Last active September 12, 2021 07:09
Show Gist options
  • Save BrooklinJazz/1f4946ab6fd1c7979d67979da55ed8fc to your computer and use it in GitHub Desktop.
Save BrooklinJazz/1f4946ab6fd1c7979d67979da55ed8fc to your computer and use it in GitHub Desktop.
Sort module functions alphabetically
defmodule Aplicar.Checks.SortModuleFunctions do
use Credo.Check,
base_priority: :low,
explanations: [
check: """
Alphabetically ordered lists are more easily scannable by the read.
# preferred
def a ...
def b ...
def c ...
# NOT preferred
def b ...
def c ...
def a ...
Function names should be alphabetically ordered inside a module.
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.
"""
]
alias Credo.Code
@doc false
@impl true
def run(%SourceFile{} = 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
|> Code.postwalk(&find_function_definitions/2)
|> sorting_issues(issue_meta)
{ast, issues ++ new_issues}
end
defp traverse(ast, issues, _), do: {ast, issues}
defp sorting_issues([], _), do: []
defp sorting_issues(fun_list_with_lines, issue_meta) do
fun_list = Enum.map(fun_list_with_lines, &elem(&1, 0))
sorted_fun_list = Enum.sort(fun_list)
fun_list_with_lines
|> Enum.zip(sorted_fun_list)
|> Enum.filter(fn {{function, _line_no}, sorted_function} ->
function != sorted_function
end)
|> Enum.map(fn {{function, line_no}, sorted_function} ->
issue_for(issue_meta, %{trigger: function, line_no: line_no, sorted_function: sorted_function})
end)
end
defp find_function_definitions(
{key, meta, [{name, _, _} | _]} = ast,
definitions
)
when key in [:def] and name not in [:mount, :run, :init, :type, :when] do
{ast, definitions ++ [{to_string(name), meta[:line]}]}
end
defp find_function_definitions(ast, definitions), do: {ast, definitions}
defp issue_for(issue_meta, %{line_no: line_no, trigger: trigger, sorted_function: sorted_function}) do
format_issue(
issue_meta,
message:
"The function `#{trigger}` is not alphabetically ordered inside the module. it should be `#{sorted_function}`",
trigger: trigger,
line_no: line_no
)
end
end
@BrooklinJazz
Copy link
Author

You can ignore the changes I made to when key in [:def] and name not in [:mount, :run, :init, :type] do

We have several methods (mount, run, init, and type) that I think should break the pattern. So it left in how to ignore specific methods.

I'd like def, and defp etc to all sort individually because it's often nice to group private methods at the bottom (still a TODO) so I've only left in :def for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment