From a395937656853fa8ff18826ca13ab9658bb297b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 7 May 2023 21:34:50 +0200 Subject: Push cwd handling all the way up in mix format --- lib/mix/lib/mix/tasks/format.ex | 90 ++++++++++++++-------------------- lib/mix/test/mix/tasks/format_test.exs | 17 ++++--- 2 files changed, 45 insertions(+), 62 deletions(-) diff --git a/lib/mix/lib/mix/tasks/format.ex b/lib/mix/lib/mix/tasks/format.ex index a164f3aac..ff0ab84d4 100644 --- a/lib/mix/lib/mix/tasks/format.ex +++ b/lib/mix/lib/mix/tasks/format.ex @@ -218,8 +218,9 @@ defmodule Mix.Tasks.Format do @impl true def run(args) do + cwd = File.cwd!() {opts, args} = OptionParser.parse!(args, strict: @switches) - {dot_formatter, formatter_opts} = eval_dot_formatter(opts) + {dot_formatter, formatter_opts} = eval_dot_formatter(cwd, opts) if opts[:check_equivalent] do IO.warn("--check-equivalent has been deprecated and has no effect") @@ -230,10 +231,10 @@ defmodule Mix.Tasks.Format do end {formatter_opts_and_subs, _sources} = - eval_deps_and_subdirectories(dot_formatter, [], formatter_opts, [dot_formatter]) + eval_deps_and_subdirectories(cwd, dot_formatter, formatter_opts, [dot_formatter]) args - |> expand_args(dot_formatter, formatter_opts_and_subs, opts) + |> expand_args(cwd, dot_formatter, formatter_opts_and_subs, opts) |> Task.async_stream(&format_file(&1, opts), ordered: false, timeout: :infinity) |> Enum.reduce({[], []}, &collect_status/2) |> check!(opts) @@ -249,14 +250,13 @@ defmodule Mix.Tasks.Format do """ @doc since: "1.13.0" def formatter_for_file(file, opts \\ []) do - {dot_formatter, formatter_opts} = eval_dot_formatter(opts) - - prefix = Keyword.get(opts, :root, []) + cwd = Keyword.get_lazy(opts, :root, &File.cwd!/0) + {dot_formatter, formatter_opts} = eval_dot_formatter(cwd, opts) {formatter_opts_and_subs, _sources} = - eval_deps_and_subdirectories(dot_formatter, prefix, formatter_opts, [dot_formatter]) + eval_deps_and_subdirectories(cwd, dot_formatter, formatter_opts, [dot_formatter]) - find_formatter_and_opts_for_file(file, prefix, formatter_opts_and_subs) + find_formatter_and_opts_for_file(file, cwd, formatter_opts_and_subs) end @doc """ @@ -269,17 +269,14 @@ defmodule Mix.Tasks.Format do formatter_opts end - defp eval_dot_formatter(opts) do + defp eval_dot_formatter(cwd, opts) do cond do dot_formatter = opts[:dot_formatter] -> {dot_formatter, eval_file_with_keyword_list(dot_formatter)} - File.regular?(".formatter.exs") -> - {".formatter.exs", eval_file_with_keyword_list(".formatter.exs")} - - opts[:root] && File.regular?(Path.join(opts[:root], ".formatter.exs")) -> - dot_formatter = Path.join(opts[:root], ".formatter.exs") - {dot_formatter, eval_file_with_keyword_list(dot_formatter)} + File.regular?(Path.join(cwd, ".formatter.exs")) -> + dot_formatter = Path.join(cwd, ".formatter.exs") + {".formatter.exs", eval_file_with_keyword_list(dot_formatter)} true -> {".formatter.exs", []} @@ -289,7 +286,7 @@ defmodule Mix.Tasks.Format do # This function reads exported configuration from the imported # dependencies and subdirectories and deals with caching the result # of reading such configuration in a manifest file. - defp eval_deps_and_subdirectories(dot_formatter, prefix, formatter_opts, sources) do + defp eval_deps_and_subdirectories(cwd, dot_formatter, formatter_opts, sources) do deps = Keyword.get(formatter_opts, :import_deps, []) subs = Keyword.get(formatter_opts, :subdirectories, []) plugins = Keyword.get(formatter_opts, :plugins, []) @@ -356,7 +353,7 @@ defmodule Mix.Tasks.Format do {{locals_without_parens, subdirectories}, sources} = maybe_cache_in_manifest(dot_formatter, manifest, fn -> - {subdirectories, sources} = eval_subs_opts(subs, prefix, sources) + {subdirectories, sources} = eval_subs_opts(subs, cwd, sources) {{eval_deps_opts(deps), subdirectories}, sources} end) @@ -420,11 +417,11 @@ defmodule Mix.Tasks.Format do do: parenless_call end - defp eval_subs_opts(subs, prefix, sources) do + defp eval_subs_opts(subs, cwd, sources) do {subs, sources} = Enum.flat_map_reduce(subs, sources, fn sub, sources -> - prefix = Path.join(prefix ++ [sub]) - {Path.wildcard(prefix), [Path.join(prefix, ".formatter.exs") | sources]} + cwd = Path.expand(sub, cwd) + {Path.wildcard(cwd), [Path.join(cwd, ".formatter.exs") | sources]} end) Enum.flat_map_reduce(subs, sources, fn sub, sources -> @@ -434,7 +431,7 @@ defmodule Mix.Tasks.Format do formatter_opts = eval_file_with_keyword_list(sub_formatter) {formatter_opts_and_subs, sources} = - eval_deps_and_subdirectories(:in_memory, [sub], formatter_opts, sources) + eval_deps_and_subdirectories(sub, :in_memory, formatter_opts, sources) {[{sub, formatter_opts_and_subs}], sources} else @@ -470,7 +467,7 @@ defmodule Mix.Tasks.Format do opts end - defp expand_args([], dot_formatter, formatter_opts_and_subs, _opts) do + defp expand_args([], cwd, dot_formatter, formatter_opts_and_subs, _opts) do if no_entries_in_formatter_opts?(formatter_opts_and_subs) do Mix.raise( "Expected one or more files/patterns to be given to mix format " <> @@ -479,13 +476,13 @@ defmodule Mix.Tasks.Format do end dot_formatter - |> expand_dot_inputs([], formatter_opts_and_subs, %{}) + |> expand_dot_inputs(cwd, formatter_opts_and_subs, %{}) |> Enum.map(fn {file, {_dot_formatter, formatter_opts}} -> {file, find_formatter_for_file(file, formatter_opts)} end) end - defp expand_args(files_and_patterns, _dot_formatter, {formatter_opts, subs}, opts) do + defp expand_args(files_and_patterns, cwd, _dot_formatter, {formatter_opts, subs}, opts) do files = for file_or_pattern <- files_and_patterns, file <- stdin_or_wildcard(file_or_pattern), @@ -504,35 +501,34 @@ defmodule Mix.Tasks.Format do stdin_filename = Keyword.get(opts, :stdin_filename, "stdin.exs") {formatter, _opts} = - find_formatter_and_opts_for_file(stdin_filename, {formatter_opts, subs}) + find_formatter_and_opts_for_file(stdin_filename, cwd, {formatter_opts, subs}) {file, formatter} else - {formatter, _opts} = find_formatter_and_opts_for_file(file, {formatter_opts, subs}) + {formatter, _opts} = find_formatter_and_opts_for_file(file, cwd, {formatter_opts, subs}) {file, formatter} end end end - defp expand_dot_inputs(dot_formatter, prefix, {formatter_opts, subs}, acc) do + defp expand_dot_inputs(dot_formatter, cwd, {formatter_opts, subs}, acc) do if no_entries_in_formatter_opts?({formatter_opts, subs}) do Mix.raise("Expected :inputs or :subdirectories key in #{dot_formatter}") end map = for input <- List.wrap(formatter_opts[:inputs]), - file <- Path.wildcard(Path.join(prefix ++ [input]), match_dot: true), - do: {expand_relative_to_cwd(file), {dot_formatter, formatter_opts}}, + file <- Path.wildcard(Path.expand(input, cwd), match_dot: true), + do: {file, {dot_formatter, formatter_opts}}, into: %{} acc = Map.merge(acc, map, fn file, {dot_formatter1, _}, {dot_formatter2, formatter_opts} -> Mix.shell().error( - "Both #{dot_formatter1} and #{dot_formatter2} specify the file " <> - "#{Path.relative_to_cwd(file)} in their :inputs option. To resolve the " <> - "conflict, the configuration in #{dot_formatter1} will be ignored. " <> - "Please change the list of :inputs in one of the formatter files so only " <> - "one of them matches #{Path.relative_to_cwd(file)}" + "Both #{dot_formatter1} and #{dot_formatter2} specify the file #{file} in their " <> + ":inputs option. To resolve the conflict, the configuration in #{dot_formatter1} " <> + "will be ignored. Please change the list of :inputs in one of the formatter files " <> + "so only one of them matches #{file}" ) {dot_formatter2, formatter_opts} @@ -540,17 +536,10 @@ defmodule Mix.Tasks.Format do Enum.reduce(subs, acc, fn {sub, formatter_opts_and_subs}, acc -> sub_formatter = Path.join(sub, ".formatter.exs") - expand_dot_inputs(sub_formatter, [sub], formatter_opts_and_subs, acc) + expand_dot_inputs(sub_formatter, sub, formatter_opts_and_subs, acc) end) end - defp expand_relative_to_cwd(path) do - case File.cwd() do - {:ok, cwd} -> Path.expand(path, cwd) - _ -> path - end - end - defp find_formatter_for_file(file, formatter_opts) do ext = Path.extname(file) @@ -582,22 +571,15 @@ defmodule Mix.Tasks.Format do if plugins != [], do: plugins, else: nil end - defp find_formatter_and_opts_for_file(file, formatter_opts_and_subs) do - split = file |> Path.relative_to_cwd() |> Path.split() - formatter_opts = recur_formatter_opts_for_file(split, formatter_opts_and_subs) - {find_formatter_for_file(file, formatter_opts), formatter_opts} - end - - defp find_formatter_and_opts_for_file(file, prefix, formatter_opts_and_subs) do - split = file |> Path.relative_to(prefix) |> Path.split() - formatter_opts = recur_formatter_opts_for_file(split, formatter_opts_and_subs) + defp find_formatter_and_opts_for_file(file, cwd, formatter_opts_and_subs) do + formatter_opts = recur_formatter_opts_for_file(cwd, formatter_opts_and_subs) {find_formatter_for_file(file, formatter_opts), formatter_opts} end - defp recur_formatter_opts_for_file(split, {formatter_opts, subs}) do + defp recur_formatter_opts_for_file(cwd, {formatter_opts, subs}) do Enum.find_value(subs, formatter_opts, fn {sub, formatter_opts_and_subs} -> - if List.starts_with?(split, Path.split(sub)) do - recur_formatter_opts_for_file(split, formatter_opts_and_subs) + if String.starts_with?(sub, cwd) do + recur_formatter_opts_for_file(sub, formatter_opts_and_subs) end end) end diff --git a/lib/mix/test/mix/tasks/format_test.exs b/lib/mix/test/mix/tasks/format_test.exs index 05be1212e..d6b6793e9 100644 --- a/lib/mix/test/mix/tasks/format_test.exs +++ b/lib/mix/test/mix/tasks/format_test.exs @@ -744,7 +744,7 @@ defmodule Mix.Tasks.FormatTest do [] """) - message = "Expected :inputs or :subdirectories key in lib/.formatter.exs" + message = "Expected :inputs or :subdirectories key in #{Path.expand("lib/.formatter.exs")}" assert_raise Mix.Error, message, fn -> Mix.Tasks.Format.run([]) end end) end @@ -799,16 +799,17 @@ defmodule Mix.Tasks.FormatTest do Mix.Tasks.Format.run([]) message1 = - "Both .formatter.exs and lib/.formatter.exs specify the file lib/a.ex in their " <> - ":inputs option. To resolve the conflict, the configuration in .formatter.exs " <> - "will be ignored. Please change the list of :inputs in one of the formatter files " <> - "so only one of them matches lib/a.ex" + "Both .formatter.exs and #{Path.expand("lib/.formatter.exs")} specify the file " <> + "#{Path.expand("lib/a.ex")} in their :inputs option. To resolve the conflict, " <> + "the configuration in .formatter.exs will be ignored. Please change the list of " <> + ":inputs in one of the formatter files so only one of them matches #{Path.expand("lib/a.ex")}" message2 = - "Both lib/.formatter.exs and foo/.formatter.exs specify the file lib/a.ex in their " <> - ":inputs option. To resolve the conflict, the configuration in lib/.formatter.exs " <> + "Both #{Path.expand("lib/.formatter.exs")} and #{Path.expand("foo/.formatter.exs")} " <> + "specify the file #{Path.expand("lib/a.ex")} in their :inputs option. To resolve " <> + "the conflict, the configuration in #{Path.expand("lib/.formatter.exs")} " <> "will be ignored. Please change the list of :inputs in one of the formatter files " <> - "so only one of them matches lib/a.ex" + "so only one of them matches #{Path.expand("lib/a.ex")}" assert_received {:mix_shell, :error, [^message1]} assert_received {:mix_shell, :error, [^message2]} -- cgit v1.2.1