# `Adze.FindCallers`
[🔗](https://github.com/matthewlehner/adze/blob/v0.1.0/lib/adze/find_callers.ex#L1)

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 form `x |> Module.fun(args)` and the bare-pipe form
    `x |> Module.fun`.
  * **Captures** — `&Module.fun/arity`.
  * **Aliased references** — `alias Foo.Bar` then `Bar.fun(...)`, and
    `alias Foo, as: F` then `F.fun(...)`, and brace-form
    `alias Foo.{Bar, Baz}`.

Not yet handled (intentional v1 limits):

  * **Unqualified calls via `import Module`** — bare `fun(args)` after
    `import` is not detected. Imports rarely cross file boundaries
    in modern Elixir code; if this bites, file-local `import` scoping
    can be added later.
  * **Dynamic refs** — `apply/3`, `Module.concat(...)`, runtime
    module composition.
  * **String-literal mentions** — comments, `@moduledoc` heredocs.

## 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, %{...}}

# `caller`

```elixir
@type caller() :: %{
  line: pos_integer(),
  kind: :call | :capture,
  arity: non_neg_integer(),
  snippet: String.t(),
  in_module: String.t() | nil
}
```

# `result`

```elixir
@type result() :: %{
  target: target(),
  total: non_neg_integer(),
  files: %{required(Path.t()) =&gt; [caller()]}
}
```

# `target`

```elixir
@type target() :: %{
  module: String.t(),
  function: atom(),
  arity: non_neg_integer() | :any
}
```

# `target_spec`

```elixir
@type target_spec() ::
  String.t() | {module(), atom()} | {module(), atom(), non_neg_integer() | :any}
```

# `find_callers`

```elixir
@spec find_callers(
  target_spec(),
  keyword()
) :: {:ok, result()} | {:error, term()}
```

---

*Consult [api-reference.md](api-reference.md) for complete listing*
