Skip to content

Instantly share code, notes, and snippets.

@wojtekmach
Last active July 4, 2024 16:57
Show Gist options
  • Save wojtekmach/4e04cbda82ba88af3f84c44ec746b7ca to your computer and use it in GitHub Desktop.
Save wojtekmach/4e04cbda82ba88af3f84c44ec746b7ca to your computer and use it in GitHub Desktop.

Usage:

cd /path/to/your/project
mkdir -p lib/mix/tasks

curl https://gist.githubusercontent.com/wojtekmach/4e04cbda82ba88af3f84c44ec746b7ca/raw/import2alias.ex \
  > lib/mix/tasks/import2alias.ex

curl https://gist.githubusercontent.com/wojtekmach/4e04cbda82ba88af3f84c44ec746b7ca/raw/lib_import2alias.ex \
  > lib_import2alias.ex

elixir -r lib_import2alias.ex -S mix import2alias HexpmWeb.ViewHelpers ViewHelpers

See https://dashbit.co/blog/rewriting-imports-to-aliases-with-compilation-tracers for more information.

defmodule Mix.Tasks.Import2alias do
use Mix.Task
@impl true
def run(args) do
unless Version.match?(System.version(), ">= 1.10.0-rc") do
Mix.raise("Elixir v1.10+ is required!")
end
case args do
[module, alias] ->
run(Module.concat([module]), Module.concat([alias]))
_ ->
Mix.raise("Usage: elixir -r lib_import2alias.ex -S mix import2alias MODULE ALIAS")
end
end
defp run(module, alias) do
{:ok, _} = Import2Alias.Server.start_link(module)
Code.compiler_options(parser_options: [columns: true])
Mix.Task.rerun("compile.elixir", ["--force", "--tracer", "Import2Alias.CallerTracer"])
entries = Import2Alias.Server.entries()
Import2Alias.import2alias(alias, entries)
end
end
defmodule Import2Alias.CallerTracer do
def trace({:imported_function, meta, module, name, arity}, env) do
Import2Alias.Server.record(env.file, meta[:line], meta[:column], module, name, arity)
:ok
end
def trace(_event, _env) do
:ok
end
end
defmodule Import2Alias.Server do
use Agent
def start_link(module) do
Agent.start_link(fn -> %{module: module, entries: %{}} end, name: __MODULE__)
end
def record(file, line, column, module, name, arity) do
Agent.update(__MODULE__, fn state ->
the_module = state.module
if match?({^the_module, _, _}, {module, name, arity}) do
entry = {line, column, module, name, arity}
Map.update!(state, :entries, fn entries ->
Map.update(entries, file, [entry], &[entry | &1])
end)
else
state
end
end)
end
def entries() do
Agent.get(__MODULE__, & &1.entries)
end
end
defmodule Import2Alias do
def import2alias(alias, entries) do
for {file, entries} <- entries do
lines = File.read!(file) |> String.split("\n")
lines =
Enum.reduce(entries, lines, fn entry, acc ->
{line, column, module, name, arity} = entry
List.update_at(acc, line - 1, fn string ->
if column do
# the column in imported calls in captures is reported at "&" character
column = if String.at(string, column - 1) == "&", do: column + 1, else: column
pre = String.slice(string, 0, column - 1)
offset = column - 1 + String.length("#{name}")
post = String.slice(string, offset, String.length(string))
pre <> "#{inspect(alias)}.#{name}" <> post
else
file = Path.relative_to(file, File.cwd!())
IO.puts(
"skipping #{file}:#{line} #{inspect(module)}.#{name}/#{arity}: no column info"
)
string
end
end)
end)
File.write!(file, Enum.join(lines, "\n"))
end
end
end
@kleinernik
Copy link

kleinernik commented Apr 18, 2020

In case someone runs into the same problems I have run into:

  1. If you derive Protocols of external dependencies like Jason.Encoder, for compile.elixir to work, you need to first run mix compile.protocols
  2. Unlike stated here https://dashbit.co/blog/rewriting-imports-to-aliases-with-compilation-tracers, it seems like you need to use mix run import2alias.exs ... instead of elixir import2alias.exs ... otherwise there will be no mix environment (af far as I understand) and you'll get this error: ** (exit) exited in: GenServer.call(Mix.ProjectStack, {:get_stack, #Function<10.68469381/1 in Mix.ProjectStack.peek/0>}, :infinity). At least that worked for me.

@wojtekmach
Copy link
Author

Good catches, I'll update this shortly.

@kleinernik
Copy link

Thanks and thank you for the cool work you are doing at dashbit !

@wojtekmach
Copy link
Author

@kleinernik updated, thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment