diff --git a/lib/elixir/lib/code/formatter.ex b/lib/elixir/lib/code/formatter.ex index 1d4aa84f66d..ed2466f3796 100644 --- a/lib/elixir/lib/code/formatter.ex +++ b/lib/elixir/lib/code/formatter.ex @@ -6,7 +6,8 @@ defmodule Code.Formatter do @double_heredoc "\"\"\"" @single_quote "'" @single_heredoc "'''" - @sigil_c "~c\"" + @sigil_c_double "~c\"" + @sigil_c_single "~c'" @sigil_c_heredoc "~c\"\"\"" @newlines 2 @min_line 0 @@ -300,7 +301,7 @@ defmodule Code.Formatter do remote_to_algebra(quoted, context, state) meta[:delimiter] == ~s['''] -> - {opener, quotes} = get_charlist_quotes(true, state) + {opener, quotes} = get_charlist_quotes(:heredoc, state) {doc, state} = entries @@ -310,7 +311,7 @@ defmodule Code.Formatter do {force_unfit(doc), state} true -> - {opener, quotes} = get_charlist_quotes(false, state) + {opener, quotes} = get_charlist_quotes({:regular, entries}, state) list_interpolation_to_algebra(entries, quotes, state, opener, quotes) end end @@ -369,13 +370,14 @@ defmodule Code.Formatter do defp quoted_to_algebra({:__block__, meta, [list]}, _context, state) when is_list(list) do case meta[:delimiter] do ~s['''] -> - {opener, quotes} = get_charlist_quotes(true, state) + {opener, quotes} = get_charlist_quotes(:heredoc, state) string = list |> List.to_string() |> escape_heredoc(quotes) {opener |> concat(string) |> concat(quotes) |> force_unfit(), state} ~s['] -> - {opener, quotes} = get_charlist_quotes(false, state) - string = list |> List.to_string() |> escape_string(quotes) + string = list |> List.to_string() + {opener, quotes} = get_charlist_quotes({:regular, [string]}, state) + string = escape_string(string, quotes) {opener |> concat(string) |> concat(quotes), state} _other -> @@ -2422,19 +2424,23 @@ defmodule Code.Formatter do {left, right} end - defp get_charlist_quotes(_heredoc = false, state) do + defp get_charlist_quotes(:heredoc, state) do if state.normalize_charlists_as_sigils do - {@sigil_c, @double_quote} + {@sigil_c_heredoc, @double_heredoc} else - {@single_quote, @single_quote} + {@single_heredoc, @single_heredoc} end end - defp get_charlist_quotes(_heredoc = true, state) do - if state.normalize_charlists_as_sigils do - {@sigil_c_heredoc, @double_heredoc} - else - {@single_heredoc, @single_heredoc} + defp get_charlist_quotes({:regular, chunks}, state) do + cond do + !state.normalize_charlists_as_sigils -> {@single_quote, @single_quote} + Enum.any?(chunks, &has_double_quote?/1) -> {@sigil_c_single, @single_quote} + true -> {@sigil_c_double, @double_quote} end end + + defp has_double_quote?(chunk) do + is_binary(chunk) and chunk =~ @double_quote + end end diff --git a/lib/elixir/test/elixir/code_formatter/literals_test.exs b/lib/elixir/test/elixir/code_formatter/literals_test.exs index ec614b6949e..7f5a7632faf 100644 --- a/lib/elixir/test/elixir/code_formatter/literals_test.exs +++ b/lib/elixir/test/elixir/code_formatter/literals_test.exs @@ -210,10 +210,15 @@ defmodule Code.Formatter.LiteralsTest do test "with escapes" do assert_format ~S['f\a\b\ro'], ~S[~c"f\a\b\ro"] assert_format ~S['single \' quote'], ~S[~c"single ' quote"] - assert_format ~S['double " quote'], ~S[~c"double \" quote"] + assert_format ~S['double " quote'], ~S[~c'double " quote'] + assert_format ~S['escaped \" quote'], ~S[~c'escaped \" quote'] + assert_format ~S['\\"'], ~S[~c'\\"'] assert_same ~S['f\a\b\ro'], @keep_charlists assert_same ~S['single \' quote'], @keep_charlists + assert_same ~S['double " quote'], @keep_charlists + assert_same ~S['escaped \" quote'], @keep_charlists + assert_same ~S['\\"'], @keep_charlists end test "keeps literal new lines" do @@ -235,13 +240,15 @@ defmodule Code.Formatter.LiteralsTest do test "with interpolation" do assert_format ~S['one #{2} three'], ~S[~c"one #{2} three"] + assert_format ~S['#{1}\n \\ " \"'], ~S[~c'#{1}\n \\ " \"'] assert_same ~S['one #{2} three'], @keep_charlists + assert_same ~S['#{1}\n \\ " \"'], @keep_charlists end test "with escape and interpolation" do assert_format ~S['one\n\'#{2}\'\nthree'], ~S[~c"one\n'#{2}'\nthree"] - assert_format ~S['one\n"#{2}"\nthree'], ~S[~c"one\n\"#{2}\"\nthree"] + assert_format ~S['one\n"#{2}"\nthree'], ~S[~c'one\n"#{2}"\nthree'] assert_same ~S['one\n\'#{2}\'\nthree'], @keep_charlists end