Formatter & linter
One parser serves the compiler, the formatter and the linter, so all three read a template the same way. Both commands split the templates between every core.
Formatter
slimx fmt [PATH…] # rewrite templates in place
slimx fmt --check [PATH…] # list the ones it would rewrite, exit 1 if any
One layout: two spaces per level, - code and = output spacing, p = output after a tag, no redundant div, no trailing whitespace, single blank lines, one final newline. Text blocks move as a whole, so the whitespace they render stays put.
Before writing a file, the formatter compiles the template before and after, and refuses if the Ruby differs: formatting cannot change a page. A refused file is reported and left untouched, and the command exits 1.
Paths default to the current directory; .git, node_modules, tmp and vendor are skipped.
Strict mode
# config/initializers/slimx.rb
SlimX.strict = Rails.env.local?
Not formatted, `slimx fmt` writes: " li Home"
app/views/home/show.html.slim, Line 2, Column 3
li Home
^
A template slimx fmt would change fails to compile, pointing at the first line and what it should be. Templates stay formatted without a CI step catching them late. Per template: strict: true.
Linter
$ slimx lint [PATH…]
app/views/posts/_card.html.slim:3: [ImageAlt] Describe the image with `alt`, or mark it decorative with `alt=""`
29 rules taken from slim-lint, haml-lint, erb_lint, Herb and eslint-plugin-jsx-a11y: layout, template hygiene, Ruby in templates, Rails conventions, HTML and accessibility. It exits 1 when it reports an offense.
Configuration
Both commands read .slimx.yml from the current directory. Every key is optional:
tailwind_classes: true # read templates the way the app compiles them
linters:
LineLength:
max: 150 # default 120
ConsecutiveControlStatements:
max: 3 # lines of `-` before asking for a ruby: block, default 2
ButtonType:
enabled: true # rules off by default: ButtonType, InlineStyles, StrictLocals
DeprecatedClasses:
classes:
text-muted: text-body-secondary
InstanceVariables:
enabled: false
In CI
# .github/workflows/lint.yml
- run: bundle exec slimx fmt --check app
- run: bundle exec slimx lint app
Both print one line per file or offense, path:line: [Rule] message for the linter, and exit 0 when there is nothing to report, 1 otherwise, 64 on a usage error.
Rules
Each example below is run through the linter when the page is generated: the flagged one must be flagged, the clean one must pass.
Layout — fixed by slimx fmt
Indentation
Flagged
ul
li Home
Clean
ul
li Home
Tab
Flagged
ul
→ li Home
Clean
ul
li Home
TrailingWhitespace
Flagged
p Hello·
Clean
p Hello
EmptyLines
Flagged
h1 Title
p Text
Clean
h1 Title
p Text
TrailingBlankLines
Flagged
p Text
Clean
p Text
ControlStatementSpacing
Flagged
-if admin?
p Admin
Clean
- if admin?
p Admin
RedundantDiv
Flagged
div.card
p Text
Clean
.card
p Text
Template hygiene — from slim-lint
LineLength
Flagged
p word word word word word word word word word word word word word word word word word word word word word word word word word
Clean
p
| word word word word word word word word word word
CommentControlStatement
Flagged
- # TODO
p Text
Clean
/ TODO
p Text
EmptyControlStatement
Flagged
-
p Text
Clean
p Text
ConsecutiveControlStatements
Flagged
- a = 1
- b = 2
- c = 3
p = a + b + c
Clean
ruby:
a = 1
b = 2
c = 3
p = a + b + c
TagCase
Flagged
P Text
Clean
p Text
Zwsp
Flagged
p Hello\u200Bworld
Clean
p Hello world
Ruby in templates — from haml-lint and erb_lint
UnnecessaryInterpolation
Flagged
p #{post.title}
Clean
p = post.title
UnnecessaryStringOutput
Flagged
p
= "Hello"
Clean
p
| Hello
DeprecatedClasses
Flagged
p.text-muted Text
Clean
p.text-body-secondary Text
InlineStylesoff by default
Flagged
p style="color: red" Text
Clean
p.text-danger Text
Rails conventions
InstanceVariables
Flagged
p = @post.title
Clean
/# locals: (post:)
p = post.title
StrictLocalsoff by default
Flagged
p = post.title
Clean
/# locals: (post:)
p = post.title
HTML and accessibility — from Herb and jsx-a11y
DuplicateId
Flagged
#menu
#menu
Clean
#menu
#footer-menu
ImageAlt
Flagged
img src=post.image_url
Clean
img src=post.image_url alt=post.title
IframeTitle
Flagged
iframe src=video_url
Clean
iframe src=video_url title="Intro video"
AnchorHref
Flagged
a Read more
Clean
a href=post_path(post) Read more
NestedLinks
Flagged
a href="/a"
a href="/b" B
Clean
a href="/a" A
a href="/b" B
PositiveTabindex
Flagged
button tabindex="3" type="button" Save
Clean
button tabindex="0" type="button" Save
BooleanAttributeValue
Flagged
input disabled="disabled"
Clean
input disabled=true
AriaRole
Flagged
div role="buton"
Clean
div role="button"
AriaAttribute
Flagged
button aria-lable="Close" type="button"
Clean
button aria-label="Close" type="button"
ButtonTypeoff by default
Flagged
button Save
Clean
button type="submit" Save