The part of both rewrite tasks that is the same: walk a tree, plan each file, report, and write only what still parses.
virtual_time.migrate (GenServer → virtual-time GenServer) and
virtual_time.upgrade (deprecated module names → their replacements) differ
only in their planner, so the walking and the writing live here once.
Summary
Functions
The paths to plan, from the --paths flags or the defaults.
The files whose plan would change something.
Plans every .ex/.exs file under paths.
Prints one section per file that has something to say.
Writes every pending plan, refusing to write one that would not parse.
Types
@type path() :: String.t()
Functions
The paths to plan, from the --paths flags or the defaults.
@spec pending([{path(), term()}]) :: [{path(), VirtualTimeMigrate.Edits.plan()}]
The files whose plan would change something.
@spec plan([path()], (String.t(), MapSet.t(String.t()) -> {:ok, VirtualTimeMigrate.Edits.plan()} | {:error, term()})) :: [ {path(), VirtualTimeMigrate.Edits.plan() | :unparsable | {:unreadable, term()}} ]
Plans every .ex/.exs file under paths.
planner.(source, converted) returns {:ok, plan} or {:error, reason}; the
second argument is the set of module names the whole tree converts, which only
the GenServer rewrite needs.
Returns [{file, plan}], [{file, :unparsable}] or [{file, {:unreadable, reason}}].
Prints one section per file that has something to say.
@spec write([{path(), VirtualTimeMigrate.Edits.plan()}]) :: :ok
Writes every pending plan, refusing to write one that would not parse.
A rewrite that breaks the syntax is a bug in the planner; aborting the whole run keeps the tree compilable and makes the bug visible.