Skip to content

Conversation

mononym
Copy link
Contributor

@mononym mononym commented Aug 6, 2025

Currently when configuring the formatter there is only the ability to include and not exclude files from the match. This leads to some interesting situations depending on project setup.

The only way to exclude files is to actually pattern match the files yourself inside the formatter.exs file so that you can then exclude specific files you care about, returning the modified list for the mix formatter code to work with.

Things start getting more complicated when you start talking Umbrella apps, or just subdirectories (as defined in formatter.exs) in general. If you have a subdirectory, running mix format from the root does NOT change the CWD you are in. So that formatter.exs file sitting in an apps/awesome_umbrella_sub_app_1 that tries to find its files using Path.wildcard might find surprising behavior depending on where mix format was run, whether it was from the umbrella root or the application subfolder.

In short, if you want to exclude a file from within an umbrella sub app you have to do something like this:

# This can be called from either the root umbrella folder or this folder.
cwd = File.cwd!()
app = "elixir_ls_test_web"

app_directory =
  if String.ends_with?(cwd, app) do
    cwd
  else
    "#{cwd}/apps/#{app}"
  end

[
  import_deps: [:phoenix],
  plugins: [Phoenix.LiveView.HTMLFormatter],
  inputs:
    Enum.flat_map(
      ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}"],
      &Path.wildcard(Path.expand(&1, app_directory), match_dot: true)
    ) --
      Enum.map(
        ["lib/elixir_ls_test_web/router.ex"],
        &Path.expand(&1, app_directory)
      )
]

That will check if you're running the formatter from the umbrella root or the application subdirectory, find the files based off of the correct root directory, allow you to define relative paths for files you want to exclude, and then return a final list of cared about files minus the excludes.

This...is a lot of work just to exclude a single file. And you have to be aware that the formatter file might get run from an entirely different working directory than the one it is defined in which is definitely not intuitive.

I think there is a far, far easier way. Just allow us to define an excludes option for every formatter. Any files that match from the excludes option are removed from the results of the inputs config. By using the exact same matching code for both inputs and excludes the above formatter becomes dead simple:

[
  inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}"],
  excludes: ["lib/elixir_ls_test_web/router.ex", "test/special_directory/*.{heex,ex,exs}"]
]

I tried actually running the test I wrote but I ran into the following error inside the mix folder and don't have the bandwidth to figure it out:

mix compile
** (UndefinedFunctionError) function Mix.Compilers.Protocol.status/2 is undefined (module Mix.Compilers.Protocol is not available)
    Mix.Compilers.Protocol.status(true, [tracers: [], consolidate_protocols: true])
    (mix 1.18.3) lib/mix/compilers/elixir.ex:168: Mix.Compilers.Elixir.compile/7
    (mix 1.18.3) lib/mix/tasks/compile.elixir.ex:186: Mix.Tasks.Compile.Elixir.with_logger_app/2
    (mix 1.18.3) lib/mix/task.ex:495: anonymous fn/3 in Mix.Task.run_task/5
    (mix 1.18.3) lib/mix/task.compiler.ex:298: Mix.Task.Compiler.run_compiler/2
    (mix 1.18.3) lib/mix/task.compiler.ex:287: Mix.Task.Compiler.run/4
    (mix 1.18.3) lib/mix/tasks/compile.all.ex:75: Mix.Tasks.Compile.All.do_run/2
    (mix 1.18.3) lib/mix/sync/lock.ex:122: Mix.Sync.Lock.with_lock/3

@josevalim josevalim merged commit 04f1b7c into elixir-lang:main Aug 6, 2025
13 checks passed
@josevalim
Copy link
Member

💚 💙 💜 💛 ❤️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants