Running the new Elixir formatter

Then you take option B: not using the formatter. This is a valid option, since mix format is opnionated. All discussion about what to format how has taken place on the core dev mailinglist github issues and other public places.

Believe me, I fought the go formatter for months, until I realized that even if the formatting doesn’t suite my eyes, in the long term it was a big help to find myself in code of other people, the same will be true for elixir once mix format has been properly adopted in all teams.

1 Like

Yeah, I’m not gonna be using it, since these are not bugs.
I just don’t understand why you guys committed to having parentheses, but then remove them from if statements, then completely ignore why they exist in the first place, by placing closing ones on new lines, and then double the lines of code with new line for everything, not to mention what the formatter does with code that uses long, descriptive names.
My code looks like a Tasmanian devil went and had its way with every line. :joy:
I’ll pray that it doesn’t get adopted :blush:

1 Like

Thank you for great formatting tool. I’ve found it very helpful.

After generating new umbrella project I started adding .formatter.exs file with the following contents:

[
  inputs: ["mix.exs", "{apps,config}/**/*.{ex,exs}"],
]

But mix format failed to format some files

mix format failed for file: apps/app_web/assets/node_modules/phoenix/priv/templates/phx.gen.schema/schema.ex
** (SyntaxError) apps/app_web/assets/node_modules/phoenix/priv/templates/phx.gen.schema/schema.ex:1: syntax error before: '='
    (elixir) lib/code.ex:442: Code.format_string!/2
    (mix) lib/mix/tasks/format.ex:293: Mix.Tasks.Format.format_file/3
    (elixir) lib/task/supervised.ex:88: Task.Supervised.do_apply/2
    (elixir) lib/task/supervised.ex:38: Task.Supervised.reply/5
    (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3

This error is due the file with .ex extension contains some string interpolations, like defmodule <%= inspect schema.module %> do. This fact is not a problem, because entire node_modules dir is in .gitignore, so .formatter.exs inputs must skip formatting this file. I would like to share my solution of this problem, because I think someone else could find it helpful.

Now my basic .formatter.exs file looks like:

inputs = "git ls-files | grep -E \"*\\.exs?$\""
         |> String.to_charlist()
         |> :os.cmd()
         |> to_string()
         |> String.split(~r{(\r\n\|\r|\n)}, trim: true)

[
  inputs: inputs,
]

So it formats elixir files only under the version source root.

2 Likes

I had the same problem, but my solution was a little bit different:

[
  inputs: [
    "mix.exs",
    "apps/*/{config,lib,test,priv}/**/*.{ex,exs}",
    "config/**/*.{ex,exs}"
  ]
]

:wink:

1 Like

We will tackle it in the next release, we have an open issue here: https://github.com/elixir-lang/elixir/issues/7280

1 Like

Hi,

I like the new code formatter. I just noticed an inconsistency though between the with and if formats:

  def new(type, %Coord{} = origin) when type in @types do
    with %MapSet{} = coords <- coords(@offsets[type], origin),
         do: {:ok, %Island{type: type, coords: coords, hits: MapSet.new()}},
         else: ({:error, _reason} -> {:error, :invalid_island_args})
  end
  def guess(%Island{} = island, %Coord{} = guess) do
    if MapSet.member?(island.coords, guess),
      do: {:hit, update_in(island.hits, &MapSet.put(&1, guess))},
      else: :miss
  end

As you can see above, the do: and else: clauses are more indented for with than for if.
And I understand why but it just struck me.

Thanks

1 Like

Yep it bugs me too, it’s because with is a multi-arity function call (ack! Those should be banned from elixir!), however you can kind of ‘work around’ it by surrounding the with ‘arguments’ with parenthesis (which is what I do now since the formatter broke my previous with\ syntax form), like:

  def new(type, %Coord{} = origin) when type in @types do
    with(
      %MapSet{} = coords <- coords(@offsets[type], origin),
      do: {:ok, %Island{type: type, coords: coords, hits: MapSet.new()}},
      else: ({:error, _reason} -> {:error, :invalid_island_args})
    )
  end

What’s the consensus on grouping multiline functions with the new formatter?

I know that it still allows to group one-line functions, but it’s no longer possible to apply the same styling to the longer functions.

I noticed that it was a quite common way to format the code, e.g. hex.pm grouped multiline functions not long ago:

But now it was run through new formatter, and I think that it looks worse (notice haphazard new lines around extract_params):

I’m aware that the formatter doesn’t know what’s a function, and therefore cannot make an intelligent decision here. But does it mean that the official position is against the old style of grouping and you aren’t planning to bring that ability back? :pensive:

EDIT: José confirmed on IRC that the behaviour I’ve reported in this post is a bug, so I created the issue on Github.

I’ve noticed some weird formatter behaviour in Elixir 1.6.4, which I personally don’t like. I’m not sure if it’s a feature or a bug, so I’m reporting it here.

From what I can tell, the formatter seems to respect my vertical choice of function parameters. So both of the following are fine:

Enum.reduce(foo, bar, baz)

Enum.reduce(
  foo,
  bar,
  baz
)

This is cool, because in some cases, I like to break each param on a separate line, even if the line itself is not long.

Now, if the last param is a lambda, the formatter doesn’t respect the vertical choice. In other words, if I start with this code:

Enum.reduce(
  foo, 
  bar, 
  fn el, acc -> baz(el, acc) end
)

It’s always turned into

Enum.reduce(foo, bar, fn el, acc -> baz(el, acc) end)

That becomes somewhat annoying when the lambda is multiline, because my code is always force formatted into

Enum.reduce(foo, bar, fn el, acc ->
  baz(el)
  qux(acc)
end)

Personally, I have problems when reading this layout, because the lambda arguments are further right than the body.

This becomes particularly annoying in the following case:

Enum.reduce(
  moderately_complex_expression, 
  moderately_complex_expression, 
  fn element, accumulator ->
    do_something(element)
    do_something(accumulator)
  end
)

The code gets reformatted into:

Enum.reduce(moderately_complex_expression, moderately_complex_expression, fn element,
                                                                             accumulator ->
  do_something(element)
  do_something(accumulator)
end)

Which I find quite terrible to read.

Of course, the code can be improved by using temp local vars and/or named functions, but in some cases I just prefer to inline everything. Sadly, given the formatted code above, inlining is not an option anymore.

So my question would be: is such formatting intended or is this a bug?

cc @josevalim

5 Likes

For anyone curious, it’s fixed in master. :slight_smile:

2 Likes

You can always use

alias Foo.Api.Google, as: ApiGoogle
alias Foo.Authentication.Google, as: AuthenticationGoogle
1 Like