Find every project-wide reference to a Module.fun[/arity].
Read-only consumer of Adze.ProjectRewrite's file enumeration — same
source loading as rename / extract!, no mutation. Output is a
per-file list of call sites, captures, and pipe references.
Scope
- Qualified calls —
Module.fun(args), including the pipe-expanded formx |> Module.fun(args)and the bare-pipe formx |> Module.fun. - Captures —
&Module.fun/arity. - Aliased references —
alias Foo.BarthenBar.fun(...), andalias Foo, as: FthenF.fun(...), and brace-formalias Foo.{Bar, Baz}.
Not yet handled (intentional v1 limits):
- Unqualified calls via
import Module— barefun(args)afterimportis not detected. Imports rarely cross file boundaries in modern Elixir code; if this bites, file-localimportscoping can be added later. - Dynamic refs —
apply/3,Module.concat(...), runtime module composition. - String-literal mentions — comments,
@moduledocheredocs.
Output shape
%{
target: %{module: "MyApp.Foo", function: :bar, arity: 2 | :any},
total: 3,
files: %{
"lib/x.ex" => [
%{line: 10, kind: :call, arity: 2, snippet: "MyApp.Foo.bar(a, b)",
in_module: "MyApp.Caller"},
%{line: 15, kind: :capture, arity: 2, snippet: "&Foo.bar/2",
in_module: "MyApp.Caller"}
]
}
}in_module is the innermost-enclosing defmodule name at the ref's
position, or nil for top-level refs (scripts, .exs without
defmodule).
Usage
iex> Adze.FindCallers.find_callers("MyApp.Foo.bar/2", mix_root: ".")
{:ok, %{target: ..., files: %{...}, total: 3}}
iex> Adze.FindCallers.find_callers({MyApp.Foo, :bar, :any},
...> files: %{"lib/x.ex" => "..."})
{:ok, %{...}}
Summary
Types
@type caller() :: %{ line: pos_integer(), kind: :call | :capture, arity: non_neg_integer(), snippet: String.t(), in_module: String.t() | nil }
@type result() :: %{ target: target(), total: non_neg_integer(), files: %{required(Path.t()) => [caller()]} }
@type target() :: %{ module: String.t(), function: atom(), arity: non_neg_integer() | :any }
Functions
@spec find_callers( target_spec(), keyword() ) :: {:ok, result()} | {:error, term()}