Skip to content

Pass :sigils to mix format plugins invoked for a sigil - #15672

Merged
josevalim merged 5 commits into
elixir-lang:mainfrom
aptinio:pass-sigils-to-sigil-plugins
Jul 25, 2026
Merged

Pass :sigils to mix format plugins invoked for a sigil#15672
josevalim merged 5 commits into
elixir-lang:mainfrom
aptinio:pass-sigils-to-sigil-plugins

Conversation

@aptinio

@aptinio aptinio commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

mix format passes the :sigils option to a plugin invoked for a file extension, but not to a plugin invoked for a sigil.

The plugin docs say format/2 receives "all the formatting options". The sigil formatter closure captures formatter_opts before :sigils is put into it, so a plugin invoked for a sigil is the one caller that does not get it; the extension path passes the options load_plugins/2 returned, which do carry it.

The consequence is that a plugin cannot dispatch a sigil nested inside the sigil it is formatting by passing on the options it received. Phoenix.LiveView.HTMLFormatter formats {...} attribute expressions by handing its own options to Code.quoted_to_algebra/2, so a custom sigil inside an attribute is formatted by its plugin in a .heex file but is left alone in an ~H sigil. Nothing about this is HEEx specific: it applies to any plugin that formats embedded Elixir code.

The included test fails before the change.

With :sigils present, a plugin sigil nested inside itself now dispatches to the plugin again. A plugin that formats its contents once recurses as deep as the source nests, which is finite, and the extension path already allows this today.

I checked this against Phoenix.LiveView.HTMLFormatter plus a custom sigil plugin: every sigil in the file now formats the same way whether it sits at the top level, inside ~H, or in a .heex file, and the result is idempotent.

mix format passes the :sigils option to a plugin invoked for a file
extension, but not to a plugin invoked for a sigil, because the sigil
formatter closure captures the options before :sigils is put into them.
A plugin that formats embedded Elixir code therefore cannot dispatch a
sigil nested inside the sigil it is formatting by passing on the options
it received, so the same snippet formats differently in a .heex file and
in an ~H sigil.

The sigil formatters are part of the options they receive, so a private
function rebuilds the list whenever a sigil is formatted.
Comment thread lib/mix/lib/mix/tasks/format.ex Outdated
Comment thread lib/mix/lib/mix/tasks/format.ex Outdated
Comment thread lib/mix/lib/mix/tasks/format.ex
@josevalim
josevalim merged commit 44ea7a6 into elixir-lang:main Jul 25, 2026
15 checks passed
@josevalim

Copy link
Copy Markdown
Member

💚 💙 💜 💛 ❤️

@aptinio
aptinio deleted the pass-sigils-to-sigil-plugins branch July 26, 2026 00:15
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.

2 participants