Packages
credo
0.3.8
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.7-rc.0
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.2-rc.4
1.7.2-rc.3
1.7.2-rc.2
1.7.2-rc.1
1.7.2-rc.0
1.7.1
1.7.0
1.7.0-rc.2
1.7.0-rc.1
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.6.0-rc.1
1.6.0-rc.0
1.5.6
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.5.0-rc.5
1.5.0-rc.4
1.5.0-rc.3
1.5.0-rc.2
1.5.0-rc.1
1.4.1
1.4.0
1.4.0-rc.2
1.4.0-rc.1
1.3.2
1.3.1
1.3.0
1.3.0-rc3
1.3.0-rc2
1.3.0-rc1
1.2.3
1.2.2
1.2.1
1.2.0
1.2.0-rc4
1.2.0-rc3
1.2.0-rc2
1.2.0-rc1
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.1.0-rc3
1.1.0-rc2
1.1.0-rc1
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.1-rc1
1.0.0
1.0.0-rc1
0.10.2
0.10.1
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.9.0-rc8
0.9.0-rc7
0.9.0-rc6
0.9.0-rc5
0.9.0-rc4
0.9.0-rc3
0.9.0-rc2
0.9.0-rc1
0.8.10
0.8.9
0.8.8
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.8.0-rc7
0.8.0-rc6
0.8.0-rc5
0.8.0-rc4
0.8.0-rc3
0.8.0-rc2
0.8.0-rc1
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.1
0.6.0
0.6.0-rc2
0.6.0-rc1
0.5.3
0.5.2
0.5.1
0.5.0
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.10-dev
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.4.0-beta5
0.4.0-beta4
0.4.0-beta3
0.4.0-beta2
0.4.0-beta1
0.3.13
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.3.0-dev2
0.3.0-dev
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.10
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
0.0.1-dev
A static code analysis tool with a focus on code consistency and teaching.
Current section
Files
Jump to
Current section
Files
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({:defmodule, _, _} = ast, issues, issue_meta, excluded_namespaces, excluded_lastnames) do
aliases = Credo.Code.traverse(ast, &find_aliases/2)
new_issues = Credo.Code.traverse(ast, &find_alias_usage(&1, &2, issue_meta, excluded_namespaces, excluded_lastnames, aliases))
{ast, issues ++ new_issues}
end
defp traverse(ast, issues, _source_file, _excluded_namespaces, _excluded_lastnames) do
{ast, issues}
end
# Ignore multi alias call
defp find_alias_usage({:., _, [{:__aliases__, _, _}, :{}]} = ast, issues, _issue_meta, _excluded_namespaces, _excluded_lastnames, _aliases) do
{ast, issues}
end
defp find_alias_usage({:., _, [{:__aliases__, meta, mod_list}, fun_atom]} = ast, issues, issue_meta, excluded_namespaces, excluded_lastnames, aliases) 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
excluded? =
Enum.member?(excluded_namespaces, first_name) ||
Enum.member?(excluded_lastnames, last_name)
if !excluded? do
conflicting_alias =
aliases
|> Enum.find(&conflicting_alias?(&1, mod_list))
if conflicting_alias do
{ast, issues}
else
trigger = mod_list |> Enum.join(".")
{ast, issues ++ [issue_for(issue_meta, meta[:line], trigger)]}
end
else
{ast, issues}
end
else
{ast, issues}
end
end
defp find_alias_usage(ast, issues, _source_file, _excluded_namespaces, _excluded_lastnames, _aliases) do
{ast, issues}
end
# Returns true if mod_list and alias_name would result in the same alias
# since they share the same last name.
defp conflicting_alias?(alias_name, mod_list) do
last_name = mod_list |> List.last |> to_string
full_name = mod_list |> to_module_name
alias_last_name = alias_name |> String.split(".") |> List.last
full_name != alias_name && alias_last_name == last_name
end
# Single alias
defp find_aliases({:alias, _, [{:__aliases__, _, mod_list}]} = ast, aliases) do
module_names = mod_list |> to_module_name |> List.wrap
{ast, aliases ++ module_names}
end
# Multi alias
defp find_aliases({:alias, _, [{{:., _, [{:__aliases__, _, mod_list}, :{}]}, _, multi_mod_list}]} = ast, aliases) do
module_names =
multi_mod_list
|> Enum.map(fn(tuple) ->
[to_module_name(mod_list), to_module_name(tuple)] |> to_module_name
end)
{ast, aliases ++ module_names}
end
defp find_aliases(ast, aliases) do
{ast, aliases}
end
def tuple?(t) when is_tuple(t), do: true
def tuple?(_), do: false
defp to_module_name({:__aliases__, _, mod_list}) do
mod_list |> to_module_name()
end
defp to_module_name(mod_list) when is_list(mod_list) do
mod_list |> Enum.join(".")
end
defp issue_for(issue_meta, line_no, trigger) 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