Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 23 additions & 12 deletions lib/mix/lib/mix/tasks/format.ex
Original file line number Diff line number Diff line change
Expand Up @@ -349,24 +349,17 @@ defmodule Mix.Tasks.Format do
end
end

sigils =
flatten_sigils =
for plugin <- plugins,
sigil <- find_sigils_from_plugins(plugin, formatter_opts),
do: {sigil, plugin}

sigils =
sigils
formatter_opts =
flatten_sigils
|> Enum.group_by(&elem(&1, 0), &elem(&1, 1))
|> Enum.map(fn {sigil, plugins} ->
{sigil,
fn input, opts ->
Enum.reduce(plugins, input, fn plugin, input ->
plugin.format(input, opts ++ formatter_opts)
end)
end}
end)
|> prepend_sigils(Keyword.delete(formatter_opts, :sigils))

{Keyword.put(formatter_opts, :sigils, sigils),
{formatter_opts,
Enum.map(subs, fn {path, formatter_opts_and_subs} ->
{path, load_plugins(formatter_opts_and_subs, opts)}
end)}
Expand All @@ -384,6 +377,24 @@ defmodule Mix.Tasks.Format do
plugins
end

# A sigil formatter must receive :sigils itself, so a plugin can format sigils
# nested inside the sigil it was given. The list is rebuilt on each invocation.
defp prepend_sigils(grouped_sigils, formatter_opts_without_sigils) do
sigils =
Enum.map(grouped_sigils, fn {sigil, plugins} ->
{sigil,
fn input, opts ->
formatter_opts = prepend_sigils(grouped_sigils, formatter_opts_without_sigils)
Comment thread
josevalim marked this conversation as resolved.

Enum.reduce(plugins, input, fn plugin, input ->
plugin.format(input, opts ++ formatter_opts)
end)
end}
end)

[sigils: sigils] ++ formatter_opts_without_sigils
end

@typedoc """
Options for `formatter_for_file/2`.
"""
Expand Down
60 changes: 60 additions & 0 deletions lib/mix/test/mix/tasks/format_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,66 @@ defmodule Mix.Tasks.FormatTest do
end)
end

defmodule Elixir.SigilOuterPlugin do
@behaviour Mix.Tasks.Format

def features(opts) do
assert opts[:from_formatter_exs] == :yes
[sigils: [:O]]
end

def format(contents, opts) do
assert opts[:from_formatter_exs] == :yes
assert opts[:sigil] == :O
[Code.format_string!(contents, opts), ?\n]
end
end

defmodule Elixir.SigilInnerPlugin do
@behaviour Mix.Tasks.Format

def features(opts) do
assert opts[:from_formatter_exs] == :yes
[sigils: [:I]]
end

def format(contents, opts) do
assert opts[:from_formatter_exs] == :yes
assert opts[:sigil] == :I
String.upcase(contents)
end
end

test "uses sigil plugins for sigils nested inside sigils", context do
in_tmp(context.test, fn ->
File.write!(".formatter.exs", """
[
inputs: ["a.ex"],
plugins: [SigilOuterPlugin, SigilInnerPlugin],
from_formatter_exs: :yes
]
""")

File.write!("a.ex", """
def nested_sigil_test do
~O'''
inner(~I"foo bar")
'''
end
""")

Mix.Tasks.Format.run([])

assert File.read!("a.ex") == """
def nested_sigil_test do
~O'''
inner(~I"FOO BAR")
'''
end
"""
end)
end

test "customizes plugin loading", context do
in_tmp(context.test, fn ->
File.write!(".formatter.exs", """
Expand Down
Loading