@@ -1144,48 +1144,69 @@ impl Evaluator {
11441144 let mut result_nodes =
11451145 replace_children_and_slots(body_nodes, &caller_children, &slot_contents);
11461146
1147− if !forwarded.is_empty() {
1147+ // Attributes that aren't parameters style the function's root
1148+ // element, and a scoped @style's class goes on it too.
1149+ let scope_class = ctx.scoped_functions.contains(name).then(|| format!("hl-{}", name));
1150+ if !forwarded.is_empty() || scope_class.is_some() {
11481151 let mut roots = result_nodes.iter_mut().filter_map(|n| match n {
11491152 Node::Element(e) => Some(e),
11501153 _ => None,
11511154 });
1152− match (roots.next(), roots.next()) {
1153− (Some(root), None) => root.attrs.extend(forwarded),
1154− _ => ctx.diagnostics.push(Diagnostic {
1155+ let root = match (roots.next(), roots.next()) {
1156+ (Some(root), None) => Some(root),
1157+ _ => None,
1158+ };
1159+ let mut problems = Vec::new();
1160+ match root {
1161+ Some(root) => {
1162+ root.attrs.extend(forwarded);
1163+ if let Some(class) = scope_class {
1164+ match root.attrs.iter_mut().find(|a| a.html && a.key == "class") {
1165+ Some(attr) => {
1166+ let existing = attr.value.take().unwrap_or_default();
1167+ attr.value = Some(format!("{} {}", existing, class).trim().to_string());
1168+ }
1169+ None => root.attrs.push(Attribute {
1170+ key: "class".to_string(),
1171+ value: Some(class),
1172+ html: true,
1173+ }),
1174+ }
1175+ }
1176+ }
1177+ None => {
1178+ if !forwarded.is_empty() {
1179+ problems.push(format!(
1180+ "attributes {} on @{} are not parameters, and its body has no single \
1181+ root element to receive them",
1182+ forwarded
1183+ .iter()
1184+ .map(|a| format!("'{}'", a.key))
1185+ .collect::<Vec<_>>()
1186+ .join(", "),
1187+ name
1188+ ));
1189+ }
1190+ if scope_class.is_some() {
1191+ problems.push(format!(
1192+ "@{} has a scoped @style, but its body has no single root element \
1193+ to scope it to",
1194+ name
1195+ ));
1196+ }
1197+ }
1198+ }
1199+ for message in problems {
1200+ ctx.diagnostics.push(Diagnostic {
11551201 line: line_num,
11561202 column: None,
1157− message: format!(
1158− "attributes {} on @{} are not parameters, and its body has no single \
1159− root element to receive them",
1160− forwarded
1161− .iter()
1162− .map(|a| format!("'{}'", a.key))
1163− .collect::<Vec<_>>()
1164− .join(", "),
1165− name
1166− ),
1203+ message,
11671204 severity: Severity::Warning,
11681205 source_line: Some(content.to_string()),
1169− }),
1206+ });
11701207 }
11711208 }
11721209
1173− // A function with a scoped @style wraps its output in the scope class
1174− if ctx.scoped_functions.contains(name) {
1175− let wrapper = Element {
1176− kind: ElementKind::El,
1177− attrs: vec![Attribute {
1178− key: "class".to_string(),
1179− value: Some(format!("hl-{}", name)),
1180− html: true,
1181− }],
1182− argument: None,
1183− children: result_nodes,
1184− line_num,
1185− };
1186− result_nodes = vec![Node::Element(wrapper)];
1187− }
1188−
11891210 Ok(result_nodes)
11901211 }
11911212