Current section

Files

Jump to
credo lib credo check design alias_usage.ex
Raw

lib/credo/check/design/alias_usage.ex

defmodule Credo.Check.Design.AliasUsage do
@moduledoc """
Functions from other modules should be used via an alias if the module's
namespace is not top-level.
While this is completely fine:
defmodule MyApp.Web.Search do
def twitter_mentions do
MyApp.External.TwitterAPI.search(...)
end
end
... you might want to refactor it to look like this:
defmodule MyApp.Web.Search do
alias MyApp.External.TwitterAPI
def twitter_mentions do
TwitterAPI.search(...)
end
end
The thinking behind this is that you can see the dependencies of your module
at a glance. So if you are attempting to build a medium to large project,
this can help you to get your boundaries/layers/contracts right.
Like all `Software Design` issues, this is just advice and might not be
applicable to your project/situation.
"""
@explanation [check: @moduledoc]
@default_params [
excluded_namespaces: [
"File",
"IO",
"Inspect",
"Kernel",
"Macro",
"Supervisor",
"Task",
"Version"
],
excluded_lastnames: ~w[Access Agent Application Atom Base Behaviour
Bitwise Code Dict Enum Exception File Float
GenEvent GenServer HashDict HashSet IO Integer
Kernel Keyword List Macro Map MapSet Module Node
OptionParser Path Port Process Protocol Range
Record Regex Set Stream String StringIO Supervisor
System Task Tuple URI Version]
]
use Credo.Check, base_priority: :normal
@doc false
def run(%SourceFile{ast: ast} = source_file, params \\ []) do
issue_meta = IssueMeta.for(source_file, params)
excluded_namespaces = params |> Params.get(:excluded_namespaces, @default_params)
excluded_lastnames = params |> Params.get(:excluded_lastnames, @default_params)
Credo.Code.traverse(ast, &traverse(&1, &2, issue_meta, excluded_namespaces, excluded_lastnames))
end
defp traverse({:., _, [{:__aliases__, meta, mod_list}, fun_atom]} = ast, issues, issue_meta, excluded_namespaces, excluded_lastnames) when is_list(mod_list) and is_atom(fun_atom) do
if Enum.count(mod_list) > 1 && !Enum.any?(mod_list, &tuple?/1) do
first_name = mod_list |> List.first |> to_string
last_name = mod_list |> List.last |> to_string
if !Enum.member?(excluded_namespaces, first_name) && !Enum.member?(excluded_lastnames, last_name) do
trigger = mod_list |> Enum.join(".")
{ast, issues ++ [issue_for(meta[:line], trigger, issue_meta)]}
else
{ast, issues}
end
else
{ast, issues}
end
end
defp traverse(ast, issues, _source_file, _excluded_namespaces, _excluded_lastnames) do
{ast, issues}
end
def tuple?(t) when is_tuple(t), do: true
def tuple?(_), do: false
defp issue_for(line_no, trigger, issue_meta) do
format_issue issue_meta,
message: "Nested modules could be aliased at the top of the invoking module.",
trigger: trigger,
line_no: line_no
end
end