mirror of
https://github.com/ash-project/igniter.git
synced 2024-09-19 13:02:51 +12:00
improvement: Support for extensions in igniter config
improvement: Add a phoenix extension to prevent moving modules that may be phoenix-y fix: reevaluate .igniter.exs when it changes
This commit is contained in:
parent
9994dcf5be
commit
c8b35915a5
21 changed files with 1260 additions and 529 deletions
|
@ -86,7 +86,7 @@
|
|||
# If you don't want TODO comments to cause `mix credo` to fail, just
|
||||
# set this value to 0 (zero).
|
||||
#
|
||||
{Credo.Check.Design.TagTODO, [exit_status: 2]},
|
||||
{Credo.Check.Design.TagTODO, false},
|
||||
{Credo.Check.Design.TagFIXME, []},
|
||||
|
||||
#
|
||||
|
|
|
@ -15,7 +15,3 @@ if Mix.env() == :dev do
|
|||
],
|
||||
version_tag_prefix: "v"
|
||||
end
|
||||
|
||||
if Mix.env() == :test do
|
||||
config :igniter, :testing?, true
|
||||
end
|
||||
|
|
|
@ -167,7 +167,7 @@ defmodule Igniter do
|
|||
if igniter.assigns[:test_mode?] do
|
||||
igniter.assigns[:test_files]
|
||||
|> Map.keys()
|
||||
|> Enum.filter(&GlobEx.match?(glob, &1))
|
||||
|> Enum.filter(&GlobEx.match?(glob, Path.expand(&1)))
|
||||
else
|
||||
glob
|
||||
|> GlobEx.ls()
|
||||
|
@ -705,19 +705,17 @@ defmodule Igniter do
|
|||
end
|
||||
|
||||
@doc "This function stores in the igniter if its been run before, so it is only run once, which is expensive."
|
||||
if Application.compile_env(:igniter, :testing?, false) do
|
||||
def include_all_elixir_files(igniter) do
|
||||
def include_all_elixir_files(igniter) do
|
||||
if igniter.assigns[:private][:included_all_elixir_files?] do
|
||||
igniter
|
||||
end
|
||||
else
|
||||
def include_all_elixir_files(igniter) do
|
||||
if igniter.assigns[:private][:included_all_elixir_files?] do
|
||||
igniter
|
||||
else
|
||||
igniter
|
||||
|> include_glob("{lib,test,config}/**/*.{ex,exs}")
|
||||
|> assign_private(:included_all_elixir_files?, true)
|
||||
end
|
||||
else
|
||||
igniter
|
||||
|> Igniter.Project.IgniterConfig.get(:source_folders)
|
||||
|> Enum.reduce(igniter, fn source_folder, igniter ->
|
||||
include_glob(igniter, Path.join(source_folder, "/**/*.{ex,exs}"))
|
||||
end)
|
||||
|> include_glob("{test,config}/**/*.{ex,exs}")
|
||||
|> assign_private(:included_all_elixir_files?, true)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -992,7 +990,7 @@ defmodule Igniter do
|
|||
|> Mix.shell().info()
|
||||
end
|
||||
|
||||
defp format(igniter, adding_paths \\ nil) do
|
||||
defp format(igniter, adding_paths, reevaluate_igniter_config? \\ true) do
|
||||
igniter =
|
||||
igniter
|
||||
|> include_existing_elixir_file("config/config.exs", require?: false)
|
||||
|
@ -1000,7 +998,8 @@ defmodule Igniter do
|
|||
|
||||
if adding_paths &&
|
||||
Enum.any?(List.wrap(adding_paths), &(Path.basename(&1) == ".formatter.exs")) do
|
||||
format(igniter)
|
||||
format(igniter, nil, false)
|
||||
|> reevaluate_igniter_config(adding_paths, reevaluate_igniter_config?)
|
||||
else
|
||||
igniter =
|
||||
"**/.formatter.exs"
|
||||
|
@ -1077,9 +1076,22 @@ defmodule Igniter do
|
|||
end)
|
||||
|
||||
%{igniter | rewrite: rewrite}
|
||||
|> reevaluate_igniter_config(adding_paths, reevaluate_igniter_config?)
|
||||
end
|
||||
end
|
||||
|
||||
defp reevaluate_igniter_config(igniter, adding_paths, true) do
|
||||
if is_nil(adding_paths) || ".igniter.exs" in List.wrap(adding_paths) do
|
||||
parse_igniter_config(igniter)
|
||||
else
|
||||
igniter
|
||||
end
|
||||
end
|
||||
|
||||
defp reevaluate_igniter_config(igniter, _adding_paths, false) do
|
||||
igniter
|
||||
end
|
||||
|
||||
# for now we only eval `config.exs`
|
||||
defp with_evaled_configs(rewrite, fun) do
|
||||
[
|
||||
|
@ -1331,7 +1343,7 @@ defmodule Igniter do
|
|||
warnings: Enum.uniq(igniter.warnings),
|
||||
tasks: Enum.uniq(igniter.tasks)
|
||||
}
|
||||
|> Igniter.Code.Module.move_files()
|
||||
|> Igniter.Project.Module.move_files()
|
||||
|> remove_unchanged_files()
|
||||
|> then(fn igniter ->
|
||||
if needs_test_support? do
|
||||
|
@ -1363,7 +1375,20 @@ defmodule Igniter do
|
|||
|
||||
{:ok, source} ->
|
||||
{igniter_exs, _} = Rewrite.Source.get(source, :quoted) |> Code.eval_quoted()
|
||||
assign(igniter, :igniter_exs, igniter_exs)
|
||||
|
||||
assign(
|
||||
igniter,
|
||||
:igniter_exs,
|
||||
Keyword.update(igniter_exs, :extensions, [], fn extensions ->
|
||||
Enum.map(extensions, fn
|
||||
{extension, opts} ->
|
||||
{extension, opts}
|
||||
|
||||
extension ->
|
||||
{extension, []}
|
||||
end)
|
||||
end)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -102,7 +102,16 @@ defmodule Igniter.Code.Common do
|
|||
"""
|
||||
@spec expand_literal(Zipper.t()) :: {:ok, any()} | :error
|
||||
def expand_literal(zipper) do
|
||||
if Macro.quoted_literal?(zipper.node) do
|
||||
quoted_literal? =
|
||||
case zipper.node do
|
||||
{:__block__, _, _} = value ->
|
||||
!extendable_block?(value)
|
||||
|
||||
node ->
|
||||
Macro.quoted_literal?(node)
|
||||
end
|
||||
|
||||
if quoted_literal? do
|
||||
{v, _} = Code.eval_quoted(zipper.node)
|
||||
{:ok, v}
|
||||
else
|
||||
|
|
|
@ -6,458 +6,6 @@ defmodule Igniter.Code.Module do
|
|||
|
||||
require Logger
|
||||
|
||||
@doc """
|
||||
Finds a module and updates its contents wherever it is.
|
||||
|
||||
If the module does not yet exist, it is created with the provided contents. In that case,
|
||||
the path is determined with `Igniter.Code.Module.proper_location/2`, but may optionally be overwritten with options below.
|
||||
|
||||
# Options
|
||||
|
||||
- `:path` - Path where to create the module, relative to the project root. Default: `nil` (uses `:kind` to determine the path).
|
||||
"""
|
||||
def find_and_update_or_create_module(igniter, module_name, contents, updater, opts \\ [])
|
||||
|
||||
def find_and_update_or_create_module(
|
||||
igniter,
|
||||
module_name,
|
||||
contents,
|
||||
updater,
|
||||
opts
|
||||
)
|
||||
when is_list(opts) do
|
||||
case find_and_update_module(igniter, module_name, updater) do
|
||||
{:ok, igniter} ->
|
||||
igniter
|
||||
|
||||
{:error, igniter} ->
|
||||
create_module(igniter, module_name, contents, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def find_and_update_or_create_module(
|
||||
igniter,
|
||||
module_name,
|
||||
contents,
|
||||
updater,
|
||||
path
|
||||
)
|
||||
when is_binary(path) do
|
||||
Logger.warning("You should use `opts` instead of `path` and pass `path` as a keyword.")
|
||||
find_and_update_or_create_module(igniter, module_name, contents, updater, path: path)
|
||||
end
|
||||
|
||||
@doc "Creates a new file & module in its appropriate location."
|
||||
def create_module(igniter, module_name, contents, opts \\ []) do
|
||||
contents =
|
||||
"""
|
||||
defmodule #{inspect(module_name)} do
|
||||
#{contents}
|
||||
end
|
||||
"""
|
||||
|
||||
location =
|
||||
case Keyword.get(opts, :path, nil) do
|
||||
nil ->
|
||||
proper_location(module_name)
|
||||
|
||||
path ->
|
||||
path
|
||||
end
|
||||
|
||||
Igniter.create_new_file(igniter, location, contents)
|
||||
end
|
||||
|
||||
@doc "Checks if a module is defined somewhere in the project. The returned igniter should not be discarded."
|
||||
def module_exists?(igniter, module_name) do
|
||||
case find_module(igniter, module_name) do
|
||||
{:ok, {igniter, _, _}} -> {true, igniter}
|
||||
{:error, igniter} -> {false, igniter}
|
||||
end
|
||||
end
|
||||
|
||||
def find_and_update_module!(igniter, module_name, updater) do
|
||||
case find_and_update_module(igniter, module_name, updater) do
|
||||
{:ok, igniter} -> igniter
|
||||
{:error, _igniter} -> raise "Could not find module #{inspect(module_name)}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Finds a module and updates its contents. Returns `{:error, igniter}` if the module could not be found. Do not discard this igniter."
|
||||
@spec find_and_update_module(Igniter.t(), module(), (Zipper.t() -> {:ok, Zipper.t()} | :error)) ::
|
||||
{:ok, Igniter.t()} | {:error, Igniter.t()}
|
||||
def find_and_update_module(igniter, module_name, updater) do
|
||||
case find_module(igniter, module_name) do
|
||||
{:ok, {igniter, source, zipper}} ->
|
||||
case Common.move_to_do_block(zipper) do
|
||||
{:ok, zipper} ->
|
||||
case updater.(zipper) do
|
||||
{:ok, zipper} ->
|
||||
new_quoted =
|
||||
zipper
|
||||
|> Zipper.topmost()
|
||||
|> Zipper.node()
|
||||
|
||||
new_source = Rewrite.Source.update(source, :quoted, new_quoted)
|
||||
{:ok, %{igniter | rewrite: Rewrite.update!(igniter.rewrite, new_source)}}
|
||||
|
||||
{:error, error} ->
|
||||
{:ok, Igniter.add_issue(igniter, error)}
|
||||
|
||||
{:warning, error} ->
|
||||
{:ok, Igniter.add_warning(igniter, error)}
|
||||
end
|
||||
|
||||
_ ->
|
||||
{:error, igniter}
|
||||
end
|
||||
|
||||
{:error, igniter} ->
|
||||
{:error, igniter}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Finds a module, returning a new igniter, and the source and zipper location. This new igniter should not be discarded.
|
||||
|
||||
In general, you should not use the returned source and zipper to update the module, instead, use this to interrogate
|
||||
the contents or source in some way, and then call `find_and_update_module/3` with a function to perform an update.
|
||||
"""
|
||||
@spec find_module(Igniter.t(), module()) ::
|
||||
{:ok, {Igniter.t(), Rewrite.Source.t(), Zipper.t()}} | {:error, Igniter.t()}
|
||||
def find_module(igniter, module_name) do
|
||||
igniter = Igniter.include_all_elixir_files(igniter)
|
||||
|
||||
igniter
|
||||
|> Map.get(:rewrite)
|
||||
|> Task.async_stream(
|
||||
fn source ->
|
||||
{source
|
||||
|> Rewrite.Source.get(:quoted)
|
||||
|> Zipper.zip()
|
||||
|> move_to_defmodule(module_name), source}
|
||||
end,
|
||||
timeout: :infinity
|
||||
)
|
||||
|> Enum.find_value({:error, igniter}, fn
|
||||
{:ok, {{:ok, zipper}, source}} ->
|
||||
{:ok, {igniter, source, zipper}}
|
||||
|
||||
_other ->
|
||||
false
|
||||
end)
|
||||
end
|
||||
|
||||
@spec find_all_matching_modules(igniter :: Igniter.t(), (module(), Zipper.t() -> boolean)) ::
|
||||
{Igniter.t(), [module()]}
|
||||
def find_all_matching_modules(igniter, predicate) do
|
||||
igniter =
|
||||
igniter
|
||||
|> Igniter.include_all_elixir_files()
|
||||
|
||||
matching_modules =
|
||||
igniter
|
||||
|> Map.get(:rewrite)
|
||||
|> Enum.filter(&match?(%Rewrite.Source{filetype: %Rewrite.Source.Ex{}}, &1))
|
||||
|> Task.async_stream(
|
||||
fn source ->
|
||||
source
|
||||
|> Rewrite.Source.get(:quoted)
|
||||
|> Zipper.zip()
|
||||
|> Zipper.traverse([], fn zipper, acc ->
|
||||
case zipper.node do
|
||||
{:defmodule, _, [_, _]} ->
|
||||
{:ok, mod_zipper} = Igniter.Code.Function.move_to_nth_argument(zipper, 0)
|
||||
|
||||
module_name =
|
||||
mod_zipper
|
||||
|> Igniter.Code.Common.expand_alias()
|
||||
|> Zipper.node()
|
||||
|> Igniter.Code.Module.to_module_name()
|
||||
|
||||
with module_name when not is_nil(module_name) <- module_name,
|
||||
{:ok, do_zipper} <- Igniter.Code.Common.move_to_do_block(zipper),
|
||||
true <- predicate.(module_name, do_zipper) do
|
||||
{zipper, [module_name | acc]}
|
||||
else
|
||||
_ ->
|
||||
{zipper, acc}
|
||||
end
|
||||
|
||||
_ ->
|
||||
{zipper, acc}
|
||||
end
|
||||
end)
|
||||
|> elem(1)
|
||||
end,
|
||||
timeout: :infinity
|
||||
)
|
||||
|> Enum.flat_map(fn {:ok, v} ->
|
||||
v
|
||||
end)
|
||||
|> Enum.uniq()
|
||||
|
||||
{igniter, matching_modules}
|
||||
end
|
||||
|
||||
@doc "Given a suffix, returns a module name with the prefix of the current project."
|
||||
@spec module_name(String.t()) :: module()
|
||||
@deprecated "Use `module_name/2` instead."
|
||||
def module_name(suffix) do
|
||||
Module.concat(module_name_prefix(), suffix)
|
||||
end
|
||||
|
||||
@doc "Given a suffix, returns a module name with the prefix of the current project."
|
||||
@spec module_name(Igniter.t(), String.t()) :: module()
|
||||
def module_name(igniter, suffix) do
|
||||
Module.concat(module_name_prefix(igniter), suffix)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the idiomatic file location for a given module, starting with "lib/".
|
||||
|
||||
Examples:
|
||||
|
||||
iex> Igniter.Code.Module.proper_location(MyApp.Hello)
|
||||
"lib/my_app/hello.ex"
|
||||
"""
|
||||
@spec proper_location(module(), source_folder :: String.t()) :: Path.t()
|
||||
def proper_location(module_name, source_folder \\ "lib") do
|
||||
do_proper_location(module_name, {:source_folder, source_folder})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the test file location for a given module, according to
|
||||
`mix test` expectations, starting with "test/" and ending with "_test.exs".
|
||||
|
||||
Examples:
|
||||
|
||||
iex> Igniter.Code.Module.proper_test_location(MyApp.Hello)
|
||||
"test/my_app/hello_test.exs"
|
||||
|
||||
iex> Igniter.Code.Module.proper_test_location(MyApp.HelloTest)
|
||||
"test/my_app/hello_test.exs"
|
||||
"""
|
||||
@spec proper_test_location(module()) :: Path.t()
|
||||
def proper_test_location(module_name) do
|
||||
do_proper_location(module_name, :test)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the test support location for a given module, starting with
|
||||
"test/support/" and dropping the module name prefix in the path.
|
||||
|
||||
Examples:
|
||||
|
||||
iex> Igniter.Code.Module.proper_test_support_location(MyApp.DataCase)
|
||||
"test/support/data_case.ex"
|
||||
"""
|
||||
@spec proper_test_support_location(module()) :: Path.t()
|
||||
def proper_test_support_location(module_name) do
|
||||
do_proper_location(module_name, {:source_folder, "test/support"})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def move_files(igniter, opts \\ []) do
|
||||
module_location_config = Igniter.Project.IgniterConfig.get(igniter, :module_location)
|
||||
dont_move_files = Igniter.Project.IgniterConfig.get(igniter, :dont_move_files)
|
||||
|
||||
igniter =
|
||||
if opts[:move_all?] do
|
||||
Igniter.include_all_elixir_files(igniter)
|
||||
else
|
||||
igniter
|
||||
end
|
||||
|
||||
igniter.rewrite
|
||||
|> Stream.filter(&(Path.extname(&1.path) in [".ex", ".exs"]))
|
||||
|> Stream.reject(&non_movable_file?(&1.path, dont_move_files))
|
||||
|> Enum.reduce(igniter, fn source, igniter ->
|
||||
zipper =
|
||||
source
|
||||
|> Rewrite.Source.get(:quoted)
|
||||
|> Zipper.zip()
|
||||
|
||||
with {:ok, zipper} <- Igniter.Code.Module.move_to_defmodule(zipper),
|
||||
{:ok, zipper} <- Igniter.Code.Function.move_to_nth_argument(zipper, 0),
|
||||
module <-
|
||||
zipper
|
||||
|> Igniter.Code.Common.expand_alias()
|
||||
|> Zipper.node(),
|
||||
module when not is_nil(module) <- to_module_name(module),
|
||||
new_path when not is_nil(new_path) <-
|
||||
should_move_file_to(igniter, source, module, module_location_config, opts) do
|
||||
Igniter.move_file(igniter, source.path, new_path, error_if_exists?: false)
|
||||
else
|
||||
_ ->
|
||||
igniter
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp non_movable_file?(path, dont_move_files) do
|
||||
Enum.any?(dont_move_files, fn
|
||||
exclusion_pattern when is_binary(exclusion_pattern) ->
|
||||
path == exclusion_pattern
|
||||
|
||||
exclusion_pattern when is_struct(exclusion_pattern, Regex) ->
|
||||
Regex.match?(exclusion_pattern, path)
|
||||
end)
|
||||
end
|
||||
|
||||
defp should_move_file_to(igniter, source, module, module_location_config, opts) do
|
||||
paths_created =
|
||||
igniter.rewrite
|
||||
|> Enum.filter(fn source ->
|
||||
Rewrite.Source.from?(source, :string)
|
||||
end)
|
||||
|> Enum.map(& &1.path)
|
||||
|
||||
split_path =
|
||||
source.path
|
||||
|> Path.relative_to_cwd()
|
||||
|> Path.split()
|
||||
|
||||
igniter
|
||||
|> Igniter.Project.IgniterConfig.get(:source_folders)
|
||||
|> Enum.filter(fn source_folder ->
|
||||
List.starts_with?(split_path, Path.split(source_folder))
|
||||
end)
|
||||
|> Enum.max_by(
|
||||
fn source_folder ->
|
||||
source_folder
|
||||
|> Path.split()
|
||||
|> Enum.zip(split_path)
|
||||
|> Enum.take_while(fn {l, r} -> l == r end)
|
||||
|> Enum.count()
|
||||
end,
|
||||
fn -> nil end
|
||||
)
|
||||
|> case do
|
||||
nil ->
|
||||
if Enum.at(split_path, 0) == "test" &&
|
||||
String.ends_with?(source.path, "_test.exs") do
|
||||
{:ok, proper_test_location(module)}
|
||||
else
|
||||
:error
|
||||
end
|
||||
|
||||
source_folder ->
|
||||
{:ok, proper_location(module, source_folder)}
|
||||
end
|
||||
|> case do
|
||||
:error ->
|
||||
nil
|
||||
|
||||
{:ok, proper_location} ->
|
||||
case module_location_config do
|
||||
:inside_matching_folder ->
|
||||
{[filename, folder], rest} =
|
||||
proper_location
|
||||
|> Path.split()
|
||||
|> Enum.reverse()
|
||||
|> Enum.split(2)
|
||||
|
||||
inside_matching_folder =
|
||||
[filename, Path.rootname(filename), folder]
|
||||
|> Enum.concat(rest)
|
||||
|> Enum.reverse()
|
||||
|> Path.join()
|
||||
|
||||
inside_matching_folder_dirname = Path.dirname(inside_matching_folder)
|
||||
|
||||
just_created_folder? =
|
||||
Enum.any?(paths_created, fn path ->
|
||||
List.starts_with?(Path.split(path), Path.split(inside_matching_folder_dirname))
|
||||
end)
|
||||
|
||||
should_use_inside_matching_folder? =
|
||||
if opts[:move_all?] do
|
||||
dir?(igniter, inside_matching_folder_dirname) || just_created_folder?
|
||||
else
|
||||
source.path == proper_location(module) &&
|
||||
!dir?(igniter, inside_matching_folder_dirname) && just_created_folder?
|
||||
end
|
||||
|
||||
if should_use_inside_matching_folder? do
|
||||
inside_matching_folder
|
||||
else
|
||||
proper_location
|
||||
end
|
||||
|
||||
:outside_matching_folder ->
|
||||
if opts[:move_all?] || Rewrite.Source.from?(source, :string) do
|
||||
proper_location
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp dir?(igniter, folder) do
|
||||
if igniter.assigns[:test_mode?] do
|
||||
igniter.assigns[:test_files]
|
||||
|> Map.keys()
|
||||
|> Enum.any?(fn file_path ->
|
||||
List.starts_with?(Path.split(file_path), Path.split(folder))
|
||||
end)
|
||||
else
|
||||
File.dir?(folder)
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def to_module_name({:__aliases__, _, parts}), do: Module.concat(parts)
|
||||
def to_module_name(value) when is_atom(value) and not is_nil(value), do: value
|
||||
def to_module_name(_), do: nil
|
||||
|
||||
defp do_proper_location(module_name, kind) do
|
||||
path =
|
||||
module_name
|
||||
|> Module.split()
|
||||
|> case do
|
||||
["Mix", "Tasks" | rest] ->
|
||||
suffix =
|
||||
rest
|
||||
|> Enum.map(&to_string/1)
|
||||
|> Enum.map_join(".", &Macro.underscore/1)
|
||||
|
||||
["mix", "tasks", suffix]
|
||||
|
||||
other ->
|
||||
other
|
||||
|> Enum.map(&to_string/1)
|
||||
|> Enum.map(&Macro.underscore/1)
|
||||
end
|
||||
|
||||
last = List.last(path)
|
||||
leading = :lists.droplast(path)
|
||||
|
||||
case kind do
|
||||
:test ->
|
||||
if String.ends_with?(last, "_test") do
|
||||
Path.join(["test" | leading] ++ ["#{last}.exs"])
|
||||
else
|
||||
Path.join(["test" | leading] ++ ["#{last}_test.exs"])
|
||||
end
|
||||
|
||||
{:source_folder, "test/support"} ->
|
||||
case leading do
|
||||
[] ->
|
||||
Path.join(["test/support", "#{last}.ex"])
|
||||
|
||||
[_prefix | leading_rest] ->
|
||||
Path.join(["test/support" | leading_rest] ++ ["#{last}.ex"])
|
||||
end
|
||||
|
||||
{:source_folder, source_folder} ->
|
||||
Path.join([source_folder | leading] ++ ["#{last}.ex"])
|
||||
end
|
||||
end
|
||||
|
||||
def module?(zipper) do
|
||||
Common.node_matches_pattern?(zipper, {:__aliases__, _, [_ | _]})
|
||||
end
|
||||
|
||||
@doc "Parses a string into a module name"
|
||||
@spec parse(String.t()) :: module()
|
||||
def parse(module_name) do
|
||||
|
@ -467,7 +15,7 @@ defmodule Igniter.Code.Module do
|
|||
end
|
||||
|
||||
@doc "The module name prefix based on the mix project's module name"
|
||||
@deprecated "Use `module_name_prefix/1` instead"
|
||||
@deprecated "Use `Igniter.Project.Module.module_name_prefix/1` instead"
|
||||
@spec module_name_prefix() :: module()
|
||||
def module_name_prefix do
|
||||
Mix.Project.get!()
|
||||
|
@ -478,37 +26,9 @@ defmodule Igniter.Code.Module do
|
|||
|
||||
@doc "The module name prefix based on the mix project's module name"
|
||||
@spec module_name_prefix(Igniter.t()) :: module()
|
||||
# TODO: deprecate
|
||||
def module_name_prefix(igniter) do
|
||||
zipper =
|
||||
igniter
|
||||
|> Igniter.include_existing_file("mix.exs")
|
||||
|> Map.get(:rewrite)
|
||||
|> Rewrite.source!("mix.exs")
|
||||
|> Rewrite.Source.get(:quoted)
|
||||
|> Sourceror.Zipper.zip()
|
||||
|
||||
with {:ok, zipper} <- Igniter.Code.Module.move_to_defmodule(zipper),
|
||||
{:ok, zipper} <- Igniter.Code.Function.move_to_nth_argument(zipper, 0) do
|
||||
case Igniter.Code.Common.expand_alias(zipper) do
|
||||
%Zipper{node: module_name} when is_atom(module_name) ->
|
||||
module_name
|
||||
|> Module.split()
|
||||
|> :lists.droplast()
|
||||
|> Module.concat()
|
||||
|
||||
%Zipper{node: {:__aliases__, _, parts}} ->
|
||||
parts
|
||||
|> :lists.droplast()
|
||||
|> Module.concat()
|
||||
end
|
||||
else
|
||||
_ ->
|
||||
raise """
|
||||
Failed to parse the module name from mix.exs.
|
||||
|
||||
Please ensure that you are defining a Mix.Project module in your mix.exs file.
|
||||
"""
|
||||
end
|
||||
Igniter.Project.Module.module_name_prefix(igniter)
|
||||
end
|
||||
|
||||
@doc "Moves the zipper to a defmodule call"
|
||||
|
@ -596,4 +116,157 @@ defmodule Igniter.Code.Module do
|
|||
def move_to_def(zipper, fun, arity) do
|
||||
Igniter.Code.Function.move_to_def(zipper, fun, arity)
|
||||
end
|
||||
|
||||
def module?(zipper) do
|
||||
Common.node_matches_pattern?(zipper, {:__aliases__, _, [_ | _]})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Finds a module and updates its contents wherever it is.
|
||||
|
||||
If the module does not yet exist, it is created with the provided contents. In that case,
|
||||
the path is determined with `Igniter.Code.Module.proper_location/2`, but may optionally be overwritten with options below.
|
||||
|
||||
# Options
|
||||
|
||||
- `:path` - Path where to create the module, relative to the project root. Default: `nil` (uses `:kind` to determine the path).
|
||||
"""
|
||||
# TODO: Deprecate
|
||||
def find_and_update_or_create_module(igniter, module_name, contents, updater, opts \\ []) do
|
||||
Igniter.Project.Module.find_and_update_or_create_module(
|
||||
igniter,
|
||||
module_name,
|
||||
contents,
|
||||
updater,
|
||||
opts
|
||||
)
|
||||
end
|
||||
|
||||
@doc "Checks if the value is a module that matches a given predicate"
|
||||
def module_matching?(zipper, pred) do
|
||||
zipper =
|
||||
zipper
|
||||
|> Igniter.Code.Common.maybe_move_to_single_child_block()
|
||||
|> Igniter.Code.Common.expand_aliases()
|
||||
|
||||
case zipper.node do
|
||||
{:__aliases__, _, parts} ->
|
||||
pred.(Module.concat(parts))
|
||||
|
||||
value when is_atom(value) ->
|
||||
pred.(value)
|
||||
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Creates a new file & module in its appropriate location."
|
||||
# TODO: deprecate
|
||||
def create_module(igniter, module_name, contents, opts \\ []) do
|
||||
Igniter.Project.Module.create_module(igniter, module_name, contents, opts)
|
||||
end
|
||||
|
||||
@doc "Checks if a module is defined somewhere in the project. The returned igniter should not be discarded."
|
||||
# TODO: Deprecate
|
||||
def module_exists?(igniter, module_name) do
|
||||
Igniter.Project.Module.module_exists?(igniter, module_name)
|
||||
end
|
||||
|
||||
# TODO: deprecate
|
||||
def find_and_update_module!(igniter, module_name, updater) do
|
||||
Igniter.Project.Module.find_and_update_module!(igniter, module_name, updater)
|
||||
end
|
||||
|
||||
@doc "Finds a module and updates its contents. Returns `{:error, igniter}` if the module could not be found. Do not discard this igniter."
|
||||
@spec find_and_update_module(Igniter.t(), module(), (Zipper.t() -> {:ok, Zipper.t()} | :error)) ::
|
||||
{:ok, Igniter.t()} | {:error, Igniter.t()}
|
||||
# TODO: deprecate
|
||||
def find_and_update_module(igniter, module_name, updater) do
|
||||
Igniter.Project.Module.find_and_update_module(igniter, module_name, updater)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Finds a module, returning a new igniter, and the source and zipper location. This new igniter should not be discarded.
|
||||
|
||||
In general, you should not use the returned source and zipper to update the module, instead, use this to interrogate
|
||||
the contents or source in some way, and then call `find_and_update_module/3` with a function to perform an update.
|
||||
"""
|
||||
@spec find_module(Igniter.t(), module()) ::
|
||||
{:ok, {Igniter.t(), Rewrite.Source.t(), Zipper.t()}} | {:error, Igniter.t()}
|
||||
# TODO deprecate
|
||||
def find_module(igniter, module_name) do
|
||||
Igniter.Project.Module.find_module(igniter, module_name)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Finds a module, raising an error if its not found.
|
||||
|
||||
See `find_module/2` for more information.
|
||||
"""
|
||||
@spec find_module!(Igniter.t(), module()) ::
|
||||
{Igniter.t(), Rewrite.Source.t(), Zipper.t()} | no_return
|
||||
# TODO deprecate
|
||||
def find_module!(igniter, module_name) do
|
||||
Igniter.Project.Module.find_module!(igniter, module_name)
|
||||
end
|
||||
|
||||
@spec find_all_matching_modules(igniter :: Igniter.t(), (module(), Zipper.t() -> boolean)) ::
|
||||
{Igniter.t(), [module()]}
|
||||
# TODO: deprecate
|
||||
def find_all_matching_modules(igniter, predicate) do
|
||||
Igniter.Project.Module.find_all_matching_modules(igniter, predicate)
|
||||
end
|
||||
|
||||
@doc "Given a suffix, returns a module name with the prefix of the current project."
|
||||
@spec module_name(String.t()) :: module()
|
||||
@deprecated "Use `module_name/2` instead."
|
||||
def module_name(suffix) do
|
||||
Module.concat(module_name_prefix(), suffix)
|
||||
end
|
||||
|
||||
@doc "Given a suffix, returns a module name with the prefix of the current project."
|
||||
@spec module_name(Igniter.t(), String.t()) :: module()
|
||||
# TODO: deprecate
|
||||
def module_name(igniter, suffix) do
|
||||
Igniter.Project.Module.module_name(igniter, suffix)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the idiomatic file location for a given module, starting with "lib/".
|
||||
"""
|
||||
@spec proper_location(module(), source_folder :: String.t()) :: Path.t()
|
||||
@deprecated "Use `Igniter.Project.Module.proper_location/3`"
|
||||
def proper_location(module_name, source_folder \\ "lib") do
|
||||
Igniter.Project.Module.proper_location(
|
||||
Igniter.new(),
|
||||
module_name,
|
||||
{:source_folder, source_folder}
|
||||
)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the test file location for a given module, according to
|
||||
`mix test` expectations, starting with "test/" and ending with "_test.exs".
|
||||
|
||||
"""
|
||||
@spec proper_test_location(module()) :: Path.t()
|
||||
@deprecated "Use `Igniter.Project.Module.proper_location/3`"
|
||||
def proper_test_location(module_name) do
|
||||
Igniter.Project.Module.proper_location(Igniter.new(), module_name, :test)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the test support location for a given module, starting with
|
||||
"test/support/" and dropping the module name prefix in the path.
|
||||
"""
|
||||
@spec proper_test_support_location(module()) :: Path.t()
|
||||
@deprecated "Use `Igniter.Project.Module.proper_location/3`"
|
||||
def proper_test_support_location(module_name) do
|
||||
Igniter.Project.Module.proper_location(
|
||||
Igniter.new(),
|
||||
module_name,
|
||||
{:source_folder, "test/support"}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
29
lib/igniter/extension.ex
Normal file
29
lib/igniter/extension.ex
Normal file
|
@ -0,0 +1,29 @@
|
|||
defmodule Igniter.Extension do
|
||||
@moduledoc """
|
||||
Alter igniter's behavior by adding new functionality.
|
||||
|
||||
This is used to allow frameworks to modify things like
|
||||
the conventional location of files.
|
||||
"""
|
||||
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
@behaviour Igniter.Extension
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Choose a proper location for any given module.
|
||||
|
||||
Possible return values:
|
||||
|
||||
- `{:ok, path}`: The path where the module should be located.
|
||||
- `:error`: It should go in the default place, or according to other extensions.
|
||||
- `:keep`: Keep the module in the same location, unless another extension has a place for it, or its just been created.
|
||||
"""
|
||||
@callback proper_location(
|
||||
Igniter.t(),
|
||||
module(),
|
||||
Keyword.t()
|
||||
) :: {:ok, Path.t()} | :error
|
||||
end
|
93
lib/igniter/extensions/phoenix.ex
Normal file
93
lib/igniter/extensions/phoenix.ex
Normal file
|
@ -0,0 +1,93 @@
|
|||
defmodule Igniter.Extensions.Phoenix do
|
||||
@moduledoc """
|
||||
A phoenix extension for Igniter.
|
||||
|
||||
Install with `mix igniter.add_extension phoenix`
|
||||
"""
|
||||
use Igniter.Extension
|
||||
|
||||
def proper_location(igniter, module, opts) do
|
||||
case Keyword.get(opts, :location_convention, :phoenix_generators) do
|
||||
:phoenix_generators ->
|
||||
phoenix_generators_proper_location(igniter, module)
|
||||
|
||||
other ->
|
||||
raise "Unknown phoenix location convention #{inspect(other)}"
|
||||
end
|
||||
end
|
||||
|
||||
defp phoenix_generators_proper_location(igniter, module) do
|
||||
split = Module.split(module)
|
||||
|
||||
cond do
|
||||
String.ends_with?(to_string(module), "Web.Layouts") ->
|
||||
:keep
|
||||
|
||||
String.ends_with?(to_string(module), "Controller") && List.last(split) != "Controller" &&
|
||||
String.ends_with?(List.first(split), "Web") ->
|
||||
[base | rest] = split = Module.split(module)
|
||||
|
||||
[type] = List.last(split) |> String.split("Controller", trim: true)
|
||||
|
||||
rest = :lists.droplast(rest)
|
||||
|
||||
{:ok,
|
||||
base
|
||||
|> Macro.underscore()
|
||||
|> Path.join("controllers")
|
||||
|> then(fn path ->
|
||||
rest
|
||||
|> Enum.map(&Macro.underscore/1)
|
||||
|> case do
|
||||
[] -> [path]
|
||||
nested -> Path.join([path | nested])
|
||||
end
|
||||
|> Path.join()
|
||||
end)
|
||||
|> Path.join(Macro.underscore(type) <> "_controller.ex")}
|
||||
|
||||
String.ends_with?(to_string(module), "HTML") && List.last(split) != "HTML" &&
|
||||
String.ends_with?(List.first(split), "Web") ->
|
||||
[base | rest] = split = Module.split(module)
|
||||
|
||||
[type] = List.last(split) |> String.split("HTML", trim: true)
|
||||
|
||||
rest = :lists.droplast(rest)
|
||||
|
||||
potential_controller_module =
|
||||
Module.concat([base | rest] ++ [type <> "Controller"])
|
||||
|
||||
{exists?, _} = Igniter.Code.Module.module_exists?(igniter, potential_controller_module)
|
||||
|
||||
if exists? && Igniter.Libs.Phoenix.controller?(igniter, potential_controller_module) do
|
||||
{:ok,
|
||||
base
|
||||
|> Macro.underscore()
|
||||
|> Path.join("controllers")
|
||||
|> then(fn path ->
|
||||
rest
|
||||
|> Enum.map(&Macro.underscore/1)
|
||||
|> case do
|
||||
[] -> [path]
|
||||
nested -> Path.join([path | nested])
|
||||
end
|
||||
|> Path.join()
|
||||
end)
|
||||
|> Path.join(Macro.underscore(type) <> "_html.ex")}
|
||||
else
|
||||
:keep
|
||||
end
|
||||
|
||||
String.ends_with?(to_string(module), "CoreComponents") &&
|
||||
String.contains?(to_string(module), "Web") ->
|
||||
:keep
|
||||
|
||||
String.ends_with?(to_string(module), "JSON") && List.last(Module.split(module)) != "JSON" &&
|
||||
String.ends_with?(List.first(Module.split(module)), "Web") ->
|
||||
:keep
|
||||
|
||||
true ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
end
|
|
@ -18,6 +18,56 @@ defmodule Igniter.Libs.Phoenix do
|
|||
Module.concat([inspect(Igniter.Code.Module.module_name_prefix(igniter)) <> "Web"])
|
||||
end
|
||||
|
||||
@spec html?(Igniter.t(), module()) :: boolean()
|
||||
def html?(igniter, module) do
|
||||
zipper = elem(Igniter.Code.Module.find_module!(igniter, module), 2)
|
||||
|
||||
case Igniter.Code.Common.move_to(zipper, fn zipper ->
|
||||
if Igniter.Code.Function.function_call?(zipper, :use, 2) do
|
||||
using_a_webbish_module?(zipper) &&
|
||||
Igniter.Code.Function.argument_equals?(zipper, 1, :html)
|
||||
else
|
||||
false
|
||||
end
|
||||
end) do
|
||||
{:ok, _} ->
|
||||
true
|
||||
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
@spec controller?(Igniter.t(), module()) :: boolean()
|
||||
def controller?(igniter, module) do
|
||||
zipper = elem(Igniter.Code.Module.find_module!(igniter, module), 2)
|
||||
|
||||
case Igniter.Code.Common.move_to(zipper, fn zipper ->
|
||||
if Igniter.Code.Function.function_call?(zipper, :use, 2) do
|
||||
using_a_webbish_module?(zipper) &&
|
||||
Igniter.Code.Function.argument_equals?(zipper, 1, :controller)
|
||||
else
|
||||
false
|
||||
end
|
||||
end) do
|
||||
{:ok, _} ->
|
||||
true
|
||||
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
defp using_a_webbish_module?(zipper) do
|
||||
case Igniter.Code.Function.move_to_nth_argument(zipper, 0) do
|
||||
{:ok, zipper} ->
|
||||
Igniter.Code.Module.module_matching?(zipper, &String.ends_with?(to_string(&1), "Web"))
|
||||
|
||||
:error ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Generates a module name that lives in the Web directory of the current app.
|
||||
"""
|
||||
|
|
|
@ -143,7 +143,7 @@ defmodule Igniter.Project.Application do
|
|||
end
|
||||
|
||||
def do_add_child(igniter, application, to_supervise, opts) do
|
||||
path = Igniter.Code.Module.proper_location(application)
|
||||
path = Igniter.Project.Module.proper_location(igniter, application, :source_folder)
|
||||
|
||||
to_supervise =
|
||||
case to_supervise do
|
||||
|
@ -274,7 +274,7 @@ defmodule Igniter.Project.Application do
|
|||
end
|
||||
|
||||
def create_application_file(igniter, application) do
|
||||
path = Igniter.Code.Module.proper_location(application)
|
||||
path = Igniter.Project.Module.proper_location(igniter, application)
|
||||
supervisor = Igniter.Code.Module.module_name(igniter, "Supervisor")
|
||||
|
||||
contents = """
|
||||
|
|
|
@ -9,6 +9,17 @@ defmodule Igniter.Project.IgniterConfig do
|
|||
or moved there if the folder is created.
|
||||
"""
|
||||
],
|
||||
extensions: [
|
||||
type:
|
||||
{:list,
|
||||
{:or,
|
||||
[
|
||||
{:behaviour, Igniter.Extension},
|
||||
{:tuple, [{:behaviour, Igniter.Extension}, :keyword_list]}
|
||||
]}},
|
||||
default: [],
|
||||
doc: "A list of extensions to use in the project."
|
||||
],
|
||||
source_folders: [
|
||||
type: {:list, :string},
|
||||
default: ["lib", "test/support"],
|
||||
|
@ -47,6 +58,58 @@ defmodule Igniter.Project.IgniterConfig do
|
|||
igniter.assigns[:igniter_exs][config] || @configs[config][:default]
|
||||
end
|
||||
|
||||
def add_extension(igniter, extension) do
|
||||
extension =
|
||||
case extension do
|
||||
{mod, opts} -> {mod, opts}
|
||||
mod -> {mod, []}
|
||||
end
|
||||
|
||||
quoted =
|
||||
extension
|
||||
|> Macro.escape()
|
||||
|> Sourceror.to_string()
|
||||
|> Sourceror.parse_string!()
|
||||
|
||||
igniter
|
||||
|> setup()
|
||||
|> Igniter.update_elixir_file(".igniter.exs", fn zipper ->
|
||||
rightmost = Igniter.Code.Common.rightmost(zipper)
|
||||
|
||||
if Igniter.Code.List.list?(rightmost) do
|
||||
Igniter.Code.Keyword.set_keyword_key(
|
||||
zipper,
|
||||
:extensions,
|
||||
[quoted],
|
||||
fn zipper ->
|
||||
case Igniter.Code.List.move_to_list_item(zipper, fn zipper ->
|
||||
if Igniter.Code.Tuple.tuple?(zipper) do
|
||||
with {:ok, item} <- Igniter.Code.Tuple.tuple_elem(zipper, 0),
|
||||
true <- Igniter.Code.Common.nodes_equal?(item, elem(extension, 0)) do
|
||||
true
|
||||
else
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
else
|
||||
Igniter.Code.Common.nodes_equal?(zipper, elem(extension, 0))
|
||||
end
|
||||
end) do
|
||||
{:ok, _} ->
|
||||
{:ok, zipper}
|
||||
|
||||
_ ->
|
||||
Igniter.Code.List.prepend_to_list(zipper, quoted)
|
||||
end
|
||||
end
|
||||
)
|
||||
else
|
||||
{:warning,
|
||||
"Failed to modify `.igniter.exs` when adding the extension #{inspect(extension)} because its last return value is not a list literal."}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
def setup(igniter) do
|
||||
Igniter.create_or_update_elixir_file(
|
||||
igniter,
|
||||
|
|
578
lib/igniter/project/module.ex
Normal file
578
lib/igniter/project/module.ex
Normal file
|
@ -0,0 +1,578 @@
|
|||
defmodule Igniter.Project.Module do
|
||||
@moduledoc "Codemods and utilities for interacting with modules"
|
||||
|
||||
require Igniter.Code.Common
|
||||
alias Igniter.Code.Common
|
||||
alias Sourceror.Zipper
|
||||
require Logger
|
||||
|
||||
@typedoc """
|
||||
Placement instruction for a module.
|
||||
|
||||
- `:source_folder` - The first source folder of the project
|
||||
- `{:source_folder, path}` - The selected source folder, i.e `"lib"`
|
||||
- `:test` - Creating a test file
|
||||
- `:test_support` - Creating a test support file
|
||||
"""
|
||||
@type location_type :: :source_folder | {:source_folder, String.t()} | :test | :test_support
|
||||
|
||||
@doc """
|
||||
Determines where a module should be placed in a project.
|
||||
"""
|
||||
@spec proper_location(Igniter.t(), module(), location_type()) :: String.t()
|
||||
def proper_location(igniter, module_name, type \\ :source_folder) do
|
||||
type =
|
||||
case type do
|
||||
:source_folder ->
|
||||
igniter
|
||||
|> Igniter.Project.IgniterConfig.get(:source_folders)
|
||||
|> Enum.at(0)
|
||||
|> then(&{:source_folder, &1})
|
||||
|
||||
:test_support ->
|
||||
{:source_folder, "test/support"}
|
||||
|
||||
type ->
|
||||
type
|
||||
end
|
||||
|
||||
do_proper_location(igniter, module_name, type)
|
||||
end
|
||||
|
||||
@doc "Given a suffix, returns a module name with the prefix of the current project."
|
||||
@spec module_name(Igniter.t(), String.t()) :: module()
|
||||
def module_name(igniter, suffix) do
|
||||
Module.concat(module_name_prefix(igniter), suffix)
|
||||
end
|
||||
|
||||
@doc "The module name prefix based on the mix project's module name"
|
||||
@spec module_name_prefix(Igniter.t()) :: module()
|
||||
def module_name_prefix(igniter) do
|
||||
zipper =
|
||||
igniter
|
||||
|> Igniter.include_existing_file("mix.exs")
|
||||
|> Map.get(:rewrite)
|
||||
|> Rewrite.source!("mix.exs")
|
||||
|> Rewrite.Source.get(:quoted)
|
||||
|> Sourceror.Zipper.zip()
|
||||
|
||||
with {:ok, zipper} <- Igniter.Code.Module.move_to_defmodule(zipper),
|
||||
{:ok, zipper} <- Igniter.Code.Function.move_to_nth_argument(zipper, 0) do
|
||||
case Igniter.Code.Common.expand_alias(zipper) do
|
||||
%Zipper{node: module_name} when is_atom(module_name) ->
|
||||
module_name
|
||||
|> Module.split()
|
||||
|> :lists.droplast()
|
||||
|> Module.concat()
|
||||
|
||||
%Zipper{node: {:__aliases__, _, parts}} ->
|
||||
parts
|
||||
|> :lists.droplast()
|
||||
|> Module.concat()
|
||||
end
|
||||
else
|
||||
_ ->
|
||||
raise """
|
||||
Failed to parse the module name from mix.exs.
|
||||
|
||||
Please ensure that you are defining a Mix.Project module in your mix.exs file.
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Finds a module, raising an error if its not found.
|
||||
|
||||
See `find_module/2` for more information.
|
||||
"""
|
||||
@spec find_module!(Igniter.t(), module()) ::
|
||||
{Igniter.t(), Rewrite.Source.t(), Zipper.t()} | no_return
|
||||
def find_module!(igniter, module_name) do
|
||||
case find_module(igniter, module_name) do
|
||||
{:ok, {igniter, source, zipper}} ->
|
||||
{igniter, source, zipper}
|
||||
|
||||
{:error, _igniter} ->
|
||||
raise "Could not find module `#{inspect(module_name)}`"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Finds a module and updates its contents wherever it is.
|
||||
|
||||
If the module does not yet exist, it is created with the provided contents. In that case,
|
||||
the path is determined with `Igniter.Code.Module.proper_location/2`, but may optionally be overwritten with options below.
|
||||
|
||||
# Options
|
||||
|
||||
- `:path` - Path where to create the module, relative to the project root. Default: `nil` (uses `:kind` to determine the path).
|
||||
"""
|
||||
def find_and_update_or_create_module(igniter, module_name, contents, updater, opts \\ [])
|
||||
|
||||
def find_and_update_or_create_module(
|
||||
igniter,
|
||||
module_name,
|
||||
contents,
|
||||
updater,
|
||||
opts
|
||||
)
|
||||
when is_list(opts) do
|
||||
case find_and_update_module(igniter, module_name, updater) do
|
||||
{:ok, igniter} ->
|
||||
igniter
|
||||
|
||||
{:error, igniter} ->
|
||||
create_module(igniter, module_name, contents, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def find_and_update_or_create_module(
|
||||
igniter,
|
||||
module_name,
|
||||
contents,
|
||||
updater,
|
||||
path
|
||||
)
|
||||
when is_binary(path) do
|
||||
Logger.warning("You should use `opts` instead of `path` and pass `path` as a keyword.")
|
||||
find_and_update_or_create_module(igniter, module_name, contents, updater, path: path)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new file & module in its appropriate location.
|
||||
|
||||
## Options
|
||||
|
||||
- `:location` - A location type. See `t:location_type` for more.
|
||||
"""
|
||||
def create_module(igniter, module_name, contents, opts \\ []) do
|
||||
contents =
|
||||
"""
|
||||
defmodule #{inspect(module_name)} do
|
||||
#{contents}
|
||||
end
|
||||
"""
|
||||
|
||||
location =
|
||||
case Keyword.get(opts, :path, nil) do
|
||||
nil ->
|
||||
proper_location(igniter, module_name, opts[:location] || :source_folder)
|
||||
|
||||
path ->
|
||||
path
|
||||
end
|
||||
|
||||
Igniter.create_new_file(igniter, location, contents)
|
||||
end
|
||||
|
||||
@doc "Checks if a module is defined somewhere in the project. The returned igniter should not be discarded."
|
||||
def module_exists?(igniter, module_name) do
|
||||
case find_module(igniter, module_name) do
|
||||
{:ok, {igniter, _, _}} -> {true, igniter}
|
||||
{:error, igniter} -> {false, igniter}
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Finds a module and updates its contents. Raises an error if it doesn't exist"
|
||||
def find_and_update_module!(igniter, module_name, updater) do
|
||||
case find_and_update_module(igniter, module_name, updater) do
|
||||
{:ok, igniter} -> igniter
|
||||
{:error, _igniter} -> raise "Could not find module #{inspect(module_name)}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Finds a module and updates its contents. Returns `{:error, igniter}` if the module could not be found. Do not discard this igniter."
|
||||
@spec find_and_update_module(Igniter.t(), module(), (Zipper.t() -> {:ok, Zipper.t()} | :error)) ::
|
||||
{:ok, Igniter.t()} | {:error, Igniter.t()}
|
||||
def find_and_update_module(igniter, module_name, updater) do
|
||||
case find_module(igniter, module_name) do
|
||||
{:ok, {igniter, source, zipper}} ->
|
||||
case Common.move_to_do_block(zipper) do
|
||||
{:ok, zipper} ->
|
||||
case updater.(zipper) do
|
||||
{:ok, zipper} ->
|
||||
new_quoted =
|
||||
zipper
|
||||
|> Zipper.topmost()
|
||||
|> Zipper.node()
|
||||
|
||||
new_source = Rewrite.Source.update(source, :quoted, new_quoted)
|
||||
{:ok, %{igniter | rewrite: Rewrite.update!(igniter.rewrite, new_source)}}
|
||||
|
||||
{:error, error} ->
|
||||
{:ok, Igniter.add_issue(igniter, error)}
|
||||
|
||||
{:warning, error} ->
|
||||
{:ok, Igniter.add_warning(igniter, error)}
|
||||
end
|
||||
|
||||
_ ->
|
||||
{:error, igniter}
|
||||
end
|
||||
|
||||
{:error, igniter} ->
|
||||
{:error, igniter}
|
||||
end
|
||||
end
|
||||
|
||||
@spec find_all_matching_modules(igniter :: Igniter.t(), (module(), Zipper.t() -> boolean)) ::
|
||||
{Igniter.t(), [module()]}
|
||||
def find_all_matching_modules(igniter, predicate) do
|
||||
igniter =
|
||||
igniter
|
||||
|> Igniter.include_all_elixir_files()
|
||||
|
||||
matching_modules =
|
||||
igniter
|
||||
|> Map.get(:rewrite)
|
||||
|> Enum.filter(&match?(%Rewrite.Source{filetype: %Rewrite.Source.Ex{}}, &1))
|
||||
|> Task.async_stream(
|
||||
fn source ->
|
||||
source
|
||||
|> Rewrite.Source.get(:quoted)
|
||||
|> Zipper.zip()
|
||||
|> Zipper.traverse([], fn zipper, acc ->
|
||||
case zipper.node do
|
||||
{:defmodule, _, [_, _]} ->
|
||||
{:ok, mod_zipper} = Igniter.Code.Function.move_to_nth_argument(zipper, 0)
|
||||
|
||||
module_name =
|
||||
mod_zipper
|
||||
|> Igniter.Code.Common.expand_alias()
|
||||
|> Zipper.node()
|
||||
|> Igniter.Project.Module.to_module_name()
|
||||
|
||||
with module_name when not is_nil(module_name) <- module_name,
|
||||
{:ok, do_zipper} <- Igniter.Code.Common.move_to_do_block(zipper),
|
||||
true <- predicate.(module_name, do_zipper) do
|
||||
{zipper, [module_name | acc]}
|
||||
else
|
||||
_ ->
|
||||
{zipper, acc}
|
||||
end
|
||||
|
||||
_ ->
|
||||
{zipper, acc}
|
||||
end
|
||||
end)
|
||||
|> elem(1)
|
||||
end,
|
||||
timeout: :infinity
|
||||
)
|
||||
|> Enum.flat_map(fn {:ok, v} ->
|
||||
v
|
||||
end)
|
||||
|> Enum.uniq()
|
||||
|
||||
{igniter, matching_modules}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Finds a module, returning a new igniter, and the source and zipper location. This new igniter should not be discarded.
|
||||
|
||||
In general, you should not use the returned source and zipper to update the module, instead, use this to interrogate
|
||||
the contents or source in some way, and then call `find_and_update_module/3` with a function to perform an update.
|
||||
"""
|
||||
@spec find_module(Igniter.t(), module()) ::
|
||||
{:ok, {Igniter.t(), Rewrite.Source.t(), Zipper.t()}} | {:error, Igniter.t()}
|
||||
def find_module(igniter, module_name) do
|
||||
igniter = Igniter.include_all_elixir_files(igniter)
|
||||
|
||||
igniter
|
||||
|> Map.get(:rewrite)
|
||||
|> Enum.filter(&match?(%Rewrite.Source{filetype: %Rewrite.Source.Ex{}}, &1))
|
||||
|> Task.async_stream(
|
||||
fn source ->
|
||||
{source
|
||||
|> Rewrite.Source.get(:quoted)
|
||||
|> Zipper.zip()
|
||||
|> Igniter.Code.Module.move_to_defmodule(module_name), source}
|
||||
end,
|
||||
timeout: :infinity
|
||||
)
|
||||
|> Enum.find_value({:error, igniter}, fn
|
||||
{:ok, {{:ok, zipper}, source}} ->
|
||||
{:ok, {igniter, source, zipper}}
|
||||
|
||||
_other ->
|
||||
false
|
||||
end)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def move_files(igniter, opts \\ []) do
|
||||
module_location_config = Igniter.Project.IgniterConfig.get(igniter, :module_location)
|
||||
dont_move_files = Igniter.Project.IgniterConfig.get(igniter, :dont_move_files)
|
||||
|
||||
igniter =
|
||||
if opts[:move_all?] do
|
||||
Igniter.include_all_elixir_files(igniter)
|
||||
else
|
||||
igniter
|
||||
end
|
||||
|
||||
igniter.rewrite
|
||||
|> Stream.filter(&(Path.extname(&1.path) in [".ex", ".exs"]))
|
||||
|> Stream.reject(&non_movable_file?(&1.path, dont_move_files))
|
||||
|> Enum.reduce(igniter, fn source, igniter ->
|
||||
zipper =
|
||||
source
|
||||
|> Rewrite.Source.get(:quoted)
|
||||
|> Zipper.zip()
|
||||
|
||||
with {:ok, zipper} <- Igniter.Code.Module.move_to_defmodule(zipper),
|
||||
{:ok, zipper} <- Igniter.Code.Function.move_to_nth_argument(zipper, 0),
|
||||
module <-
|
||||
zipper
|
||||
|> Igniter.Code.Common.expand_alias()
|
||||
|> Zipper.node(),
|
||||
module when not is_nil(module) <- to_module_name(module),
|
||||
new_path when not is_nil(new_path) <-
|
||||
should_move_file_to(igniter, source, module, module_location_config, opts) do
|
||||
Igniter.move_file(igniter, source.path, new_path, error_if_exists?: false)
|
||||
else
|
||||
_ ->
|
||||
igniter
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp non_movable_file?(path, dont_move_files) do
|
||||
Enum.any?(dont_move_files, fn
|
||||
exclusion_pattern when is_binary(exclusion_pattern) ->
|
||||
path == exclusion_pattern
|
||||
|
||||
exclusion_pattern when is_struct(exclusion_pattern, Regex) ->
|
||||
Regex.match?(exclusion_pattern, path)
|
||||
end)
|
||||
end
|
||||
|
||||
defp should_move_file_to(igniter, source, module, module_location_config, opts) do
|
||||
paths_created =
|
||||
igniter.rewrite
|
||||
|> Enum.filter(fn source ->
|
||||
Rewrite.Source.from?(source, :string)
|
||||
end)
|
||||
|> Enum.map(& &1.path)
|
||||
|
||||
split_path =
|
||||
source.path
|
||||
|> Path.relative_to_cwd()
|
||||
|> Path.split()
|
||||
|
||||
igniter
|
||||
|> Igniter.Project.IgniterConfig.get(:source_folders)
|
||||
|> Enum.filter(fn source_folder ->
|
||||
List.starts_with?(split_path, Path.split(source_folder))
|
||||
end)
|
||||
|> Enum.max_by(
|
||||
fn source_folder ->
|
||||
source_folder
|
||||
|> Path.split()
|
||||
|> Enum.zip(split_path)
|
||||
|> Enum.take_while(fn {l, r} -> l == r end)
|
||||
|> Enum.count()
|
||||
end,
|
||||
fn -> nil end
|
||||
)
|
||||
|> case do
|
||||
nil ->
|
||||
if Enum.at(split_path, 0) == "test" &&
|
||||
String.ends_with?(source.path, "_test.exs") do
|
||||
{:ok, proper_location(igniter, module, :test), :test}
|
||||
else
|
||||
:error
|
||||
end
|
||||
|
||||
source_folder ->
|
||||
{:ok, proper_location(igniter, module, {:source_folder, source_folder}),
|
||||
{:source_folder, source_folder}}
|
||||
end
|
||||
|> case do
|
||||
:error ->
|
||||
nil
|
||||
|
||||
{:ok, proper_location, location_type} ->
|
||||
case module_location_config do
|
||||
:inside_matching_folder ->
|
||||
{[filename, folder], rest} =
|
||||
proper_location
|
||||
|> Path.split()
|
||||
|> Enum.reverse()
|
||||
|> Enum.split(2)
|
||||
|
||||
inside_matching_folder =
|
||||
[filename, Path.rootname(filename), folder]
|
||||
|> Enum.concat(rest)
|
||||
|> Enum.reverse()
|
||||
|> Path.join()
|
||||
|
||||
inside_matching_folder_dirname = Path.dirname(inside_matching_folder)
|
||||
|
||||
just_created_folder? =
|
||||
Enum.any?(paths_created, fn path ->
|
||||
List.starts_with?(Path.split(path), Path.split(inside_matching_folder_dirname))
|
||||
end)
|
||||
|
||||
should_use_inside_matching_folder? =
|
||||
if opts[:move_all?] do
|
||||
dir?(igniter, inside_matching_folder_dirname) || just_created_folder?
|
||||
else
|
||||
source.path == proper_location(igniter, module, location_type) &&
|
||||
!dir?(igniter, inside_matching_folder_dirname) && just_created_folder?
|
||||
end
|
||||
|
||||
if should_use_inside_matching_folder? do
|
||||
inside_matching_folder
|
||||
else
|
||||
proper_location
|
||||
end
|
||||
|
||||
:outside_matching_folder ->
|
||||
if opts[:move_all?] || Rewrite.Source.from?(source, :string) do
|
||||
proper_location
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp dir?(igniter, folder) do
|
||||
if igniter.assigns[:test_mode?] do
|
||||
igniter.assigns[:test_files]
|
||||
|> Map.keys()
|
||||
|> Enum.any?(fn file_path ->
|
||||
List.starts_with?(Path.split(file_path), Path.split(folder))
|
||||
end)
|
||||
else
|
||||
File.dir?(folder)
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def to_module_name({:__aliases__, _, parts}), do: Module.concat(parts)
|
||||
def to_module_name(value) when is_atom(value) and not is_nil(value), do: value
|
||||
def to_module_name(_), do: nil
|
||||
|
||||
defp do_proper_location(igniter, module_name, kind) do
|
||||
path =
|
||||
module_name
|
||||
|> Module.split()
|
||||
|> case do
|
||||
["Mix", "Tasks" | rest] ->
|
||||
suffix =
|
||||
rest
|
||||
|> Enum.map(&to_string/1)
|
||||
|> Enum.map_join(".", &Macro.underscore/1)
|
||||
|
||||
["mix", "tasks", suffix]
|
||||
|
||||
_other ->
|
||||
modified_module_name =
|
||||
case kind do
|
||||
:test ->
|
||||
string_module = to_string(module_name)
|
||||
|
||||
if String.ends_with?(string_module, "Test") do
|
||||
Module.concat([String.slice(string_module, 0..-5//1)])
|
||||
else
|
||||
module_name
|
||||
end
|
||||
|
||||
_ ->
|
||||
module_name
|
||||
end
|
||||
|
||||
module_to_path(igniter, modified_module_name, module_name)
|
||||
end
|
||||
|
||||
last = List.last(path)
|
||||
leading = :lists.droplast(path)
|
||||
|
||||
case kind do
|
||||
:test ->
|
||||
if String.ends_with?(last, "_test") do
|
||||
Path.join(["test" | leading] ++ ["#{last}.exs"])
|
||||
else
|
||||
Path.join(["test" | leading] ++ ["#{last}_test.exs"])
|
||||
end
|
||||
|
||||
{:source_folder, "test/support"} ->
|
||||
case leading do
|
||||
[] ->
|
||||
Path.join(["test/support", "#{last}.ex"])
|
||||
|
||||
[_prefix | leading_rest] ->
|
||||
Path.join(["test/support" | leading_rest] ++ ["#{last}.ex"])
|
||||
end
|
||||
|
||||
{:source_folder, source_folder} ->
|
||||
Path.join([source_folder | leading] ++ ["#{last}.ex"])
|
||||
end
|
||||
end
|
||||
|
||||
defp module_to_path(igniter, module, original) do
|
||||
Enum.reduce_while(
|
||||
igniter.assigns[:igniter_exs][:extensions] || [],
|
||||
:error,
|
||||
fn {extension, opts}, status ->
|
||||
case extension.proper_location(igniter, module, opts) do
|
||||
:error ->
|
||||
{:cont, status}
|
||||
|
||||
:keep ->
|
||||
{:cont, :keep}
|
||||
|
||||
{:ok, path} ->
|
||||
{:halt, {:ok, path}}
|
||||
end
|
||||
end
|
||||
)
|
||||
|> case do
|
||||
:keep ->
|
||||
case find_module(igniter, original) do
|
||||
{:ok, {_, source, _}} ->
|
||||
split_path =
|
||||
source.path
|
||||
|> Path.rootname(".ex")
|
||||
|> Path.rootname(".exs")
|
||||
|> Path.split()
|
||||
|
||||
igniter
|
||||
|> Igniter.Project.IgniterConfig.get(:source_folders)
|
||||
|> Enum.concat(["test"])
|
||||
|> Enum.uniq()
|
||||
|> Enum.map(&Path.split/1)
|
||||
|> Enum.sort_by(&(-length(&1)))
|
||||
|> Enum.find(fn source_folder ->
|
||||
List.starts_with?(split_path, Path.split(source_folder))
|
||||
end)
|
||||
|> case do
|
||||
nil ->
|
||||
default_location(module)
|
||||
|
||||
source_path ->
|
||||
Enum.drop(split_path, Enum.count(source_path))
|
||||
end
|
||||
|
||||
_ ->
|
||||
default_location(module)
|
||||
end
|
||||
|
||||
:error ->
|
||||
default_location(module)
|
||||
|
||||
{:ok, path} ->
|
||||
path
|
||||
|> Path.rootname(".ex")
|
||||
|> Path.rootname(".exs")
|
||||
|> Path.split()
|
||||
end
|
||||
end
|
||||
|
||||
defp default_location(module) do
|
||||
module
|
||||
|> Module.split()
|
||||
|> Enum.map(&to_string/1)
|
||||
|> Enum.map(&Macro.underscore/1)
|
||||
end
|
||||
end
|
|
@ -44,11 +44,19 @@ defmodule Igniter.Test do
|
|||
## Options
|
||||
|
||||
* `label` - A label to put before the diff.
|
||||
* `only` - File path(s) to only show the diff for
|
||||
"""
|
||||
@spec puts_diff(Igniter.t(), opts :: Keyword.t()) :: Igniter.t()
|
||||
def puts_diff(igniter, opts \\ []) do
|
||||
igniter.rewrite.sources
|
||||
|> Map.values()
|
||||
|> then(fn sources ->
|
||||
if opts[:only] do
|
||||
Enum.filter(sources, &(&1.path in List.wrap(opts[:only])))
|
||||
else
|
||||
sources
|
||||
end
|
||||
end)
|
||||
|> Igniter.diff()
|
||||
|> String.trim()
|
||||
|> case do
|
||||
|
@ -211,19 +219,56 @@ defmodule Igniter.Test do
|
|||
defmacro assert_creates(igniter, path, content \\ nil) do
|
||||
quote bind_quoted: [igniter: igniter, path: path, content: content] do
|
||||
assert source = igniter.rewrite.sources[path],
|
||||
"Expected #{inspect(path)} to have been created, but it was not."
|
||||
"""
|
||||
Expected #{inspect(path)} to have been created, but it was not.
|
||||
#{Igniter.Test.created_files(igniter)}
|
||||
"""
|
||||
|
||||
assert source.from == :string,
|
||||
"Expected #{inspect(path)} to have been created, but it already existed."
|
||||
"""
|
||||
Expected #{inspect(path)} to have been created, but it already existed.
|
||||
#{Igniter.Test.created_files(igniter)}
|
||||
"""
|
||||
|
||||
if content do
|
||||
assert Rewrite.Source.get(source, :content) == content
|
||||
actual_content = Rewrite.Source.get(source, :content)
|
||||
|
||||
if actual_content != content do
|
||||
flunk("""
|
||||
Expected created file #{inspect(path)} to have the following contents:
|
||||
|
||||
#{content}
|
||||
|
||||
But it actually had the following contents:
|
||||
|
||||
#{actual_content}
|
||||
|
||||
Diff, showing your assertion against the actual contents:
|
||||
|
||||
#{Rewrite.TextDiff.format(actual_content, content)}
|
||||
""")
|
||||
end
|
||||
end
|
||||
|
||||
igniter
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def created_files(igniter) do
|
||||
igniter.rewrite
|
||||
|> Rewrite.sources()
|
||||
|> Enum.filter(&(&1.from == :string))
|
||||
|> Enum.map(& &1.path)
|
||||
|> case do
|
||||
[] ->
|
||||
"\nNo files were created."
|
||||
|
||||
modules ->
|
||||
"\nThe following files were created:\n\n#{Enum.map_join(modules, "\n", &"* #{&1}")}"
|
||||
end
|
||||
end
|
||||
|
||||
defp simulate_write(igniter) do
|
||||
igniter.rewrite
|
||||
|> Rewrite.sources()
|
||||
|
@ -239,7 +284,11 @@ defmodule Igniter.Test do
|
|||
|> Map.put(:warnings, [])
|
||||
|> Map.put(:notices, [])
|
||||
|> Map.put(:issues, [])
|
||||
|> Map.put(:assigns, %{test_mode?: true, test_files: test_files})
|
||||
|> Map.put(:assigns, %{
|
||||
test_mode?: true,
|
||||
test_files: test_files,
|
||||
igniter_exs: igniter.assigns[:igniter_exs]
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
|
@ -276,24 +325,24 @@ defmodule Igniter.Test do
|
|||
end
|
||||
""")
|
||||
|> Map.put_new("lib/#{app_name}.ex", """
|
||||
defmodule #{module_name} do
|
||||
@moduledoc \"\"\"
|
||||
Documentation for `#{module_name}`.
|
||||
\"\"\"
|
||||
defmodule #{module_name} do
|
||||
@moduledoc \"\"\"
|
||||
Documentation for `#{module_name}`.
|
||||
\"\"\"
|
||||
|
||||
@doc \"\"\"
|
||||
Hello world.
|
||||
@doc \"\"\"
|
||||
Hello world.
|
||||
|
||||
## Examples
|
||||
## Examples
|
||||
|
||||
iex> #{module_name}.hello()
|
||||
:world
|
||||
|
||||
\"\"\"
|
||||
def hello do
|
||||
iex> #{module_name}.hello()
|
||||
:world
|
||||
end
|
||||
|
||||
\"\"\"
|
||||
def hello do
|
||||
:world
|
||||
end
|
||||
end
|
||||
""")
|
||||
|> Map.put_new("README.md", """
|
||||
# #{module_name}
|
||||
|
|
41
lib/mix/tasks/igniter.add_extension.ex
Normal file
41
lib/mix/tasks/igniter.add_extension.ex
Normal file
|
@ -0,0 +1,41 @@
|
|||
defmodule Mix.Tasks.Igniter.AddExtension do
|
||||
use Igniter.Mix.Task
|
||||
|
||||
@example "mix igniter.add_extension phoenix"
|
||||
|
||||
@shortdoc "Adds an extension to your `.igniter.exs` configuration file."
|
||||
@moduledoc """
|
||||
#{@shortdoc}
|
||||
|
||||
The extension can be the module name of an extension,
|
||||
or the string `phoenix`, which maps to `Igniter.Extensions.Phoenix`.
|
||||
|
||||
## Example
|
||||
|
||||
```bash
|
||||
#{@example}
|
||||
```
|
||||
|
||||
"""
|
||||
|
||||
def info(_argv, _composing_task) do
|
||||
%Igniter.Mix.Task.Info{
|
||||
example: @example,
|
||||
positional: [:extension]
|
||||
}
|
||||
end
|
||||
|
||||
def igniter(igniter, argv) do
|
||||
{%{extension: extension}, _argv} = positional_args!(argv)
|
||||
|
||||
extension =
|
||||
if extension == "phoenix" do
|
||||
Igniter.Extensions.Phoenix
|
||||
else
|
||||
Igniter.Code.Module.parse(extension)
|
||||
end
|
||||
|
||||
igniter
|
||||
|> Igniter.Project.IgniterConfig.add_extension(extension)
|
||||
end
|
||||
end
|
|
@ -5,6 +5,6 @@ defmodule Mix.Tasks.Igniter.MoveFiles do
|
|||
|
||||
def igniter(igniter, _argv) do
|
||||
Mix.shell().info("Finding all modules and determining proper locations...")
|
||||
Igniter.Code.Module.move_files(igniter, move_all?: true)
|
||||
Igniter.Project.Module.move_files(igniter, move_all?: true)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -130,7 +130,7 @@ defmodule Igniter.Code.ModuleTest do
|
|||
""")
|
||||
|> Igniter.create_new_file("test.txt", "Foo")
|
||||
|
||||
assert {_igniter, [Foo]} =
|
||||
assert {_igniter, [Foo, Test, TestTest]} =
|
||||
Igniter.Code.Module.find_all_matching_modules(igniter, fn _module, _zipper ->
|
||||
true
|
||||
end)
|
||||
|
|
80
test/igniter/extensions/phoenix_test.exs
Normal file
80
test/igniter/extensions/phoenix_test.exs
Normal file
|
@ -0,0 +1,80 @@
|
|||
defmodule Igniter.Extensions.PhoenixTest do
|
||||
use ExUnit.Case
|
||||
import Igniter.Test
|
||||
|
||||
describe "proper_location/2" do
|
||||
test "extensions are honored even if the extension is added in the same check" do
|
||||
test_project()
|
||||
|> Igniter.Project.IgniterConfig.add_extension(Igniter.Extensions.Phoenix)
|
||||
|> Igniter.Project.Module.create_module(TestWeb.FooController, """
|
||||
use TestWeb, :controller
|
||||
""")
|
||||
|> assert_creates("lib/test_web/controllers/foo_controller.ex")
|
||||
end
|
||||
|
||||
test "returns a controller location" do
|
||||
igniter =
|
||||
test_project()
|
||||
|> Igniter.create_new_file("lib/test_web/controllers/foo_controller.ex", """
|
||||
defmodule TestWeb.FooController do
|
||||
use TestWeb, :controller
|
||||
|
||||
end
|
||||
""")
|
||||
|
||||
assert {:ok, "test_web/controllers/foo_controller.ex"} =
|
||||
Igniter.Extensions.Phoenix.proper_location(igniter, TestWeb.FooController, [])
|
||||
end
|
||||
|
||||
test "when belonging to a controller, it returns an html location" do
|
||||
igniter =
|
||||
test_project()
|
||||
|> Igniter.create_new_file("lib/test_web/controllers/foo_controller.ex", """
|
||||
defmodule TestWeb.FooController do
|
||||
use TestWeb, :controller
|
||||
|
||||
end
|
||||
""")
|
||||
|> Igniter.create_new_file("lib/test_web/controllers/foo_html.ex", """
|
||||
defmodule TestWeb.FooHTML do
|
||||
use TestWeb, :html
|
||||
end
|
||||
""")
|
||||
|
||||
assert {:ok, "test_web/controllers/foo_html.ex"} =
|
||||
Igniter.Extensions.Phoenix.proper_location(igniter, TestWeb.FooHTML, [])
|
||||
end
|
||||
|
||||
test "when not belonging to a controller, we instruct to keep its current location" do
|
||||
igniter =
|
||||
test_project()
|
||||
|> Igniter.create_new_file("lib/test_web/controllers/foo_html.ex", """
|
||||
defmodule TestWeb.FooHTML do
|
||||
use TestWeb, :html
|
||||
end
|
||||
""")
|
||||
|
||||
assert :keep =
|
||||
Igniter.Extensions.Phoenix.proper_location(igniter, TestWeb.FooHTML, [])
|
||||
end
|
||||
|
||||
test "returns a json location" do
|
||||
igniter =
|
||||
test_project()
|
||||
|> Igniter.create_new_file("test_web/controllers/foo_controller.ex", """
|
||||
defmodule TestWeb.FooController do
|
||||
use TestWeb, :controller
|
||||
|
||||
end
|
||||
""")
|
||||
|> Igniter.create_new_file("lib/test_web/controllers/foo_json.ex", """
|
||||
defmodule TestWeb.FooJSON do
|
||||
|
||||
def render(_), do: %{foo: "bar"}
|
||||
end
|
||||
""")
|
||||
|
||||
assert Igniter.Extensions.Phoenix.proper_location(igniter, TestWeb.FooJSON, []) == :keep
|
||||
end
|
||||
end
|
||||
end
|
27
test/igniter/libs/phoenix_test.exs
Normal file
27
test/igniter/libs/phoenix_test.exs
Normal file
|
@ -0,0 +1,27 @@
|
|||
defmodule Igniter.Libs.PhoenixTest do
|
||||
use ExUnit.Case
|
||||
import Igniter.Test
|
||||
|
||||
describe "controller?/2" do
|
||||
test "detects a phoenix controller" do
|
||||
igniter =
|
||||
assert test_project()
|
||||
|> Igniter.create_new_file("lib/test_web/controllers/foo_controller.ex", """
|
||||
defmodule TestWeb.FooController do
|
||||
use TestWeb, :controller
|
||||
|
||||
end
|
||||
""")
|
||||
|> Igniter.create_new_file("lib/test_web/controllers/foo_view.ex", """
|
||||
defmodule TestWeb.ThingView do
|
||||
use TestWeb, :view
|
||||
|
||||
end
|
||||
""")
|
||||
|> apply_igniter!()
|
||||
|
||||
assert Igniter.Libs.Phoenix.controller?(igniter, TestWeb.FooController)
|
||||
refute Igniter.Libs.Phoenix.controller?(igniter, TestWeb.ThingView)
|
||||
end
|
||||
end
|
||||
end
|
18
test/igniter/project/igniter_config_test.exs
Normal file
18
test/igniter/project/igniter_config_test.exs
Normal file
|
@ -0,0 +1,18 @@
|
|||
defmodule Igniter.Project.IgniterConfigTest do
|
||||
use ExUnit.Case
|
||||
import Igniter.Test
|
||||
|
||||
describe "add_extension/2" do
|
||||
test "adds an extension to the list" do
|
||||
test_project()
|
||||
|> Igniter.Project.IgniterConfig.add_extension(Foobar)
|
||||
|> assert_has_patch(".igniter.exs", """
|
||||
11 11 | dont_move_files: [
|
||||
12 12 | ~r"lib/mix"
|
||||
13 - | ]
|
||||
13 + | ],
|
||||
14 + | extensions: [{Foobar, []}]
|
||||
""")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,4 +1,4 @@
|
|||
defmodule Igniter.Mix.Tasks.Igniter.Gen.TaskTest do
|
||||
defmodule Mix.Tasks.Igniter.Gen.TaskTest do
|
||||
use ExUnit.Case
|
||||
import Igniter.Test
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
defmodule Igniter.Mix.Tasks.InstallTest do
|
||||
defmodule Mix.Tasks.Igniter.InstallTest do
|
||||
use ExUnit.Case
|
||||
|
||||
setup do
|
||||
|
|
Loading…
Reference in a new issue