Yes! What you have to be mindful of with this kind of manipulations in contrast with macros is that you need to consider how line numbers move around. You need to both reorder the dependencies, and correct the line numbers, otherwise comments may be misplaced. The reason is that Code.quoted_to_algebra
requires the ast and comments as separate arguments and mixes them by their line numbers.
One way to achieve what you want is by doing this:
"""
defp deps do
[
{:a, "~> 1.0"},
{:z, "~> 1.0"},
{:g, "~> 1.0"},
# Comment for r
{:r, "~> 1.0"},
{:y, "~> 1.0"},
# Comment for :u
{:u, "~> 1.0"},
{:e, "~> 1.0"},
{:s, "~> 1.0"},
{:v, "~> 1.0"},
{:c, "~> 1.0"},
{:b, "~> 1.0"},
]
end
"""
|> Sourceror.parse_string()
|> Sourceror.postwalk(fn
{:defp, meta, [{:deps, _, _} = fun, body]}, state ->
[{{_, _, [:do]}, block_ast}] = body
{:__block__, block_meta, [deps]} = block_ast
lines = Enum.map(deps, fn {:__block__, meta, _} -> meta[:line] end)
deps =
Enum.sort_by(deps, fn {:__block__, _, [{{_, _, [name]}, _}]} ->
Atom.to_string(name)
end)
deps =
Enum.zip([lines, deps])
|> Enum.map(fn {old_line, dep} ->
{_, tuple_meta, [{left, right}]} = dep
line_correction = old_line - tuple_meta[:line]
tuple_meta = Sourceror.correct_lines(tuple_meta, line_correction)
left = Macro.update_meta(left, &Sourceror.correct_lines(&1, line_correction))
right = Macro.update_meta(right, &Sourceror.correct_lines(&1, line_correction))
{:__block__, tuple_meta, [{left, right}]}
end)
quoted = {:defp, meta, [fun, [do: {:__block__, block_meta, [deps]}]]}
state = Map.update!(state, :line_correction, & &1)
{quoted, state}
quoted, state ->
{quoted, state}
end)
|> Sourceror.to_string()
|> IO.puts()
# =>
defp deps do
[
{:a, "~> 1.0"},
{:b, "~> 1.0"},
{:c, "~> 1.0"},
{:e, "~> 1.0"},
{:g, "~> 1.0"},
# Comment for r
{:r, "~> 1.0"},
{:s, "~> 1.0"},
# Comment for :u
{:u, "~> 1.0"},
{:v, "~> 1.0"},
{:y, "~> 1.0"},
{:z, "~> 1.0"}
]
end
The other thing to note is that this is not your regular AST, Sourceror uses the literal_encoder: &{:ok, {:__block__, &2, [&1]}}
option for Code.string_to_quoted_with_comments/2
under the hood, so you need to expect literals to be wrapped in blocks, so for example {:a, "~> 1.0"}
will become:
{:__block__, [line: 1], [
{{:__block__, [line: 1], [:a]},
{:__block__, [line: 1, delimiter: "\""], ["~> 1.0"]}}
]}
This is explained a bit in the Formatting considerations section of the new functions
Of course I will try to expand Sourceror as we find more complex use cases that could be simplified