Skip to content

Commit

Permalink
fns added. refactor and cleanup needed.
Browse files Browse the repository at this point in the history
  • Loading branch information
ckoch-cars committed Sep 13, 2021
1 parent 8bc9c52 commit eca25dd
Show file tree
Hide file tree
Showing 6 changed files with 451 additions and 37 deletions.
8 changes: 7 additions & 1 deletion lib/ex_factor.ex
@@ -1,5 +1,11 @@
defmodule ExFactor do
@moduledoc """
Documentation for `ExFactor`.
`ExFactor` is a refactoring helper.
By identifying a Module, function name, and arity, it will identify all non-test usages
and extract them to a new Module.
If the Module exists, it add the function to the end of the file and change all calls to the
new module's name.
"""
end
25 changes: 16 additions & 9 deletions lib/ex_factor/extractor.ex
Expand Up @@ -2,41 +2,48 @@ defmodule ExFactor.Extractor do
@moduledoc """
Documentation for `ExFactor.Extractor`.
"""
alias ExFactor.Neighbors
alias ExFactor.Parser

def emplace(files, opts) do
def emplace(opts) do
source_module = Keyword.get(opts, :source_module)
target_module = Keyword.get(opts, :target_module)
source_function = Keyword.get(opts, :source_function)
arity = Keyword.get(opts, :arity)
target_function = Keyword.get(opts, :target_function, source_function)
target_path = Keyword.get(opts, :target_path, path(target_module))
source_path = Keyword.get(opts, :source_path, path(source_module))
{_ast, functions} = Parser.public_functions(source_path)
# ast |> IO.inspect(label: "")
map = Enum.find(functions, &(&1.name == source_function && &1.arity == arity))
{ast, block_contents} = Parser.block_contents(source_path)
to_extract = Neighbors.prev(block_contents, source_function)

case File.exists?(target_path) do
true ->
"somehow we need to add the fn to this file"
{ast, list} = Parser.read_file(target_path)
{:defmodule, [do: [line: _begin_line], end: [line: end_line], line: _], _} = ast
string_fns = Macro.to_string(to_extract)

list
|> List.insert_at(end_line - 1, refactor_message())
|> List.insert_at(end_line, string_fns)
|> Enum.join("\n")
|> then(fn contents -> File.write(target_path, contents, [:write]) end)

_ ->
content =
quote generated: true do
defmodule unquote(target_module) do
@moduledoc false
unquote(map.ast)
unquote(to_extract)
end
end
|> Macro.to_string()

# |> IO.inspect(label: "quoted")

File.write(target_path, content)
end
end

defp path(module) do
Path.join(["lib", Macro.underscore(module) <> ".ex"])
end

defp refactor_message, do: "#refactored function moved with ExFactor"
end
30 changes: 30 additions & 0 deletions lib/ex_factor/neighbors.ex
@@ -0,0 +1,30 @@
defmodule ExFactor.Neighbors do
@moduledoc """
Documentation for `ExFactor.Neighbors`.
"""

def prev(block, fn_name) do
block
|> Enum.reduce({[], []}, fn el, acc ->
eval_elem(el, acc, fn_name)
end)
|> elem(1)
end

defp eval_elem({type, _, [{name, _, _} | _]} = el, {pending, acc}, name)
when type in [:def, :defp] do
{[], acc ++ pending ++ [el]}
end

defp eval_elem({type, _, _}, {_pending, acc}, _name) when type in [:def, :defp] do
{[], acc}
end

defp eval_elem({type, _, _}, {pending, acc}, _name) when type in [:alias] do
{pending, acc}
end

defp eval_elem(el, {pending, acc}, _name) do
{pending ++ [el], acc}
end
end
166 changes: 155 additions & 11 deletions test/ex_factor/extractor_test.exs
Expand Up @@ -2,12 +2,6 @@ defmodule ExFactor.ExtractorTest do
use ExUnit.Case
alias ExFactor.Extractor

setup do
File.rm("test/support/source_module.ex")
File.rm("test/support/target_module.ex")
:ok
end

test "write a new file with the function" do
content = """
defmodule ExFactorSampleModule do
Expand All @@ -21,6 +15,7 @@ defmodule ExFactor.ExtractorTest do
"""

File.write("test/support/source_module.ex", content)

target_path = "test/support/target_module.ex"
File.rm(target_path)

Expand All @@ -33,15 +28,19 @@ defmodule ExFactor.ExtractorTest do
arity: 1
]

path = Path.join([Mix.Project.app_path(), "lib", target_path <> ".ex"])
Extractor.emplace(["test/support/source_module.ex"], opts)
Extractor.emplace(opts)

