mirror of
https://github.com/ash-project/igniter.git
synced 2024-09-20 13:33:00 +12:00
144 lines
3.9 KiB
Elixir
144 lines
3.9 KiB
Elixir
defmodule Igniter.Code.Module do
|
|
@moduledoc "Utilities for working with Elixir modules"
|
|
require Igniter.Code.Common
|
|
alias Igniter.Code.Common
|
|
alias Sourceror.Zipper
|
|
|
|
@doc "Given a suffix, returns a module name with the prefix of the current project"
|
|
@spec module_name(String.t()) :: module()
|
|
def module_name(suffix) do
|
|
Module.concat(module_name_prefix(), suffix)
|
|
end
|
|
|
|
@doc "Returns the idiomatic file location for a given module"
|
|
@spec proper_location(module()) :: Path.t()
|
|
def proper_location(module_name) do
|
|
path =
|
|
module_name
|
|
|> Module.split()
|
|
|> Enum.map(&to_string/1)
|
|
|> Enum.map(&Macro.underscore/1)
|
|
|
|
last = List.last(path)
|
|
leading = :lists.droplast(path)
|
|
|
|
Path.join(["lib" | leading] ++ ["#{last}.ex"])
|
|
end
|
|
|
|
@doc "Parses a string into a module name"
|
|
@spec parse(String.t()) :: module()
|
|
def parse(module_name) do
|
|
module_name
|
|
|> String.split(".")
|
|
|> Module.concat()
|
|
end
|
|
|
|
@doc "The module name prefix based on the mix project's module name"
|
|
@spec module_name_prefix() :: module()
|
|
def module_name_prefix do
|
|
Mix.Project.get!()
|
|
|> Module.split()
|
|
|> :lists.droplast()
|
|
|> Module.concat()
|
|
end
|
|
|
|
# sobelow_skip ["DOS.StringToAtom"]
|
|
@doc "Moves the zipper to the body of a module that `use`s the provided module (or one of the provided modules)."
|
|
@spec move_to_module_using(Zipper.t(), module | list(module)) :: {:ok, Zipper.t()} | :error
|
|
def move_to_module_using(zipper, [module]) do
|
|
move_to_module_using(zipper, module)
|
|
end
|
|
|
|
def move_to_module_using(zipper, [module | rest] = one_of_modules)
|
|
when is_list(one_of_modules) do
|
|
case move_to_module_using(zipper, module) do
|
|
{:ok, zipper} ->
|
|
{:ok, zipper}
|
|
|
|
:error ->
|
|
move_to_module_using(zipper, rest)
|
|
end
|
|
end
|
|
|
|
# sobelow_skip ["DOS.StringToAtom"]
|
|
def move_to_module_using(zipper, module) do
|
|
split_module =
|
|
module
|
|
|> Module.split()
|
|
|> Enum.map(&String.to_atom/1)
|
|
|
|
with {:ok, zipper} <- Common.move_to_pattern(zipper, {:defmodule, _, [_, _]}),
|
|
subtree <- Zipper.subtree(zipper),
|
|
subtree <- subtree |> Zipper.down() |> Zipper.rightmost(),
|
|
subtree <- remove_module_definitions(subtree),
|
|
{:ok, _found} <-
|
|
Common.move_to(subtree, fn
|
|
{:use, _, [^module | _]} ->
|
|
true
|
|
|
|
{:use, _, [{:__aliases__, _, ^split_module} | _]} ->
|
|
true
|
|
end),
|
|
{:ok, zipper} <- Common.move_to_do_block(zipper) do
|
|
{:ok, zipper}
|
|
else
|
|
_ ->
|
|
:error
|
|
end
|
|
end
|
|
|
|
@doc "Moves the zipper to the `use` statement for a provided module."
|
|
def move_to_use(zipper, module) do
|
|
Igniter.Code.Function.move_to_function_call_in_current_scope(zipper, :use, [1, 2], fn call ->
|
|
Igniter.Code.Function.argument_matches_predicate?(
|
|
call,
|
|
0,
|
|
&Igniter.Code.Common.nodes_equal?(&1, module)
|
|
)
|
|
end)
|
|
end
|
|
|
|
def move_to_defp(zipper, fun, arity) do
|
|
do_move_to_def(zipper, fun, arity, :defp)
|
|
end
|
|
|
|
def move_to_def(zipper, fun, arity) do
|
|
do_move_to_def(zipper, fun, arity, :def)
|
|
end
|
|
|
|
defp do_move_to_def(zipper, fun, arity, kind) do
|
|
case Common.move_to_pattern(
|
|
zipper,
|
|
{^kind, _, [{^fun, _, args}, _]} when length(args) == arity
|
|
) do
|
|
:error ->
|
|
if arity == 0 do
|
|
case Common.move_to_pattern(
|
|
zipper,
|
|
{^kind, _, [{^fun, _, context}, _]} when is_atom(context)
|
|
) do
|
|
:error ->
|
|
:error
|
|
|
|
{:ok, zipper} ->
|
|
Common.move_to_do_block(zipper)
|
|
end
|
|
else
|
|
:error
|
|
end
|
|
|
|
{:ok, zipper} ->
|
|
Common.move_to_do_block(zipper)
|
|
end
|
|
end
|
|
|
|
defp remove_module_definitions(zipper) do
|
|
Zipper.traverse(zipper, fn
|
|
{:defmodule, _, _} ->
|
|
Zipper.remove(zipper)
|
|
|
|
other ->
|
|
other
|
|
end)
|
|
end
|
|
end
|