Packages

A set of community-maintained checks for the Credo static analysis tool. Many of the checks are implementations of rules from https://github.com/christopheradams/elixir_style_guide.

Current section

Files

Jump to
credo_contrib lib check module_directives_order.ex
Raw

lib/check/module_directives_order.ex

defmodule CredoContrib.Check.ModuleDirectivesOrder do
@moduledoc """
Module directives and special attributes should follow a consistent order
https://github.com/christopheradams/elixir_style_guide#module-attribute-ordering
"""
@explanation [
check: @moduledoc
]
use Credo.Check, base_priority: :high, category: :readability
@order [
:moduledoc,
:behaviour,
:use,
:import,
:alias,
:require,
:defstruct,
:type,
:module_attribute,
:callback,
:macrocallback,
:optional_callbacks
]
|> Enum.with_index()
|> Enum.into(%{})
@attributes [
:behaviour,
:callback,
:macrocallback,
:module_attribute,
:moduledoc,
:optional_callbacks,
:type
]
@directives [
:alias,
:defstruct,
:import,
:require,
:use
]
@printable_attributes Enum.map(@attributes, &{&1, "@#{&1}"})
@printable_directives Enum.map(@directives, &{&1, "#{&1}"})
@printable_order (@printable_attributes ++ @printable_directives)
|> Enum.sort_by(fn {k, _} -> Map.fetch!(@order, k) end)
|> Enum.map(&elem(&1, 1))
|> Enum.join(", ")
def run(source_file, params \\ []) do
issue_meta = IssueMeta.for(source_file, params)
Credo.Code.prewalk(source_file, &traverse(&1, &2, issue_meta))
end
defp traverse({:defmodule, _, [_, [do: {:__block__, _, module_body}]]} = ast, acc, issue_meta) do
case first_unordered_directive(module_body) do
nil ->
{ast, acc}
{_, directive_meta} ->
{ast, [issue_for(issue_meta, directive_meta[:line]) | acc]}
end
end
defp traverse(ast, acc, _) do
{ast, acc}
end
defp first_unordered_directive(module_body) do
directives = Enum.reduce(module_body, [], &find_directives/2)
sorted_directives =
Enum.sort_by(directives, fn {name, _} -> Map.fetch!(@order, name) * -1 end)
if directives == sorted_directives do
nil
else
[{directive, _} | _] =
[directives, sorted_directives]
|> Enum.zip()
|> Enum.reverse()
|> Enum.drop_while(fn {a, b} -> a == b end)
directive
end
end
defp find_directives({:@, _, [{directive, meta, _}]}, directives)
when directive in @attributes do
[{directive, meta} | directives]
end
defp find_directives({directive, meta, _}, directives) when directive in @directives do
[{directive, meta} | directives]
end
defp find_directives(_, directives) do
directives
end
defp issue_for(issue_meta, line_no) do
format_issue(
issue_meta,
message:
"Module directives and special attributes must be in the following order: " <>
@printable_order,
line_no: line_no,
trigger: nil
)
end
end