elixir-editors / emacs-elixir

Emacs major mode for Elixir
448 stars 94 forks source link

.formatter.exs not used #421

Open zizhengtai opened 6 years ago

zizhengtai commented 6 years ago

I'm having this weird problem where elixir-format doesn't respect the .formatter.exs file in my project's root directory when I follow the settings here.

How to reproduce:

  1. mix new a project and change the generated .formatter.exs to be

    # Used by "mix format"
    [
    inputs: []
    ]

    which should not format anything. Assume the project is located at ~/Projects/test.

  2. Mess with the format of mix.exs in some way, and run mix format from the project root directory manually to confirm that mix.exs doesn't get formatted.

  3. Now open mix.exs with Emacs, enable projectile-mode, and run elixir-format. This time mix.exs does get formatted, although the value of elixir-format-arguments is shown to be ("--dot-formatter" "~/Projects/test/.formatter.exs").

My elixir-mode settings:

(use-package elixir-mode
  :ensure t
  :config
  (add-hook 'elixir-format-hook
            (lambda () (if (projectile-project-p)
                           (setq elixir-format-arguments
                                 (list "--dot-formatter"
                                       (concat (locate-dominating-file
                                                buffer-file-name
                                                ".formatter.exs")
                                               ".formatter.exs")))
                           (setq elixir-format-arguments nil)))))

No matter what I do, the file always gets formatted when I run elixir-format. I'm not sure whether I'm configuring things incorrectly or hitting a bug.

Trevoke commented 6 years ago

Thanks for reporting this! It sure sounds like a bug. I'll investigate on the code side. Meanwhile, can you tell me what happens if you remove your hook for elixir format and just run the function without any additional configuration?

zizhengtai commented 6 years ago

Hi Trevor, the problem persists with plain elixir-format. By the way, I'm observing the exact same bug with ElixirLS on VS Code.

smaximov commented 6 years ago

I believe elixir-format does use .formatter.exs. What you observe happens because elixir-format--run-format passes the current formatted buffer's filename (or the path to temporary copy, to be precise) to the mix format task as the additional inputs which are merged with the inputs specified in .formatter.exs. This makes sense since the doc says that :inputs in .formatter.exs specifies the default inputs for the formatter, which means that this option is overriden, or augmented, by the command line arguments passed to the task.

So when run elixir-format on mix.exs, it does roughly the following:

  1. makes a temporary copy of mix.exs (let's call it /tmp/mix.exs);
  2. run hooks to obtain additional arguments: ("--dot-formatter" "~/Projects/test/.formatter.exs");
  3. appends /tmp/mix.exs to the arguments;
  4. invokes mix format --dot-formatter ~/Projects/test/.formatter.exs /tmp/mix.exs;
  5. mix format merges the :input setting ([]) in .formatter.exs with extra inputs passed as the arguments to the task (/tmp/mix.exs) and figures it should format this file;
  6. elixir-format then finds the diff between the formatted '/tmp/mix.exs` and the original file and applies it to the original file.

What options do we have?

  1. Run mix format without specifying what file we want to format. This means that every time you run elixir format as the before-save-hook, the whole project is formatted, which is kinda inefficient.
  2. Change the semantics of the :inputs setting (so mix format would ignore extra inputs passed as the command line arguments if they are not found in :inputs).
  3. Have some elixir-format-ignore buffer- and/or file-local variable which holds a list of files that has to be ignored by `elixir-format'.
  4. Make elixir-format evaluate .formatter.exs and check that the original file is contained in :inputs before invoking mix format. I think it can be done with a custom Elixir script, though it won't be trivial.