Pass :sigils to mix format plugins invoked for a sigil - #15672
Merged
josevalim merged 5 commits intoJul 25, 2026
Conversation
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.
josevalim
reviewed
Jul 25, 2026
josevalim
reviewed
Jul 25, 2026
josevalim
reviewed
Jul 25, 2026
Member
|
💚 💙 💜 💛 ❤️ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
mix formatpasses the:sigilsoption to a plugin invoked for a file extension, but not to a plugin invoked for a sigil.The plugin docs say
format/2receives "all the formatting options". The sigil formatter closure capturesformatter_optsbefore:sigilsis put into it, so a plugin invoked for a sigil is the one caller that does not get it; the extension path passes the optionsload_plugins/2returned, 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.HTMLFormatterformats{...}attribute expressions by handing its own options toCode.quoted_to_algebra/2, so a custom sigil inside an attribute is formatted by its plugin in a.heexfile but is left alone in an~Hsigil. Nothing about this is HEEx specific: it applies to any plugin that formats embedded Elixir code.The included test fails before the change.
With
:sigilspresent, 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.HTMLFormatterplus 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.heexfile, and the result is idempotent.