igniter/lib/code/module.ex

145 lines
3.9 KiB
Elixir
Raw Normal View History

2024-06-13 10:22:08 +12:00
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
2024-06-13 11:16:03 +12:00
# sobelow_skip ["DOS.StringToAtom"]
2024-06-13 10:22:08 +12:00
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