Core file-rewriting logic for the :lockstep_rewrite Mix compiler.
Reads source files, runs them through Lockstep.Rewriter, and writes
the rewritten output to a build directory.
The Mix integration lives in Mix.Tasks.Compile.LockstepRewrite.
This module is also useful directly from tests when you want to
exercise the rewriting pipeline without going through Mix.
Summary
Functions
Apply the rewrite pipeline to every file matching config.paths,
writing the result to config.output. Returns {:ok, [paths]} on
success or {:error, reason} on the first parse failure.
Helper for assembling elixirc_paths when using the
:lockstep_rewrite Mix compiler. Returns the list of paths your
project should compile from in the rewriting env (typically
:test), given the source paths you want rewritten.
Rewrite a single file from source_path into output_dir. The
output preserves the source's relative path under output_dir.
Types
@type config() :: %{ :paths => [String.t()], optional(:output) => Path.t(), optional(:preprocess) => preprocess_fn() | [preprocess_fn()], optional(:skip) => [String.t()] }
Functions
Apply the rewrite pipeline to every file matching config.paths,
writing the result to config.output. Returns {:ok, [paths]} on
success or {:error, reason} on the first parse failure.
Files whose source mtime is older than the corresponding output file are skipped (incremental compilation).
Options
:paths(required) — list of file path patterns (wildcards supported viaPath.wildcard/1).:output— output directory. Defaults to_build/<env>/lockstep_rewritten.:skip— list of glob patterns. Source files matching ANY of these patterns are skipped entirely (not rewritten, not copied to the output). Use this to omit Lite/SQLite engine sources when stubbing Postgres, or other modules you know aren't needed for the system under test. Patterns are checked against the absolute source path withString.match?(path, ~r/<pattern>/).:preprocess— single function or list of functions(source, path -> source)applied to each file's contents before AST parsing. Use this to strip or rewrite constructs the rewriter can't handle directly. Built-in helpers inLockstep.MixCompiler.Preprocessorscover the common cases:strip_compile_time_external_reads/2— replaces@moduledoc File.read!(...) |> ...blocks (used by nimble_pool, highlander) with a literal string. The original breaks because the rewritten file lives in a different cwd.strip_code_ensure_loaded/2— comments outCode.ensure_loaded!(SomeMod)lines (used by Hammer's__before_compile__) where the module isn't actually available during the rewrite test run.
Multiple preprocessors are applied in order. See the
Preprocessorsmodule for examples.
Example
Lockstep.MixCompiler.compile(%{
paths: ["/tmp/nimble_pool/lib/**/*.ex"],
output: "/tmp/nimble_pool_lockstep",
preprocess: [
&Lockstep.MixCompiler.Preprocessors.strip_compile_time_external_reads/2
]
})
Helper for assembling elixirc_paths when using the
:lockstep_rewrite Mix compiler. Returns the list of paths your
project should compile from in the rewriting env (typically
:test), given the source paths you want rewritten.
Example
defp elixirc_paths(:test) do
Lockstep.MixCompiler.elixirc_paths_for(["lib/**/*.ex"]) ++
["test/support"]
end
defp elixirc_paths(_), do: ["lib"]Returns the rewritten output dir prepended to the parent directories of the source paths, deduplicated.
@spec rewrite_file(Path.t(), Path.t(), [preprocess_fn()]) :: {:ok, Path.t()} | :skipped | {:error, term()}
Rewrite a single file from source_path into output_dir. The
output preserves the source's relative path under output_dir.
- Returns
{:ok, output_path}on a successful rewrite. - Returns
:skippedif the source mtime is not newer than the output mtime. - Returns
{:error, {parse_error, source_path, line, message}}if the source doesn't parse.