Skip to content

Commit

Permalink
refactor(cleanup and modify): use tracers
Browse files Browse the repository at this point in the history
Use compilation tracer.
Configure test trace static mod.
Update tests
  • Loading branch information
ckoch-cars committed Aug 14, 2022
1 parent 2a17d00 commit 6924a9b
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 100 deletions.
55 changes: 40 additions & 15 deletions lib/ex_factor/callers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,35 @@ defmodule ExFactor.Callers do
@doc """
use `mix xref` list all the callers of a given module.
"""
def callers(mod) do
Mix.Tasks.Xref.calls([])
|> Enum.filter(fn x ->
module = cast(mod)
match?({^module, _, _}, x.callee)
def callers(mod, trace_function \\ get_trace_function()) do
entries = trace_function.()

callers = fn -> Mix.Tasks.Xref.run(["callers", mod]) end
|> capture_io()
|> String.split("\n")

paths = Enum.map(callers, fn line ->
Regex.run(~r/\S.*\.ex*\S/, line)
|> case do
[path] -> path
other -> other
end
end)

Enum.filter(entries, fn {{path, _module}, _fn_calls} = _tuple ->
rel_path = Path.relative_to(path, File.cwd!())

rel_path in paths
end)
end

def trace_function do
ExFactor.Traces.trace()
end

def callers(mod, func, arity) do
Mix.Tasks.Xref.calls([])
# |> IO.inspect(label: "callers/3 XREF calls")
|> Enum.filter(fn x ->
x.callee == {cast(mod), cast(func), arity}
end)
Expand Down Expand Up @@ -48,17 +67,23 @@ defmodule ExFactor.Callers do
|> Module.concat()
end

defp mangle_list([""]), do: []
defp mangle_list(["Compiling" <> _ | tail]), do: mangle_list(tail)
# defp mangle_list([""]), do: []
# defp mangle_list(["Compiling" <> _ | tail]), do: mangle_list(tail)

defp mangle_list(list) do
Enum.map(list, fn string ->
[path, type] = String.split(string, " ")
# defp mangle_list(list) do
# Enum.map(list, fn string ->
# [path, type] = String.split(string, " ")

%{
file: path,
dependency_type: type
}
end)
# %{
# file: path,
# dependency_type: type
# }
# end)
# end

defp get_trace_function do
:ex_factor
|> Application.get_env(__MODULE__, trace_function: &ExFactor.Callers.trace_function/0)
|> Keyword.fetch!(:trace_function)
end
end
34 changes: 21 additions & 13 deletions lib/ex_factor/changer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,35 @@ defmodule ExFactor.Changer do
"""

alias ExFactor.Callers
alias ExFactor.Parser

@doc """
Given all the Callers of a module, find the instances of usage of the module and refactor the
module reference to the new module. Respect any existing aliases.
"""
def rename_module(opts) do
Mix.Tasks.Compile.Elixir.run([])
:timer.sleep(100)
source_module = Keyword.fetch!(opts, :source_module)

source_module
|> Callers.callers()
|> Enum.group_by(& &1.file)
|> update_caller_module(opts)
|> update_caller_modules(opts)
end

@doc """
Given all the Callers to a module, find the instances of the target function and refactor the
function module reference to the new module. Respect any existing aliases.
"""
def change(opts) do
Mix.Tasks.Compile.Elixir.run([])
:timer.sleep(100)
# Mix.Tasks.Compile.Elixir.run([])
# :timer.sleep(100)
source_module = Keyword.fetch!(opts, :source_module)
source_function = Keyword.fetch!(opts, :source_function)
arity = Keyword.fetch!(opts, :arity)

source_module
|> Callers.callers(source_function, arity)
|> Enum.group_by(& &1.file)
# |> IO.inspect(label: "callers")
# |> Enum.group_by(& &1.file)
|> update_caller_groups(opts)
end

Expand Down Expand Up @@ -70,21 +69,30 @@ defmodule ExFactor.Changer do
end)
end

defp update_caller_module(callers, opts) do
defp update_caller_modules(callers, opts) do
dry_run = Keyword.get(opts, :dry_run, false)
source_module = Keyword.fetch!(opts, :source_module)
# source_path = Keyword.get(opts, :source_path)

Enum.map(callers, fn {file, [first | _] = grouped_callers} ->
mod = Callers.cast(source_module)
Enum.map(callers, fn {{file_path, module}, fn_calls} ->
file_list =
File.read!(file)
file_path
|> File.read!()
|> String.split("\n")

grouped_callers
|> Enum.reduce({[:unchanged], file_list}, fn %{line: line}, acc ->
matching_functions = Enum.filter(fn_calls, fn
{_, _, _, ^mod, _, _} -> true
_ -> false
end)
|> Enum.reduce({[:unchanged], file_list}, fn {_, line, _, _, _, _}, acc ->
find_and_replace_module(acc, opts, line)
end)
# |> IO.inspect(label: "FUNS")
|> maybe_add_import(opts)
|> maybe_add_alias(opts)
|> write_file(first.caller_module, file, dry_run)
|> write_file(module, file_path, dry_run)

end)
end

Expand Down
6 changes: 3 additions & 3 deletions lib/ex_factor/server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ defmodule ExFactor.Server do
Agent.start_link(fn -> %{module: __MODULE__, entries: %{}} end, name: __MODULE__)
end

def record(type, file, line, column, module, name, arity, _context_modules, caller_module) do
def record(type, file, line, column, module, name, arity, caller_module) do
Agent.update(__MODULE__, fn state ->
entry = {type, line, column, module, name, arity}

path = Path.relative_to(file, File.cwd!)
Map.update!(state, :entries, fn entries ->
Map.update(entries, {file, caller_module}, [entry], &[entry | &1])
Map.update(entries, {path, caller_module}, [entry], &[entry | &1])
end)
end)
end
Expand Down
17 changes: 17 additions & 0 deletions lib/ex_factor/traces.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
defmodule ExFactor.Traces do
def setup do
Code.compiler_options(parser_options: [columns: true])
ExFactor.Tracer = Code.ensure_loaded!(ExFactor.Tracer)
ExFactor.Server = Code.ensure_loaded!(ExFactor.Server)

_ = ExFactor.Server.start_link(__MODULE__)
end

def trace do
:ok = Application.ensure_loaded(:ex_factor)
setup()
Mix.Task.rerun("compile", ["--force", "--tracer=ExFactor.Tracer"])

ExFactor.Server.entries
end
end
24 changes: 17 additions & 7 deletions test/ex_factor/callers_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,26 @@ defmodule ExFactor.CallersTest do
alias ExFactor.Callers

describe "callers/1" do
test "it should report callers of a module" do
callers = Callers.callers(ExFactor.Parser)
test "it should report callers of a module using a 0-arity trace function" do
callers = Callers.callers(ExFactor.Parser, fn -> ExFactor.Support.Trace.trace_function() end)

assert Enum.find(callers, fn {{path, _module}, _funs} -> path == "lib/ex_factor/evaluater.ex" end)
assert support =Enum.find(callers, fn {{_path, module}, _funs} -> module == ExFactor.Evaluater end)

assert Enum.find(callers, fn caller -> caller.file == "lib/ex_factor/callers.ex" end)
{{_, _}, support_funs} = support

%{} =
support = Enum.find(callers, fn caller -> caller.file == "test/support/support.ex" end)
assert Enum.find(support_funs, fn
{_, _, _, ExFactor.Parser, :public_functions, _} -> true
_ -> false
end)
assert Enum.find(support_funs, fn
{_, _, _, ExFactor.Callers, :callers, _} -> true
_ -> false
end)
end

assert support.caller_module == ExFactor.Support
assert support.callee == {ExFactor.Parser, :all_functions, 1}
test "it uses the default tracer" do
assert [] = Callers.callers(ExFactor.Parser, &Callers.trace_function/0)
end

test "when no callers" do
Expand Down
67 changes: 5 additions & 62 deletions test/ex_factor/changer_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -10,73 +10,16 @@ defmodule ExFactor.ChangerTest do

describe "rename_module/1" do
test "it renames all the instances of a module" do
content = """
defmodule ExFactor.Tmp.SourceMod do
def refactor1(_), do: :ok
end
"""

File.write("lib/ex_factor/tmp/source_module.ex", content)

content = """
defmodule ExFactor.Tmp.CallerModule do
@moduledoc \"\"\"
This is a multiline moduedoc.
Its in the caller module
\"\"\"
alias ExFactor.Tmp.SourceMod
alias ExFactor.Tmp.SourceMod.Other
def pub1(arg_a) do
SourceMod.refactor1(arg_a)
end
def pub2, do: Other
def pub3(arg_a) do
SourceMod.refactor1(arg_a)
end
end
"""

File.write("lib/ex_factor/tmp/caller_module.ex", content)

content = """
defmodule ExFactor.Tmp.CallerTwoModule do
def pub1(arg_a) do
ExFactor.Tmp.SourceMod.refactor1(arg_a)
end
def pub2, do: Enum
end
"""

File.write("lib/ex_factor/tmp/caller_two_module.ex", content)

opts = [
target_module: "ExFactor.Tmp.TargetModule",
source_module: "ExFactor.Tmp.SourceMod"
target_module: "ExFactor.Modified.Parser",
source_module: "ExFactor.Parser",
dry_run: true
]

changes = Changer.rename_module(opts)
assert Enum.find(changes, &(&1.path == "lib/ex_factor/tmp/caller_module.ex"))
assert Enum.find(changes, &(&1.path == "lib/ex_factor/tmp/caller_two_module.ex"))

caller = File.read!("lib/ex_factor/tmp/caller_module.ex")
assert caller =~ "alias ExFactor.Tmp.TargetModule"
# ensure we don't match dumbly
assert caller =~ "alias ExFactor.Tmp.SourceMod.Other"
refute caller =~ "alias ExFactor.Tmp.TargetModule.Other"
# assert the alias doesn't get spliced into the moduledoc
refute caller =~ "Its in the caller module\nalias ExFactor.Tmp.TargetModule\n \""
assert caller =~ "TargetModule.refactor1(arg_a)"
# asser the function uses the alias
refute caller =~ "ExFactor.Tmp.TargetModule.refactor1(arg_a)"
assert caller =~ "def pub3(arg_a) do\n TargetModule.refactor1(arg_a)"

caller_two = File.read!("lib/ex_factor/tmp/caller_two_module.ex")
assert caller_two =~ "alias ExFactor.Tmp.TargetModule"
# ensure we don't match dumbly
assert caller_two =~ "TargetModule.refactor1(arg_a)"
# asser the function uses the alias
refute caller_two =~ "ExFactor.Tmp.TargetModule.refactor1(arg_a)"
assert %ExFactor{} = change = Enum.find(changes, &(&1.module == ExFactor.Remover))
assert [{ExFactor.Remover, _bin}] = Code.compile_string(change.file_contents)
end
end

Expand Down

0 comments on commit 6924a9b

Please sign in to comment.