-
-
Save BrooklinJazz/1f4946ab6fd1c7979d67979da55ed8fc to your computer and use it in GitHub Desktop.
Sort module functions alphabetically
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.