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

Thin wrapper around Igniter for project-wide write ops.

All cross-file write ops in adze — `rename`, `extract!`'s caller
rewriting (Session 6.5), `find-callers` (Session 7) — funnel through
this module so the Igniter-shaped plumbing (build the rewrite, run
the op, extract per-file diffs, write changes) lives in exactly one
place.

## Lifecycle

    iex> {:ok, rewrite} = Adze.ProjectRewrite.new(mix_root: ".")
    iex> {:ok, rewrite, _report} =
    ...>   Adze.ProjectRewrite.rename_module(rewrite, MyApp.Old, MyApp.New)
    iex> {:ok, result} = Adze.ProjectRewrite.result(rewrite)
    iex> Adze.ProjectRewrite.write!(rewrite)

`result/1` is dry-run shaped; it inspects the Igniter without
touching disk. `write!/1` flushes the same Igniter to the filesystem.

## Working directory

Igniter is built around the assumption that the current working
directory is the project root — `Igniter.new/0` reads `.igniter.exs`
and the dot formatter from `cwd`; `prepare_for_write/1` walks
relative paths. The `Adze.ProjectRewrite` struct carries the project
root so every operation can `cd` into it before delegating to
Igniter and restore the previous `cwd` afterwards. Callers already
in the right `cwd` (e.g. the `mix adze` task) pay no cost — the
wrapper short-circuits when `root == File.cwd!()`.

## Test mode

    Adze.ProjectRewrite.new(files: %{"lib/foo.ex" => "..."})

builds an in-memory test Igniter backed by
`Igniter.Test.test_project/1`. No files are read from disk;
`write!/1` is unsupported (raises). Used by the tests under
`test/project_rewrite_test.exs` and `test/rename_test.exs`.

# `opts`

```elixir
@type opts() :: [
  mix_root: Path.t(),
  files: %{required(Path.t()) =&gt; String.t()},
  app_name: atom()
]
```

# `ref`

```elixir
@type ref() :: %{path: Path.t(), line: non_neg_integer(), short: atom()}
```

# `rename_report`

```elixir
@type rename_report() :: %{fixed_refs: [ref()], surviving_refs: [ref()]}
```

# `result`

```elixir
@type result() :: %{
  diffs: %{required(Path.t()) =&gt; String.t()},
  moves: %{required(Path.t()) =&gt; Path.t()},
  warnings: [String.t()],
  issues: [String.t()],
  notices: [String.t()]
}
```

# `t`

```elixir
@type t() :: %Adze.ProjectRewrite{
  igniter: Igniter.t(),
  root: Path.t() | nil,
  test?: boolean()
}
```

# `create_file`

```elixir
@spec create_file(t(), Path.t(), String.t()) :: {:ok, t()} | {:error, term()}
```

Add a brand-new file to the project. Wraps
`Igniter.create_new_file/4` so the eventual `write!/1` writes it
alongside any other rewrites.

# `new`

```elixir
@spec new(opts()) :: {:ok, t()} | {:error, term()}
```

Build a `ProjectRewrite` rooted at `mix_root:` (defaults to the
current directory). When `files:` is supplied, returns an in-memory
test rewrite instead — see the moduledoc.

# `put_content`

```elixir
@spec put_content(t(), Path.t(), String.t()) :: {:ok, t()} | {:error, term()}
```

Replace the content of an existing tracked source. Used by
`Adze.Extract` to inject its in-file rewrite (cut + caller fix-up +
alias insert) into the project before the cross-file pass runs.

# `rename_function`

```elixir
@spec rename_function(t(), {module(), atom()}, {module(), atom()}, keyword()) ::
  {:ok, t()}
```

Rewrite call sites of `{old_module, old_function}` to
`{new_module, new_function}` across the project. Wraps
`Igniter.Refactors.Rename.rename_function/4`.

Pass `arity: n` (or a list) to narrow to a specific arity; defaults
to `:any`. When the function definition has already been removed
from `old_module` (e.g. by `Adze.Extract` cutting it before this
runs), Igniter's def-move step is a no-op and only the call-site
rewrites take effect.

# `rename_module`

```elixir
@spec rename_module(t(), module(), module()) :: {:ok, t(), rename_report()}
```

Rename a module globally across the project. Wraps
`Igniter.Refactors.Rename.rename_module/3` and adds the adze-specific
follow-ups documented below.

Returns `{:ok, rewrite, report}` where `report` describes the
short-ref fix-up:

  * `fixed_refs` — bare `OldShort.fun(...)` AST nodes that Igniter's
    same-namespace pass left in place and adze patched on the
    caller's behalf.
  * `surviving_refs` — bare-short references the fix-up could not
    safely rewrite (no qualifying non-`as:` alias declaration in
    pre-rewrite content). Callers like `Adze.Rename` surface these
    as a warning and refuse to write without `force: true`.

## Follow-up work performed

  1. **Test-file move.** Igniter rewrites the test module's
     `defmodule` but doesn't move its file. We find the rewritten
     test source and schedule a move to its canonical-for-new-name
     path so the module name and file path stay in sync.
  2. **Short-ref fix-up.** Igniter's same-namespace rename has an
     upstream bug — the string-substitution pass rewrites the alias
     declaration before the AST pass runs, so bare
     `OldShort.fun(...)` call sites are no longer resolvable to the
     old aliases list and get left un-rewritten. Adze patches them
     itself in files that had a non-`as:` alias to the renamed
     module in their pre-rewrite content. Files without that
     evidence get left alone and reported in `surviving_refs`.

# `result`

```elixir
@spec result(t()) :: {:ok, result()}
```

Extract a dry-run-shaped result from the rewrite without writing.

`diffs` keys are file paths; values are plain-text unified-ish diffs
rendered by Rewrite (no ANSI color). `moves` keys are pre-move
paths, values are post-move paths. Errors/warnings are surfaced
separately so the caller can decide whether to refuse to write.

# `write!`

```elixir
@spec write!(t()) :: :ok
```

Flush the rewrite to disk. Refuses to write when the rewrite is in
test mode or carries unresolved issues.

---

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