Skip to content

Linter Rule: Spell the herb:state directive in its canonical form

Rule: herb-state-directive-syntax

Description

Require <%# herb:state (...) %> to be written on one line, with exactly one space after <%#, one space before the signature, and one space before %>. Trim markers are not allowed on either delimiter.

Rationale

The parser recognizes the directive permissively so it can refuse a bad spelling with a diagnostic. Were it to simply stop matching, the states would quietly cease to exist and the failure would surface much later as an undefined local, far from the line that caused it.

Keeping one spelling means every consumer reads a directive the same way. Leading indentation stays legal, since item-scoped states are declared inside <% items.each do |item| %> blocks and are normally indented.

Examples

✅ Good

erb
<%# herb:state (open: false) %>
`herb:state` declares client-owned state, but this template never opts into slots, so the states compile to nothing. Add `<%# herb:slots client %>` to park branch markup up front, or `<%# herb:slots server %>` to fetch it on demand. (herb-state-requires-slots)
The state `open` is never read or written in this template. Remove it, or disable this line when only app code uses it through `stateFor` or `useState`. (herb-state-no-unused-states)
<%# herb:state (open: false, count: 0, title: "") %>
The state `open` is declared twice in the same scope. Remove one of the two declarations. (herb-state-valid-declaration)
This scope already declares its states in the `herb:state` directive on line 1. Merge these states into that signature, so every state of the scope reads from one declaration. (herb-state-single-declaration)
The state `title` is never read or written in this template. Remove it, or disable this line when only app code uses it through `stateFor` or `useState`. (herb-state-no-unused-states)
The state `count` is never read or written in this template. Remove it, or disable this line when only app code uses it through `stateFor` or `useState`. (herb-state-no-unused-states)
The state `open` is never read or written in this template. Remove it, or disable this line when only app code uses it through `stateFor` or `useState`. (herb-state-no-unused-states)
<% items.each do |item| %>
Block argument `item` is never used. Remove it and write `<% items.each do %>`, or prefix it with an underscore as `_item` to show it is intentionally unused. (erb-no-unused-block-argument)
<%# herb:state (selected: false) %>
The state `selected` is never read or written in this template. Remove it, or disable this line when only app code uses it through `stateFor` or `useState`. (herb-state-no-unused-states)
<% end %>

🚫 Bad

erb
<%#- herb:state (open: false) -%>
The `herb:state` directive has to be spelled `<%# herb:state (open: false) %>`. Write it on one line as `<%# herb:state (...) %>`, with a single space in each gap, so the states it declares are read the same way everywhere. (herb-state-directive-syntax)
<%#herb:state (open: false) %>
The `herb:state` directive has to be spelled `<%# herb:state (open: false) %>`. Write it on one line as `<%# herb:state (...) %>`, with a single space in each gap, so the states it declares are read the same way everywhere. (herb-state-directive-syntax)
`herb:state` declares client-owned state, but this template never opts into slots, so the states compile to nothing. Add `<%# herb:slots client %>` to park branch markup up front, or `<%# herb:slots server %>` to fetch it on demand. (herb-state-requires-slots)
The state `open` is never read or written in this template. Remove it, or disable this line when only app code uses it through `stateFor` or `useState`. (herb-state-no-unused-states)
Add whitespace after `<%#`. (erb-require-whitespace-inside-tags)
<%# herb:state (open: false) %>
The state `open` is declared twice in the same scope. Remove one of the two declarations. (herb-state-valid-declaration)
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)
The `herb:state` directive has to be spelled `<%# herb:state (open: false) %>`. Write it on one line as `<%# herb:state (...) %>`, with a single space in each gap, so the states it declares are read the same way everywhere. (herb-state-directive-syntax)
The state `open` is never read or written in this template. Remove it, or disable this line when only app code uses it through `stateFor` or `useState`. (herb-state-no-unused-states)
<%# herb:state (open: false)%>
The state `open` is declared twice in the same scope. Remove one of the two declarations. (herb-state-valid-declaration)
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)
The `herb:state` directive has to be spelled `<%# herb:state (open: false) %>`. Write it on one line as `<%# herb:state (...) %>`, with a single space in each gap, so the states it declares are read the same way everywhere. (herb-state-directive-syntax)
The state `open` is never read or written in this template. Remove it, or disable this line when only app code uses it through `stateFor` or `useState`. (herb-state-no-unused-states)
Add whitespace before `%>`. (erb-require-whitespace-inside-tags)
<%# herb:state (
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)
The `herb:state` directive has to be spelled `<%# herb:state (open: false, count: 0) %>`. Write it on one line as `<%# herb:state (...) %>`, with a single space in each gap, so the states it declares are read the same way everywhere. (herb-state-directive-syntax)
open: false,
The state `open` is declared twice in the same scope. Remove one of the two declarations. (herb-state-valid-declaration)
The state `open` is never read or written in this template. Remove it, or disable this line when only app code uses it through `stateFor` or `useState`. (herb-state-no-unused-states)
count: 0
The state `count` is never read or written in this template. Remove it, or disable this line when only app code uses it through `stateFor` or `useState`. (herb-state-no-unused-states)
) %>

Autofix

This rule is autocorrectable. The fix rewrites the directive into its canonical spelling, joining a multi-line signature onto one line and collapsing runs of whitespace outside string literals, so a default like (title: "a b") keeps its spacing.

References

Released under the MIT License.