Add ERB Lint to lint templates (RuboCop only covers .rb)#1988
Conversation
RuboCop only parses .rb, so nothing checked the Ruby inside <% %> tags or the ERB/HTML conventions documented in CLAUDE.md. Add erb_lint with a conservative, fully-autocorrectable ruleset (mechanical HTML/ERB formatting plus an omakase-matching RuboCop subset), wire it into ai/lint, and run it in CI alongside RuboCop. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mechanical only: normalizes whitespace around/inside ERB tags and applies the omakase Ruby style (double quotes, brace/bracket spacing) to code inside <%= %> tags. No behavioral changes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The HTML/ERB conventions are now enforced, not just documented; note that and update the ai/lint command descriptions to mention ERB Lint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The array/hash/block brace cops default to no_space in bare RuboCop but omakase wants a space inside them; enabling the cops without pinning EnforcedStyle silently enforced the opposite, so 772 array-bracket violations went undetected. Pin the styles to omakase's values and extend the subset to the other autocorrectable rules documented in CLAUDE.md: block-brace spacing, no trailing commas, no redundant .to_s coercion, and no semicolons. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Applies the newly-covered rules — omakase array/block-brace spacing and trailing-comma removal — across templates. Mechanical only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CI installs gems into vendor/bundle (bundler-cache), so `erb_lint --lint-all` was linting third-party gem templates and failing on 237 offenses in files we don't own. RuboCop excludes vendor by default; erb_lint doesn't, so exclude it (plus node_modules and tmp) explicitly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| # checked the Ruby inside <% %> tags or our ERB/HTML conventions (CLAUDE.md). | ||
| # Scoped conservatively: mechanical HTML/ERB formatting linters plus a small, | ||
| # fully autocorrectable RuboCop subset mirroring our omakase style rules. | ||
| EnableDefaultLinters: false |
There was a problem hiding this comment.
Why not use the defaults and be explicit on what we disable? This is set to false and the majority of the default are re-enabled below.
There was a problem hiding this comment.
🤖 From Claude: Done — EnableDefaultLinters is back on (all 14 defaults run). Only RequireInputAutocomplete is explicitly disabled (adding autofill tokens to ~64 existing inputs is a per-field UX call, out of scope), and AllowedScriptType is configured to also allow type="module". That last check caught a real bug: a webfonts partial using curly quotes in its <script> tag.
| Rubocop: | ||
| enabled: true |
There was a problem hiding this comment.
| Rubocop: | |
| enabled: true | |
| Rubocop: | |
| enabled: true | |
| rubocop_config: | |
| inherit_from: | |
| - .rubocop.yml | |
Why duplicate everything in our rubocop.yml ?
There was a problem hiding this comment.
🤖 From Claude: Done — the Rubocop linter now inherit_from: .rubocop.yml, so nothing is duplicated. The only cops set here are the ones that can't work on the Ruby extracted from a single tag: the README's recommended list plus the block/indentation-alignment family (a multi-line if/case is split across separate <% %> tags, so RuboCop sees misaligned fragments). Net effect: it also surfaced more real style issues (space after colon/comma) than my earlier hand-picked subset.
Per review: stop disabling the default linters and re-enabling a subset, and stop duplicating our RuboCop rules. Follow Shopify/erb_lint's recommended config instead: - EnableDefaultLinters stays on; the Rubocop linter inherits .rubocop.yml so Ruby in tags is held to the exact same omakase style as our .rb files (no rules restated here). Disable only the cops that can't work on the Ruby extracted from a tag — the README's list plus the block/indentation alignment family (a multi-line if/case is split across separate tags). - Explicitly disable RequireInputAutocomplete (adding autofill tokens to every existing input is a per-field UX decision, out of scope) and allow <script type="module"> (legit ESM) — the "be explicit about what we disable" approach rather than turning the defaults off wholesale. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tType) The <script> type attribute (and the JS body) used curly quotes, which are invalid — the script would raise a syntax error. This partial appears unused; fixing the quotes rather than deleting it for now. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mechanical only: the full default linter set now normalizes HTML too (SelfClosingTag drops the slash on HTML void elements, keeps it on SVG; SpaceInHtmlTag tidies tag spacing), and the inherited omakase config adds a few more space cops (after colon/comma) on Ruby in tags. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
🤖 suggested review level: 3 Read 📖 lint tooling + a large but mechanical autocorrect sweep — review the config, skim the sweep
Why
RuboCop only parses
.rb, so nothing checked the Ruby inside<% %>tags or the ERB/HTML conventions in CLAUDE.md — documented but unenforced. This adds an actual ERB linter.What
erb_lintgem +.erb_lint.yml, following the gem's recommended setup:inherit_from: .rubocop.yml— Ruby inside tags is held to the same omakase style as our.rbfiles, nothing duplicated. Only the cops that can't work on Ruby extracted from a single tag are disabled (README list + the indentation/alignment family, since a multi-lineif/casespans separate tags).RequireInputAutocomplete(adding autofill tokens to ~64 existing inputs is a per-field UX decision, out of scope) and an allowance for<script type="module">(legit ESM).ai/lint(runs with RuboCop;--fixautocorrects both) and CI.Notes
AllowedScriptTypecheck caught a real bug: an (unused) webfonts partial whose<script>tag and JS body used curly quotes. Fixed the quotes; flagged as a deletion candidate.