From 4126cdd8abb63fe55677edf289b4b07ee645ea0e Mon Sep 17 00:00:00 2001 From: Jean Klingler Date: Sat, 21 Sep 2024 21:47:16 +0900 Subject: [PATCH] Add --migrate flag to mix format (#13846) --- lib/elixir/src/elixir_bitstring.erl | 7 ++++-- lib/elixir/src/elixir_tokenizer.erl | 5 +++- lib/mix/lib/mix/tasks/format.ex | 33 ++++++++++++++++++-------- lib/mix/test/mix/tasks/format_test.exs | 12 ++++++++++ 4 files changed, 44 insertions(+), 13 deletions(-) diff --git a/lib/elixir/src/elixir_bitstring.erl b/lib/elixir/src/elixir_bitstring.erl index 963be78377b..e392ed68cad 100644 --- a/lib/elixir/src/elixir_bitstring.erl +++ b/lib/elixir/src/elixir_bitstring.erl @@ -403,10 +403,13 @@ format_error({undefined_bittype, Expr}) -> io_lib:format("unknown bitstring specifier: ~ts", ['Elixir.Macro':to_string(Expr)]); format_error({unknown_bittype, Name}) -> io_lib:format("bitstring specifier \"~ts\" does not exist and is being expanded to \"~ts()\"," - " please use parentheses to remove the ambiguity", [Name, Name]); + " please use parentheses to remove the ambiguity.\n" + "You may run \"mix format --migrate\" to fix this warning automatically.", [Name, Name]); format_error({parens_bittype, Name}) -> io_lib:format("extra parentheses on a bitstring specifier \"~ts()\" have been deprecated. " - "Please remove the parentheses: \"~ts\"", + "Please remove the parentheses: \"~ts\".\n" + "You may run \"mix format --migrate\" to fix this warning automatically." + , [Name, Name]); format_error({bittype_mismatch, Val1, Val2, Where}) -> io_lib:format("conflicting ~ts specification for bit field: \"~p\" and \"~p\"", [Where, Val1, Val2]); diff --git a/lib/elixir/src/elixir_tokenizer.erl b/lib/elixir/src/elixir_tokenizer.erl index 92c12894912..e517d72302b 100644 --- a/lib/elixir/src/elixir_tokenizer.erl +++ b/lib/elixir/src/elixir_tokenizer.erl @@ -267,7 +267,10 @@ tokenize([$" | T], Line, Column, Scope, Tokens) -> %% TODO: Remove me in Elixir v2.0 tokenize([$' | T], Line, Column, Scope, Tokens) -> - NewScope = prepend_warning(Line, Column, "single-quoted strings represent charlists. Use ~c\"\" if you indeed want a charlist or use \"\" instead", Scope), + Message = "single-quoted strings represent charlists. " + "Use ~c\"\" if you indeed want a charlist or use \"\" instead.\n" + "You may run \"mix format --migrate\" to fix this warning automatically.", + NewScope = prepend_warning(Line, Column, Message, Scope), handle_strings(T, Line, Column + 1, $', NewScope, Tokens); % Operator atoms diff --git a/lib/mix/lib/mix/tasks/format.ex b/lib/mix/lib/mix/tasks/format.ex index ebf44b6eb50..4572f08e036 100644 --- a/lib/mix/lib/mix/tasks/format.ex +++ b/lib/mix/lib/mix/tasks/format.ex @@ -78,6 +78,14 @@ defmodule Mix.Tasks.Format do as `.heex`. Without passing this flag, it is assumed that the code being passed via stdin is valid Elixir code. Defaults to "stdin.exs". + * `--migrate` - enables the `:migrate` option, which should be able to + automatically fix some deprecation warnings but is changing the AST. + This should be safe in typical projects, but there is a non-zero risk + of breaking code for meta-programming heavy projects that relied on a + specific AST, or projects that are re-defining functions from the `Kernel`. + See the "Migration formatting" section in `Code.format_string!/2` for + more information. + ## When to format code We recommend developers to format code directly in their editors, either @@ -184,7 +192,8 @@ defmodule Mix.Tasks.Format do dot_formatter: :string, dry_run: :boolean, stdin_filename: :string, - force: :boolean + force: :boolean, + migrate: :boolean ] @manifest_timestamp "format_timestamp" @@ -362,17 +371,21 @@ defmodule Mix.Tasks.Format do end defp eval_dot_formatter(cwd, opts) do - cond do - dot_formatter = opts[:dot_formatter] -> - {dot_formatter, eval_file_with_keyword_list(dot_formatter)} + {dot_formatter, format_opts} = + cond do + dot_formatter = opts[:dot_formatter] -> + {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)} + 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", []} - end + true -> + {".formatter.exs", []} + end + + # the --migrate flag overrides settings from the dot formatter + {dot_formatter, Keyword.take(opts, [:migrate]) ++ format_opts} end # This function reads exported configuration from the imported diff --git a/lib/mix/test/mix/tasks/format_test.exs b/lib/mix/test/mix/tasks/format_test.exs index 476b40340d6..49c53394f49 100644 --- a/lib/mix/test/mix/tasks/format_test.exs +++ b/lib/mix/test/mix/tasks/format_test.exs @@ -534,6 +534,18 @@ defmodule Mix.Tasks.FormatTest do end) end + test "respects the --migrate flag", context do + in_tmp(context.test, fn -> + File.write!("a.ex", "unless foo, do: 'bar'\n") + + Mix.Tasks.Format.run(["a.ex"]) + assert File.read!("a.ex") == "unless foo, do: 'bar'\n" + + Mix.Tasks.Format.run(["a.ex", "--migrate"]) + assert File.read!("a.ex") == "if !foo, do: ~c\"bar\"\n" + end) + end + test "uses inputs and configuration from --dot-formatter", context do in_tmp(context.test, fn -> File.write!("custom_formatter.exs", """