Mob ships with first-class support for mix format so that ~MOB sigils are
formatted alongside the rest of your Elixir code. Generated projects include a
.formatter.exs that wires this up automatically.
Setting up the formatter
Add Mob.Formatter to your .formatter.exs:
# .formatter.exs
[
plugins: [Mob.Formatter],
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]Then run:
mix format
That's it. Every ~MOB sigil in your project is now formatted with the rest
of your code in one pass.
What gets formatted
Indentation
Children are indented 2 spaces per nesting level, matching Elixir convention:
# Before
~MOB"""
<Column>
<Text text="Hello" />
<Button text="OK" on_tap={tap} />
</Column>
"""
# After
~MOB"""
<Column>
<Text text="Hello" />
<Button text="OK" on_tap={tap} />
</Column>
"""Attribute wrapping
When a tag's attributes would exceed line_length, each attribute moves to its
own line with the closing /> or > on a dedicated line:
# Short enough to stay inline
~MOB(<Button text="Save" on_tap={save_tap} />)
# Wraps when attributes are too long
~MOB"""
<Image
source={assigns.avatar_url}
width={120}
height={120}
corner_radius={:radius_pill}
fill_width={false}
/>
"""Expression children
{expr} slots are indented to match their sibling nodes:
~MOB"""
<Column>
<Text text="Items" text_size={:xl} />
{Enum.map(assigns.items, fn item ->
~MOB(<Text text={item} />)
end)}
</Column>
"""Configuration
Line length
Mob.Formatter respects the standard line_length option:
[
plugins: [Mob.Formatter],
line_length: 120,
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]The default is 98 characters.
CI enforcement
Add mix format --check-formatted to your CI pipeline to catch unformatted
sigils in the same pass as unformatted Elixir:
mix format --check-formatted
mix credo --strict
mix test
How it works
Mob.Formatter implements the Mix.Tasks.Format behaviour introduced in
Elixir 1.14. Elixir's formatter calls it for every ~MOB sigil it encounters,
passing the raw sigil content. The formatter re-parses the content using the
same NimbleParsec grammar as the sigil itself and emits normalised output.
If a sigil can't be parsed (e.g. the file is in a mid-edit incomplete state),
the formatter returns the content unchanged rather than raising. mix format
never breaks a file it can't fully understand.
See Mob.Formatter for the full API reference.
Credo
Projects generated by mix mob.new include Credo in dev/test dependencies.
Run it with:
mix credo --strict
The generated .credo.exs is pre-configured to work well with Mob's conventions
(the ~MOB sigil, module attribute patterns, etc.).