Skip to content

Rules

Published  · Updated by x_ror

This page is the catalogue of rules, and a public roadmap. Rules marked Available or Partial work in the extension today. Rules marked Planned do not exist yet; they are listed with a phase number, and lower phases come first. Rule names are the diagnostic codes, and the config file described below is read for the rules that exist.

The formatter and the checks work without configuration, and a .ruby-templates.toml changes them. Formatting describes what the formatter does, and the diagnostics reference lists every code the checks report.

  • Type. An autofix is a formatter rule that rewrites the template. A warning is a check that reports a problem and rewrites nothing.
  • Rendered output. Safe means the rule leaves the rendered page as it is: it only changes whitespace the browser ignores, or changes markup into an equal form. Not safe says what can change.
  • Examples. An autofix shows the template before and after. A warning shows a flagged template and a fine one. The examples of available rules are run through ruby-templates format and ruby-templates check by the tests; the ones of planned rules are written by hand.
  • Defaults. A formatting rule says whether it is on by default. Layout/TrailingEmptyLines is, for Haml and Slim, and so are Layout/TrailingWhitespace (all three languages) and Layout/IndentationStyle (ERB, which writes spaces unless the editor, .editorconfig or its style say tabs); the others, Layout/EmptyLines among them, are off until you turn them on, so a file is never changed beyond what the entry says unless you ask. The planned Rails checks are on.

Rules, and the switches that VS Code has as settings, are configured in a .ruby-templates.toml. The editor, ruby-templates check and ruby-templates format read the same file. A rule is a table named after it, with the department and the rule joined by a dot (["Html/ImgAlt"] in quotes is an error, and the message says how to write it):

[Html.ImgAlt]
enabled = false

A department table with only enabled switches all its rules; a rule’s own enabled beats it:

[Html]
enabled = false


[Html.DuplicateId]
enabled = true

To change a rule for some files only, give it include or exclude globs. They follow the EditorConfig syntax (*, **, ?, [abc], {a,b}): a pattern without / matches in any folder, one with a leading or inner / is anchored to the folder of the file that holds it.

[Html.ImgAlt]
exclude = ["**/*.haml", "app/views/legacy/**"]

A rule with options takes them in its own table. max and max_line_length are whole numbers, 0 or more; style is one of a few words; tags is a list of element names. A value that does not fit is a problem in the file:

[Layout.EmptyLines]
enabled = true
max = 2


[Layout.EmptyLinesAroundBlockBody]
enabled = true
style = "empty_lines"


[Layout.ElementContentOnOwnLine]
enabled = true
tags = ["div", "section"]
root = true                        # stop looking in the folders above
exclude = ["vendor/**", "tmp/**"]  # no checks and no formatting for these files


[erb]
format.enabled = false             # no formatting for ERB
format.align_comments = true       # same as --align-comments
diagnostics.enabled = true         # false: no diagnostics for ERB
disabled_diagnostics = ["Html/ImgAlt", "unclosed-element"]  # rule names, or the codes of 0.10 and earlier


[haml]                             # format.enabled, diagnostics.enabled, disabled_diagnostics
[slim]                             # the same, and: extra_engines = ["mjml"]; Skim files too
[jbuilder]                         # disabled_diagnostics, for Jbuilder and Builder views

The language tables take the keys of the rubyTemplates.<language>.* settings, in snake case. A key under the wrong table (extra_engines under [erb]) is reported.

  • The file looks for .ruby-templates.toml in the template’s folder and every folder above it, nearest first, and stops at one with root = true.
  • The files merge key by key: the nearest file that sets a key wins. A list (exclude, include, disabled_diagnostics, extra_engines) is replaced, not extended. Globs are relative to the folder of the file that holds them.
  • The file is stronger than VS Code: precedence is the nearest file, then the VS Code settings (where no file says anything), then .editorconfig for the indentation width and, for ERB, indent_style (the editor’s insertSpaces counts as VS Code here), then the defaults. style of Layout.IndentationStyle is a file setting, so it beats insertSpaces and indent_style.
  • A rule is on, if it is on by default, unless the nearest file that sets either its enabled or its department’s enabled says false, and a formatting rule that is off by default is on when that file says true; within that one file, the rule’s own enabled beats the department’s. A root file with [Html.ImgAlt] enabled = true and a legacy/ file with [Html] enabled = false therefore leave Html/ImgAlt off under legacy/. A rule that is off (its own or its department’s enabled = false, or left out by include/exclude) does nothing, which for the two rules that are on by default means the old behaviour: Layout/IndentationStyle off writes spaces whatever the editor or .editorconfig say, and Layout/TrailingWhitespace off keeps the whitespace at the line ends, so [Layout] enabled = false stops that trimming too. Then the rule’s include and exclude apply, and the rule must exist for the file’s language. A file in the global exclude gets no diagnostics and is not formatted.

A key or a value the server can’t read is dropped, and the rest of the file is used. A file that exists but can’t be read (not UTF-8, no permission, a folder with that name) is one problem at its first line. Problems show on the .ruby-templates.toml itself in the Problems panel, and in the Ruby Templates output, once a template it applies to is open or formatted; they refresh when the file is saved. A .ruby-templates.toml above the workspace folders is watched too, if the editor allows it. ruby-templates check prints a problem as file:line:column: warning: message and exits with 1 (the legacy ruby-templates-ls --check prints it but doesn’t fail on it), and format --verbose prints it too. A file that isn’t valid TOML is ignored whole.

50 rules: 50 available, 0 partial, 0 planned
RuleLanguagesTypeStatus
Layout/EmptyLinesERB, Haml, and SlimAutofixAvailable
Layout/EmptyLinesAroundBlockBodyERB, Haml, and SlimAutofixAvailable
Layout/EmptyLinesAroundElementBodyERB, Haml, and SlimAutofixAvailable
Layout/EmptyLinesAroundBranchesERB, Haml, and SlimAutofixAvailable
Layout/SpaceInHtmlTagERBAutofixAvailable
Layout/SpaceInsideErbTagERBAutofixAvailable
Layout/SpaceAfterScriptMarkerHaml and SlimAutofixAvailable
Layout/IndentationStyleERBAutofixAvailable
Layout/TrailingWhitespaceERB, Haml, and SlimAutofixAvailable
Layout/TrailingEmptyLinesERB, Haml, and SlimAutofixAvailable
Layout/ElementContentOnOwnLineERB, Haml, and SlimAutofixAvailable
Layout/AttributesPerLineERBAutofixAvailable
Layout/LineLengthERB, Haml, and SlimWarningAvailable
Style/ErbCommentSyntaxERBAutofixAvailable
Style/AttributeQuotesERBAutofixAvailable
Style/BooleanAttributesERBAutofixAvailable
Style/VoidTagClosingERBAutofixAvailable
Style/TagNameCaseERB and SlimAutofixAvailable
Style/ImplicitDivHaml and SlimAutofixAvailable
Style/ClassesBeforeIdsHamlAutofixAvailable
Style/HashAttributesHamlAutofixAvailable
Style/ErbTrimModeERBWarningAvailable
Rails/StrictLocalsERB, Haml, Slim, and JbuilderWarningAvailable
Rails/MissingPartialERB, Haml, Slim, and JbuilderWarningAvailable
Rails/SilentRenderERB, Haml, and SlimWarningAvailable
Rails/SilentHelperERB, Haml, and SlimWarningAvailable
Rails/InstanceVariableInPartialERB, Haml, and SlimWarningAvailable
Rails/UnusedStrictLocalERB, Haml, and SlimWarningAvailable
Rails/RenderInLoopERB, Haml, and SlimWarningAvailable
Rails/DebugOutputERB, Haml, and SlimWarningAvailable
Rails/OutputControlFlowERBWarningAvailable
Rails/UnsafeOutputERB, Haml, and SlimWarningAvailable
Rails/UnusedBlockArgumentERB, Haml, and SlimWarningAvailable
Rails/I18nMissingKeyERB, Haml, and SlimWarningAvailable
Rails/UnknownRouteERB, Haml, and SlimWarningAvailable
Turbo/PreferTurboAttributesERB, Haml, and SlimAutofixAvailable
Turbo/PermanentRequiresIdERB, Haml, and SlimWarningAvailable
Stimulus/UnknownControllerERB, Haml, and SlimWarningAvailable
Stimulus/UnknownTargetOrActionERB, Haml, and SlimWarningAvailable
Html/ImgAltERB, Haml, and SlimWarningAvailable
Html/DuplicateIdERB, Haml, and SlimWarningAvailable
Html/UnexpectedCloseTagERBWarningAvailable
Html/MismatchedCloseTagERBWarningAvailable
Html/UnclosedElementERBWarningAvailable
Html/VoidChildrenHaml and SlimWarningAvailable
Html/DuplicateAttributeERB, Haml, and SlimWarningAvailable
Html/NestedInteractiveERB, Haml, and SlimWarningAvailable
Html/AnchorHrefERB, Haml, and SlimWarningAvailable
A11y/IframeTitleERB, Haml, and SlimWarningAvailable
A11y/GenericLinkTextERB, Haml, and SlimWarningAvailable

Layout

Layout/EmptyLines

Available

At most a set number of blank lines in a row.

Languages ERB, Haml, and Slim

Type Autofix, run by the formatter. Off by default.

Rendered output Safe. Browsers collapse the whitespace between elements, so a shorter run of blank lines renders the same. Content where whitespace counts is left alone: <pre>, <textarea>, <script>, <style>, quoted attribute values, comments and the Ruby in ERB tags in ERB, filters in Haml, text blocks and engine bodies in Slim. Two things can't be detected: elements that CSS styles white-space: pre, and blocks that capture their content and care about its whitespace (such as <%= markdown do %>). With max = 0, paragraphs there can join.

Status Available.

Today Runs of blank lines longer than max (a whole number, 1 by default; 0 removes them all) are cut to max, also at the start and the end of the file. Format selection leaves blank lines as they are. For Haml and Slim, the formatter checks that the compiled template stays the same, and keeps the blank lines of a file when it would not.

Turn it on
[Layout.EmptyLines]
enabled = true
max = 1
Example
Before · app/views/users/index.html.erb
<h1>Users</h1>



<ul>
  <li>Ada</li>
</ul>
After · app/views/users/index.html.erb
<h1>Users</h1>

<ul>
  <li>Ada</li>
</ul>
Before · app/views/users/index.html.haml
%h1 Users



%ul
  %li Ada
After · app/views/users/index.html.haml
%h1 Users

%ul
  %li Ada
Before · app/views/users/index.html.slim
h1 Users



ul
  li Ada
After · app/views/users/index.html.slim
h1 Users

ul
  li Ada

Layout/EmptyLinesAroundBlockBody

Available

Blank lines at the start and end of the body of an ERB or Ruby block.

Languages ERB, Haml, and Slim

Type Autofix, run by the formatter. Off by default.

Rendered output Safe. Only blank lines between tags change, which render as whitespace between them, and browsers collapse that. Content where whitespace counts is left alone: <pre>, <textarea>, <script>, <style>, attribute values, comments and the Ruby in ERB tags in ERB, filters in Haml, text blocks and engine bodies in Slim. Two things can't be detected: elements that CSS styles white-space: pre, and blocks that capture their content and care about its whitespace (such as <%= markdown do %>).

Status Available.

