improvement: make apply_and_fetch_dependencies only change deps/0

This commit is contained in:
Zach Daniel 2024-07-14 19:22:37 -04:00
parent 89e08abba2
commit 4494e426a3
7 changed files with 142 additions and 90 deletions

View file

@ -89,7 +89,7 @@ defmodule Igniter do
paths =
glob
|> case do
%GlobEx{} = glob -> glob
%{__struct__: GlobEx} = glob -> glob
string -> GlobEx.compile!(Path.expand(string))
end
|> GlobEx.ls()
@ -128,7 +128,7 @@ defmodule Igniter do
def update_glob(igniter, glob, func) do
glob =
case glob do
%GlobEx{} = glob -> glob
%{__struct__: GlobEx} = glob -> glob
string -> GlobEx.compile!(Path.expand(string))
end
@ -395,19 +395,57 @@ defmodule Igniter do
def apply_and_fetch_dependencies(igniter, opts \\ []) do
if !igniter.assigns[:private][:refused_fetch_dependencies?] &&
has_changes?(igniter, ["mix.exs"]) do
write_result =
Igniter.do_or_dry_run(igniter,
title: "Fetch Required Dependencies",
yes: opts[:yes],
dry_run: !opts[:yes],
paths: ["mix.exs"]
)
source = Rewrite.source!(igniter.rewrite, "mix.exs")
case write_result do
:issues ->
raise "Exiting due to issues found while previewing changes."
original_quoted = Rewrite.Source.get(source, :quoted, 1)
original_zipper = Zipper.zip(original_quoted)
quoted = Rewrite.Source.get(source, :quoted)
zipper = Zipper.zip(quoted)
with {:ok, original_zipper} <-
Igniter.Code.Function.move_to_defp(original_zipper, :deps, 0),
{:ok, zipper} <- Igniter.Code.Function.move_to_defp(zipper, :deps, 0) do
quoted_with_only_deps_change =
original_zipper
|> Igniter.Code.Common.replace_code(zipper.node)
|> Zipper.topmost()
|> Zipper.node()
source = Rewrite.Source.update(source, :quoted, quoted_with_only_deps_change)
rewrite = Rewrite.update!(igniter.rewrite, source)
display_diff([source], opts)
message =
if opts[:error_on_abort?] do
"These dependencies #{IO.ANSI.red()}must#{IO.ANSI.reset()} be installed before continuing. Modify mix.exs and install?"
else
"These dependencies #{IO.ANSI.yellow()}should#{IO.ANSI.reset()} be installed before continuing. Modify mix.exs and install?"
end
if Mix.shell().yes?(message) do
rewrite =
case Rewrite.write(rewrite, "mix.exs", :force) do
{:ok, rewrite} -> rewrite
{:error, error} -> raise error
end
Igniter.Util.Install.get_deps!()
source = Rewrite.source!(rewrite, "mix.exs")
source = Rewrite.Source.update(source, :quoted, quoted)
%{igniter | rewrite: Rewrite.update!(rewrite, source)}
else
if opts[:error_on_abort?] do
raise "Aborted by the user."
else
assign_private(igniter, :refused_fetch_dependencies?, true)
end
end
else
_ ->
display_diff([source], opts)
message =
if opts[:error_on_abort?] do
"These dependencies #{IO.ANSI.red()}must#{IO.ANSI.reset()} be installed before continuing. Modify mix.exs and install?"
@ -415,42 +453,16 @@ defmodule Igniter do
"These dependencies #{IO.ANSI.yellow()}should#{IO.ANSI.reset()} be installed before continuing. Modify mix.exs and install?"
end
proceed? =
opts[:yes] ||
Mix.shell().yes?(message)
if Mix.shell().yes?(message) do
rewrite =
case Rewrite.write(igniter.rewrite, "mix.exs", :force) do
{:ok, rewrite} -> rewrite
{:error, error} -> raise error
end
if proceed? do
unless opts[:yes] do
Igniter.do_or_dry_run(igniter,
yes: true,
title: "Applying changes",
paths: ["mix.exs"]
)
end
Igniter.Util.Install.get_deps!()
Mix.shell().info("running mix deps.get")
case Mix.shell().cmd("mix deps.get") do
0 ->
Mix.Project.clear_deps_cache()
Mix.Project.pop()
Mix.Dep.clear_cached()
"mix.exs"
|> File.read!()
|> Code.eval_string([], file: Path.expand("mix.exs"))
Igniter.Util.DepsCompile.run()
exit_code ->
Mix.shell().info("""
mix deps.get returned exited with code: `#{exit_code}`
""")
end
Map.update!(igniter, :rewrite, fn rewrite ->
Rewrite.drop(rewrite, ["mix.exs"])
end)
%{igniter | rewrite: rewrite}
else
if opts[:error_on_abort?] do
raise "Aborted by the user."
@ -509,7 +521,7 @@ defmodule Igniter do
Executes or dry-runs a given Igniter.
"""
def do_or_dry_run(igniter, opts \\ []) do
igniter = prepare_for_write(igniter, opts)
igniter = prepare_for_write(igniter)
title = opts[:title] || "Igniter"
@ -960,15 +972,7 @@ defmodule Igniter do
end
@doc false
def prepare_for_write(igniter, opts \\ []) do
igniter =
if opts[:paths] do
all_paths = Rewrite.paths(igniter.rewrite)
%{igniter | rewrite: Rewrite.drop(igniter.rewrite, all_paths -- opts[:paths])}
else
igniter
end
def prepare_for_write(igniter) do
source_issues =
Enum.flat_map(igniter.rewrite, fn source ->
changed_issues =

View file

@ -3,6 +3,7 @@ defmodule Igniter.Code.Function do
Utilities for working with functions.
"""
require Igniter.Code.Common
alias Igniter.Code.Common
alias Sourceror.Zipper
@ -19,6 +20,55 @@ defmodule Igniter.Code.Function do
end
end
@spec move_to_defp(Zipper.t(), fun :: atom, arity :: integer | list(integer)) ::
{:ok, Zipper.t()} | :error
def move_to_defp(zipper, fun, arity) do
do_move_to_def(zipper, fun, arity, :defp)
end
@spec move_to_def(Zipper.t(), fun :: atom, arity :: integer | list(integer)) ::
{:ok, Zipper.t()} | :error
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
do_move_to_def(zipper, fun, arity, kind)
end
defp do_move_to_def(zipper, fun, [arity | rest], kind) do
case do_move_to_def(zipper, fun, arity, kind) do
{:ok, zipper} -> {:ok, zipper}
:error -> do_move_to_def(zipper, fun, rest, kind)
end
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
@doc "Moves to a function call by the given name and arity, matching the given predicate, in the current scope"
@spec move_to_function_call_in_current_scope(Zipper.t(), atom, non_neg_integer()) ::
{:ok, Zipper.t()} | :error

