Skip to content

Commit 5eddb4b

Browse files
authored
Rework migrate options for the formatter (#13841)
1 parent 8e31621 commit 5eddb4b

File tree

10 files changed

+333
-255
lines changed

10 files changed

+333
-255
lines changed

.formatter.exs

+1-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,5 @@
1717

1818
# Float tests
1919
float_assert: 1
20-
],
21-
normalize_bitstring_modifiers: false
20+
]
2221
]

lib/elixir/lib/code.ex

+27-9
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,8 @@ defmodule Code do
654654
655655
## Options
656656
657+
Regular options (do not change the AST):
658+
657659
* `:file` - the file which contains the string, used for error
658660
reporting
659661
@@ -677,28 +679,29 @@ defmodule Code do
677679
If you set it to `false` later on, `do`-`end` blocks won't be
678680
converted back to keywords.
679681
680-
* `:normalize_bitstring_modifiers` (since v1.14.0) - when `true`,
682+
Migration options (change the AST), see the "Migration formatting" section below:
683+
684+
* `:migrate` (since v1.18.0) - when `true`, sets all other migration options
685+
to `true` by default. Defaults to `false`.
686+
687+
* `:migrate_bitstring_modifiers` (since v1.18.0) - when `true`,
681688
removes unnecessary parentheses in known bitstring
682689
[modifiers](`<<>>/1`), for example `<<foo::binary()>>`
683690
becomes `<<foo::binary>>`, or adds parentheses for custom
684691
modifiers, where `<<foo::custom_type>>` becomes `<<foo::custom_type()>>`.
685-
Defaults to `true`. This option changes the AST.
692+
Defaults to the value of the `:migrate` option. This option changes the AST.
686693
687-
* `:normalize_charlists_as_sigils` (since v1.15.0) - when `true`,
694+
* `:migrate_charlists_as_sigils` (since v1.18.0) - when `true`,
688695
formats charlists as [`~c`](`Kernel.sigil_c/2`) sigils, for example
689696
`'foo'` becomes `~c"foo"`.
690-
Defaults to `true`. This option changes the AST.
697+
Defaults to the value of the `:migrate` option. This option changes the AST.
691698
692699
## Design principles
693700
694701
The formatter was designed under three principles.
695702
696-
First, the formatter never changes the semantics of the code.
703+
First, the formatter never changes the semantics of the code by default.
697704
This means the input AST and the output AST are almost always equivalent.
698-
The only cases where the formatter will change the AST is when the input AST
699-
would cause *compiler warnings* and the output AST won't. The cases where
700-
the formatter changes the AST can be disabled through formatting options
701-
if desired.
702705
703706
The second principle is to provide as little configuration as possible.
704707
This eases the formatter adoption by removing contention points while
@@ -986,6 +989,21 @@ defmodule Code do
986989
## Newlines
987990
988991
The formatter converts all newlines in code from `\r\n` to `\n`.
992+
993+
## Migration formatting
994+
995+
As part of the Elixir release cycle, deprecations are being introduced,
996+
emitting warnings which might require existing code to be changed.
997+
In order to reduce the burden on developers when upgrading Elixir to the
998+
next version, the formatter exposes some options, disabled by default,
999+
in order to automate this process.
1000+
1001+
These options should address most of the typical use cases, but given they
1002+
introduce changes to the AST, there is a non-zero risk for meta-programming
1003+
heavy projects that relied on a specific AST, or projects that are
1004+
re-defining functions from the `Kernel`. In such cases, migrations cannot
1005+
be applied blindly and some extra changes might be needed in order to
1006+
address the deprecation warnings.
9891007
"""
9901008
@doc since: "1.6.0"
9911009
@spec format_string!(binary, keyword) :: iodata

lib/elixir/lib/code/formatter.ex

+8-7
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,9 @@ defmodule Code.Formatter do
189189
locals_without_parens = Keyword.get(opts, :locals_without_parens, [])
190190
file = Keyword.get(opts, :file, nil)
191191
sigils = Keyword.get(opts, :sigils, [])
192-
normalize_bitstring_modifiers = Keyword.get(opts, :normalize_bitstring_modifiers, true)
193-
normalize_charlists_as_sigils = Keyword.get(opts, :normalize_charlists_as_sigils, true)
192+
migrate = Keyword.get(opts, :migrate, false)
193+
migrate_bitstring_modifiers = Keyword.get(opts, :migrate_bitstring_modifiers, migrate)
194+
migrate_charlists_as_sigils = Keyword.get(opts, :migrate_charlists_as_sigils, migrate)
194195
syntax_colors = Keyword.get(opts, :syntax_colors, [])
195196

196197
sigils =
@@ -215,8 +216,8 @@ defmodule Code.Formatter do
215216
comments: comments,
216217
sigils: sigils,
217218
file: file,
218-
normalize_bitstring_modifiers: normalize_bitstring_modifiers,
219-
normalize_charlists_as_sigils: normalize_charlists_as_sigils,
219+
migrate_bitstring_modifiers: migrate_bitstring_modifiers,
220+
migrate_charlists_as_sigils: migrate_charlists_as_sigils,
220221
inspect_opts: %Inspect.Opts{syntax_colors: syntax_colors}
221222
}
222223
end
@@ -1433,7 +1434,7 @@ defmodule Code.Formatter do
14331434
{doc, state} = quoted_to_algebra(segment, :parens_arg, state)
14341435

14351436
{spec, state} =
1436-
bitstring_spec_to_algebra(spec, state, state.normalize_bitstring_modifiers, :"::")
1437+
bitstring_spec_to_algebra(spec, state, state.migrate_bitstring_modifiers, :"::")
14371438

14381439
spec = wrap_in_parens_if_inspected_atom(spec)
14391440
spec = if i == last, do: bitstring_wrap_parens(spec, i, last), else: spec
@@ -2431,7 +2432,7 @@ defmodule Code.Formatter do
24312432
end
24322433

24332434
defp get_charlist_quotes(:heredoc, state) do
2434-
if state.normalize_charlists_as_sigils do
2435+
if state.migrate_charlists_as_sigils do
24352436
{@sigil_c_heredoc, @double_heredoc}
24362437
else
24372438
{@single_heredoc, @single_heredoc}
@@ -2440,7 +2441,7 @@ defmodule Code.Formatter do
24402441

24412442
defp get_charlist_quotes({:regular, chunks}, state) do
24422443
cond do
2443-
!state.normalize_charlists_as_sigils -> {@single_quote, @single_quote}
2444+
!state.migrate_charlists_as_sigils -> {@single_quote, @single_quote}
24442445
Enum.any?(chunks, &has_double_quote?/1) -> {@sigil_c_single, @single_quote}
24452446
true -> {@sigil_c_double, @double_quote}
24462447
end

lib/elixir/lib/code/normalizer.ex

+7-10
Original file line numberDiff line numberDiff line change
@@ -288,21 +288,18 @@ defmodule Code.Normalizer do
288288
# Lists
289289
defp normalize_literal(list, meta, state) when is_list(list) do
290290
if list != [] and List.ascii_printable?(list) do
291-
# It's a charlist
292-
list =
291+
# It's a charlist, we normalize it as a ~C sigil
292+
string =
293293
if state.escape do
294-
{string, _} = Code.Identifier.escape(IO.chardata_to_string(list), nil)
295-
IO.iodata_to_binary(string) |> to_charlist()
294+
{iolist, _} = Code.Identifier.escape(IO.chardata_to_string(list), nil)
295+
IO.iodata_to_binary(iolist)
296296
else
297-
list
297+
List.to_string(list)
298298
end
299299

300-
meta =
301-
meta
302-
|> Keyword.put_new(:delimiter, "'")
303-
|> patch_meta_line(state.parent_meta)
300+
meta = patch_meta_line([delimiter: "\""], state.parent_meta)
304301

305-
{:__block__, meta, [list]}
302+
{:sigil_c, meta, [{:<<>>, [], [string]}, []]}
306303
else
307304
meta =
308305
if line = state.parent_meta[:line] do

lib/elixir/lib/macro.ex

+3-1
Original file line numberDiff line numberDiff line change
@@ -1133,7 +1133,9 @@ defmodule Macro do
11331133
"""
11341134
@spec to_string(t()) :: String.t()
11351135
def to_string(tree) do
1136-
doc = Inspect.Algebra.format(Code.quoted_to_algebra(tree), 98)
1136+
doc =
1137+
Inspect.Algebra.format(Code.quoted_to_algebra(tree, migrate_charlists_as_sigils: true), 98)
1138+
11371139
IO.iodata_to_binary(doc)
11381140
end
11391141

