fix(build): reject unsupported slhx events

Validate static data-slhx-on values against the runtime-supported delegated event set so generated EventName constants cannot imply browser behavior the runtime will never dispatch.

req: convention/002

req: diagnostics/002
This commit is contained in:
slhx agent
2026-06-01 20:48:18 +02:00
parent 44366252f7
commit b98820e819
2 changed files with 22 additions and 4 deletions
+21 -3
View File
@@ -1027,6 +1027,14 @@ fn reject_invalid_slhx_attr_values(path: &Path, attrs: &[SurfaceAttribute]) -> i
"expected one of `latest`, `queue`, `drop`, or `parallel`",
));
}
"data-slhx-on" if !event_tokens(value).all(valid_runtime_event) => {
return Err(invalid_slhx_value(
path,
&attr.name,
value,
"expected runtime-supported events: `click`, `submit`, `input`, `change`, `dragstart`, `dragover`, or `drop`",
));
}
"data-slhx-debounce" | "data-slhx-throttle" | "data-slhx-every"
if !valid_duration(value) =>
{
@@ -1047,6 +1055,10 @@ fn valid_policy(value: &str) -> bool {
matches!(value.trim(), "latest" | "queue" | "drop" | "parallel")
}
fn valid_runtime_event(value: &str) -> bool {
matches!(value, "click" | "submit" | "input" | "change" | "dragstart" | "dragover" | "drop")
}
fn valid_duration(value: &str) -> bool {
let value = value.trim();
let digits = value
@@ -1195,7 +1207,7 @@ mod tests {
std::fs::create_dir_all(&templates).unwrap();
std::fs::write(
templates.join("todo.heml"),
r#"<form data-slhx-handle="create" data-slhx-form="new_todo"><input name="title"></form><button data-slhx-handle="delete" data-todo-id="7" data-slhx-on="click keydown">Delete</button><ul data-slhx-slot="todos"></ul><section data-slhx-atom="filter"></section>"#,
r#"<form data-slhx-handle="create" data-slhx-form="new_todo"><input name="title"></form><button data-slhx-handle="delete" data-todo-id="7" data-slhx-on="click change">Delete</button><ul data-slhx-slot="todos"></ul><section data-slhx-atom="filter"></section>"#,
)
.unwrap();
@@ -1215,7 +1227,7 @@ mod tests {
assert!(generated.contains("pub const filter"));
assert!(generated.contains("pub mod events"));
assert!(generated.contains("pub const click: ::slhx::EventName = ::slhx::EventName::new(\"click\")"));
assert!(generated.contains("pub const keydown: ::slhx::EventName = ::slhx::EventName::new(\"keydown\")"));
assert!(generated.contains("pub const change: ::slhx::EventName = ::slhx::EventName::new(\"change\")"));
assert!(generated.contains("pub const new_todo"));
assert!(generated.contains("pub mod todo"));
assert!(generated.contains("pub const ALL_IDS"));
@@ -1233,7 +1245,7 @@ mod tests {
assert!(syms.contains("handle_param\tdelete\ttodo_id\n"));
assert!(syms.contains("event\t"));
assert!(syms.contains("\tclick\tclick\n"));
assert!(syms.contains("\tkeydown\tkeydown\n"));
assert!(syms.contains("\tchange\tchange\n"));
let _ = std::fs::remove_dir_all(&base);
}
@@ -1537,6 +1549,12 @@ fn main() {{
"data-slhx-every",
"1s",
),
(
"event",
r#"<button data-slhx-handle="save" data-slhx-on="keydown">Save</button>"#,
"data-slhx-on",
"click",
),
] {
let base = test_dir(&format!("slhx-build-invalid-convention-{case}-test"));
let templates = base.join("templates");