Skip to content

Commit

Permalink
WIP Use Xref.calls/1 to find all the function calls
Browse files Browse the repository at this point in the history
  • Loading branch information
ckoch-cars committed Sep 25, 2021
1 parent bab3323 commit 6864707
Show file tree
Hide file tree
Showing 5 changed files with 280 additions and 93 deletions.
8 changes: 8 additions & 0 deletions lib/ex_factor/callers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,16 @@ defmodule ExFactor.Callers do
end

def callers(mod, func, arity) do

# Code.compiler_options(parser_options: [columns: true])
# Mix.Task.rerun("compile.elixir", ["--force", "--tracer", "ExFactor.CallerTracer"])

Mix.Tasks.Xref.calls([])
|> Enum.filter(fn x ->
# {:ok, _} = Import2Alias.Server.start_link(elem(x.callee, 0))
# entries = Import2Alias.Server.entries()
# |> IO.inspect(label: "")

x.callee == {cast(mod), cast(func), arity}
end)
end
Expand Down
177 changes: 125 additions & 52 deletions lib/ex_factor/changer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,33 +11,88 @@ defmodule ExFactor.Changer do
function module reference to the new module. Respect any existing aliases.
"""
def change(opts) do
source_module = Keyword.fetch!(opts, :source_module)
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()
|> update_callers(opts)
|> Callers.callers(source_function, arity)
|> update_callers_from3(opts)
end

defp update_callers([], opts) do
# defp update_callers([], opts) do
# source_module = Keyword.fetch!(opts, :source_module)
# [%ExFactor{state: [:unchanged], message: "module: #{source_module} not found"}]
# end

# defp update_callers(callers, opts) do
# Enum.map(callers, fn caller ->
# File.read!(caller.filepath)
# |> String.split("\n")
# |> find_and_replace_target(opts, caller.filepath)
# end)
# end

defp update_callers_from3([], opts) do
source_module = Keyword.fetch!(opts, :source_module)
[%ExFactor{state: [:unchanged], message: "module: #{source_module} not found"}]
end

defp update_callers(callers, opts) do
defp update_callers_from3(callers, opts) do
Enum.map(callers, fn caller ->
File.read!(caller.filepath)
File.read!(caller.file)
|> String.split("\n")
|> find_and_replace_target(opts, caller.filepath)
|> find_and_replace_target(opts, caller.file, caller.line)
end)
end

defp find_and_replace_target(list, opts, filepath) do
# defp find_and_replace_target(list, opts, filepath) do
# # opts values
# source_module = Keyword.fetch!(opts, :source_module)
# source_function = Keyword.fetch!(opts, :source_function)
# target_module = Keyword.fetch!(opts, :target_module)
# _arity = Keyword.fetch!(opts, :arity)
# dry_run = Keyword.get(opts, :dry_run, false)

# # modified values
# source_string = Util.module_to_string(source_module)
# source_modules = String.split(source_module, ".")
# source_alias = Enum.at(source_modules, -1)
# target_alias = preferred_alias(list, target_module)
# source_alias_alt = find_alias_as(list, source_module)

# Enum.reduce(list, {:unchanged, []}, fn elem, {state, acc} ->
# cond do
# # match full module name
# String.match?(elem, ~r/#{source_string}\.#{source_function}/) ->
# elem = String.replace(elem, source_module, target_alias)
# {:changed, [elem | acc]}

# # match aliased module name
# String.match?(elem, ~r/#{source_alias}\.#{source_function}/) ->
# elem = String.replace(elem, source_alias, target_alias)
# {:changed, [elem | acc]}

# # match module name aliased :as
# String.match?(elem, ~r/#{source_alias_alt}\.#{source_function}/) ->
# elem = String.replace(elem, source_alias_alt, target_alias)
# {:changed, [elem | acc]}

# true ->
# {state, [elem | acc]}
# end
# end)
# |> maybe_add_alias(opts)
# |> write_file(source_function, filepath, dry_run)
# end

defp find_and_replace_target(list, opts, filepath, line) do
# opts values
source_module = Keyword.fetch!(opts, :source_module)
source_function = Keyword.fetch!(opts, :source_function)
target_module = Keyword.fetch!(opts, :target_module)
_arity = Keyword.fetch!(opts, :arity)
dry_run = Keyword.get(opts, :dry_run, false)

# modified values
Expand All @@ -47,33 +102,35 @@ defmodule ExFactor.Changer do
target_alias = preferred_alias(list, target_module)
source_alias_alt = find_alias_as(list, source_module)

Enum.reduce(list, {:unchanged, []}, fn elem, {state, acc} ->
cond do
fn_line = Enum.at(list, line - 1)
{state, new_line} = cond do
# match full module name
String.match?(elem, ~r/#{source_string}\.#{source_function}/) ->
elem = String.replace(elem, source_module, target_alias)
{:changed, [elem | acc]}
String.match?(fn_line, ~r/#{source_string}\.#{source_function}/) ->
fn_line = String.replace(fn_line, source_module, target_alias)
{:changed, fn_line}

# match aliased module name
String.match?(elem, ~r/#{source_alias}\.#{source_function}/) ->
elem = String.replace(elem, source_alias, target_alias)
{:changed, [elem | acc]}
String.match?(fn_line, ~r/#{source_alias}\.#{source_function}/) ->
fn_line = String.replace(fn_line, source_alias, target_alias)
{:changed, fn_line}

# match module name aliased :as
String.match?(elem, ~r/#{source_alias_alt}\.#{source_function}/) ->
elem = String.replace(elem, source_alias_alt, target_alias)
{:changed, [elem | acc]}
String.match?(fn_line, ~r/#{source_alias_alt}\.#{source_function}/) ->
fn_line = String.replace(fn_line, source_alias_alt, target_alias)
{:changed, fn_line}

true ->
{state, [elem | acc]}
{:unchanged, fn_line}
end
end)

{state, List.replace_at(list, line - 1, new_line)}
|> maybe_add_alias(opts)
|> maybe_add_import(opts)
|> write_file(source_function, filepath, dry_run)
end

defp find_alias_as(list, module) do
aalias = Enum.find(list, "", fn el -> match_alias?(el, module) end)
aalias = Enum.find(list, "", fn el -> str_match?(el, module) end)

if String.match?(aalias, ~r/, as: /) do
aalias
Expand All @@ -99,28 +156,19 @@ defmodule ExFactor.Changer do
defp write_file({state, contents_list}, _, target_path, true) do
%ExFactor{
path: target_path,
state: [:dry_run, state],
state: [:dry_run | [state]],
message: "--dry_run changes to make",
file_contents: list_to_string(contents_list)
}
end

defp write_file({:unchanged, contents_list}, source_function, target_path, _dry_run) do
%ExFactor{
path: target_path,
state: [:unchanged],
message: "function: #{source_function} not found, no changes to make",
file_contents: list_to_string(contents_list)
}
end

defp write_file({state, contents_list}, _, target_path, _dry_run) do
contents = list_to_string(contents_list)
File.write(target_path, contents, [:write])

%ExFactor{
path: target_path,
state: [state],
state: state,
message: "changes made",
file_contents: contents
}
Expand All @@ -132,49 +180,74 @@ defmodule ExFactor.Changer do
|> Enum.join("\n")
end

defp maybe_add_alias({:unchanged, contents_list}, _), do: {:unchanged, contents_list}

defp maybe_add_alias({state, contents_list}, opts) do
source_module = Keyword.fetch!(opts, :source_module)
source_string = Util.module_to_string(source_module)
target_module = Keyword.fetch!(opts, :target_module)
target_string = Util.module_to_string(target_module)

# when module has not aliases
contents_list = if Enum.find(contents_list, fn el -> match_alias?(el, "") end) do
# when module has no aliases
contents_list = if Enum.find(contents_list, fn el -> str_match?(el, "") end) do
contents_list
else
List.insert_at(contents_list, 1, "alias #{target_string}")
end

if Enum.find(contents_list, fn el -> match_alias?(el, target_string) end) do
if Enum.find(contents_list, fn el -> str_match?(el, target_string) end) do
{state, contents_list}
else
contents_list
|> Enum.reduce({:none, []}, fn elem, {prev, acc} ->
cond do
match_alias?(elem, source_string) ->
str_match?(elem, source_string) ->
new_alias = String.replace(elem, source_string, target_string)
{:alias, [elem | [new_alias | acc]]}
prev == :alias and not match_alias?(elem, "") ->
{:none, [elem | ["alias #{target_string}" | acc]]}
match_alias?(elem, "") ->
{:alias, [elem | acc]}
prev == :alias and not str_match?(elem, "") ->
{:alias_added, [elem | ["alias #{target_string}" | acc]]}
str_match?(elem, "") ->
{state, [elem | acc]}
true ->
{:none, [elem | acc]}
{state, [elem | acc]}
end
end)
|> elem(1)
|> Enum.reverse()
|> then(fn list -> {state, list} end)
|> then(fn {state, list} -> {state, Enum.reverse(list)} end)
end
end

defp match_alias?(string, "") do
String.match?(string, ~r/(^|\s)alias\s/)
defp maybe_add_import({state, contents_list}, opts) do
source_module = Keyword.fetch!(opts, :source_module)
target_module = Keyword.fetch!(opts, :target_module)
target_string = Util.module_to_string(target_module)
source_modules = String.split(source_module, ".")
source_alias = Enum.at(source_modules, -1)
source_alias_alt = find_alias_as(contents_list, source_module)

# when module has no imports
index = Enum.find_index(contents_list, fn el -> str_match?(el, source_alias, "import") end) ||
Enum.find_index(contents_list, fn el -> str_match?(el, source_alias_alt, "import") end)

new_state = if state == :unchanged do
[:import_added]
else
[:import_added | [state]]
end
if index do
{new_state, List.insert_at(contents_list, index + 1, "import #{target_string}")}
else
{state, contents_list}
end
end

defp match_alias?(string, module_string) do
String.match?(string, ~r/(^|\s)alias #{module_string}(\s|$|\,)/)
defp str_match?(string, match, token \\ "alias")
defp str_match?(string, "", token) do
String.match?(string, ~r/(^|\s)#{token}\s/)
end

defp str_match?(string, module_string, token) do
String.match?(string, ~r/(^|\s)#{token} #{module_string}(\s|$|\,)/)
end

# defp try_replace(string, "", _), do: string
# defp try_replace(string, nil, _), do: string
# defp try_replace(string, source, target), do: String.replace(string, source, target)
end
4 changes: 3 additions & 1 deletion test/ex_factor/callers_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ defmodule ExFactor.CallersTest do
end

test "it converts strings to atoms" do
[one, _two, _three] = Callers.callers("ExFactor.Parser", "all_functions", 1)
[one, _two, _three] = "ExFactor.Parser"
|> Callers.callers("all_functions", 1)
|> Enum.sort_by(&(&1.file))

assert one.caller_module == ExFactor.Callers
assert one.file == "lib/ex_factor/callers.ex"
Expand Down
Loading

0 comments on commit 6864707

Please sign in to comment.