Skip to content

Linter Rule: Validate data-herb-* action attributes

Rule: herb-state-valid-actions

Description

Validates the declarative action attributes data-herb-set, data-herb-toggle, data-herb-increment, data-herb-decrement, data-herb-reset and data-herb-by. Clause syntax must parse, quotes must balance, a named state must be declared in a scope enclosing the element, the operation must match the state's declared kind, and a set value must parse to that kind. A derived state cannot be the target of any action, since its value follows from the states it reads.

Rationale

An action attribute is wiring the browser executes, so a typo in it fails silently at runtime. The client runtime reports these same problems as diagnostics when the page runs in debug mode. This rule reports them in the editor, using the same messages, before the page runs at all.

Type checks come from the declaration. toggle needs a boolean, increment and decrement need an integer, and a set value is read as whatever the state was declared to hold, so attempts=lots against attempts: 0 can never parse. A state seeded from an expression has no static kind and is exempt.

The scope check follows the runtime's resolution. A name resolves through the scopes enclosing the element, the loop body for an item-scoped state and the template for a region-scoped one. When the template declares no states at all the rule stays quiet, since the states an element writes may be declared by an enclosing template.

Examples

✅ Good

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

<button data-herb-toggle="open">Details</button>
<button data-herb-set="open=true,sort=date">Both</button>
<button data-herb-increment="attempts" data-herb-by="2">More</button>
<select data-herb-set="change->sort=$value"></select>
<input data-herb-reset="blur->draft" autocomplete="off">
<button data-herb-set="draft='hello, world'">Quote</button>

🚫 Bad

erb
<%# herb:slots client %>
<%# herb:state (open: false, attempts: 0, sort: "name") %>

<button data-herb-toggle="missing">Details</button>
Nothing in scope here declares the state `missing`. Declare it with `<%# herb:state (missing: ...) %>`, or use one of the states in scope. States in scope: `open`, `attempts`, `sort`. (herb-state-valid-actions)
<button data-herb-toggle="sort">Sort</button>
`data-herb-toggle` on `sort` can never work, because `sort` is a String and it needs a Boolean. Set a value instead, like `data-herb-set="sort=..."`, or declare a boolean flag. (herb-state-valid-actions)
<button data-herb-set="open">Send</button>
`open` in `data-herb-set` is not a `state=value` pair. Write the value to set, like `open=true`, or use `data-herb-toggle` to flip a boolean. (herb-state-valid-actions)
<button data-herb-set="attempts=lots">More</button>
`attempts=lots` does not parse as an Integer, and `attempts` is declared as one. Use a whole number, like `attempts=0`. (herb-state-valid-actions)
<button data-herb-increment="attempts" data-herb-by="two">More</button>
`data-herb-by="two"` is not an integer, so the step falls back to 1. Use a whole number, like `data-herb-by="2"`. (herb-state-valid-actions)
<button data-herb-set="sort='oops">Sort</button>
`data-herb-set="sort='oops"` has an unbalanced quote. Close the `'`, since separators inside quotes stay literal. (herb-state-valid-actions)

References

-

Released under the MIT License.