Today style is no_empty_lines (the default) or empty_lines, which keeps exactly one blank line there and adds it where there is none. In ERB the body is what lies between the opening tag and the closing tag or <% end %>. Haml and Slim have no closing line, so only the start of the body is covered: the blank lines after - if or - each do are set, and those before the next line at the same or a lower depth are siblings' separators and stay. A block with nothing in it loses its blank lines under no_empty_lines and is left alone under empty_lines. The lines before and after else and when belong to Layout/EmptyLinesAroundBranches, and Layout/EmptyLines max is applied after this rule, so max = 0 wins over empty_lines. Format selection leaves blank lines as they are. For Haml and Slim, the formatter checks that the compiled template stays the same, and keeps the blank lines of a file when it would not.

Turn it on
[Layout.EmptyLinesAroundBlockBody]
enabled = true
style = "no_empty_lines" # or empty_lines
Example

With no_empty_lines. empty_lines would instead keep exactly one blank line after the opening tag and before the closing one.

Before · app/views/users/index.html.erb
<% if @admin_section %>

  <% @reports.each do |report| %>

    <p><%= report.title %></p>



  <% end %>


<% end %>
After · app/views/users/index.html.erb
<% if @admin_section %>
  <% @reports.each do |report| %>
    <p><%= report.title %></p>
  <% end %>
<% end %>

Haml and Slim have no closing line, so only the blank lines after - if and - each go; the one before %footer separates siblings.

Before · app/views/users/index.html.haml
- if @admin_section

  - @reports.each do |report|

    %p= report.title

%footer Done
After · app/views/users/index.html.haml
- if @admin_section
  - @reports.each do |report|
    %p= report.title

%footer Done
Before · app/views/users/index.html.slim
- if @admin_section

  - @reports.each do |report|

    p = report.title

footer Done
After · app/views/users/index.html.slim
- if @admin_section
  - @reports.each do |report|
    p = report.title

footer Done

Layout/EmptyLinesAroundElementBody

Available

Blank lines at the start and end of the body of an HTML element.

Languages ERB, Haml, and Slim

Type Autofix, run by the formatter. Off by default.

Rendered output Safe. The blank lines sit between tags, where they render as whitespace that browsers collapse. Content where whitespace counts is left alone: <pre>, <textarea>, <script>, <style>, attribute values, comments and the Ruby in ERB tags in ERB, filters in Haml, text blocks and engine bodies in Slim. Elements that CSS styles white-space: pre can't be detected.

Status Available.

Today style is no_empty_lines (the default) or empty_lines, which keeps exactly one blank line there and adds it where there is none. In ERB the body is what lies between the start tag and the end tag. Haml and Slim have no closing line, so only the start of the body is covered: the blank lines after %section or section are set, and those before the next line at the same or a lower depth are siblings' separators and stay. An element with nothing in it (<div> and </div> on two lines) loses its blank lines under no_empty_lines and is left alone under empty_lines. Layout/EmptyLines max is applied after this rule, so max = 0 wins over empty_lines. Format selection leaves blank lines as they are. For Haml and Slim, the formatter checks that the compiled template stays the same, and keeps the blank lines of a file when it would not.

Turn it on
[Layout.EmptyLinesAroundElementBody]
enabled = true
style = "no_empty_lines" # or empty_lines
Example
Before · app/views/users/index.html.erb
<section class="users">

  <h2>Users</h2>
  <p>Active this week</p>


</section>
After · app/views/users/index.html.erb
<section class="users">
  <h2>Users</h2>
  <p>Active this week</p>
</section>

Haml and Slim have no closing line, so only the blank line after %section.users goes; the one before %footer separates siblings.

Before · app/views/users/index.html.haml
%section.users

  %h2 Users
  %p Active this week

%footer Done
After · app/views/users/index.html.haml
%section.users
  %h2 Users
  %p Active this week

%footer Done
Before · app/views/users/index.html.slim
section.users

  h2 Users
  p Active this week

footer Done
After · app/views/users/index.html.slim
section.users
  h2 Users
  p Active this week

footer Done

Layout/EmptyLinesAroundBranches

Available

Blank lines before and after an else, elsif or when.

Languages ERB, Haml, and Slim

Type Autofix, run by the formatter. Off by default.

Rendered output Safe. The blank lines sit between tags, where they render as whitespace that browsers collapse. Content where whitespace counts is left alone, as for Layout/EmptyLines. Two things can't be detected: elements that CSS styles white-space: pre, and blocks that capture their content and care about its whitespace (such as <%= markdown do %>).

Status Available.

Today style is no_empty_lines (the default) or empty_lines, which keeps exactly one blank line before and after the line and adds it where there is none. It covers else, elsif, when, in, rescue and ensure lines in ERB, Haml and Slim. A branch with nothing in it (<% if a %> directly above <% else %>, or - case a above - when 1) loses its blank lines under no_empty_lines and is left alone under empty_lines. Layout/EmptyLines max is applied after this rule, so max = 0 wins over empty_lines. Format selection leaves blank lines as they are. For Haml and Slim, the formatter checks that the compiled template stays the same, and keeps the blank lines of a file when it would not.

Turn it on
[Layout.EmptyLinesAroundBranches]
enabled = true
style = "no_empty_lines" # or empty_lines
Example
Before · app/views/users/index.html.erb
<% if @user.admin? %>
  <p>Admin</p>

<% else %>

  <p>Member</p>
<% end %>
After · app/views/users/index.html.erb
<% if @user.admin? %>
  <p>Admin</p>
<% else %>
  <p>Member</p>
<% end %>
Before · app/views/users/index.html.haml
- if @user.admin?
  %p Admin

- else

  %p Member
After · app/views/users/index.html.haml
- if @user.admin?
  %p Admin
- else
  %p Member
Before · app/views/users/index.html.slim
- if @user.admin?
  p Admin

- else

  p Member
After · app/views/users/index.html.slim
- if @user.admin?
  p Admin
- else
  p Member

Layout/SpaceInHtmlTag

Available

One space between attributes, none before > or around =.

Languages ERB

Type Autofix, run by the formatter. Off by default.

Rendered output Safe. Whitespace inside a start tag doesn't change the element. The formatter checks that every tag has the same name, attributes and values, that a blank stays where an ERB tag in a tag had one, and that the text between tags is byte for byte the same (the blanks inside ERB tags aside), and leaves the tags alone when it does not.

Status Available.

Today In the start and end tags of ERB templates, a run of blanks between the name and the attributes or between attributes becomes one space, blanks around the = of an attribute and before > go, and <br /> keeps one space before />. A run with a newline, a quoted value, an unquoted value, an ERB tag (<div <%= attrs %> class="a"> loses only the extra blank; the last space next to an ERB tag is never added or removed, and blanks beside an ERB tag in a name or value stay), a tag whose attributes are not plain enough to read (a quote after an ERB tag, say), and everything that is not markup (the bodies of <script>, <style>, <textarea>, <title>, <xmp>, <iframe>, <noscript>, <noembed>, <noframes> and <plaintext>, comments, CDATA, a < b text, <%% literals) stay. A run with a line break, a bare \r included, is never touched. Format selection applies it to the lines it covers.

Turn it on
[Layout.SpaceInHtmlTag]
enabled = true
Example
Before · app/views/users/index.html.erb
<div  class="card"   id="user_<%= user.id %>" >
  <img src = "<%= user.avatar_url %>"  alt="">
</div>
After · app/views/users/index.html.erb
<div class="card" id="user_<%= user.id %>">
  <img src="<%= user.avatar_url %>" alt="">
</div>
Before · app/views/users/index.html.erb
<div  class="card"   id="u"><%= u.name %></div  >
After · app/views/users/index.html.erb
<div class="card" id="u"><%= u.name %></div>

Layout/SpaceInsideErbTag

Available

One space inside the delimiters of an ERB tag: <%= x %>, not <%=x%> or <%= x %>.

Languages ERB

Type Autofix, run by the formatter. Off by default.

Rendered output Safe. The spaces are outside the Ruby code, so the compiled template is the same. The formatter checks that the trimmed Ruby of every tag is the same before and after, and keeps the spacing of a file when it is not.

Status Available.