lib/elixir/test/elixir/code_formatter/containers_test.exs

+5-23
Original file line numberDiff line numberDiff line change
@@ -293,33 +293,15 @@ defmodule Code.Formatter.ContainersTest do
293293
assert_same "<<(<<y>> <- x)>>"
294294
end
295295

296-
test "normalizes bitstring modifiers by default" do
297-
assert_format "<<foo::binary()>>", "<<foo::binary>>"
296+
test "keeps parentheses by default" do
297+
assert_same "<<foo::binary()>>"
298298
assert_same "<<foo::binary>>"
299299

300-
assert_format "<<foo::custom_type>>", "<<foo::custom_type()>>"
300+
assert_same "<<foo::custom_type>>"
301301
assert_same "<<foo::custom_type()>>"
302302

303-
assert_format "<<x::binary()-(13 * 6)-custom>>", "<<x::binary-(13 * 6)-custom()>>"
304-
assert_same "<<x::binary-(13 * 6)-custom()>>"
305-
assert_same "<<0::size*unit, bytes::binary>>"
306-
assert_format "<<0::size*unit, bytes::custom>>", "<<0::size*unit, bytes::custom()>>"
307-
308-
assert_format "<<0, 1::2-integer() <- x>>", "<<0, 1::2-integer <- x>>"
309-
assert_same "<<0, 1::2-integer <- x>>"
310-
end
311-
312-
test "keeps parentheses when normalize_bitstring_modifiers is false" do
313-
opts = [normalize_bitstring_modifiers: false]
314-
315-
assert_same "<<foo::binary()>>", opts
316-
assert_same "<<foo::binary>>", opts
317-
318-
assert_same "<<foo::custom_type>>", opts
319-
assert_same "<<foo::custom_type()>>", opts
320-
321-
assert_same "<<x::binary()-(13 * 6)-custom>>", opts
322-
assert_same "<<0, 1::2-integer() <- x>>", opts
303+
assert_same "<<x::binary()-(13 * 6)-custom>>"
304+
assert_same "<<0, 1::2-integer() <- x>>"
323305
end
324306

325307
test "is flex on line limits" do

0 commit comments

Comments
 (0)