file = File.read!(target_path) |> IO.inspect(label: "target_path")
file = File.read!(target_path)
assert file =~ "def(pub1(arg1))"
assert file =~ "defmodule(ExFactor.NewMod) do"
File.rm("test/support/source_module.ex")
File.rm("test/support/target_module.ex")
end

test "write a new file with the function, infer some defaults" do
File.rm("lib/ex_factor/source_module.ex")
File.rm("lib/ex_factor/target_module.ex")

content = """
defmodule ExFactor.SourceModule do
@somedoc "This is somedoc"
Expand All @@ -60,8 +59,7 @@ defmodule ExFactor.ExtractorTest do
arity: 1
]

# path = Path.join([Mix.Project.app_path(), "lib", target_path <> ".ex"])
Extractor.emplace(["lib/ex_factor/source_module.ex"], opts)
Extractor.emplace(opts)

file = File.read!("lib/ex_factor/target_module.ex")
assert file =~ "def(pub1(arg1))"
Expand All @@ -70,4 +68,150 @@ defmodule ExFactor.ExtractorTest do
File.rm("lib/ex_factor/source_module.ex")
File.rm("lib/ex_factor/target_module.ex")
end

test "write the function into an existing module" do
File.rm("lib/ex_factor/source_module.ex")
File.rm("lib/ex_factor/target_module.ex")

content = """
defmodule ExFactor.SourceModule do
@somedoc "This is somedoc"
def refactor1(arg1) do
:ok
end
end
"""

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

content = """
defmodule ExFactor.TargetModule do
@somedoc "This is somedoc TargetModule"
# this is a comment, it will get elided
def pub_exists(arg_exists) do
:ok
end
end
"""

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

opts = [
target_module: ExFactor.TargetModule,
source_module: ExFactor.SourceModule,
source_function: :refactor1,
arity: 1
]

Extractor.emplace(opts)

file = File.read!("lib/ex_factor/target_module.ex")
assert file =~ "def(refactor1(arg1)) do"
assert file =~ "def pub_exists(arg_exists) do"
assert file =~ "defmodule ExFactor.TargetModule do"

File.rm("lib/ex_factor/source_module.ex")
File.rm("lib/ex_factor/target_module.ex")
end

test "write multiple functions, into an existing module" do
File.rm("lib/ex_factor/source_module.ex")
File.rm("lib/ex_factor/target_module.ex")

content = """
defmodule ExFactor.SourceModule do
@somedoc "This is somedoc"
def refactor1(arg1) do
:ok
end
end
"""

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

content = """
defmodule ExFactor.TargetModule do
@somedoc "This is somedoc TargetModule"
# this is a comment, it will get elided
def pub_exists(arg_exists) do
:ok
end
def pub_exists(:error) do
:error
end
end
"""

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

opts = [
target_module: ExFactor.TargetModule,
source_module: ExFactor.SourceModule,
source_function: :refactor1,
arity: 1
]

Extractor.emplace(opts)

file = File.read!("lib/ex_factor/target_module.ex")
assert file =~ "def(refactor1(arg1)) do"
assert file =~ "def pub_exists(arg_exists) do"
assert file =~ "def pub_exists(:error) do"
assert file =~ "defmodule ExFactor.TargetModule do"

File.rm("lib/ex_factor/source_module.ex")
File.rm("lib/ex_factor/target_module.ex")
end

test "write multiple functions and their docs, into an existing module" do
File.rm("lib/ex_factor/source_module.ex")
File.rm("lib/ex_factor/target_module.ex")

content = """
defmodule ExFactor.SourceModule do
@somedoc "This is somedoc"
@doc "this is some documentation for refactor1/1"
def refactor1(arg1) do
:ok
end
def refactor1([]) do
:empty
end
end
"""

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

content = """
defmodule ExFactor.TargetModule do
@somedoc "This is somedoc TargetModule"
@doc "some docs"
def pub_exists(arg_exists) do
:ok
end
def pub_exists(:error) do
:error
end
end
"""

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

opts = [
target_module: ExFactor.TargetModule,
source_module: ExFactor.SourceModule,
source_function: :refactor1,
arity: 1
]

Extractor.emplace(opts)

file = File.read!("lib/ex_factor/target_module.ex")
assert file =~ "def(refactor1(arg1)) do"
assert file =~ "def(refactor1([])) do"
assert file =~ " @doc \"some docs\""

File.rm("lib/ex_factor/source_module.ex")
File.rm("lib/ex_factor/target_module.ex")
end
end

0 comments on commit eca25dd

Please sign in to comment.