Skip to content

Linter Rule: One herb:state declaration per scope

Rule: herb-state-single-declaration

Description

Requires each scope to declare all of its states in a single herb:state directive.

Rationale

The engine merges several directives in one scope without complaint, so splitting a scope's states across directives changes nothing about how the template runs. What it changes is how the template reads. The declaration is the one place that enumerates a scope's states with their defaults, and a reader who finds one directive reasonably stops looking. A second directive further down quietly widens the scope's state set behind their back. The signature syntax holds any number of states, so there is no cost to keeping them together.

A directive inside a collection body declares item states and is its own scope, so a region directive and an item directive never conflict with each other.

Examples

✅ Good

erb
<%# herb:slots client %>
<%# herb:state (open: false, draft: "") %>

<input value="<%= draft %>">
<% if open? %><nav>Menu</nav><% end %>
erb
<%# herb:slots client %>
<%# herb:state (open: false) %>

<% @rows.each do |row| %>
Block argument `row` is never used. Remove it and write `<% @rows.each do %>`, or prefix it with an underscore as `_row` to show it is intentionally unused. (erb-no-unused-block-argument)
<%# herb:state (selected: false) %> <input type="checkbox" checked="<%= selected %>"> <% end %> <% if open? %><nav>Menu</nav><% end %>

🚫 Bad

erb
<%# herb:slots client %>
<%# herb:state (open: false) %>
<%# herb:state (draft: "") %>
This scope already declares its states in the `herb:state` directive on line 2. Merge these states into that signature, so every state of the scope reads from one declaration. (herb-state-single-declaration)
<input value="<%= draft %>"> <% if open? %><nav>Menu</nav><% end %>

References

-

Released under the MIT License.