View file

@ -506,37 +506,13 @@ defmodule Igniter.Code.Module do
end)
end
@deprecated "Use `Igniter.Code.Function.move_to_defp/3` instead"
def move_to_defp(zipper, fun, arity) do
do_move_to_def(zipper, fun, arity, :defp)
Igniter.Code.Function.move_to_defp(zipper, fun, arity)
end
@deprecated "Use `Igniter.Code.Function.move_to_def/3` instead"
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
Igniter.Code.Function.move_to_def(zipper, fun, arity)
end
end

View file

@ -65,7 +65,7 @@ defmodule Igniter.Project.Application do
Igniter.update_elixir_file(igniter, path, fn zipper ->
with {:ok, zipper} <- Igniter.Code.Module.move_to_module_using(zipper, Application),
{:ok, zipper} <- Igniter.Code.Module.move_to_def(zipper, :start, 2),
{:ok, zipper} <- Igniter.Code.Function.move_to_def(zipper, :start, 2),
{:ok, zipper} <-
Igniter.Code.Function.move_to_function_call_in_current_scope(
zipper,
@ -121,7 +121,7 @@ defmodule Igniter.Project.Application do
Igniter.update_elixir_file(igniter, "mix.exs", fn zipper ->
case Igniter.Code.Module.move_to_module_using(zipper, Mix.Project) do
{:ok, zipper} ->
case Igniter.Code.Module.move_to_def(zipper, :application, 0) do
case Igniter.Code.Function.move_to_def(zipper, :application, 0) do
{:ok, zipper} ->
zipper
|> Zipper.rightmost()

View file

@ -74,7 +74,7 @@ defmodule Igniter.Project.Deps do
|> Zipper.zip()
with {:ok, zipper} <- Igniter.Code.Module.move_to_module_using(zipper, Mix.Project),
{:ok, zipper} <- Igniter.Code.Module.move_to_defp(zipper, :deps, 0),
{:ok, zipper} <- Igniter.Code.Function.move_to_defp(zipper, :deps, 0),
true <- Common.node_matches_pattern?(zipper, value when is_list(value)),
{:ok, current_declaration} <-
Igniter.Code.List.move_to_list_item(zipper, fn item ->
@ -101,7 +101,7 @@ defmodule Igniter.Project.Deps do
igniter
|> Igniter.update_elixir_file("mix.exs", fn zipper ->
with {:ok, zipper} <- Igniter.Code.Module.move_to_module_using(zipper, Mix.Project),
{:ok, zipper} <- Igniter.Code.Module.move_to_defp(zipper, :deps, 0),
{:ok, zipper} <- Igniter.Code.Function.move_to_defp(zipper, :deps, 0),
true <- Common.node_matches_pattern?(zipper, value when is_list(value)),
current_declaration_index when not is_nil(current_declaration_index) <-
Igniter.Code.List.find_list_item_index(zipper, fn item ->
@ -133,7 +133,7 @@ defmodule Igniter.Project.Deps do
igniter
|> Igniter.update_elixir_file("mix.exs", fn zipper ->
with {:ok, zipper} <- Igniter.Code.Module.move_to_module_using(zipper, Mix.Project),
{:ok, zipper} <- Igniter.Code.Module.move_to_defp(zipper, :deps, 0),
{:ok, zipper} <- Igniter.Code.Function.move_to_defp(zipper, :deps, 0),
true <- Common.node_matches_pattern?(zipper, value when is_list(value)) do
quoted =
if opts[:dep_opts] do

View file

@ -3,7 +3,7 @@ defmodule Igniter.Project.Test do
def ensure_test_support(igniter) do
Igniter.update_elixir_file(igniter, "mix.exs", fn zipper ->
with {:ok, zipper} <- Igniter.Code.Module.move_to_module_using(zipper, Mix.Project),
{:ok, zipper} <- Igniter.Code.Module.move_to_def(zipper, :project, 0),
{:ok, zipper} <- Igniter.Code.Function.move_to_def(zipper, :project, 0),
{:ok, zipper} <-
Igniter.Code.Common.move_right(zipper, &Igniter.Code.List.list?/1) do
case Igniter.Code.Keyword.get_key(zipper, :elixirc_paths) do

View file

@ -159,4 +159,26 @@ defmodule Igniter.Util.Install do
end
end
end
def get_deps! do
Mix.shell().info("running mix deps.get")
case Mix.shell().cmd("mix deps.get") do
0 ->
Mix.Project.clear_deps_cache()
Mix.Project.pop()
Mix.Dep.clear_cached()
"mix.exs"
|> File.read!()
|> Code.eval_string([], file: Path.expand("mix.exs"))
Igniter.Util.DepsCompile.run()
exit_code ->
Mix.shell().info("""
mix deps.get returned exited with code: `#{exit_code}`
""")
end
end
end