Elixir script file without extension is ignored by `mix format`

My env and context

  • Elixir 1.19.1
  • Erlang 28.1.1
  • Editor: vscodium (Basically same as vscode)
  • Vscodium Extension: ElixirLS v0.30.0
  • Not sure if it matters: zsh, macos arm
  • I’m a beginner in elixir and beam

My problem

I have a language agnostic repo, and I’m trying to get my editor to format my script files.

Example scripts/upcase

  #!/usr/bin/env elixir

  case System.argv() do
    [] -> IO.read(:stdio, :all)
    args -> Enum.join(args, " ")
  end
  |> String.upcase()
  |> IO.puts()

When I open this file on my editor, it detects the language and syntax highlights correctly but format file command doesn’t work.

So I thought I could do it via mix format but that didn’t work either, silently exits without formatting.

What I tried

  1. mix format scripts/upcase is silently exiting, no changes.
  2. Could it be because my lang agnostic repo doesn’t have .formatter.exs file?
    1. mix format –-dot-formatter ~/.formatter.exs scripts/upcase
    2. ❯ cat ~/.formatter.exs                                        
      [
        inputs: ["scripts/upcase"]
      ]
      
    3. still silently exiting, file is not changed.
  3. After reading the mix format docs carefully, I realized I can pipe string into mix format -
    1.   mix format - < scripts/upcase > /tmp/temp && mv /tmp/temp scripts/upcase
      
    2. Yes this works, but it’s very clunky.

Why could this be?

So I dived into the source, and I think this block is the culprit

defp find_formatter_for_file(file, formatter_opts) do
  # ...
  cond do
    # ...
    ext in ~w(.ex .exs) ->
        &elixir_format(&1, [file: file] ++ formatter_opts)
    true ->
        & &1
  end
  # ...
end

Can I create a formatter plugin for extensionless files?

I thought this could be a solution, but I am not sure what’s the best way to do this since elixir_format/2 is a private method, see on github.

Should I copy the method’s body, and use Code.format_string!/2 ?

1 Like

I would not recommend writing Elixir scripts without extension. I understand that you don’t want to type .exs, but you can create a shell aliases for such cases. You can even write a simple look that would iterate all of your projects, enter the scripts directory, read files and create alias for each of them. In my opinion that the best you can do.

However it’s not the limit of your possibilities. If you are fine with a workaround then you can add a custom plugin for your formatter.exs configuration. Such plugin can register itself to "" (empty string) as one of allowed extensions. Since Path.extname/1 returns empty string only if a string does not have extension your plugin should always work only for the scripts you wanted.

The rest is to call the standard formatter inside your plugin as you already did in your 3rd point in the shell.

For more information please take a look at the following documentation pages:

  1. Plugins | mix format (task) @ mix documentation
  2. System.cmd/3 @ Elixir documentation
  3. Path.extname/1 @ Elixir documentation

I’d recommend to keep the .exs for source file, and make a separate installation of the scripts by:

  • copy to a well known dir, such as ~/bin, with extension drpped
  • chmod a+x it

The same can be done for any scripting languages, not just elixir.

I know that I can copy, or even better create symlinks, but I would rather not.

It’s not impossible, vscode’s official typescript extension can handle this just fine. Try it yourself, it works for both unsaved editor or extensionless file.

#! /usr/bin/env node

console.log(
        "white space is way off"
    )