sbrink
How do I get a well-formatted string from an AST?
Hi, I want to modify elixir files based on the AST.
For the result I want to the elixir formatter.
For a prototype I tried the following and it almost works:
File.read!("example.ex")
|> read_contents_of_file()
|> Code.string_to_quoted!()
|> Macro.to_string()
# |> Code.format_string!()
# |> IO.iodata_to_binary
The problem are the parentheses. They’re added everywhere. An example result:
"defmodule(Example) do\n def(output(str)) do\n IO.inspect(str)\n end\nend"
The documentation for Macro.to_string states “This function discards all formatting of the original code.”.
So is it possible to keep the formatting of the original code?
Thanks in advance for every piece of advice or idea 
- Sascha
Most Liked
josevalim
The formatter works on heavily annotated AST to be able to preserve the user’s choice in some cases, that’s why it doesn’t work directly on the given AST, because it cannot guaranteed something close to a similar result. The best solution here would be to improve Macro.to_string. For example, it can skip adding parens in do/end blocks.
OvermindDL1
Look at it’s options, specifically the :locals_without_parens option, it’s default empty when run directly (the mix formatter default sets up some options).
OvermindDL1
Hmm…
iex(7)> s
"def(blah(i)) do 42 end"
iex(8)> Code.format_string!(s, locals_without_parens: [def: 1, def: 2, def: 3, def: :*])|>IO.puts()
def(blah(i)) do
42
end
:ok
And yet the Code.format_string! docs state:
### Parens and no parens in function calls
Elixir has two syntaxes for function calls. With parens and no parens. By
default, Elixir will add parens to all calls except for:
1. calls that have do/end blocks
2. local calls without parens where the name and arity of the local call
is also listed under :locals_without_parens (except for calls with arity
0, where the compiler always require parens)
The choice of parens and no parens also affects indentation. When a function
call with parens doesn't fit on the same line, the formatter introduces a
newline around parens and indents the arguments with two spaces:
some_call(
arg1,
arg2,
arg3
)
On the other hand, function calls without parens are always indented by the
function call length itself, like this:
some_call arg1,
arg2,
arg3
If the last argument is a data structure, such as maps and lists, and the
beginning of the data structure fits on the same line as the function call,
then no indentation happens, this allows code like this:
Enum.reduce(some_collection, initial_value, fn element, acc ->
# code
end)
some_function_without_parens %{
foo: :bar,
baz: :bat
}
And yet it’s not working, at least in Elixir 1.9.1. Might be worth trying on master and if still not working then submitting a bug issue?