Today Each code and output tag that sits on one line gets one space after its opening and one before its closing delimiter, the - of <%- and -%> and the = of <%== included. Tags that span lines, comments (<%#) and literal <%% tags stay. Format selection applies it to the lines it covers. The code action does the same without the rule: "Add spaces inside this ERB tag" is offered on the tags under the cursor, and "Add spaces inside every ERB tag" is the source action source.erbSpacing.

Run the code action on save, without the rule
"[erb]": {
  "editor.codeActionsOnSave": { "source.erbSpacing": "explicit" }
}
Turn it on
[Layout.SpaceInsideErbTag]
enabled = true
Example
Before · app/views/users/index.html.erb
<p><%=user.name%></p>
<p><%=  user.email  %></p>
<%if user.admin?%>
  <span>Admin</span>
<%end%>
After · app/views/users/index.html.erb
<p><%= user.name %></p>
<p><%= user.email %></p>
<% if user.admin? %>
  <span>Admin</span>
<% end %>

Layout/SpaceAfterScriptMarker

Available

A space after the = or - that starts Ruby code.

Languages Haml and Slim

Type Autofix, run by the formatter. Off by default.

Rendered output Safe. The marker and the code are the same tokens with a space between. The formatter checks that the template compiles to the same Ruby before and after, and keeps the markers of a file when it does not.

Status Available.

Today Where the parser reads Ruby code, the marker gets a space when the code follows it directly: -, =, ~, !=, &=, !~ and &~ in Haml, -, = and == (with < and >) in Slim, both at the start of a line and after a tag (%p=x, p=x). Text, comments (-# in Haml and Slim, / in Slim), filters and engine bodies, | text blocks, attribute values, == and \= lines in Haml (text, not code) and the continuation lines of multi-line Ruby are not touched. Also skipped: the first line of a multi-line Haml script written with |, the code after an attribute hash that spans lines, and Slim's p: =x. Format selection applies it to the lines it covers.

Turn it on
[Layout.SpaceAfterScriptMarker]
enabled = true
Example
Before · app/views/users/index.html.haml
-if @user
  %h1=@user.name
  =render "users/card", user: @user
After · app/views/users/index.html.haml
- if @user
  %h1= @user.name
  = render "users/card", user: @user
Before · app/views/users/index.html.slim
-if @user
  h1=@user.name
  =render "users/card", user: @user
After · app/views/users/index.html.slim
- if @user
  h1= @user.name
  = render "users/card", user: @user

Layout/IndentationStyle

Available

Indent with tabs or with spaces.

Languages ERB

Type Autofix, run by the formatter. On by default.

Rendered output Safe. Leading whitespace between tags renders as nothing. Content where whitespace counts is left alone.

Status Available.

Today ERB only; Haml and Slim always use spaces, since Slim rejects mixed indentation and Haml wants it consistent. Each indent level the formatter writes is one tab with style = "tabs", or the width in spaces (the default of 2) with "spaces". Only the leading whitespace of the lines the formatter indents changes: <pre>, <textarea>, <script>, <style>, <xmp> and <listing> bodies, Ruby inside tags, quoted attribute values and comments keep every byte. The lines of a tag that spans several lines move with its first line and keep their indentation relative to it, as written, so a tag attribute lined up with spaces stays lined up. Without style, the style comes from the first of these that has one: the editor.insertSpaces setting, then .editorconfig (indent_style = tab or space), then spaces. In an editor insertSpaces always decides, because the language server protocol always sends it, as it does the tab size; .editorconfig counts only on the command line, or through an EditorConfig extension that sets insertSpaces. style in .ruby-templates.toml beats all of them, and enabled = false always writes spaces. The width is the editor's tab size, then .editorconfig (indent_size, or tab_width for indent_size = tab), then 2; it only matters for spaces. Format selection applies the style to the lines it covers.

Always indent with spaces
[Layout.IndentationStyle]
enabled = false
Turn it on
[Layout.IndentationStyle]
enabled = true
style = "tabs" # or "spaces"
Example

With style = "tabs".

Before · app/views/users/index.html.erb
<ul>
  <% @users.each do |user| %>
    <li><%= user.name %></li>
  <% end %>
</ul>
After · app/views/users/index.html.erb
<ul>
	<% @users.each do |user| %>
		<li><%= user.name %></li>
	<% end %>
</ul>

Layout/TrailingWhitespace

Available

No spaces or tabs at the end of a line.

Languages ERB, Haml, and Slim

Type Autofix, run by the formatter. On by default.

Rendered output Safe. Whitespace that is content stays: <pre> and <textarea> bodies, ERB comments, Haml filters, and Slim text lines (| text, text after a tag).

Status Available.

Today The formatter does it in ERB, Haml and Slim, on Format Document, on save and in ruby-templates format. It is on for all three; with enabled = false the spaces and tabs at the line ends stay as written, whitespace-only lines included.

Turn it off
[Layout.TrailingWhitespace]
enabled = false
Turn it on
[Layout.TrailingWhitespace]
enabled = true
Example

The spaces and tabs at the line ends are invisible here.

Before · app/views/users/index.html.erb
<ul>  
  <li class="user">  
    <%= user.name %>	
  </li>
</ul> 
After · app/views/users/index.html.erb
<ul>
  <li class="user">
    <%= user.name %>
  </li>
</ul>
Before · app/views/users/index.html.haml
%ul  
  %li.user  
    = user.name 	
After · app/views/users/index.html.haml
%ul
  %li.user
    = user.name
Before · app/views/users/index.html.slim
ul  
  li.user  
    a href="/users"   
      = user.name
After · app/views/users/index.html.slim
ul
  li.user
    a href="/users"
      = user.name

Layout/TrailingEmptyLines

Available

The file ends with exactly one line break.

Languages ERB, Haml, and Slim

Type Autofix, run by the formatter. On by default in Haml and Slim, off in ERB.

Rendered output Not safe. Safe in Haml and Slim, which ignore the blank lines at the end. Not safe in ERB: Rails renders a template or partial byte for byte, so a final line break or the blank lines at the end become output. A partial _icon.html.erb that is <svg></svg> renders with a line break after it once the rule adds one, and that shows as a space in <button><%= render "icon" %>Save</button>.

Status Available.

Today The blank lines at the end of the file go, and a file that doesn't end with a line break gets one, using the line ending the file mostly has. It is on for Haml and Slim and off for ERB, where enabled = true turns it on, and include limits it to some files. Format selection leaves the end of the file as it is.

Turn it off (Haml and Slim)
[Layout.TrailingEmptyLines]
enabled = false
Turn it on
[Layout.TrailingEmptyLines]
enabled = true
Example
Before · app/views/users/index.html.erb
<h1>Users</h1>


After · app/views/users/index.html.erb
<h1>Users</h1>
Before · app/views/users/index.html.haml
%h1 Users


After · app/views/users/index.html.haml
%h1 Users
Before · app/views/users/index.html.slim
h1 Users


After · app/views/users/index.html.slim
h1 Users

Layout/ElementContentOnOwnLine

Available

The content of a block-level element on its own indented line between the open and close tags.

Languages ERB, Haml, and Slim

Type Autofix, run by the formatter. Off by default.

Rendered output Not safe. It adds whitespace. Around block-level content that is invisible, but inside an inline element such as <a> or <span> the new line break shows as a space, so only the tags in the list are touched, and <a class="btn"><%= name %></a> stays as it is. Haml writes the nested content on a line of its own in the output; Slim output is the same. The formatter reads the result again and keeps it only when nothing but that whitespace is new: ERB keeps every character and element, Haml generates the same Ruby and reads the moved content one level below its tag, Slim compiles to the same template.

Status Available.

Today The content of an element in tags that is written next to its tags moves to its own line, one level in: ERB <div>…</div> on one line, Haml %div text and %div= x, Slim div text (which becomes | text) and div = x. The element names are not case-sensitive. It stays where the element or its content is not plain: ERB with a <% %> tag in the start tag or the content (output tags <%= %> and comments are fine), a tag or content over several lines, an empty element; Haml with <, > or / on the tag, a | line, content that starts with a character that makes a line something else (-, %, ., =), and a tag with lines nested under it; Slim with div: span x, =' and a tag with lines nested under it. pre, textarea, script and style are never touched, nor is anything inside them or inside an inline element such as <span> or <a>. Elements inside each other all move. Format selection leaves the rule out, since it adds lines.

Turn it on
[Layout.ElementContentOnOwnLine]
enabled = true
tags = ["div", "section", "article", "header", "footer", "ul", "ol"]
Example
Before · app/views/users/index.html.erb
<div class="card" id="<%= dom_id(user) %>"><%= user.name %></div>
After · app/views/users/index.html.erb
<div class="card" id="<%= dom_id(user) %>">
  <%= user.name %>
</div>
Before · app/views/users/index.html.haml
%section.card#user= user.name
After · app/views/users/index.html.haml
%section.card#user
  = user.name
Before · app/views/users/index.html.slim
section.card#user = user.name
After · app/views/users/index.html.slim
section.card#user
  = user.name

Layout/AttributesPerLine

Available

A start tag longer than the limit gets one attribute per line.

Languages ERB

Type Autofix, run by the formatter. Off by default.

Rendered output Safe. Whitespace between attributes doesn't change the element.

Status Available.

Today A start tag that stands first on its line, fits on it and has two or more attributes is split when its line is longer than max_line_length characters (default 100, counted like Layout/LineLength). Each attribute goes on its own line one indent level in, and the > (or />) stays after the last one. Tags already over several lines, tags after other markup on their line, tags with an ERB tag between attributes or in a name, <% control flow in the tag, values over several lines and tags inside <pre> or <textarea> are left alone. Off by default; Format Selection leaves it out because it adds lines.

Turn it on
[Layout.AttributesPerLine]
enabled = true
max_line_length = 100
Example
Before · app/views/users/index.html.erb
<input type="email" name="user[email]" id="user_email" class="form-control form-control-lg" placeholder="Email" required>
After · app/views/users/index.html.erb
<input
  type="email"
  name="user[email]"
  id="user_email"
  class="form-control form-control-lg"
  placeholder="Email"
  required>

Layout/LineLength

Available

A line longer than the limit.

Languages ERB, Haml, and Slim

Type Warning, shown as a diagnostic. Off by default.

Rendered output Safe. It only reports; nothing is rewritten.

Status Available.

Today Lines longer than max characters (default 100) are reported from the first character past the limit. Characters are counted, not display columns, a tab is one, and trailing whitespace is not counted. A line that cannot be made shorter is left alone: one without a space after its indentation (a long URL) and the # locals: comment of strict locals. Off by default. See Layout/LineLength in the diagnostics reference.

Turn it on
[Layout.LineLength]
enabled = true
max = 100
Examples
Flagged · app/views/users/index.html.erb
<p class="lead text-muted"><%= link_to "Read the full announcement", announcement_path(@announcement), class: "link" %></p>
  1. line is 123 characters long; the limit is 1001:101Layout/LineLength
Fine · app/views/users/index.html.erb
<p class="lead text-muted">
  <%= link_to "Read the full announcement", announcement_path(@announcement), class: "link" %>
</p>
Flagged · app/views/users/index.html.haml
%p.lead.text-muted= link_to "Read the full announcement", announcement_path(@announcement), class: "link"
  1. line is 105 characters long; the limit is 1001:101Layout/LineLength
Fine · app/views/users/index.html.haml
%p.lead.text-muted
  = link_to "Read the full announcement", announcement_path(@announcement), class: "link"
Flagged · app/views/users/index.html.slim
p.lead.text-muted = link_to "Read the full announcement", announcement_path(@announcement), class: "link"
  1. line is 105 characters long; the limit is 1001:101Layout/LineLength
Fine · app/views/users/index.html.slim
p.lead.text-muted
  = link_to "Read the full announcement", announcement_path(@announcement), class: "link"

Style

Style/ErbCommentSyntax

Available

An ERB comment written <%#, not as Ruby code that starts with #.

Languages ERB

Type Autofix, run by the formatter. Off by default.

Rendered output Safe. Both forms output nothing. Only a tag that is nothing but comment and stands alone on its line is rewritten: beside other content, Rails comments out the output that follows a <% # tag on the same line, and <%# would bring it back.

Status Available.

Today <% # note %> becomes <%# note %>, keeping a -%> as it is. A tag over several lines is rewritten when every line is a comment or blank, since <%# comments out everything up to %>. <%= # x %> is not a comment and stays, <%- # x -%> is skipped to be conservative, and so is a tag with content before or after it on its line (it usually breaks the template: Rails comments out what follows it), and the encoding comment. The ERB formatter checks that the trimmed Ruby of every tag is the same afterwards.

Turn it on
[Style.ErbCommentSyntax]
enabled = true
Example
Before · app/views/users/index.html.erb
<% # TODO: show the avatar %>
<p><%= user.name %></p>
After · app/views/users/index.html.erb
<%# TODO: show the avatar %>
<p><%= user.name %></p>

The second tag stays: <%- is not a comment tag.

Before · app/views/users/index.html.erb
<% # a comment over
   # two lines %>
<%- # a trimming tag %>
After · app/views/users/index.html.erb
<%# a comment over
   # two lines %>
<%- # a trimming tag %>

Style/AttributeQuotes

Available

Attribute values in double quotes (or single, by choice), with none left unquoted.

Languages ERB

Type Autofix, run by the formatter. Off by default.

Rendered output Safe. The attribute values are the same, in quotes or not. A value that contains the quote character, spans lines, or holds ERB output (<%=, <%==: helpers return text with quotes of their own) keeps its quotes, and a bare value with any ERB tag is not quoted, since its output may be empty or hold blanks. The formatter reads every tag again and keeps the rewrite only when each has the same name, attributes and values.

Status Available.

Today In the start tags of ERB templates, class='card' and data-controller=toggle become class="card" and data-controller="toggle" (or the other way with single_quotes). A value with the target quote in it (title='say "hi"'), a value over several lines, a quoted value with an output tag (title='<%= link_to … %>'), a bare value with any ERB tag (value=<%= q %>, href=<% if a %>…), a tag with a quote in a bare value (title=it's) or one the reader gives up on, an attribute name with ERB, and everything that is not markup (the bodies of <script>, <style>, <textarea>, <title> and the like, comments, <%= %> code) stay. A quoted value with only control tags (class='<% if a %>b<% end %>') is converted. Format selection applies it to the lines it covers.

Turn it on
[Style.AttributeQuotes]
enabled = true
style = "double_quotes" # or single_quotes
Example
Before · app/views/users/index.html.erb
<div class='card' data-controller=toggle>
  <%= user.name %>
</div>
After · app/views/users/index.html.erb
<div class="card" data-controller="toggle">
  <%= user.name %>
</div>

Style/BooleanAttributes

Available

A boolean attribute written without a value.

Languages ERB

Type Autofix, run by the formatter. Off by default.

Rendered output Safe. Only the presence of a boolean attribute counts, so disabled="disabled" and disabled are the same. checked="false" is not touched, since it means checked. The formatter reads every tag again and keeps the rewrite only when each has the same name and attributes. A CSS selector like [disabled=disabled] would no longer match.

Status Available.

Today disabled="disabled", disabled="" and disabled=disabled become disabled when the attribute is a boolean attribute of that element (checked and readonly on input, selected on option, open on details, autofocus and hidden on any, and so on: the HTML standard's list) and the value is empty or the name, in any case. disabled="false", hidden="until-found", a value with ERB, a boolean attribute on an element it does not belong to, custom elements, anything inside <svg> or <math>, and attributes over a line break (disabled = and the value on the next line) stay. Format selection applies it to the lines it covers.

Turn it on
[Style.BooleanAttributes]
enabled = true
Example
Before · app/views/users/index.html.erb
<input type="checkbox" checked="checked" disabled="disabled">
After · app/views/users/index.html.erb
<input type="checkbox" checked disabled>

Style/VoidTagClosing

Available

Void elements written <br>, or <br />, throughout.

Languages ERB

Type Autofix, run by the formatter. Off by default.

Rendered output Safe. HTML ignores the slash on a void element. The formatter reads every tag again and keeps the rewrite only when each has the same name and attributes.

Status Available.

Today Void elements (area, base, br, col, embed, hr, img, input, link, meta, source, track, wbr) lose the / (and the blanks before it) with no_slash, or end in /> with slash. A slash that is part of an unquoted value (<img src=a.png/>), one right after an ERB tag, a slash ending after a bare value that ends in an ERB tag (<input value=<%= x %>>: empty output would make / the value), a tag with a line break before the >, anything inside <svg> or <math>, end tags and non-void elements (<div/> is not self-closing in HTML) stay. Format selection applies it to the lines it covers.

Turn it on
[Style.VoidTagClosing]
enabled = true
style = "no_slash" # or slash
Example
Before · app/views/users/index.html.erb
<img src="<%= user.avatar_url %>" alt=""/>
<br/>
After · app/views/users/index.html.erb
<img src="<%= user.avatar_url %>" alt="">
<br>

Style/TagNameCase

Available

Lowercase tag names, with the camelCase SVG elements as the SVG standard spells them.

Languages ERB and Slim

Type Autofix, run by the formatter. Off by default.

Rendered output Safe. HTML tag names are case-insensitive, and the HTML parser maps SVG names to their camelCase form. The formatter reads every tag again, and Slim's output, and keeps the rewrite only when it is the same but for the case of names.

Status Available.

Today Tag names of start and end tags in ERB, and of tags in Slim, become lower case; foreignObject, linearGradient, clipPath and the other SVG elements the HTML parser spells in camel case get that spelling wherever they are written, since a partial may hold only the inside of an <svg>. Custom elements are lower-cased too. In Slim, a name that lower case would turn into doctype or an embedded engine (Javascript:), and a template that does not output HTML (.xml.slim), stay. Format selection applies it to the lines it covers.

Turn it on
[Style.TagNameCase]
enabled = true
Example
Before · app/views/users/index.html.erb
<DIV class="icon">
  <svg viewBox="0 0 8 8">
    <lineargradient id="fade"></lineargradient>
  </svg>
</DIV>
After · app/views/users/index.html.erb
<div class="icon">
  <svg viewBox="0 0 8 8">
    <linearGradient id="fade"></linearGradient>
  </svg>
</div>
Before · app/views/users/index.html.slim
DIV.icon
  SPAN Hi
After · app/views/users/index.html.slim
div.icon
  span Hi

Style/ImplicitDiv

Available

No div where a class or id already implies it.

Languages Haml and Slim

Type Autofix, run by the formatter. Off by default.

Rendered output Safe. Haml and Slim render a bare .card or #main as a div, unless a Slim app changes the tag a bare shortcut makes (Slim::Engine.set_options default_tag: "span"): the rule assumes div, so leave it off there. The formatter reads the file again and keeps the rewrite only when Haml or Slim compile it to the same template.

Status Available.

Today %div.card and %div#main in Haml, div.card and div#main in Slim lose the div. A div with no class or id shorthand right after it (%div{class: "a"}, %div(class="a"), div class="a", a bare div), a name in other case (%DIV.card: it would be output as <div>), %%div, and a line over several lines with | stay. Format selection applies it to the lines it covers.

Turn it on
[Style.ImplicitDiv]
enabled = true
Example
Before · app/views/users/index.html.haml
%div.card
  %div#details
    %p= user.bio
After · app/views/users/index.html.haml
.card
  #details
    %p= user.bio
Before · app/views/users/index.html.slim
div.card
  div#details
    p = user.bio
After · app/views/users/index.html.slim
.card
  #details
    p = user.bio

Style/ClassesBeforeIds

Available

Classes before the id in a Haml tag.

Languages Haml

Type Autofix, run by the formatter. Off by default.

Rendered output Safe. Haml sorts the attributes it renders, so class comes before id whichever way round they are written. The classes and the ids keep their order among themselves. The formatter reads the file again and keeps the rewrite only when Haml compiles it to the same template.

Status Available.

Today %p#intro.lead becomes %p.lead#intro and #a.b.c becomes .b.c#a, also with attributes after it. A head that is already in order, one with @ in it, and a line over several lines with | stay. Format selection applies it to the lines it covers.

Turn it on
[Style.ClassesBeforeIds]
enabled = true
Example
Before · app/views/users/index.html.haml
%section#profile.card.wide
  %p= user.bio
After · app/views/users/index.html.haml
%section.card.wide#profile
  %p= user.bio

Style/HashAttributes

Available

Haml attributes written as a Ruby hash, or in HTML style, throughout.

Languages Haml

Type Autofix, run by the formatter. Off by default.

Rendered output Safe. Only attributes both syntaxes can say the same way are converted: strings without escapes or interpolation, numbers, true, false, nil, and variables. Anything else leaves the tag as it is, and so does a data or aria with a value that is not a literal hash, which may become data-* attributes at run time. The formatter reads the file again and keeps the rewrite only when Haml compiles it to the same template and each tag sets the same attributes.

Status Available.

Today style = "ruby_hash" (the default) writes %a(href="/" title=@t checked) as %a{href: "/", title: @t, checked: true}, with "data-n": … for names that need quotes; style = "html" writes the hash as %a(href="/" title=@t checked). A tag with both a hash and a list, a nested hash (data: {…}), data or aria with a variable or other value that is not a hash, a splat, a key that is not a literal name, a repeated name, a value that is a method call with a receiver or arguments, an expression, a string with \ or #{}, :symbol or a list, an attribute list or hash over several lines, and a tag in a line joined with | stay. Format selection applies it to the lines it covers.

Turn it on
[Style.HashAttributes]
enabled = true
style = "html" # or ruby_hash
Example

With style = "html"; "ruby_hash" converts the other way.

Before · app/views/users/index.html.haml
%a.btn{href: "/users", target: "_blank"} Users
After · app/views/users/index.html.haml
%a.btn(href="/users" target="_blank") Users

Style/ErbTrimMode

Available

Both %> and -%> in one template.

Languages ERB

Type Warning, shown as a diagnostic. Off by default.

Rendered output Not safe. There is no autofix. In Rails, -%> also removes the line break after an output tag, so changing one to the other can change the page.

Status Available.

Today Every closer of the kind the template uses less is reported, -%> when there are as many of each. Comments (<%#) and literal <%% tags are not counted, nor are closers with more on their line (-%> only trims a line break). Off by default, as a style preference. See Style/ErbTrimMode in the diagnostics reference.

Turn it on
[Style.ErbTrimMode]
enabled = true
Examples
Flagged · app/views/users/index.html.erb
<% @users.each do |user| -%>
  <li><%= user.name %></li>
<% end %>
  1. -%> here, but most closers in this template are %>1:26Style/ErbTrimMode
Fine · app/views/users/index.html.erb
<% @users.each do |user| %>
  <li><%= user.name %></li>
<% end %>

Rails

Rails/StrictLocals

Available

A render whose locals don't match the partial's locals: comment.

Languages ERB, Haml, Slim, and Jbuilder

Type Warning, shown as a diagnostic.

Rendered output Safe. It only reports; nothing is rewritten.

Status Available.

Today With Rails 7.1 or later in Gemfile.lock, a literal render that resolves to a partial with a locals: comment is compared with it, using Rails' messages (missing local, unknown local, no locals accepted). When a render matches several files (a variant next to the base file, or two handlers), a local is missing when every file requires it and unknown when none takes it. Quick fixes "Remove x from the render" and "Declare x in the locals of …" (it edits all the files). See Rails/StrictLocals in the diagnostics reference.

Turn it off
[Rails.StrictLocals]
enabled = false
Examples

_card.html.erb starts with <%# locals: (user:) -%>.

Flagged · app/views/users/show.html.erb
<%= render "users/card", person: @user %>
  1. missing local: :user for app/views/users/_card.html.erb1:13Rails/StrictLocals
Fine · app/views/users/show.html.erb
<%= render "users/card", user: @user %>

Rails/MissingPartial

Available

A render of a partial or template that isn't in app/views.

Languages ERB, Haml, Slim, and Jbuilder

Type Warning, shown as a diagnostic.

Rendered output Safe. It only reports; nothing is rewritten.

Status Available.

Today Checks literal names in render "users/card", render partial:, render template: and Jbuilder's json.partial!, with Rails' "Missing partial" or "Missing template" message. The quick fix "Create partial users/_profile.html.erb" creates the file next to the others, in the format and template language of the view. See Rails/MissingPartial in the diagnostics reference.

Turn it off
[Rails.MissingPartial]
enabled = false
Examples
Flagged · app/views/users/show.html.erb
<%= render "users/profile", user: @user %>
  1. Missing partial users/_profile1:13Rails/MissingPartial
Fine · app/views/users/show.html.erb, with app/views/users/_profile.html.erb in place
<%= render "users/profile", user: @user %>

Rails/SilentRender

Available

A render in a tag that outputs nothing, so the partial never reaches the page.

Languages ERB, Haml, and Slim

Type Warning, shown as a diagnostic.

Rendered output Safe. It only reports; nothing is rewritten.

Status Available.

Today In a tag that outputs nothing (<% %> in ERB, - in Haml and Slim), a render with arguments, also the block form (render layout: "x" do). Not reported when the result is assigned or passed on, or when the call is the last statement of a block, since the helper that takes the block may use its value. Only in Rails projects, in templates under app/views, since Padrino and Middleman write helper output to the page from <% %>. The quick fix turns the tag into an output tag when the call is alone in its tag or line. See Rails/SilentRender in the diagnostics reference.

Turn it off
[Rails.SilentRender]
enabled = false
Examples
Flagged · app/views/users/index.html.erb
<% render "users/card", user: @user %>
  1. render in <% %> outputs nothing; use <%=1:4Rails/SilentRender
Fine · app/views/users/index.html.erb
<%= render "users/card", user: @user %>
Flagged · app/views/users/index.html.haml
- render "users/card", user: @user
  1. render in - outputs nothing; use =1:3Rails/SilentRender
Fine · app/views/users/index.html.haml
= render "users/card", user: @user
Flagged · app/views/users/index.html.slim
- render "users/card", user: @user
  1. render in - outputs nothing; use =1:3Rails/SilentRender
Fine · app/views/users/index.html.slim
= render "users/card", user: @user

Rails/SilentHelper

Available

A helper that returns markup, such as link_to or image_tag, called in a tag that outputs nothing.

Languages ERB, Haml, and Slim

Type Warning, shown as a diagnostic.

Rendered output Safe. It only reports; nothing is rewritten.

Status Available.

Today In a tag that outputs nothing, a call to one of these helpers, without a receiver: link_to, link_to_if, link_to_unless, link_to_unless_current, button_to, mail_to, image_tag, content_tag, tag and tag.div-style calls for HTML elements, form_with, form_for, form_tag, fields_for, label_tag, text_field_tag, hidden_field_tag, submit_tag, simple_format, the number_to_* formatters, time_ago_in_words and distance_of_time_in_words. Helpers with side effects, such as content_for and provide, helpers of your own and calls on a receiver (form.fields_for) are left alone. Only in Rails projects, in templates under app/views, since Padrino and Middleman write helper output to the page from <% %>. The quick fix turns the tag into an output tag when the call is alone in its tag or line. See Rails/SilentHelper in the diagnostics reference.

Turn it off
[Rails.SilentHelper]
enabled = false
Examples
Flagged · app/views/users/index.html.erb
<% link_to "Home", root_path %>
  1. link_to returns markup that <% %> throws away; use <%=1:4Rails/SilentHelper
Fine · app/views/users/index.html.erb
<%= link_to "Home", root_path %>
Flagged · app/views/users/index.html.haml
- link_to "Home", root_path
  1. link_to returns markup that - throws away; use =1:3Rails/SilentHelper
Fine · app/views/users/index.html.haml
= link_to "Home", root_path
Flagged · app/views/users/index.html.slim
- link_to "Home", root_path
  1. link_to returns markup that - throws away; use =1:3Rails/SilentHelper
Fine · app/views/users/index.html.slim
= link_to "Home", root_path

Rails/InstanceVariableInPartial

Available

An instance variable read inside a partial, which only works for the controllers that set it.

Languages ERB, Haml, and Slim

Type Warning, shown as a diagnostic.

Rendered output Safe. It only reports; nothing is rewritten.

Status Available.

Today The first read of each @name in a partial (a file starting with _), also inside #{}. Not reported: a variable the partial assigns itself (@count = 0, @x ||= …), defined?(@name), ActionView's own (@output_buffer, @virtual_path, @view_flow, anything starting @_) and partials under app/views/layouts. Only in Rails projects, in templates under app/views. See Rails/InstanceVariableInPartial in the diagnostics reference.

Turn it off
[Rails.InstanceVariableInPartial]
enabled = false
Examples
Flagged · app/views/users/_card.html.erb
<p><%= @user.name %></p>
  1. @user is read in a partial, so it works only where the controller sets it; pass it as a local1:8Rails/InstanceVariableInPartial
Fine · app/views/users/_card.html.erb
<%# locals: (user:) %>
<p><%= user.name %></p>
Flagged · app/views/users/_card.html.haml
%p= @user.name
  1. @user is read in a partial, so it works only where the controller sets it; pass it as a local1:5Rails/InstanceVariableInPartial
Fine · app/views/users/_card.html.haml
-# locals: (user:)
%p= user.name
Flagged · app/views/users/_card.html.slim
p = @user.name
  1. @user is read in a partial, so it works only where the controller sets it; pass it as a local1:5Rails/InstanceVariableInPartial
Fine · app/views/users/_card.html.slim
/# locals: (user:)
p = user.name

Rails/UnusedStrictLocal

Available

A local in the locals: comment that the partial never uses.

Languages ERB, Haml, and Slim

Type Warning, shown as a diagnostic.

Rendered output Safe. It only reports; nothing is rewritten.

Status Available.

Today A keyword in the locals: comment (Rails 7.1+) that the template never reads, assigns or passes on: not even as local_assigns[:name], local_assigns.fetch(:name), binding.local_variable_get(:name), defined?(name) or in the default of another local. Nothing is reported when the template uses local_assigns as a whole (render "x", local_assigns, **local_assigns), binding, local_variables or eval, and **rest and &block are never reported. The quick fix removes the local and its comma from the comment. See Rails/UnusedStrictLocal in the diagnostics reference.

Turn it off
[Rails.UnusedStrictLocal]
enabled = false
Examples
Flagged · app/views/users/_card.html.erb
<%# locals: (user:, compact: false) %>
<p><%= user.name %></p>
  1. compact is declared in locals: but the partial never uses it1:21Rails/UnusedStrictLocal
Fine · app/views/users/_card.html.erb
<%# locals: (user:) %>
<p><%= user.name %></p>
Flagged · app/views/users/_card.html.haml
-# locals: (user:, compact: false)
%p= user.name
  1. compact is declared in locals: but the partial never uses it1:20Rails/UnusedStrictLocal
Fine · app/views/users/_card.html.haml
-# locals: (user:)
%p= user.name
Flagged · app/views/users/_card.html.slim
/# locals: (user:, compact: false)
p = user.name
  1. compact is declared in locals: but the partial never uses it1:20Rails/UnusedStrictLocal
Fine · app/views/users/_card.html.slim
/# locals: (user:)
p = user.name

Rails/RenderInLoop

Available

A render of the same partial inside a loop; render collection: does it in one call.

Languages ERB, Haml, and Slim

Type Warning, shown as a diagnostic.

Rendered output Safe. It only reports; nothing is rewritten.

Status Available.

Today Inside collection.each do |item| or each_with_index, a render "partial/name", name: item that is all the loop does: a string literal for the partial, the block variable as its only local, and nothing else in the body (no wrapping element, no other output or code). Not reported for map and other iterators, render partial:, a partial name that is not a literal or a render with more locals. In Haml and Slim the render must be the only line nested under the loop. See Rails/RenderInLoop in the diagnostics reference.

Turn it off
[Rails.RenderInLoop]
enabled = false
Examples
Flagged · app/views/users/index.html.erb
<% @users.each do |user| %>
  <%= render "users/card", user: user %>
<% end %>
  1. Partial "users/card" is rendered once per item of a loop; use render partial: "users/card", collection: @users, as: :user2:7Rails/RenderInLoop
Fine · app/views/users/index.html.erb
<%= render partial: "users/card", collection: @users, as: :user %>
Flagged · app/views/users/index.html.haml
- @users.each do |user|
  = render "users/card", user: user
  1. Partial "users/card" is rendered once per item of a loop; use render partial: "users/card", collection: @users, as: :user2:5Rails/RenderInLoop
Fine · app/views/users/index.html.haml
= render partial: "users/card", collection: @users, as: :user
Flagged · app/views/users/index.html.slim
- @users.each do |user|
  = render "users/card", user: user
  1. Partial "users/card" is rendered once per item of a loop; use render partial: "users/card", collection: @users, as: :user2:5Rails/RenderInLoop
Fine · app/views/users/index.html.slim
= render partial: "users/card", collection: @users, as: :user

Rails/DebugOutput

Available

Debugging left in a view: p, pp, puts, debug, byebug, debugger, binding.irb and binding.pry.

Languages ERB, Haml, and Slim

Type Warning, shown as a diagnostic.

Rendered output Safe. It only reports; nothing is rewritten.

Status Available.

Today p, pp, puts and debug with arguments, byebug, debugger, binding.irb and binding.pry, when the call is a whole statement or the whole of an output tag. A local variable named p, a method on a receiver (form.p, obj.debug), a call used as an argument and a call under an if or unless that tests Rails.env are not reported. Only in Rails projects, in templates under app/views. See Rails/DebugOutput in the diagnostics reference.

Turn it off
[Rails.DebugOutput]
enabled = false
Examples
Flagged · app/views/users/index.html.erb
<%= debug @user %>
<% binding.irb %>
<p><%= @user.name %></p>
  1. debug is debugging left in the view1:5Rails/DebugOutput
  2. binding.irb is debugging left in the view2:4Rails/DebugOutput
Fine · app/views/users/index.html.erb
<p><%= @user.name %></p>
Flagged · app/views/users/index.html.haml
= debug @user
- binding.irb
%p= @user.name
  1. debug is debugging left in the view1:3Rails/DebugOutput
  2. binding.irb is debugging left in the view2:3Rails/DebugOutput
Fine · app/views/users/index.html.haml
%p= @user.name
Flagged · app/views/users/index.html.slim
= debug @user
- binding.irb
p = @user.name
  1. debug is debugging left in the view1:3Rails/DebugOutput
  2. binding.irb is debugging left in the view2:3Rails/DebugOutput
Fine · app/views/users/index.html.slim
p = @user.name

Rails/OutputControlFlow

Available

Control flow in an output tag: <%= if … %> where <% if … %> is meant.

Languages ERB

Type Warning, shown as a diagnostic.

Rendered output Safe. It only reports; nothing is rewritten.

Status Available.

Today An ERB output tag (<%=, <%==) that starts with if, unless, while, until, case, for or begin and doesn't close it in the same tag, or with else, elsif, when, end, rescue or ensure. The quick fix removes the =. The Ruby syntax errors the = causes are not shown (for x in y do and while c do compile, but print the loop's value), so the message is the only one; other errors in the template still are. A complete expression (<%= if a then b else c end %>, <%= x if y %>) and form_with … do blocks are fine. Reported in every ERB template, as the syntax errors are. Haml and Slim accept = if with a nested block. See Rails/OutputControlFlow in the diagnostics reference.

Turn it off
[Rails.OutputControlFlow]
enabled = false
Examples
Flagged · app/views/users/show.html.erb
<%= if @user %>
  <p>hi</p>
<% end %>
  1. if in an output tag <%=: use <% if … %>1:3Rails/OutputControlFlow
Fine · app/views/users/show.html.erb
<% if @user %>
  <p>hi</p>
<% end %>

Rails/UnsafeOutput

Available

Output that skips Rails' HTML escaping: raw, .html_safe and <%==.

Languages ERB, Haml, and Slim

Type Warning, shown as a diagnostic.

Rendered output Safe. It only reports; nothing is rewritten.

Status Available.

Today raw x, x.html_safe and safe_concat x are reported on the method name, and the unescaped output markers <%== (ERB), != and !~ (Haml) and == (Slim, also before an attribute value) on the marker. A to_json value gets the hint to use json_escape. Not reported when the value is certainly safe: a literal without interpolation ("<br>".html_safe, raw "&nbsp;"), a t/translate call with a literal key ending in _html or .html, or a call to sanitize, safe_join, strip_tags, json_escape, j, escape_javascript, h, html_escape, escape_once, link_to, button_to, mail_to, content_tag, image_tag, simple_format, render, capture or tag.div and the other tag elements. <%== render … %> is redundant, not unsafe, so it is quiet. Haml's !== and Slim's #{{x}} are not looked at. Only in Rails projects, in templates under app/views. See Rails/UnsafeOutput in the diagnostics reference.

Turn it off
[Rails.UnsafeOutput]
enabled = false
Examples
Flagged · app/views/users/index.html.erb
<%= raw @user.bio %>
<%= @user.bio.html_safe %>
<%== @user.bio %>
<a href="<%= raw @user.website %>">Website</a>
<div data-bio="<%= raw @user.bio.to_json %>"></div>
  1. raw outputs @user.bio without escaping; use sanitize if it holds user HTML1:5Rails/UnsafeOutput
  2. html_safe outputs @user.bio without escaping; use sanitize if it holds user HTML2:15Rails/UnsafeOutput
  3. <%== outputs @user.bio without escaping; use sanitize if it holds user HTML3:1Rails/UnsafeOutput
  4. raw outputs @user.website without escaping; use sanitize if it holds user HTML4:14Rails/UnsafeOutput
  5. raw outputs @user.bio.to_json without escaping; to_json doesn't escape quotes for an attribute; output it without raw5:20Rails/UnsafeOutput
Fine · app/views/users/index.html.erb
<%= sanitize @user.bio %>
<%= sanitize @user.bio %>
<%= sanitize @user.bio %>
<a href="<%= @user.website %>">Website</a>
<div data-bio="<%= @user.bio.to_json %>"></div>
Flagged · app/views/users/index.html.haml
!= @user.bio
= raw @user.bio
  1. != outputs @user.bio without escaping; use sanitize if it holds user HTML1:1Rails/UnsafeOutput
  2. raw outputs @user.bio without escaping; use sanitize if it holds user HTML2:3Rails/UnsafeOutput
Fine · app/views/users/index.html.haml
= sanitize @user.bio
= sanitize @user.bio
Flagged · app/views/users/index.html.slim
== @user.bio
= raw @user.bio
  1. == outputs @user.bio without escaping; use sanitize if it holds user HTML1:1Rails/UnsafeOutput
  2. raw outputs @user.bio without escaping; use sanitize if it holds user HTML2:3Rails/UnsafeOutput
Fine · app/views/users/index.html.slim
= sanitize @user.bio
= sanitize @user.bio

Rails/UnusedBlockArgument

Available

A block argument the block never uses.

Languages ERB, Haml, and Slim

Type Warning, shown as a diagnostic.

Rendered output Safe. It only reports; nothing is rewritten.

Status Available.

Today A plain or optional block parameter that nothing in the block reads, assigns or interpolates, nested blocks included. Not reported: names starting with _, it and numbered parameters, destructured, *rest, keyword and &block parameters, or a block that calls binding, local_variables or eval. The message suggests removing the argument when it is the block's only one, and the quick fix prefixes it with _. See Rails/UnusedBlockArgument in the diagnostics reference.

Turn it off
[Rails.UnusedBlockArgument]
enabled = false
Examples
Flagged · app/views/users/index.html.erb
<% @users.each do |user, index| %>
  <li><%= user.name %></li>
<% end %>
  1. Block argument index is never used; rename it _index1:26Rails/UnusedBlockArgument
Fine · app/views/users/index.html.erb
<% @users.each do |user| %>
  <li><%= user.name %></li>
<% end %>
Flagged · app/views/users/index.html.haml
- @users.each do |user, index|
  %li= user.name
  1. Block argument index is never used; rename it _index1:25Rails/UnusedBlockArgument
Fine · app/views/users/index.html.haml
- @users.each do |user|
  %li= user.name
Flagged · app/views/users/index.html.slim
- @users.each do |user, index|
  li = user.name
  1. Block argument index is never used; rename it _index1:25Rails/UnusedBlockArgument
Fine · app/views/users/index.html.slim
- @users.each do |user|
  li = user.name

Rails/I18nMissingKey

Available

A lazy translation key, such as t(".title"), with no entry in config/locales.

Languages ERB, Haml, and Slim

Type Warning, shown as a diagnostic.

Rendered output Safe. It only reports; nothing is rewritten.

Status Available.

Today A literal lazy key (t(".title"), translate, with or without parentheses) is turned into its full key as Rails does (users/_form gives users.form.title, a layout keeps its folder) and must be in the default locale of the YAML files under config/locales: config.i18n.default_locale, en without one. Absolute keys (t("users.index.title")) are not checked, since gems supply keys such as date.formats and helpers.submit.create; nor is a key with default:, scope: or locale:, a key with interpolation, or one in a view named for a gem in the Gemfile.lock (devise/sessions/new, a copy of the gem's view). The closest key of the view within two edits is offered. Nothing is reported without a config/application.rb or a locale file for the default locale, when the app has Ruby locale files, adds to I18n.load_path, sets I18n.backend (also config.i18n.load_path, railties_load_path) or uses a gem that keeps translations in a store (i18n-active_record, redis-i18n). Rails projects only, in templates under app/views. See Rails/I18nMissingKey in the diagnostics reference.

Turn it off
[Rails.I18nMissingKey]
enabled = false
Examples

config/locales/en.yml has en.users.index.title.

Flagged · app/views/users/index.html.erb
<h1><%= t(".titel") %></h1>
  1. Translation users.index.titel is missing in the en locale; did you mean .title?1:11Rails/I18nMissingKey
Fine · app/views/users/index.html.erb
<h1><%= t(".title") %></h1>
Flagged · app/views/users/index.html.haml
%h1= t(".titel")
  1. Translation users.index.titel is missing in the en locale; did you mean .title?1:8Rails/I18nMissingKey
Fine · app/views/users/index.html.haml
%h1= t(".title")
Flagged · app/views/users/index.html.slim
h1 = t(".titel")
  1. Translation users.index.titel is missing in the en locale; did you mean .title?1:8Rails/I18nMissingKey
Fine · app/views/users/index.html.slim
h1 = t(".title")

Rails/UnknownRoute

Available

A *_path or *_url helper that config/routes.rb doesn't define.

Languages ERB, Haml, and Slim

Type Warning, shown as a diagnostic.

Rendered output Safe. It only reports; nothing is rewritten.

Status Available.

Today A *_path or *_url call without a receiver that no route of the app defines but that is within two edits of one (usr_path for user_path) is reported, with that route offered; a name with no route near it may come from a gem, a concern or lib, which can't be seen, so it is left alone. The routes are read with bin/rails runner once the editor has loaded them (setting rubyTemplates.routes.enabled), so it is reported in the editor and never by ruby-templates check. Not reported: a call on a receiver (main_app.users_path, admin.users_path for an engine), Rails' asset and polymorphic helpers (asset_path, image_url, polymorphic_path, …), rails_*, turbo_*, action_mailbox_*, omniauth_*, vite_* and asset_pack_* helpers, Devise's own helpers (session_path, new_registration_url, edit_password_url, …) when the Gemfile.lock has devise, a view in a folder named for a gem (spree/, rails_admin/), which uses the engine's routes, a helper the app defines in app/helpers or names with a symbol (helper_method, delegate, define_method), a call under defined?, a route declared with direct, and a local variable or block parameter. A bare name (item_path with resources :items) could be a local the caller passes, so it is reported only when called with arguments or when the template's strict locals don't list it. Rails projects only, in templates under app/views. See Rails/UnknownRoute in the diagnostics reference.

Turn it off
[Rails.UnknownRoute]
enabled = false
Examples

config/routes.rb has resources :users. The editor loads the routes, so ruby-templates check doesn't report this.

Flagged · app/views/users/index.html.erb
<%= link_to @user.name, usr_path(@user) %>
  1. usr_path is not a route helper of this app; did you mean user_path?1:25Rails/UnknownRoute
Fine · app/views/users/index.html.erb
<%= link_to @user.name, user_path(@user) %>

The routes are the same.

Flagged · app/views/users/index.html.haml
= link_to @user.name, usr_path(@user)
  1. usr_path is not a route helper of this app; did you mean user_path?1:23Rails/UnknownRoute
Fine · app/views/users/index.html.haml
= link_to @user.name, user_path(@user)

The routes are the same.

Flagged · app/views/users/index.html.slim
= link_to @user.name, usr_path(@user)
  1. usr_path is not a route helper of this app; did you mean user_path?1:23Rails/UnknownRoute
Fine · app/views/users/index.html.slim
= link_to @user.name, user_path(@user)

Turbo and Stimulus

Turbo/PreferTurboAttributes

Available

The Turbo attributes in place of the Rails UJS ones: data-turbo-confirm, data-turbo-method and data-turbo-submits-with.

Languages ERB, Haml, and Slim

Type Autofix, run by the formatter. Off by default.

Rendered output Not safe. It renames HTML attributes, which changes the page: the new ones work only in an app that runs Turbo, the old ones only with Rails UJS, so an app that still relies on UJS for a link or button loses it. That is why the rule is off until enabled. The formatter reads every element's attributes before and after and refuses the rewrite unless only the renamed ones differ, then checks the renamed text as it checks any file, so nothing else changes.

Status Available.

Today On links (<a>, link_to, tag.a, content_tag :a), data-method becomes data-turbo-method, and data-confirm becomes data-turbo-confirm when the link sends a method, since Turbo asks to confirm only such a link. On submit buttons (<button> with no type or type="submit", <input type="submit">, submit_tag, button_tag, button_to, and submit or button on the builder of a form_with, form_for or fields_for block), data-disable-with becomes data-turbo-submits-with. In the options of a helper, the keys of data: { } are renamed in the spelling they have (confirm: becomes turbo_confirm:, "disable-with" => becomes "turbo-submits-with" =>), as is a key such as "data-method" =>. Left alone: a data-confirm on a link without a method, button_to's confirm, which Turbo reads from the form, link_to … method: :delete, which is not a data attribute, an attribute whose Turbo name the element has already, a button of another type or whose attributes are not all readable, disable_with: false and a confirm: with no value (which reads a variable), other elements, and keys and attribute names that are not literal. It runs before the other formatter rules, whose checks then apply to the renamed text. Format selection applies it to the lines it covers.

Turn it on
[Turbo.PreferTurboAttributes]
enabled = true
Example
Before · app/views/users/index.html.erb
<%= link_to "Delete", user_path(user), data: { confirm: "Sure?", method: :delete } %>
<%= submit_tag "Save", data: { disable_with: "Saving..." } %>
<a href="/users/1" data-confirm="Sure?" data-method="delete">Delete</a>
After · app/views/users/index.html.erb
<%= link_to "Delete", user_path(user), data: { turbo_confirm: "Sure?", turbo_method: :delete } %>
<%= submit_tag "Save", data: { turbo_submits_with: "Saving..." } %>
<a href="/users/1" data-turbo-confirm="Sure?" data-turbo-method="delete">Delete</a>
Before · app/views/users/index.html.haml
= link_to "Delete", user_path(user), data: { confirm: "Sure?", method: :delete }
%input{type: "submit", data: {disable_with: "Saving..."}}
After · app/views/users/index.html.haml
= link_to "Delete", user_path(user), data: { turbo_confirm: "Sure?", turbo_method: :delete }
%input{type: "submit", data: {turbo_submits_with: "Saving..."}}
Before · app/views/users/index.html.slim
= link_to "Delete", user_path(user), data: { confirm: "Sure?", method: :delete }
a href="/users/1" data-method="delete" data-confirm="Sure?" Delete
After · app/views/users/index.html.slim
= link_to "Delete", user_path(user), data: { turbo_confirm: "Sure?", turbo_method: :delete }
a href="/users/1" data-turbo-method="delete" data-turbo-confirm="Sure?" Delete

Turbo/PermanentRequiresId

Available

An element with data-turbo-permanent and no id, which Turbo needs to match it across pages.

Languages ERB, Haml, and Slim

Type Warning, shown as a diagnostic.

Rendered output Safe. It only reports; nothing is rewritten.

Status Available.

Today Reported on the attribute, in ERB start tags, Haml and Slim tags, and the literal options of tag.div, content_tag, link_to and the form and button helpers. Any value counts, as Turbo looks only for the attribute. An id of any value satisfies it, so does #id in Haml and Slim and an object reference such as %div[@user]. Not reported where attributes may come from code that can't be read (ERB inside the start tag, a Haml **attrs or data: attrs, a Slim *attrs or data= hash, a helper's **options) or when the value is not a literal (data: { turbo_permanent: flag }). Rails projects only, in templates under app/views. See Turbo/PermanentRequiresId in the diagnostics reference.

Turn it off
[Turbo.PermanentRequiresId]
enabled = false
Examples
Flagged · app/views/layouts/application.html.erb
<div data-turbo-permanent>
  <%= render 'shared/player' %>
</div>
  1. data-turbo-permanent needs an id: Turbo matches the element across pages by it1:6Turbo/PermanentRequiresId
Fine · app/views/layouts/application.html.erb
<div id="player" data-turbo-permanent>
  <%= render 'shared/player' %>
</div>
Flagged · app/views/layouts/application.html.haml
%div{data: {turbo_permanent: true}}
  = render 'shared/player'
  1. data-turbo-permanent needs an id: Turbo matches the element across pages by it1:13Turbo/PermanentRequiresId
Fine · app/views/layouts/application.html.haml
#player{data: {turbo_permanent: true}}
  = render 'shared/player'
Flagged · app/views/layouts/application.html.slim
div data-turbo-permanent=true
  = render 'shared/player'
  1. data-turbo-permanent needs an id: Turbo matches the element across pages by it1:5Turbo/PermanentRequiresId
Fine · app/views/layouts/application.html.slim
#player data-turbo-permanent=true
  = render 'shared/player'

Stimulus/UnknownController

Available

A data-controller name with no controller file in app/javascript/controllers.

Languages ERB, Haml, and Slim

Type Warning, shown as a diagnostic.

Rendered output Safe. It only reports; nothing is rewritten.

Status Available.

Today Each name in a static data-controller (ERB, Haml and Slim attributes, data: { controller: "…" } in Haml and in the options of tag, content_tag, link_to, button_to and the form helpers) must be a file in app/javascript/controllers (search_controller.js, admin/users_controller.js for admin--users; -controller, .ts, .jsx, .tsx and .mjs too) or a name a script in app/javascript registers with application.register("name", …); the closest controller within two edits is offered. Nothing is reported when the app has no app/javascript/controllers folder, when controllers are loaded from another folder (eagerLoadControllersFrom("components", …), a context or glob outside the controllers folder, an importmap pin) or registered under a name the code computes, or for a name with ERB or interpolation in it. Rails projects only, in templates under app/views. See Stimulus/UnknownController in the diagnostics reference.

Turn it off
[Stimulus.UnknownController]
enabled = false
Examples

app/javascript/controllers/search_controller.js exists.

Flagged · app/views/users/index.html.erb
<div data-controller="serch">
  <%= text_field_tag :q %>
</div>
  1. No Stimulus controller serch in app/javascript/controllers; did you mean search?1:23Stimulus/UnknownController
Fine · app/views/users/index.html.erb
<div data-controller="search">
  <%= text_field_tag :q %>
</div>
Flagged · app/views/users/index.html.haml
%div{data: {controller: "serch"}}
  = text_field_tag :q
  1. No Stimulus controller serch in app/javascript/controllers; did you mean search?1:26Stimulus/UnknownController
Fine · app/views/users/index.html.haml
%div{data: {controller: "search"}}
  = text_field_tag :q
Flagged · app/views/users/index.html.slim
div data-controller="serch"
  = text_field_tag :q
  1. No Stimulus controller serch in app/javascript/controllers; did you mean search?1:22Stimulus/UnknownController
Fine · app/views/users/index.html.slim
div data-controller="search"
  = text_field_tag :q

Stimulus/UnknownTargetOrAction

Available

A data-*-target or data-action that names a target or method its controller doesn't have.

Languages ERB, Haml, and Slim

Type Warning, shown as a diagnostic.

Rendered output Safe. It only reports; nothing is rewritten.

Status Available.

Today Checks the static data-<controller>-target names (several may be separated by spaces) against the static targets = ["…"] of the controller, and each method of data-action (click->search#go, search#go, several descriptors, :prevent-style options, @window and @document, key filters such as keydown.enter->) against the methods, accessors and fields its class defines; connect, disconnect and initialize always count. The same goes for data: { search_target: "…", action: "…" } in Haml and in the options of the tag, link, button and form helpers. The closest name within two edits is offered. A controller is only read when its identifier is one file in app/javascript/controllers and the app registers its controllers where this can see them (as for Stimulus/UnknownController); the file is read as text, and a base controller it extends is followed when it is imported from a relative path or controllers/…. Nothing is reported for a controller that extends something other than Controller from @hotwired/stimulus and a base controller that can't be followed (a package), has a static targets that is no plain list of strings, defines members by computed names, decorators or Object.assign(…prototype…), has more than one class, or is registered by name with application.register instead of a file. ERB, interpolation and data-action values without a # are skipped. Rails projects only, in templates under app/views. See Stimulus/UnknownTargetOrAction in the diagnostics reference.

Turn it off
[Stimulus.UnknownTargetOrAction]
enabled = false
Examples

search_controller.js has static targets = ["query"] and a submit() method.

Flagged · app/views/users/index.html.erb
<div data-controller="search">
  <input data-search-target="querry" data-action="input->search#sbumit">
</div>
  1. search controller has no target querry; did you mean query?2:30Stimulus/UnknownTargetOrAction
  2. search controller has no method sbumit; did you mean submit?2:65Stimulus/UnknownTargetOrAction
Fine · app/views/users/index.html.erb
<div data-controller="search">
  <input data-search-target="query" data-action="input->search#submit">
</div>
Flagged · app/views/users/index.html.haml
%div{data: {controller: "search"}}
  %input{data: {search_target: "querry", action: "input->search#sbumit"}}
  1. search controller has no target querry; did you mean query?2:33Stimulus/UnknownTargetOrAction
  2. search controller has no method sbumit; did you mean submit?2:65Stimulus/UnknownTargetOrAction
Fine · app/views/users/index.html.haml
%div{data: {controller: "search"}}
  %input{data: {search_target: "query", action: "input->search#submit"}}
Flagged · app/views/users/index.html.slim
div data-controller="search"
  input data-search-target="querry" data-action="input->search#sbumit"
  1. search controller has no target querry; did you mean query?2:29Stimulus/UnknownTargetOrAction
  2. search controller has no method sbumit; did you mean submit?2:64Stimulus/UnknownTargetOrAction
Fine · app/views/users/index.html.slim
div data-controller="search"
  input data-search-target="query" data-action="input->search#submit"

HTML and accessibility

Html/ImgAlt

Available

An image without alt text.

Languages ERB, Haml, and Slim

Type Warning, shown as a diagnostic.

Rendered output Safe. It only reports; nothing is rewritten.

Status Available.

Today For <img> (ERB start tags, %img and img) and for image_tag and tag.img without alt:, in templates that output HTML (not .js.erb, .text.erb or .xml.haml). An empty alt="" counts as alt text, and so does generator-unable-to-provide-required-alt. Not reported where attributes may come from code that can't be read (ERB inside the start tag, a Haml **attrs, a Slim *attrs, a helper's **options or options in a variable). See Html/ImgAlt in the diagnostics reference.

Turn it off
[Html.ImgAlt]
enabled = false
Examples
Flagged · app/views/users/show.html.erb
<img src="<%= @user.avatar_url %>">
<%= image_tag @user.avatar_url %>
  1. <img> without alt: the alt attribute must be specified (HTML 4.8.4.4.1)1:1Html/ImgAlt
  2. image_tag without alt:: the alt attribute must be specified (HTML 4.8.4.4.1)2:5Html/ImgAlt
Fine · app/views/users/show.html.erb
<img src="<%= @user.avatar_url %>" alt="<%= @user.name %>">
<%= image_tag @user.avatar_url, alt: @user.name %>
Flagged · app/views/users/index.html.haml
%img{src: @user.avatar_url}
= image_tag @user.avatar_url
  1. <img> without alt: the alt attribute must be specified (HTML 4.8.4.4.1)1:2Html/ImgAlt
  2. image_tag without alt:: the alt attribute must be specified (HTML 4.8.4.4.1)2:3Html/ImgAlt
Fine · app/views/users/index.html.haml
%img{src: @user.avatar_url, alt: @user.name}
= image_tag @user.avatar_url, alt: @user.name
Flagged · app/views/users/index.html.slim
img src=@user.avatar_url
= image_tag @user.avatar_url
  1. <img> without alt: the alt attribute must be specified (HTML 4.8.4.4.1)1:1Html/ImgAlt
  2. image_tag without alt:: the alt attribute must be specified (HTML 4.8.4.4.1)2:3Html/ImgAlt
Fine · app/views/users/index.html.slim
img src=@user.avatar_url alt=@user.name
= image_tag @user.avatar_url, alt: @user.name

Html/DuplicateId

Available

The same static id twice in one template.

Languages ERB, Haml, and Slim

Type Warning, shown as a diagnostic.

Rendered output Safe. It only reports; nothing is rewritten.

Status Available.

Today At the second occurrence, in templates that output HTML. In Haml and Slim, #id and id: count. Ids that hold ERB or interpolation, ids in different branches of a conditional (ERB if/else, Haml and Slim - if/- else) and content inside <template> are left alone; in Haml and Slim, so are ids where one of them sits in a conditional, a loop or a block the other is not in. A Haml element with both #id and id: gets one joined id and is skipped. See Html/DuplicateId in the diagnostics reference.

Turn it off
[Html.DuplicateId]
enabled = false
Examples
Flagged · app/views/users/show.html.erb
<div id="profile">
  <%= @user.name %>
</div>
<div id="profile"></div>
  1. Duplicate id profile (first on line 1): the value must be unique amongst all the IDs in the element's tree (HTML 3.2.6)4:10Html/DuplicateId
Fine · app/views/users/show.html.erb
<div id="profile">
  <%= @user.name %>
</div>
<div id="settings"></div>
Flagged · app/views/users/index.html.haml
#profile
  = @user.name
#profile
  1. Duplicate id profile (first on line 1): the value must be unique amongst all the IDs in the element's tree (HTML 3.2.6)3:2Html/DuplicateId
Fine · app/views/users/index.html.haml
#profile
  = @user.name
#settings
Flagged · app/views/users/index.html.slim
#profile
  = @user.name
#profile
  1. Duplicate id profile (first on line 1): the value must be unique amongst all the IDs in the element's tree (HTML 3.2.6)3:2Html/DuplicateId
Fine · app/views/users/index.html.slim
#profile
  = @user.name
#settings

Html/UnexpectedCloseTag

Available

An end tag with no open element of that name.

Languages ERB

Type Warning, shown as a diagnostic.

Rendered output Safe. It only reports; nothing is rewritten.

Status Available.

Today In ERB with an HTML host, including the end tag of a void element. A stray end tag in a partial is not reported, since a partial may close what another one opened. See Html/UnexpectedCloseTag in the diagnostics reference.

Turn it off
[Html.UnexpectedCloseTag]
enabled = false
Examples
Flagged · app/views/users/show.html.erb
<div class="profile">
  <p><%= @user.name %></p>
</div>
</div>
  1. Unexpected end tag </div>: no open element of that name (HTML 13.2.6.4.7)4:1Html/UnexpectedCloseTag
Fine · app/views/users/show.html.erb
<div class="profile">
  <p><%= @user.name %></p>
</div>

Html/MismatchedCloseTag

Available

An end tag that doesn't close the innermost open element.

Languages ERB

Type Warning, shown as a diagnostic.

Rendered output Safe. It only reports; nothing is rewritten.

Status Available.

Today In ERB with an HTML host, partials included. When the end tag crosses an open element, the browser could recover in more than one way, so checking stops there. See Html/MismatchedCloseTag in the diagnostics reference.

Turn it off
[Html.MismatchedCloseTag]
enabled = false
Examples
Flagged · app/views/users/show.html.erb
<div class="profile">
  <span><%= @user.name %></div>
  1. End tag </div> doesn't close the open <span> (line 2) (HTML 13.2.6.4.7)2:26Html/MismatchedCloseTag
Fine · app/views/users/show.html.erb
<div class="profile">
  <span><%= @user.name %></span>
</div>

Html/UnclosedElement

Available

An element whose end tag never comes.

Languages ERB

Type Warning, shown as a diagnostic.

Rendered output Safe. It only reports; nothing is rewritten.

Status Available.

Today In ERB with an HTML host, at the start tag. Elements whose end tag HTML lets you omit, void elements, partials and files that end inside an ERB block are left alone. See Html/UnclosedElement in the diagnostics reference.

Turn it off
[Html.UnclosedElement]
enabled = false
Examples
Flagged · app/views/users/index.html.erb
<section class="users">
  <h2>Users</h2>
  1. <section> is never closed (HTML 13.2.6.4.7)1:1Html/UnclosedElement
Fine · app/views/users/index.html.erb
<section class="users">
  <h2>Users</h2>
</section>

Html/VoidChildren

Available

Content inside a void element such as img or input.

Languages Haml and Slim

Type Warning, shown as a diagnostic.

Rendered output Safe. It only reports; nothing is rewritten.

Status Available.

Today Haml reports content on the tag's line or nested under it; Slim reports lines nested under a void element, in HTML templates only. See Html/VoidChildren in the diagnostics reference.

Turn it off
[Html.VoidChildren]
enabled = false
Examples
Flagged · app/views/users/show.html.slim
img src="/logo.png"
  | Logo
  1. img is a void element and can't have children2:3Html/VoidChildren
Fine · app/views/users/show.html.slim
img src="/logo.png" alt="Logo"

Html/DuplicateAttribute

Available

An attribute given twice on one tag.

Languages ERB, Haml, and Slim

Type Warning, shown as a diagnostic.

Rendered output Safe. It only reports; nothing is rewritten.

Status Available.

Today At the second one, for names written out in ERB start tags, Haml tags (the (…) list and the {…} hash, data: { … } keys included) and Slim tags, and in the literal options of tag.div, content_tag, link_to and the form and button helpers, in templates that output HTML. class and id are repeated on purpose in Haml, Slim and the helpers, which merge them (.card{class: "wide"}), so only plain ERB start tags are checked for them. An ERB start tag with ERB inside is left alone, since it may pick one of two attributes. Slim itself rejects a repeated attribute other than class, so there it is an error with Slim's message, Multiple href attributes specified. See Html/DuplicateAttribute in the diagnostics reference.

Turn it off
[Html.DuplicateAttribute]
enabled = false
Examples
Flagged · app/views/users/index.html.erb
<div class="card" class="wide">
  <%= user.name %>
</div>
  1. Duplicate attribute class on <div>: a start tag must not repeat an attribute (HTML 13.1.2.3)1:19Html/DuplicateAttribute
Fine · app/views/users/index.html.erb
<div class="card wide">
  <%= user.name %>
</div>
Flagged · app/views/users/index.html.haml
%a{href: "/users", href: "/people"} Users
  1. href is given twice; only one value is rendered1:4Html/DuplicateAttribute
Fine · app/views/users/index.html.haml
%a{href: "/users"} Users

Slim's own error, Multiple href attributes specified, is reported as Syntax/Template.

Flagged · app/views/users/index.html.slim
a href="/users" href="/people" Users
Fine · app/views/users/index.html.slim
a href="/users" Users

Html/NestedInteractive

Available

An interactive element inside another: a link in a link, a form in a form, including link_to and form_with.

Languages ERB, Haml, and Slim

Type Warning, shown as a diagnostic.

Rendered output Safe. It only reports; nothing is rewritten.

Status Available.

Today Reported on the inner element's tag name (or helper name). Inside an <a> or <button> (also link_to, tag.a, button_tag, content_tag :button with a block) these are interactive content and can't be nested (HTML 4.5.1): <a> (inside another <a> always, inside a <button> when it has an href), <button>, <input> (not type="hidden"), <select>, <textarea>, <label>, <iframe>, <embed>, <details>, <audio> and <video> with controls, and <img> with usemap. A <form> can't contain a form (HTML 4.10.3), and button_to renders a form around its button, so it counts as both, inside form_with, form_for, form_tag and <form>. Nesting is the template's own: ERB by the matching start and end tags and do … end blocks of the helper, Haml and Slim by indentation (an element on the same line as its parent, li: a …, isn't followed). Elements whose start and end tags sit in different branches of an if, partials rendered inside the element, a layout's yield, and anything inside <svg>, <math> or <template> are not followed. A <label> around an <input>, a <summary> in <details>, a <button form="…"> outside its form and a hidden input are fine. Templates that output HTML only. See Html/NestedInteractive in the diagnostics reference.

Turn it off
[Html.NestedInteractive]
enabled = false
Examples
Flagged · app/views/users/index.html.erb
<%= link_to user_path(user) do %>
  <%= link_to "Edit", edit_user_path(user) %>
<% end %>
  1. <a> inside <a>: interactive content can't be nested (HTML 4.5.1)2:7Html/NestedInteractive
Fine · app/views/users/index.html.erb
<%= link_to user_path(user) do %>
  <%= user.name %>
<% end %>
<%= link_to "Edit", edit_user_path(user) %>

button_to renders its own <form>.

Flagged · app/views/users/index.html.erb
<%= form_with model: @user do |f| %>
  <%= f.text_field :name %>
  <%= button_to "Delete", @user, method: :delete %>
<% end %>
  1. <form> inside <form>: a form can't contain another form (HTML 4.10.3)3:7Html/NestedInteractive
Fine · app/views/users/index.html.erb
<%= form_with model: @user do |f| %>
  <%= f.text_field :name %>
<% end %>
<%= button_to "Delete", @user, method: :delete %>
Flagged · app/views/users/index.html.haml
%a{href: "/users"}
  %a{href: "/users/new"} New
  1. <a> inside <a>: interactive content can't be nested (HTML 4.5.1)2:4Html/NestedInteractive
Fine · app/views/users/index.html.haml
%a{href: "/users"} Users
%a{href: "/users/new"} New
Flagged · app/views/users/index.html.slim
a href="/users"
  a href="/users/new" New
  1. <a> inside <a>: interactive content can't be nested (HTML 4.5.1)2:3Html/NestedInteractive
Fine · app/views/users/index.html.slim
a href="/users" Users
a href="/users/new" New

Html/AnchorHref

Available

An <a> without an href, which is not a link and cannot be reached with the keyboard.

Languages ERB, Haml, and Slim

Type Warning, shown as a diagnostic.

Rendered output Safe. It only reports; nothing is rewritten.

Status Available.

Today On the tag name of <a> in ERB start tags and of %a and a in Haml and Slim, in templates that output HTML. link_to always has an href and is not checked. An <a> with a name (the old way to mark a place to jump to), a tabindex (the keyboard can reach it), an id and no text of its own (a target to jump to), or an attribute ending in :href (:href, x-bind:href, xlink:href) is left alone, and so is one inside an <svg>, and so is one whose attributes may come from code that can't be read (ERB inside the start tag, a Haml **attrs or data: attrs, a Slim *attrs or data= hash). See Html/AnchorHref in the diagnostics reference.

Turn it off
[Html.AnchorHref]
enabled = false
Examples
Flagged · app/views/users/index.html.erb
<a class="btn" data-action="click->menu#open">Menu</a>
  1. <a> without href is not a link and the keyboard can't reach it; use a <button> for an action1:2Html/AnchorHref
Fine · app/views/users/index.html.erb
<button class="btn" data-action="click->menu#open">Menu</button>
Flagged · app/views/users/index.html.haml
%a.btn{data: {action: "click->menu#open"}} Menu
  1. <a> without href is not a link and the keyboard can't reach it; use a <button> for an action1:2Html/AnchorHref
Fine · app/views/users/index.html.haml
%button.btn{data: {action: "click->menu#open"}} Menu
Flagged · app/views/users/index.html.slim
a.btn data-action="click->menu#open" Menu
  1. <a> without href is not a link and the keyboard can't reach it; use a <button> for an action1:1Html/AnchorHref
Fine · app/views/users/index.html.slim
button.btn data-action="click->menu#open" Menu

A11y/IframeTitle

Available

An <iframe> without a title, which screen readers use to name it.

Languages ERB, Haml, and Slim

Type Warning, shown as a diagnostic.

Rendered output Safe. It only reports; nothing is rewritten.

Status Available.

Today On the tag name of an <iframe> in ERB start tags, Haml and Slim tags, and of tag.iframe and content_tag :iframe, in templates that output HTML. An aria-label or aria-labelledby (also in an aria: { label: … } hash) names the frame as well. Not reported where attributes may come from code that can't be read (ERB inside the start tag, **attrs, *attrs). See A11y/IframeTitle in the diagnostics reference.

Turn it off
[A11y.IframeTitle]
enabled = false
Examples
Flagged · app/views/users/index.html.erb
<iframe src="https://www.youtube.com/embed/<%= video.youtube_id %>"></iframe>
  1. <iframe> without title: screen readers announce it to name the frame (WCAG 4.1.2)1:2A11y/IframeTitle
Fine · app/views/users/index.html.erb
<iframe src="https://www.youtube.com/embed/<%= video.youtube_id %>" title="<%= video.title %>"></iframe>
Flagged · app/views/users/index.html.haml
%iframe{src: "https://www.youtube.com/embed/#{video.youtube_id}"}
  1. <iframe> without title: screen readers announce it to name the frame (WCAG 4.1.2)1:2A11y/IframeTitle
Fine · app/views/users/index.html.haml
%iframe{src: "https://www.youtube.com/embed/#{video.youtube_id}", title: video.title}
Flagged · app/views/users/index.html.slim
iframe src="https://www.youtube.com/embed/#{video.youtube_id}"
  1. <iframe> without title: screen readers announce it to name the frame (WCAG 4.1.2)1:1A11y/IframeTitle
Fine · app/views/users/index.html.slim
iframe src="https://www.youtube.com/embed/#{video.youtube_id}" title=video.title

A11y/GenericLinkText

Available

Link text that says nothing out of context: "click here", "here", "read more".

Languages ERB, Haml, and Slim

Type Warning, shown as a diagnostic.

Rendered output Safe. It only reports; nothing is rewritten.

Status Available.

Today On the text of an <a> that holds only text (ERB, Haml and Slim tags) and of a link_to, tag.a or content_tag :a given a string literal: "click here", "here", "read more", "learn more", "more", "more info", "more information", "link", "this link" and "click", in any case and ignoring punctuation. A link with an aria-label, aria-labelledby or aria-describedby (also in an aria: { … } hash), with ERB, interpolation, other elements or code in its content, or whose attributes may come from code that can't be read is left alone. See A11y/GenericLinkText in the diagnostics reference.

Turn it off
[A11y.GenericLinkText]
enabled = false
Examples
Flagged · app/views/users/index.html.erb
<p>Our new pricing is live. <%= link_to "Click here", pricing_path %></p>
  1. Link text Click here says nothing out of context; name where the link goes (WCAG 2.4.9, F84)1:42A11y/GenericLinkText
Fine · app/views/users/index.html.erb
<p><%= link_to "See the new pricing", pricing_path %></p>
Flagged · app/views/users/index.html.haml
%p= link_to "Click here", pricing_path
  1. Link text Click here says nothing out of context; name where the link goes (WCAG 2.4.9, F84)1:14A11y/GenericLinkText
Fine · app/views/users/index.html.haml
%p= link_to "See the new pricing", pricing_path
Flagged · app/views/users/index.html.slim
p = link_to "Click here", pricing_path
  1. Link text Click here says nothing out of context; name where the link goes (WCAG 2.4.9, F84)1:14A11y/GenericLinkText
Fine · app/views/users/index.html.slim
p = link_to "See the new pricing", pricing_path