Current section

Files

Jump to
nicene lib avoid_forbidden_namespaces.ex
Raw

lib/avoid_forbidden_namespaces.ex

defmodule Nicene.AvoidForbiddenNamespaces do
@moduledoc """
Avoid calling forbidden namespaces. This is meant to be used for example
to avoid calling `AppWeb` from `App` in Phoenix applications.
It only works for top level namespaces.
"""
@explanation [check: @moduledoc]
use Credo.Check,
base_priority: :high,
category: :refactoring,
param_defaults: [from: nil, forbid: nil]
@doc false
def run(source_file, params \\ []) do
issue_meta = IssueMeta.for(source_file, params)
from_namespace = params |> Params.get(:from, __MODULE__) |> get_namespace()
forbidden_namespaces =
params
|> Params.get(:forbid, __MODULE__)
|> Enum.map(&get_namespace/1)
source_file
|> Credo.Code.prewalk(
&check_usage(&1, &2, from_namespace, forbidden_namespaces, issue_meta),
{[], nil}
)
|> elem(0)
end
defp check_usage({:defmodule, _, _} = ast, {issues, _current_namespace}, _, _, _) do
current_namespace = ast |> Credo.Code.Module.name() |> get_namespace()
{ast, {issues, current_namespace}}
end
defp check_usage(
{:__aliases__, meta, [used_namespace | _]} = ast,
{issues, namespace},
namespace,
forbidden_namespaces,
issue_meta
) do
if used_namespace in forbidden_namespaces do
{ast,
{[issue_for(issue_meta, Keyword.get(meta, :line), used_namespace) | issues], namespace}}
else
{ast, {issues, namespace}}
end
end
defp check_usage(ast, ctx, _, _, _) do
{ast, ctx}
end
defp get_namespace(module_name) when is_atom(module_name) do
module_name
|> Module.split()
|> hd()
|> String.to_atom()
end
defp get_namespace(module_name) when is_binary(module_name) do
module_name
|> List.wrap()
|> Module.concat()
|> get_namespace()
end
defp issue_for(issue_meta, line_no, trigger) do
format_issue(issue_meta,
message: "Usage of forbidden namespace found",
line_no: line_no,
trigger: trigger
)
end
end