-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Add :migrate_unless option to formatter #13844
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -192,6 +192,7 @@ defmodule Code.Formatter do | |
migrate = Keyword.get(opts, :migrate, false) | ||
migrate_bitstring_modifiers = Keyword.get(opts, :migrate_bitstring_modifiers, migrate) | ||
migrate_charlists_as_sigils = Keyword.get(opts, :migrate_charlists_as_sigils, migrate) | ||
migrate_unless = Keyword.get(opts, :migrate_unless, migrate) | ||
syntax_colors = Keyword.get(opts, :syntax_colors, []) | ||
|
||
sigils = | ||
|
@@ -218,6 +219,7 @@ defmodule Code.Formatter do | |
file: file, | ||
migrate_bitstring_modifiers: migrate_bitstring_modifiers, | ||
migrate_charlists_as_sigils: migrate_charlists_as_sigils, | ||
migrate_unless: migrate_unless, | ||
inspect_opts: %Inspect.Opts{syntax_colors: syntax_colors} | ||
} | ||
end | ||
|
@@ -485,6 +487,27 @@ defmodule Code.Formatter do | |
binary_op_to_algebra(:in, "not in", meta, left, right, context, state) | ||
end | ||
|
||
# rewrite unless as if! | ||
defp quoted_to_algebra( | ||
{:unless, meta, [condition, block]}, | ||
context, | ||
%{migrate_unless: true} = state | ||
) do | ||
quoted_to_algebra({:if, meta, [negate_condition(condition), block]}, context, state) | ||
end | ||
|
||
defp quoted_to_algebra( | ||
{:|>, meta1, [condition, {:unless, meta2, [block]}]}, | ||
context, | ||
%{migrate_unless: true} = state | ||
) do | ||
quoted_to_algebra( | ||
{:|>, meta1, [negate_condition(condition), {:if, meta2, [block]}]}, | ||
context, | ||
state | ||
) | ||
end | ||
|
||
# .. | ||
defp quoted_to_algebra({:.., _meta, []}, context, state) do | ||
if context in [:no_parens_arg, :no_parens_one_arg] do | ||
|
@@ -2450,4 +2473,47 @@ defmodule Code.Formatter do | |
defp has_double_quote?(chunk) do | ||
is_binary(chunk) and chunk =~ @double_quote | ||
end | ||
|
||
# Migration rewrites | ||
|
||
@bool_operators [ | ||
:>, | ||
:>=, | ||
:<, | ||
:<=, | ||
:in | ||
] | ||
@guards [ | ||
:is_atom, | ||
:is_boolean, | ||
:is_nil, | ||
:is_number, | ||
:is_integer, | ||
:is_float, | ||
:is_binary, | ||
:is_map, | ||
:is_struct, | ||
:is_non_struct_map, | ||
:is_exception, | ||
:is_list, | ||
:is_tuple, | ||
:is_function, | ||
:is_reference, | ||
:is_pid, | ||
:is_port | ||
] | ||
|
||
defp negate_condition(condition) do | ||
case condition do | ||
{neg, _, [condition]} when neg in [:!, :not] -> condition | ||
{:|>, _, _} -> {:|>, [], [condition, {{:., [], [Kernel, :!]}, [closing: []], []}]} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we found the migrations more readable not doing this particular transformation, as this hides the most impactful part of the statement. compare: # given:
unless Widget |> where(foo: ^bar) |> Repo.exists?(), do: ...
# rewritten to if with pipe
if Widget |> where(foo: ^bar) |> Repo.exists?() |> Kernel.!(), do: ...
# treated as any other statement
if !(Widget |> where(foo: ^bar) |> Repo.exists?()), do:
our team asked for the latter as it has a much more natural-language read (not to mention that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for the suggestion 💜 Even if I'm the one who originally implemented it as Plus this is removing complexity so I'm all for it. Do you want to send a PR @novaugust? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. happy to, i'll kick one out tomorrow |
||
{op, _, [_, _]} when op in @bool_operators -> {:not, [], [condition]} | ||
{guard, _, [_ | _]} when guard in @guards -> {:not, [], [condition]} | ||
{:==, meta, [left, right]} -> {:!=, meta, [left, right]} | ||
{:===, meta, [left, right]} -> {:!==, meta, [left, right]} | ||
{:!=, meta, [left, right]} -> {:==, meta, [left, right]} | ||
{:!==, meta, [left, right]} -> {:===, meta, [left, right]} | ||
_ -> {:!, [], [condition]} | ||
end | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about the corner cases we discussed in the past, such as
unless x = expr() do
, do we handle those as well? Or we don't? If we don't, we must say not all cases can be automatically migrated. :)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do handle these, it becomes
if !(x = expr())
which is terrible code, but it does work fine and I expectunless x = expr()
to be a very rare need anyway sincex
could only befalse
ornil
. WDYT?#13769 (comment)
#13769 (comment)