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

Flip a public `def` (or `defmacro` / `defguard`) to its private form
when `find-callers` reports zero external callers.

Bookkeeping op — the analysis is "does anything outside this module
call this function?" and the mechanical edit is a one-keyword swap
per clause line. Dry-run by default; `extract_private!/2` writes.

## Scope

  * `def` → `defp`, `defmacro` → `defmacrop`, `defguard` → `defguardp`.
  * `defdelegate` has no private form — returns
    `{:error, :cannot_be_private}`.
  * Multi-clause defs flip every clause line.
  * Attached `@spec` / `@doc` / `@impl` / etc. are left in place. A
    `@spec` on a `defp` is allowed by the compiler; an `@impl` on a
    private callable is rejected, but flipping a function with `@impl`
    to private is almost certainly wrong anyway — `find-callers`
    would normally surface the behaviour-callback callers and refuse
    the flip. We let the compiler complain if the user forces it via
    a future override.

## Safety

External = a caller in any file other than the source file, OR a
caller in the source file whose enclosing `defmodule` is not the
target's module. The latter catches the case where a file holds
multiple modules and a sibling calls the target via its
fully-qualified name. We rely on `Adze.FindCallers` to find the
refs, then re-parse each affected file to determine each ref's
enclosing module for classification.

Limitations inherited from `find-callers`: unqualified calls via
`import` aren't detected, nor dynamic `apply/3`, nor string-literal
mentions. If the codebase uses these against the target, this op
will give a green light incorrectly. The compiler will catch the
break on the next build, but the failure mode is loud — the user
reads the error and reverts. Document it; don't hide it.

## Output shape

    {:ok, %{
      diff: "...",                 # unified-style line diff
      new_source: "...",
      module: "MyApp.Foo",
      name: :helper,
      arity: 2,
      from_kind: :def,
      to_kind: :defp
    }}

    {:error, {:external_callers, [
      %{path: "lib/x.ex", line: 10, kind: :call, arity: 2, snippet: "...",
        in_module: "MyApp.Bar"},
      ...
    ]}}

## Usage

    iex> Adze.ExtractPrivate.extract_private_file(
    ...>   "lib/foo.ex", definition: "helper/2")
    {:ok, %{diff: "...", from_kind: :def, to_kind: :defp}}

    Adze.ExtractPrivate.extract_private!("lib/foo.ex",
      definition: "helper/2")
    # → writes lib/foo.ex with `def helper` flipped to `defp helper`

# `external_ref`

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

# `opts`

```elixir
@type opts() :: [
  definition: Adze.Definition.definition_spec(),
  path: Path.t(),
  mix_root: Path.t(),
  files: %{required(Path.t()) =&gt; String.t()},
  include_attrs: [atom()]
]
```

# `result`

```elixir
@type result() :: %{
  diff: String.t(),
  new_source: String.t(),
  module: String.t(),
  name: atom(),
  arity: non_neg_integer(),
  from_kind: atom(),
  to_kind: atom()
}
```

# `extract_private`

```elixir
@spec extract_private(String.t(), opts()) :: {:ok, result()} | {:error, term()}
```

# `extract_private!`

```elixir
@spec extract_private!(Path.t(), opts()) :: {:ok, result()} | {:error, term()}
```

# `extract_private_file`

```elixir
@spec extract_private_file(Path.t(), opts()) :: {:ok, result()} | {:error, term()}
```

---

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