defmodule Credo.Check.Warning.ForbiddenModule do use Credo.Check, id: "EX5004", base_priority: :high, category: :warning, param_defaults: [modules: []], explanations: [ check: """ Some modules that are included by a package may be hazardous if used by your application. Use this check to allow these modules in your dependencies but forbid them to be used in your application. Examples: The `:ecto_sql` package includes the `Ecto.Adapters.SQL` module, but direct usage of the `Ecto.Adapters.SQL.query/4` function, and related functions, may cause issues when using Ecto's dynamic repositories. """, params: [ modules: "List of modules or `{Module, \"Error message\"}` tuples that must not be used." ] ] alias Credo.Code.Name @impl Credo.Check def run(%SourceFile{} = source_file, params) do forbidden_modules = params |> Params.get(:modules, __MODULE__) |> Enum.map(fn {key, value} -> {Name.full(key), value} key -> {Name.full(key), nil} end) |> Map.new() Credo.Code.prewalk( source_file, &traverse(&1, &2, forbidden_modules, IssueMeta.for(source_file, params)) ) end defp traverse({:__aliases__, meta, modules} = ast, issues, forbidden_modules, issue_meta) do module = Name.full(modules) issues = put_issue_if_forbidden(issues, issue_meta, meta, module, forbidden_modules, module) {ast, issues} end defp traverse( {:alias, _meta, [{{_, _, [{:__aliases__, _opts, base_alias}, :{}]}, _, aliases}]} = ast, issues, forbidden_modules, issue_meta ) do issues = Enum.reduce(aliases, issues, fn {:__aliases__, meta, module}, issues -> full_module = Name.full([base_alias, module]) module = Name.full(module) put_issue_if_forbidden(issues, issue_meta, meta, full_module, forbidden_modules, module) end) {ast, issues} end defp traverse(ast, issues, _, _), do: {ast, issues} defp put_issue_if_forbidden(issues, issue_meta, meta, module, forbidden_modules, trigger) do if Map.has_key?(forbidden_modules, module) do [issue_for(issue_meta, meta, module, forbidden_modules, trigger) | issues] else issues end end defp issue_for(issue_meta, meta, module, forbidden_modules, trigger) do message = forbidden_modules[module] || "The `#{trigger}` module is not allowed." format_issue( issue_meta, message: message, trigger: trigger, line_no: meta[:line], column: meta[:column] ) end end