Packages

Damn simple mix integration of xref.

Current section

Files

Jump to
exref lib exref mix tasks exref.ex
Raw

lib/exref/mix/tasks/exref.ex

defmodule Mix.Tasks.Compile.Exref do
use Mix.Task
@shortdoc "Checks all function calls using xref"
def run(_args) do
results = :xref.d(ebin_dir)
if !Enum.all?(results, &match?({_, []}, &1)) do
Enum.flat_map(results, &format_errors/1) |> Enum.join("\n") |> IO.puts
Mix.raise "Error in xref check! Please fix any errors and try again."
end
end
defp ebin_dir do
app_name = Mix.Project.config[:app] |> Atom.to_string
Path.join([Mix.Project.build_path, "lib", app_name, "ebin"]) |> String.to_char_list
end
defp format_errors({analysis, errors}) do
Enum.map(errors, &format(analysis, &1))
end
defp format(:deprecated, {caller, called}) do
"xref warning: #{mfa_with_location(caller)} calls deprecated function #{mfa(called)}"
end
defp format(:undefined, {caller, called}) do
"xref warning: #{mfa_with_location(caller)} calls undefined function #{mfa(called)}"
end
defp format(:unused, mfa) do
"xref warning: #{mfa_with_location(mfa)} is unused local function"
end
defp mfa({m, f, a}) do
module_str = Atom.to_string(m) |> String.replace_prefix("Elixir.", "")
"#{module_str}.#{f}/#{a}"
end
defp mfa_with_location({m, f, a}) do
"#{extract_source_and_line(m, f, a)}: #{mfa({m, f, a})}"
end
defp extract_source_and_line(m, f, a) do
{_, bin, _} = :code.get_object_code(m)
{:ok, {^m, [abstract_code: {:raw_abstract_v1, defs}]}} = :beam_lib.chunks(bin, [:abstract_code])
{_, _, _, {source, _}} = Enum.find(defs, &match?({:attribute, _, :file, _}, &1))
case Enum.find(defs, &match?({:function, _, ^f, ^a, _}, &1)) do
{_, line, _, _, _} -> "#{source}:#{line}"
nil -> List.to_string(source)
end
end
end