1use std::{
2 collections::HashSet,
3 fmt::{Display, Write},
4 sync::OnceLock,
5};
6
7use askama_escape::Escaper;
8use proc_macro2::{Ident, Span, TokenStream};
9use quote::{format_ident, quote, quote_spanned, ToTokens, TokenStreamExt};
10use rstml::node::{
11 KeyedAttribute, KeyedAttributeValue, Node as RstmlNode, NodeAttribute, NodeBlock, NodeName,
12};
13use syn::{
14 braced, parenthesized,
15 parse::{Parse, ParseStream},
16 parse_macro_input, parse_quote_spanned,
17 punctuated::Punctuated,
18 spanned::Spanned,
19 token, Token,
20};
21
22mod nodes;
23
24use nodes::*;
25
26#[rustfmt::skip]
27const HTML_ELEMENTS: [&str; 137] = [
28 "a", "abbr", "acronym", "address", "area", "article",
29 "aside", "audio", "b", "base", "bdi", "bdo", "big",
30 "blockquote", "body", "br", "button", "canvas", "caption", "center",
31 "cite", "code", "col", "colgroup", "data", "datalist", "dd",
32 "del", "details", "dfn", "dialog", "dir", "div", "dl",
33 "dt", "em", "embed", "fieldset", "figcaption", "figure", "font",
34 "footer", "form", "frame", "frameset", "h1", "head", "header",
35 "hgroup", "hr", "html", "i", "iframe", "image", "img",
36 "input", "ins", "kbd", "label", "legend", "li", "link",
37 "main", "map", "mark", "marquee", "menu", "menuitem", "meta",
38 "meter", "nav", "nobr", "noembed", "noframes", "noscript", "object",
39 "ol", "optgroup", "option", "output", "p", "param", "picture",
40 "plaintext", "portal", "pre", "progress", "q", "rb", "rp",
41 "rt", "rtc", "ruby", "s", "samp", "script", "search",
42 "section", "select", "slot", "small", "source", "span", "strike",
43 "strong", "style", "sub", "summary", "sup", "table", "tbody",
44 "td", "template", "textarea", "tfoot", "th", "thead", "time",
45 "title", "tr", "track", "tt", "u", "ul", "var",
46 "video", "wbr", "xmp", "h2", "h3", "h4", "h5",
47 "h6", "svg", "math", "content", "shadow",
48];
49
50fn fn_binding_to_pat(
51 mut fn_binding: rstml::node::FnBinding,
52 allow_types: bool,
53) -> syn::Result<(token::Paren, syn::Pat)> {
54 if fn_binding.inputs.len() == 1 && !fn_binding.inputs.trailing_punct() {
55 let binding = fn_binding.inputs.pop().unwrap().into_value();
56 if let (
57 false,
58 syn::Pat::Type(syn::PatType {
59 colon_token, ty, ..
60 }),
61 ) = (allow_types, &binding)
62 {
63 Err(syn::Error::new_spanned(
64 quote! { #colon_token #ty },
65 "specifying the type of a pattern isn't supported here",
66 ))
67 } else {
68 Ok((fn_binding.paren, binding))
69 }
70 } else if allow_types {
71 Err(syn::Error::new_spanned(
72 fn_binding,
73 "Expected a single pattern with an optional type, e.g. `(pat: ty)` or `(mut pat)`",
74 ))
75 } else {
76 Err(syn::Error::new_spanned(
77 fn_binding,
78 "Expected a single pattern type, e.g. `(mut pat)`",
79 ))
80 }
81}
82
83fn html_elements_set() -> &'static HashSet<&'static str> {
84 static HTML_ELEMENTS_SET: OnceLock<HashSet<&str>> = OnceLock::new();
85
86 HTML_ELEMENTS_SET.get_or_init(|| HTML_ELEMENTS.into_iter().collect())
87}
88
89fn path_to_ident(mut path: syn::Path) -> Result<syn::Ident, syn::Path> {
90 if path.get_ident().is_some() {
91 Ok(path.segments.pop().unwrap().into_value().ident)
92 } else {
93 Err(path)
94 }
95}
96
97fn expr_path_to_ident(
98 syn::ExprPath { attrs, qself, path }: syn::ExprPath,
99) -> Result<syn::Ident, syn::ExprPath> {
100 if attrs.is_empty() && qself.is_none() {
101 match path_to_ident(path) {
102 Ok(ident) => Ok(ident),
103 Err(path) => Err(syn::ExprPath { attrs, qself, path }),
104 }
105 } else {
106 Err(syn::ExprPath { attrs, qself, path })
107 }
108}
109
110fn node_name_to_html_name(name: NodeName) -> syn::Result<HtmlNodeName> {
111 match name {
112 NodeName::Block(_block) => unreachable!(),
113 NodeName::Path(path) => Ok(HtmlNodeName::Ident(expr_path_to_ident(path).map_err(
114 |path| {
115 syn::Error::new_spanned(
116 path,
117 "Expected either identifier or a punctuated name. \
118 NOTE: you can try to prefix this with `raw:`",
119 )
120 },
121 )?)),
122 NodeName::Punctuated(pname) => 'not_raw: {
123 use rstml::node::NodeNameFragment;
124 use syn::punctuated::Pair;
125 'raw: {
126 if pname.len() < 2 {
127 break 'raw;
128 }
129
130 let Some(Pair::Punctuated(NodeNameFragment::Ident(ident), punct)) =
131 pname.pairs().next()
132 else {
133 break 'raw;
134 };
135
136 if ident != "raw" || punct.as_char() != ':' {
137 break 'raw;
138 }
139
140 let mut pairs = pname.into_pairs();
141
142 let Pair::Punctuated(NodeNameFragment::Ident(raw), colon) = pairs.next().unwrap()
143 else {
144 unreachable!()
145 };
146 break 'not_raw Ok(HtmlNodeName::Raw(RawHtmlNodeName {
147 raw_token: kw::raw(raw.span()),
148 colon_token: {
149 type Colon = Token![:];
150 Colon {
151 spans: [colon.span()],
152 }
153 },
154 punctuated: pairs.collect(),
155 }));
156 }
157
158 Ok(HtmlNodeName::Punctuated(pname))
159 }
160 }
161}
162
163fn is_html_element(name: &NodeName) -> bool {
164 match name {
165 NodeName::Path(syn::ExprPath {
166 qself: None,
167 attrs,
168 path,
169 }) if attrs.is_empty() => {
170 let Some(ident) = path.get_ident() else {
171 return false;
172 };
173 html_elements_set().contains(&*ident.to_string())
174 }
175 NodeName::Path(_) => false,
176 NodeName::Block(_) => false,
177 NodeName::Punctuated(_) => true,
178 }
179}
180
181fn is_name_ident(name: &NodeName, ident: &str) -> bool {
182 match name {
183 NodeName::Path(syn::ExprPath {
184 qself: None,
185 attrs,
186 path,
187 }) if attrs.is_empty() => {
188 let Some(name_ident) = path.get_ident() else {
189 return false;
190 };
191 name_ident == ident
192 }
193 _ => false,
194 }
195}
196
197fn crate_path(span: Span) -> syn::Path {
204 syn::parse_quote_spanned!(span => ::russx)
205}
206
207fn parse_node(node: RstmlNode) -> syn::Result<Node> {
208 match node {
209 RstmlNode::Comment(comment) => Ok(Node::Comment(comment)),
210 RstmlNode::Doctype(doctype) => Ok(Node::Doctype(doctype)),
211 RstmlNode::Fragment(fragment) => Ok(Node::Fragment(NodeFragment {
212 open_tag: fragment.tag_open,
213 children: fragment
214 .children
215 .into_iter()
216 .map(parse_node)
217 .collect::<syn::Result<_>>()?,
218 close_tag: fragment.tag_close,
219 })),
220 RstmlNode::Element(node) if is_name_ident(node.name(), "else") => Err(
221 syn::Error::new_spanned(node, "unexpected `<else ...>` node"),
222 ),
223 RstmlNode::Element(node) if is_name_ident(node.name(), "prop") => Err(
224 syn::Error::new_spanned(node, "unexpected `<prop ...></_>` node"),
225 ),
226 RstmlNode::Element(rstml::node::NodeElement {
227 open_tag:
228 rstml::node::atoms::OpenTag {
229 token_lt: open_tag_start,
230 name,
231 generics,
232 attributes,
233 end_tag: open_tag_end,
234 },
235 children,
236 close_tag,
237 }) if is_name_ident(&name, "_") => {
238 if generics.lt_token.is_some() {
239 return Err(syn::Error::new_spanned(generics, "unexpected generics"));
240 }
241 if !children.is_empty() {
242 return Err(syn::Error::new_spanned(
243 quote! { #(#children)* },
244 "discard elements cannot have children",
245 ));
246 }
247 if close_tag.is_some() {
248 return Err(syn::Error::new_spanned(
249 close_tag,
250 "discard elements must self close `/>`, separate close tags aren't permitted",
251 ));
252 }
253
254 let value = match <[_; 1]>::try_from(attributes) {
255 Ok([NodeAttribute::Block(value)]) => value,
256 Ok([attr]) => {
257 return Err(syn::Error::new_spanned(
258 quote! { #name #attr },
259 r#"expected a single attribute. e.g. `<_ {x = 4}>`"#,
260 ))
261 }
262 Err(attrs) => {
263 return Err(syn::Error::new_spanned(
264 quote! { #name #(#attrs)* },
265 r#"expected a single attribute. e.g. `<_ {x = 4}>`"#,
266 ))
267 }
268 };
269
270 Ok(Node::DiscardElement(DiscardNodeElement {
271 start: open_tag_start,
272 discard_token: syn::parse2(name.to_token_stream())?,
273 value,
274 end: open_tag_end,
275 }))
276 }
277 RstmlNode::Element(rstml::node::NodeElement {
278 open_tag:
279 rstml::node::atoms::OpenTag {
280 token_lt: open_tag_start,
281 name,
282 generics,
283 attributes,
284 end_tag: open_tag_end,
285 },
286 children,
287 close_tag,
288 }) if is_name_ident(&name, "trust") => {
289 if generics.lt_token.is_some() {
290 return Err(syn::Error::new_spanned(generics, "unexpected generics"));
291 }
292 if !children.is_empty() {
293 return Err(syn::Error::new_spanned(
294 quote! { #(#children)* },
295 "trust elements cannot have children",
296 ));
297 }
298 if close_tag.is_some() {
299 return Err(syn::Error::new_spanned(
300 close_tag,
301 "trust elements must self close `/>`, separate close tags aren't permitted",
302 ));
303 }
304
305 let value = match <[_; 1]>::try_from(attributes) {
306 Ok([NodeAttribute::Block(value)]) => value,
307 Ok([attr]) => {
308 return Err(syn::Error::new_spanned(
309 quote! { #name #attr },
310 r#"expected a single value. e.g. `<trust {"<p>Hello</p>"}>`"#,
311 ))
312 }
313 Err(attrs) => {
314 return Err(syn::Error::new_spanned(
315 quote! { #name #(#attrs)* },
316 r#"expected a single value. e.g. `<trust {"<p>Hello</p>"}>`"#,
317 ))
318 }
319 };
320
321 Ok(Node::TrustElement(TrustNodeElement {
322 start: open_tag_start,
323 trust_token: { kw::trust(name.span()) },
324 value,
325 end: open_tag_end,
326 }))
327 }
328 RstmlNode::Element(rstml::node::NodeElement {
329 open_tag:
330 rstml::node::atoms::OpenTag {
331 token_lt: open_tag_start,
332 name,
333 generics,
334 attributes,
335 end_tag: open_tag_end,
336 },
337 children,
338 close_tag,
339 }) if is_name_ident(&name, "let") => {
340 if generics.lt_token.is_some() {
341 return Err(syn::Error::new_spanned(generics, "unexpected generics"));
342 }
343 if !children.is_empty() {
344 return Err(syn::Error::new_spanned(
345 quote! { #(#children)* },
346 "let elements cannot have children",
347 ));
348 }
349 if close_tag.is_some() {
350 return Err(syn::Error::new_spanned(
351 close_tag,
352 "let elements must self close `/>`, separate close tags aren't permitted",
353 ));
354 }
355
356 let (var_token, (binding_paren, binding), value) =
357 match <[_; 2]>::try_from(attributes) {
358 Ok(
359 [NodeAttribute::Attribute(KeyedAttribute {
360 key,
361 possible_value: KeyedAttributeValue::Binding(binding),
362 }), NodeAttribute::Block(value)],
363 ) if is_name_ident(&key, "var") => (
364 kw::var(key.span()),
365 fn_binding_to_pat(binding, true)?,
366 value,
367 ),
368 Ok([attr, attr2]) => return Err(syn::Error::new_spanned(
369 quote! { #name #attr # attr2 },
370 "expected a binding to var and a value. e.g. `<let var(pat: ty) {value}>`",
371 )),
372 Err(attrs) => return Err(syn::Error::new_spanned(
373 quote! { #name #(#attrs)* },
374 "expected a binding to var and a value. e.g. `<let var(pat: ty) {value}>`",
375 )),
376 };
377
378 Ok(Node::LetElement(LetNodeElement {
379 start: open_tag_start,
380 let_token: syn::parse2(name.to_token_stream())?,
381 var_token,
382 binding_paren,
383 binding,
384 value,
385 end: open_tag_end,
386 }))
387 }
388 RstmlNode::Element(rstml::node::NodeElement {
389 open_tag:
390 rstml::node::atoms::OpenTag {
391 token_lt: open_tag_start,
392 name,
393 generics,
394 attributes,
395 end_tag: open_tag_end,
396 },
397 children,
398 close_tag,
399 }) if is_name_ident(&name, "for") => {
400 if generics.lt_token.is_some() {
401 return Err(syn::Error::new_spanned(generics, "unexpected generics"));
402 }
403
404 let (each_token, (binding_paren, binding), in_token, iter) =
405 match <[_; 3]>::try_from(attributes) {
406 Ok(
407 [NodeAttribute::Attribute(KeyedAttribute {
408 key: each_token,
409 possible_value: KeyedAttributeValue::Binding(binding),
410 }), NodeAttribute::Attribute(KeyedAttribute {
411 key: in_token,
412 possible_value: KeyedAttributeValue::None,
413 }), NodeAttribute::Block(value)],
414 ) if is_name_ident(&each_token, "each") && is_name_ident(&in_token, "in") => (
415 kw::each(each_token.span()),
416 fn_binding_to_pat(binding, false)?,
417 {
418 type In = Token![in];
419 In { span: name.span() }
420 },
421 value,
422 ),
423 Ok(attrs) => return Err(syn::Error::new_spanned(
424 quote! { #name #(#attrs)* },
425 "expected a binding to var and a value. e.g. `<for each(pat) in {value}>`",
426 )),
427 Err(attrs) => return Err(syn::Error::new_spanned(
428 quote! { #name #(#attrs)* },
429 "expected a binding to var and a value. e.g. `<for each(pat) in {value}>`",
430 )),
431 };
432
433 Ok(Node::ForElement(ForNodeElement {
434 open_tag: ForOpenTag {
435 start: open_tag_start,
436 for_token: syn::parse2(name.to_token_stream())?,
437 each_token,
438 binding_paren,
439 binding,
440 in_token,
441 iter,
442 end: open_tag_end,
443 },
444 children: children
445 .into_iter()
446 .map(parse_node)
447 .collect::<syn::Result<_>>()?,
448 close_tag: close_tag.map(KwCloseTag::from_rstml).transpose()?,
449 }))
450 }
451 RstmlNode::Element(rstml::node::NodeElement {
452 open_tag:
453 rstml::node::atoms::OpenTag {
454 token_lt: open_tag_start,
455 name: match_token,
456 generics,
457 attributes,
458 end_tag: open_tag_end,
459 },
460 children,
461 close_tag,
462 }) if is_name_ident(&match_token, "match") => {
463 if generics.lt_token.is_some() {
464 return Err(syn::Error::new_spanned(generics, "unexpected generics"));
465 }
466
467 Ok(Node::MatchElement(MatchNodeElement {
468 open_tag: MatchOpenTag {
469 start: open_tag_start,
470 match_token: syn::parse2(match_token.to_token_stream())?,
471 value: 'not_error: {
472 let attrs = {
473 match <[_; 1]>::try_from(attributes) {
474 Ok([NodeAttribute::Block(value)]) => break 'not_error value,
475 Ok(attrs) => attrs.into(),
476 Err(attrs) => attrs,
477 }
478 };
479 return Err(syn::Error::new_spanned(
480 quote! { #match_token #(#attrs)* },
481 "expected one of the following `<if {cond}>` or `<if let(pat) {cond}>`",
482 ));
483 },
484 end: open_tag_end,
485 },
486 arms: {
487 let mut arms = vec![];
488 for child in children {
489 match child {
490 RstmlNode::Element(rstml::node::NodeElement {
491 open_tag:
492 rstml::node::atoms::OpenTag {
493 token_lt: open_tag_start,
494 name: on_token,
495 generics,
496 attributes,
497 end_tag: open_tag_end,
498 },
499 children,
500 close_tag,
501 }) if is_name_ident(&on_token, "on") => {
502 if generics.lt_token.is_some() {
503 return Err(syn::Error::new_spanned(
504 generics,
505 "unexpected generics",
506 ));
507 }
508 if !children.is_empty() {
509 return Err(syn::Error::new_spanned(
510 quote! { #(#children)* },
511 "let elements cannot have children",
512 ));
513 }
514 if close_tag.is_some() {
515 return Err(syn::Error::new_spanned(
516 close_tag,
517 "let elements must self close `/>`, \
518 separate close tags aren't permitted",
519 ));
520 }
521
522 arms.push((
523 'not_error: {
524 let attrs = match attributes.len() {
525 1 => match <[_; 1]>::try_from(attributes).unwrap() {
526 [NodeAttribute::Attribute(KeyedAttribute {
527 key: case_token,
528 possible_value:
529 KeyedAttributeValue::Binding(binding),
530 })] if is_name_ident(&case_token, "case") => {
531 let (binding_paren, binding) =
532 fn_binding_to_pat(binding, false)?;
533 break 'not_error MatchArmTag {
534 start: open_tag_start,
535 on_token: syn::parse2(
536 on_token.to_token_stream(),
537 )?,
538 case_token: syn::parse2(
539 case_token.to_token_stream(),
540 )?,
541 binding_paren,
542 binding,
543 guard: None,
544 end: open_tag_end,
545 };
546 }
547 attrs => attrs.into(),
548 },
549 3 => match <[_; 3]>::try_from(attributes).unwrap() {
550 [NodeAttribute::Attribute(KeyedAttribute {
551 key: case_token,
552 possible_value:
553 KeyedAttributeValue::Binding(binding),
554 }), NodeAttribute::Attribute(KeyedAttribute {
555 key: if_token,
556 possible_value: KeyedAttributeValue::None,
557 }), NodeAttribute::Block(if_cond)]
558 if is_name_ident(&case_token, "case")
559 && is_name_ident(&if_token, "if") =>
560 {
561 let (binding_paren, binding) =
562 fn_binding_to_pat(binding, false)?;
563 break 'not_error MatchArmTag {
564 start: open_tag_start,
565 on_token: syn::parse2(
566 on_token.to_token_stream(),
567 )?,
568 case_token: syn::parse2(
569 case_token.to_token_stream(),
570 )?,
571 binding_paren,
572 binding,
573 guard: Some((
574 syn::parse2(
575 if_token.to_token_stream(),
576 )?,
577 if_cond,
578 )),
579 end: open_tag_end,
580 };
581 }
582 attrs => attrs.into(),
583 },
584 _ => attributes,
585 };
586 return Err(syn::Error::new_spanned(
587 quote! { #on_token #(#attrs)* },
588 "expect an on-case node, \
589 e.g. `<on case(pat)>` or `<on case(pat) if {cond}>`",
590 ));
591 },
592 vec![],
593 ));
594 }
595 node => {
596 let Some((_, arm_children)) = arms.last_mut() else {
597 return Err(syn::Error::new_spanned(
598 node,
599 "expect an on-case node, \
600 e.g. `<on case(pat)>` or `<on case(pat) if {cond}>`",
601 ));
602 };
603 arm_children.push(parse_node(node)?);
604 }
605 }
606 }
607
608 arms
609 },
610 close_tag: close_tag.map(KwCloseTag::from_rstml).transpose()?,
611 }))
612 }
613 RstmlNode::Element(rstml::node::NodeElement {
614 open_tag:
615 rstml::node::atoms::OpenTag {
616 token_lt: open_tag_start,
617 name,
618 generics,
619 attributes,
620 end_tag: open_tag_end,
621 },
622 children,
623 close_tag,
624 }) if is_name_ident(&name, "if") => {
625 if generics.lt_token.is_some() {
626 return Err(syn::Error::new_spanned(generics, "unexpected generics"));
627 }
628
629 let (let_binding, value) = 'not_error: {
630 let attrs = {
631 match <[_; 1]>::try_from(attributes) {
632 Ok([NodeAttribute::Block(value)]) => break 'not_error (None, value),
633 Ok(attrs) => attrs.into(),
634 Err(attrs) => match <[_; 2]>::try_from(attrs) {
635 Ok(
636 [NodeAttribute::Attribute(KeyedAttribute {
637 key,
638 possible_value: KeyedAttributeValue::Binding(binding),
639 }), NodeAttribute::Block(value)],
640 ) if is_name_ident(&key, "let") => {
641 let (binding_paren, binding) = fn_binding_to_pat(binding, false)?;
642 break 'not_error (
643 Some(IfLetBinding {
644 let_token: {
645 type Let = Token![let];
646 Let { span: key.span() }
647 },
648 binding_paren,
649 binding,
650 }),
651 value,
652 );
653 }
654 Ok(attrs) => attrs.into(),
655 Err(attrs) => attrs,
656 },
657 }
658 };
659 return Err(syn::Error::new_spanned(
660 quote! { #name #(#attrs)* },
661 "expected one of the following `<if {cond}>` or `<if let(pat) {cond}>`",
662 ));
663 };
664
665 let mut children = children.into_iter();
666
667 let mut if_section = vec![];
668 let mut sep = None;
669 for child in &mut children {
670 let RstmlNode::Element(child) = child else {
671 if_section.push(parse_node(child)?);
672 continue;
673 };
674 if !is_name_ident(child.name(), "else") {
675 if_section.push(parse_node(RstmlNode::Element(child))?);
676 continue;
677 }
678 sep = Some(child);
679 break;
680 }
681
682 let mut else_if_sections = vec![];
683 let mut else_section = None;
684 while let Some(sep_el) = sep.take() {
685 match <[_; 2]>::try_from(sep_el.open_tag.attributes) {
686 Ok(
687 [NodeAttribute::Attribute(KeyedAttribute {
688 key: if_token,
689 possible_value: KeyedAttributeValue::None,
690 }), NodeAttribute::Block(value)],
691 ) if is_name_ident(&if_token, "if") => {
692 let else_if_tag = ElseIfTag {
693 start: sep_el.open_tag.token_lt,
694 else_token: {
695 type Else = Token![else];
696 Else {
697 span: sep_el.open_tag.name.span(),
698 }
699 },
700 if_token: {
701 type If = Token![if];
702 If {
703 span: if_token.span(),
704 }
705 },
706 let_binding: None,
707 value,
708 end: sep_el.open_tag.end_tag,
709 };
710 let mut else_if_section = vec![];
711 for child in &mut children {
712 let RstmlNode::Element(child) = child else {
713 else_if_section.push(parse_node(child)?);
714 continue;
715 };
716 if !is_name_ident(child.name(), "else") {
717 else_if_section.push(parse_node(RstmlNode::Element(child))?);
718 continue;
719 }
720 sep = Some(child);
721 break;
722 }
723 else_if_sections.push((else_if_tag, else_if_section));
724 }
725 Ok([attr, _]) => {
726 return Err(syn::Error::new_spanned(
727 attr,
728 "expected either `<else if let(pat) {}>`, `<else if {}>` or `<else>`",
729 ));
730 }
731 Err(attrs) => match <[_; 3]>::try_from(attrs) {
732 Ok(
733 [NodeAttribute::Attribute(KeyedAttribute {
734 key: if_token,
735 possible_value: KeyedAttributeValue::None,
736 }), NodeAttribute::Attribute(KeyedAttribute {
737 key: let_token,
738 possible_value: KeyedAttributeValue::Binding(binding),
739 }), NodeAttribute::Block(value)],
740 ) if is_name_ident(&if_token, "if") && is_name_ident(&let_token, "let") => {
741 let (binding_paren, binding) = fn_binding_to_pat(binding, false)?;
742 let else_if_tag = ElseIfTag {
743 start: sep_el.open_tag.token_lt,
744 else_token: {
745 type Else = Token![else];
746 Else {
747 span: sep_el.open_tag.name.span(),
748 }
749 },
750 if_token: {
751 type If = Token![if];
752 If {
753 span: if_token.span(),
754 }
755 },
756 let_binding: Some(IfLetBinding {
757 let_token: {
758 type Let = syn::Token![let];
759 Let {
760 span: let_token.span(),
761 }
762 },
763 binding_paren,
764 binding,
765 }),
766 value,
767 end: sep_el.open_tag.end_tag,
768 };
769 let mut else_if_section = vec![];
770 for child in &mut children {
771 let RstmlNode::Element(child) = child else {
772 else_if_section.push(parse_node(child)?);
773 continue;
774 };
775 if !is_name_ident(child.name(), "else") {
776 else_if_section.push(parse_node(RstmlNode::Element(child))?);
777 continue;
778 }
779 sep = Some(child);
780 break;
781 }
782 else_if_sections.push((else_if_tag, else_if_section));
783 }
784 Ok([attr, _, _]) => {
785 return Err(syn::Error::new_spanned(
786 attr,
787 "expected either `<else if let(pat) {}>`, `<else if {}>` or `<else>`",
788 ));
789 }
790 Err(attrs) if attrs.is_empty() => {
791 else_section = Some((
792 ElseTag {
793 start: sep_el.open_tag.token_lt,
794 else_token: {
795 type Else = Token![else];
796 Else {
797 span: sep_el.open_tag.name.span(),
798 }
799 },
800 end: sep_el.open_tag.end_tag,
801 },
802 (&mut children)
803 .map(parse_node)
804 .collect::<syn::Result<_>>()?,
805 ));
806 }
807 Err(attrs) => {
808 return Err(syn::Error::new_spanned(
809 &attrs[0],
810 "expected either `<else if let(pat) {}>`, `<else if {}>` or `<else>`",
811 ));
812 }
813 },
814 }
815 }
816
817 Ok(Node::IfElement(IfNodeElement {
818 open_tag: IfOpenTag {
819 start: open_tag_start,
820 if_token: {
821 type If = Token![if];
822 If { span: name.span() }
823 },
824 let_binding,
825 value,
826 end: open_tag_end,
827 },
828 if_section,
829 else_if_sections,
830 else_section,
831 close_tag: close_tag.map(KwCloseTag::from_rstml).transpose()?,
832 }))
833 }
834 RstmlNode::Element(rstml::node::NodeElement {
835 open_tag:
836 rstml::node::atoms::OpenTag {
837 token_lt: open_tag_start,
838 name,
839 generics,
840 attributes,
841 end_tag: open_tag_end,
842 },
843 children,
844 close_tag,
845 }) if is_html_element(&name) && generics.lt_token.is_none() => {
846 Ok(Node::HtmlElement(HtmlNodeElement {
847 open_tag: HtmlOpenTag {
848 start: open_tag_start,
849 name: node_name_to_html_name(name)?,
850 attributes: attributes
851 .into_iter()
852 .map(|attr| match attr {
853 NodeAttribute::Block(block) => {
854 Ok(HtmlNodeAttribute::Block(block))
855 }
860 NodeAttribute::Attribute(attr) => {
861 Ok(HtmlNodeAttribute::Keyed(KeyedHtmlNodeAttribute {
862 key: node_name_to_html_name(attr.key)?,
863 value: match attr.possible_value {
864 KeyedAttributeValue::None => None,
865 KeyedAttributeValue::Binding(binding) => {
866 return Err(syn::Error::new_spanned(
867 binding,
868 "unexpected binding attribute",
869 ))
870 }
871 KeyedAttributeValue::Value(value) => {
872 Some(NodeAttributeValue {
873 eq_token: value.token_eq,
874 value: value.value,
875 })
876 }
877 },
878 }))
879 }
880 })
881 .collect::<syn::Result<_>>()?,
882 end: open_tag_end,
883 },
884 children: children
885 .into_iter()
886 .map(parse_node)
887 .collect::<syn::Result<_>>()?,
888 close_tag: match close_tag {
889 Some(close_tag) => Some(HtmlCloseTag {
890 start: close_tag.start_tag,
891 name: node_name_to_html_name(close_tag.name)?,
892 end: close_tag.token_gt,
893 }),
894 None => None,
895 },
896 }))
897 }
898 RstmlNode::Element(rstml::node::NodeElement {
899 open_tag:
900 rstml::node::atoms::OpenTag {
901 token_lt: open_tag_start,
902 name: NodeName::Block(name_block),
903 generics,
904 attributes,
905 end_tag: open_tag_end,
906 },
907 children,
908 close_tag,
909 }) => {
910 if generics.lt_token.is_some() {
911 return Err(syn::Error::new_spanned(generics, "unexpected generics"));
912 }
913 if let Some(attr) = attributes.first() {
914 return Err(syn::Error::new_spanned(
915 attr,
916 "dynamic templates cannot take any attributes",
917 ));
918 }
919 if !children.is_empty() {
920 return Err(syn::Error::new_spanned(
921 quote! { #(#children)* },
922 "dynamic templates cannot have children",
923 ));
924 }
925 if close_tag.is_some() {
926 return Err(syn::Error::new_spanned(
927 close_tag,
928 "dynamic templates must self close `/>`, separate close tags aren't permitted",
929 ));
930 }
931
932 Ok(Node::DynTmplElement(DynTmplNodeElement {
933 start: open_tag_start,
934 name_block,
935 end1: open_tag_end.token_solidus.unwrap(),
936 end2: open_tag_end.token_gt,
937 }))
938 }
939 RstmlNode::Element(rstml::node::NodeElement {
940 open_tag:
941 rstml::node::atoms::OpenTag {
942 token_lt: open_tag_start,
943 name,
944 generics,
945 attributes,
946 end_tag: open_tag_end,
947 },
948 children,
949 close_tag,
950 }) => {
951 if generics.lt_token.is_some() {
952 return Err(syn::Error::new_spanned(generics, "unexpected generics"));
953 }
954 let NodeName::Path(name) = name else {
955 return Err(syn::Error::new_spanned(name, "expected a path name"));
956 };
957
958 let mut new_children = vec![];
959 let mut prop_children = vec![];
960
961 for child in children {
962 match child {
963 RstmlNode::Element(rstml::node::NodeElement {
964 open_tag:
965 rstml::node::atoms::OpenTag {
966 token_lt: open_tag_start,
967 name,
968 generics,
969 attributes,
970 end_tag: open_tag_end,
971 },
972 children,
973 close_tag,
974 }) if is_name_ident(&name, "prop") => {
975 if generics.lt_token.is_some() {
976 return Err(syn::Error::new_spanned(generics, "unexpected generics"));
977 }
978 const EXPECTED_IDENT: &str =
979 "expected a single identifier, the property's name";
980 let attributes = <[_; 1]>::try_from(attributes).map_err(|attributes| {
981 syn::Error::new_spanned(
982 quote! { #name #(#attributes)* },
983 EXPECTED_IDENT,
984 )
985 })?;
986 let [NodeAttribute::Attribute(KeyedAttribute {
987 key: NodeName::Path(attr),
988 possible_value: KeyedAttributeValue::None,
989 })] = attributes
990 else {
991 return Err(syn::Error::new_spanned(
992 quote! { #name #(#attributes)* },
993 EXPECTED_IDENT,
994 ));
995 };
996 let ident = expr_path_to_ident(attr)
997 .map_err(|attr| syn::Error::new_spanned(attr, EXPECTED_IDENT))?;
998 prop_children.push(PropNodeElement {
999 open_tag: PropOpenTag {
1000 start: open_tag_start,
1001 prop_token: kw::prop(name.span()),
1002 name: ident,
1003 end: open_tag_end,
1004 },
1005 children: children
1006 .into_iter()
1007 .map(parse_node)
1008 .collect::<syn::Result<_>>()?,
1009 close_tag: close_tag.map(KwCloseTag::from_rstml).transpose()?,
1010 })
1011 }
1012 _ => new_children.push(parse_node(child)?),
1013 }
1014 }
1015
1016 Ok(Node::StaticTmplElement(StaticTmplNodeElement {
1017 open_tag: StaticTmplOpenTag {
1018 start: open_tag_start,
1019 name,
1020 attributes: attributes
1021 .into_iter()
1022 .map(|attr| match attr {
1023 NodeAttribute::Block(block) => {
1024 Err(syn::Error::new_spanned(block, "unexpected block attr"))
1025 }
1026 NodeAttribute::Attribute(attr) => {
1027 Ok(StaticTmplNodeAttribute {
1028 key: match attr.key {
1029 NodeName::Path(path) => {
1030 expr_path_to_ident(path).map_err(|path| {
1031 syn::Error::new_spanned(
1032 path,
1033 "template argument name must be a single identifier",
1034 )
1035 })?
1036 }
1037 name => return Err(syn::Error::new_spanned(
1038 name,
1039 "template argument name must be a single identifier",
1040 )),
1041 },
1042 value: match attr.possible_value {
1043 KeyedAttributeValue::None => None,
1044 KeyedAttributeValue::Value(value) => {
1045 Some(NodeAttributeValue {
1046 eq_token: value.token_eq,
1047 value: value.value,
1048 })
1049 }
1050 KeyedAttributeValue::Binding(binding) => {
1051 return Err(syn::Error::new_spanned(
1052 binding,
1053 "unexpected binding attribute",
1054 ))
1055 }
1056 },
1057 })
1058 }
1059 })
1060 .collect::<syn::Result<_>>()?,
1061 end: open_tag_end,
1062 },
1063 children: new_children,
1064 prop_children,
1065 close_tag: match close_tag {
1066 Some(close_tag) => Some(StaticTmplCloseTag {
1067 start: close_tag.start_tag,
1068 name: {
1069 let NodeName::Path(name) = close_tag.name else {
1070 unreachable!();
1071 };
1072 name
1073 },
1074 end: close_tag.token_gt,
1075 }),
1076 None => None,
1077 },
1078 }))
1079 }
1080 RstmlNode::Block(block) => Ok(Node::Block(block)),
1081 RstmlNode::Text(text) => Ok(Node::Text(text)),
1082 RstmlNode::RawText(text) => Ok(Node::RawText(text)),
1083 }
1084}
1085
1086struct NodeBody(pub Vec<Node>);
1087
1088impl Parse for NodeBody {
1089 fn parse(input: ParseStream) -> syn::Result<Self> {
1090 if input.is_empty() {
1091 Ok(Self(vec![]))
1092 } else {
1093 let nodes = rstml::Parser::new(
1094 rstml::ParserConfig::new()
1095 .recover_block(true)
1096 .raw_text_elements(["style", "script"].into())
1097 .always_self_closed_elements(
1098 [
1099 "arena", "base", "br", "col", "embed", "hr", "img", "input", "link",
1100 "meta", "param", "source", "track", "wbr", "else", "on", "let", "_", "trust", ]
1103 .into(),
1104 )
1105 .element_close_use_default_wildcard_ident(false),
1106 )
1107 .parse_syn_stream(input)
1108 .into_result()?;
1109
1110 Ok(Self(
1111 nodes
1112 .into_iter()
1113 .map(parse_node)
1114 .collect::<syn::Result<_>>()?,
1115 ))
1116 }
1117 }
1118}
1119
1120impl ToTokens for NodeBody {
1121 fn to_tokens(&self, tokens: &mut TokenStream) {
1122 let Self(nodes) = self;
1123
1124 for node in nodes {
1125 node.to_tokens(tokens);
1126 }
1127 }
1128}
1129
1130#[allow(dead_code)]
1131enum TmplPropMeta {
1132 Into {
1133 into_token: kw::into,
1134 },
1135 Default {
1136 default_token: Token![default],
1137 },
1138 DefaultValue {
1139 default_token: Token![default],
1140 eq_token: Token![=],
1141 value: syn::Expr,
1142 },
1143 Optional {
1144 optional_token: kw::optional,
1145 },
1146 Toggle {
1147 toggle_token: kw::toggle,
1148 },
1149}
1150
1151impl Parse for TmplPropMeta {
1152 fn parse(input: ParseStream) -> syn::Result<Self> {
1153 if input.peek(kw::into) {
1154 Ok(Self::Into {
1155 into_token: input.parse()?,
1156 })
1157 } else if input.peek(Token![default]) {
1158 let default_token = input.parse()?;
1159 if input.peek(Token![=]) {
1160 Ok(Self::DefaultValue {
1161 default_token,
1162 eq_token: input.parse()?,
1163 value: input.parse()?,
1164 })
1165 } else {
1166 Ok(Self::Default { default_token })
1167 }
1168 } else if input.peek(kw::optional) {
1169 Ok(Self::Optional {
1170 optional_token: input.parse()?,
1171 })
1172 } else if input.peek(kw::toggle) {
1173 Ok(Self::Toggle {
1174 toggle_token: input.parse()?,
1175 })
1176 } else {
1177 Err(syn::Error::new(
1178 input.span(),
1179 "unexpected token, see documentation",
1180 ))
1181 }
1182 }
1183}
1184
1185impl ToTokens for TmplPropMeta {
1186 fn to_tokens(&self, tokens: &mut TokenStream) {
1187 match self {
1188 TmplPropMeta::Into { into_token } => into_token.to_tokens(tokens),
1189 TmplPropMeta::Default { default_token } => default_token.to_tokens(tokens),
1190 TmplPropMeta::DefaultValue {
1191 default_token,
1192 eq_token,
1193 value,
1194 } => {
1195 default_token.to_tokens(tokens);
1196 eq_token.to_tokens(tokens);
1197 value.to_tokens(tokens);
1198 }
1199 TmplPropMeta::Optional { optional_token } => optional_token.to_tokens(tokens),
1200 TmplPropMeta::Toggle { toggle_token } => toggle_token.to_tokens(tokens),
1201 }
1202 }
1203}
1204
1205#[allow(dead_code)]
1206struct TmplProp {
1207 pound_token: Token![#],
1208 style: syn::AttrStyle,
1209 bracket_token: token::Bracket,
1210 prop_token: kw::prop,
1211 delimiter: syn::MacroDelimiter,
1212 meta_inner: Punctuated<TmplPropMeta, Token![,]>,
1213}
1214
1215impl ToTokens for TmplProp {
1216 fn to_tokens(&self, tokens: &mut TokenStream) {
1217 let Self {
1218 pound_token,
1219 style,
1220 bracket_token,
1221 prop_token,
1222 delimiter,
1223 meta_inner,
1224 } = self;
1225
1226 pound_token.to_tokens(tokens);
1227 if let syn::AttrStyle::Inner(tk) = style {
1228 tk.to_tokens(tokens);
1229 }
1230 bracket_token.surround(tokens, |tokens| {
1231 prop_token.to_tokens(tokens);
1232 let f = |tokens: &mut _| {
1233 meta_inner.to_tokens(tokens);
1234 };
1235 match delimiter {
1236 syn::MacroDelimiter::Paren(delim) => delim.surround(tokens, f),
1237 syn::MacroDelimiter::Brace(delim) => delim.surround(tokens, f),
1238 syn::MacroDelimiter::Bracket(delim) => delim.surround(tokens, f),
1239 }
1240 })
1241 }
1242}
1243
1244struct TmplArg {
1245 pub prop_attrs: Vec<TmplProp>,
1246 pub attrs: Vec<syn::Attribute>,
1247 pub pat: syn::PatIdent,
1248 pub colon_token: Token![:],
1249 pub ty: Box<syn::Type>,
1250}
1251
1252impl Parse for TmplArg {
1253 fn parse(input: ParseStream) -> syn::Result<Self> {
1254 let arg: syn::FnArg = input.parse()?;
1255
1256 let syn::FnArg::Typed(arg) = arg else {
1257 return Err(syn::Error::new_spanned(
1258 arg,
1259 "`self` parameter is not allowed in templates",
1260 ));
1261 };
1262
1263 let (prop_attrs, attrs): (Vec<_>, _) = arg
1264 .attrs
1265 .into_iter()
1266 .partition(|attr| attr.path().is_ident("prop"));
1267
1268 Ok(Self {
1269 attrs,
1270 prop_attrs: prop_attrs
1271 .into_iter()
1272 .map(|attr| -> syn::Result<_> {
1273 let meta_inner = attr.parse_args_with(Punctuated::parse_terminated)?;
1274 let syn::Meta::List(mut meta) = attr.meta else {
1275 unreachable!()
1276 };
1277
1278 Ok(TmplProp {
1279 pound_token: attr.pound_token,
1280 style: attr.style,
1281 bracket_token: attr.bracket_token,
1282 prop_token: kw::prop(
1283 meta.path.segments.pop().unwrap().into_value().ident.span(),
1284 ),
1285 delimiter: meta.delimiter,
1286 meta_inner,
1287 })
1288 })
1289 .collect::<syn::Result<_>>()?,
1290 pat: match *arg.pat {
1291 syn::Pat::Ident(pat) => pat,
1292 pat => return Err(syn::Error::new_spanned(
1293 pat,
1294 "Only `prop: bool` or `prop @ (x, y): (u32, u32)` style properties are allowed",
1295 )),
1296 },
1297 colon_token: arg.colon_token,
1298 ty: arg.ty,
1299 })
1300 }
1301}
1302
1303impl ToTokens for TmplArg {
1304 fn to_tokens(&self, tokens: &mut TokenStream) {
1305 let Self {
1306 prop_attrs,
1307 attrs,
1308 pat,
1309 colon_token,
1310 ty,
1311 } = self;
1312
1313 for prop in prop_attrs {
1314 prop.to_tokens(tokens);
1315 }
1316 for attr in attrs {
1317 attr.to_tokens(tokens);
1318 }
1319 pat.to_tokens(tokens);
1320 colon_token.to_tokens(tokens);
1321 ty.to_tokens(tokens);
1322 }
1323}
1324
1325#[allow(dead_code)]
1326struct ItemTmpl {
1327 pub attrs: Vec<syn::Attribute>,
1328 pub vis: syn::Visibility,
1329
1330 pub fn_token: Token![fn],
1331 pub ident: syn::Ident,
1332 pub generics: syn::Generics,
1333 pub paren_token: token::Paren,
1334 pub inputs: Punctuated<TmplArg, Token![,]>,
1335
1336 pub brace_token: token::Brace,
1337 pub body: NodeBody,
1338}
1339
1340impl Parse for ItemTmpl {
1341 fn parse(input: ParseStream) -> syn::Result<Self> {
1342 let mut generics: syn::Generics;
1343 let args_content;
1344 let body_content;
1345
1346 Ok(Self {
1347 attrs: syn::Attribute::parse_outer(input)?,
1348 vis: input.parse()?,
1349 fn_token: input.parse()?,
1350 ident: input.parse()?,
1351 paren_token: {
1352 generics = input.parse()?;
1353 parenthesized!(args_content in input)
1354 },
1355 inputs: Punctuated::parse_terminated(&args_content)?,
1356 generics: {
1357 generics.where_clause = input.parse()?;
1358 generics
1359 },
1360 brace_token: braced!(body_content in input),
1361 body: body_content.parse()?,
1362 })
1363 }
1364}
1365
1366impl ToTokens for ItemTmpl {
1367 fn to_tokens(&self, tokens: &mut TokenStream) {
1368 let Self {
1369 attrs,
1370 vis,
1371 fn_token,
1372 ident,
1373 generics,
1374 paren_token,
1375 inputs,
1376 brace_token,
1377 body,
1378 } = self;
1379
1380 for attr in attrs {
1381 attr.to_tokens(tokens);
1382 }
1383 vis.to_tokens(tokens);
1384 fn_token.to_tokens(tokens);
1385 ident.to_tokens(tokens);
1386 generics.to_tokens(tokens);
1387 paren_token.surround(tokens, |tokens| inputs.to_tokens(tokens));
1388 brace_token.surround(tokens, |tokens| body.to_tokens(tokens));
1389 }
1390}
1391
1392const EST_EXPR_SIZE: usize = 10;
1393
1394#[rustfmt::skip]
1395fn can_attrs_break(attrs: &[syn::Attribute]) -> bool {
1396 !attrs.iter().all(|attr| {
1397 attr.path().get_ident().is_some_and(|ident| {
1398 [
1399 "cfg", "cfg_attr",
1401 "test", "ignore", "should_panic",
1403 "derive", "automatically_derived",
1405 "macro_export", "macro_use", "proc_macro", "proc_macro_derive",
1407 "proc_macro_attribute",
1408 "allow", "warn", "deny", "forbid", "deprecated", "must_use",
1410 "link", "link_name", "link_ordinal", "no_link", "repr", "crate_type",
1412 "no_main", "export_name", "link_section", "no_mangle", "used", "crate_name",
1413 "inline", "cold", "no_builtins", "target_feature", "track_caller",
1415 "instruction_set",
1416 "doc",
1418 "no_std", "no_implicit_prelude",
1420 "path",
1422 "recursion_limit", "type_length_limit",
1424 "panic_handler", "global_allocator", "windows_subsystem",
1426 "feature",
1428 "non_exhaustive",
1430 "debugger_visualizer",
1432 ]
1433 .iter()
1434 .any(|s| ident == s)
1435 })
1436 })
1437}
1438
1439fn can_macro_break(mac: &syn::Macro) -> bool {
1440 mac.path.get_ident().is_some_and(|ident| {
1441 ["cfg", "stringify", "concat", "include_str", "include_bytes"]
1442 .iter()
1443 .any(|s| ident == s)
1444 || [
1445 "todo",
1446 "unreachable",
1447 "unimplemented",
1448 "panic",
1449 "assert",
1450 "assert_eq",
1451 "assert_ne",
1452 "debug_assert",
1453 "debug_assert_eq",
1454 "debug_assert_ne",
1455 "dbg",
1456 "print",
1457 "println",
1458 "write",
1459 "writeln",
1460 "format",
1461 "format_args",
1462 ]
1463 .iter()
1464 .any(|s| {
1465 ident == s && {
1466 mac.parse_body_with(Punctuated::<syn::Expr, Token![,]>::parse_terminated)
1467 .ok()
1468 .map_or(true, |exprs| exprs.iter().any(can_expr_break))
1469 }
1470 })
1471 })
1472}
1473
1474fn can_expr_break(expr: &syn::Expr) -> bool {
1475 match expr {
1476 syn::Expr::Array(expr) => {
1477 can_attrs_break(&expr.attrs) || expr.elems.iter().any(can_expr_break)
1478 }
1479 syn::Expr::Assign(expr) => {
1480 can_attrs_break(&expr.attrs)
1481 || can_expr_break(&expr.left)
1482 || can_expr_break(&expr.right)
1483 }
1484 syn::Expr::Async(_) => false,
1485 syn::Expr::Await(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.base),
1486 syn::Expr::Binary(expr) => {
1487 can_attrs_break(&expr.attrs)
1488 || can_expr_break(&expr.left)
1489 || can_expr_break(&expr.right)
1490 }
1491 syn::Expr::Block(expr) => can_attrs_break(&expr.attrs) || can_block_break(&expr.block),
1492 syn::Expr::Break(expr) => {
1493 can_attrs_break(&expr.attrs)
1494 || expr.label.is_none()
1495 || expr.expr.as_ref().is_some_and(|expr| can_expr_break(expr))
1496 }
1497 syn::Expr::Call(expr) => {
1498 can_attrs_break(&expr.attrs)
1499 || can_expr_break(&expr.func)
1500 || expr.args.iter().any(can_expr_break)
1501 }
1502 syn::Expr::Cast(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.expr),
1503 syn::Expr::Closure(expr) => can_attrs_break(&expr.attrs),
1504 syn::Expr::Const(expr) => can_attrs_break(&expr.attrs) || can_block_break(&expr.block),
1505 syn::Expr::Continue(expr) => can_attrs_break(&expr.attrs),
1506 syn::Expr::Field(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.base),
1507 syn::Expr::ForLoop(expr) => can_attrs_break(&expr.attrs),
1508 syn::Expr::Group(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.expr),
1509 syn::Expr::If(expr) => {
1510 can_attrs_break(&expr.attrs)
1511 || can_expr_break(&expr.cond)
1512 || can_block_break(&expr.then_branch)
1513 || expr
1514 .else_branch
1515 .as_ref()
1516 .is_some_and(|(_, expr)| can_expr_break(expr))
1517 }
1518 syn::Expr::Index(expr) => {
1519 can_attrs_break(&expr.attrs)
1520 || can_expr_break(&expr.expr)
1521 || can_expr_break(&expr.index)
1522 }
1523 syn::Expr::Infer(_) => false,
1524 syn::Expr::Let(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.expr),
1525 syn::Expr::Lit(expr) => can_attrs_break(&expr.attrs),
1526 syn::Expr::Loop(expr) => can_attrs_break(&expr.attrs),
1527 syn::Expr::Macro(syn::ExprMacro { attrs, mac }) => {
1528 can_attrs_break(attrs) || can_macro_break(mac)
1529 }
1530 syn::Expr::Match(expr) => {
1531 can_attrs_break(&expr.attrs)
1532 || can_expr_break(&expr.expr)
1533 || expr.arms.iter().any(|arm| {
1534 !arm.attrs.is_empty()
1535 || arm
1536 .guard
1537 .as_ref()
1538 .is_some_and(|(_, expr)| can_expr_break(expr))
1539 || can_expr_break(&expr.expr)
1540 })
1541 }
1542 syn::Expr::MethodCall(expr) => {
1543 can_attrs_break(&expr.attrs)
1544 || can_expr_break(&expr.receiver)
1545 || expr.args.iter().any(can_expr_break)
1546 }
1547 syn::Expr::Paren(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.expr),
1548 syn::Expr::Path(expr) => can_attrs_break(&expr.attrs),
1549 syn::Expr::Range(expr) => {
1550 can_attrs_break(&expr.attrs)
1551 || expr.start.as_ref().is_some_and(|expr| can_expr_break(expr))
1552 || expr.end.as_ref().is_some_and(|expr| can_expr_break(expr))
1553 }
1554 syn::Expr::Reference(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.expr),
1555 syn::Expr::Repeat(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.expr),
1556 syn::Expr::Return(expr) => {
1557 can_attrs_break(&expr.attrs)
1558 || expr.expr.as_ref().is_some_and(|expr| can_expr_break(expr))
1559 }
1560 syn::Expr::Struct(expr) => {
1561 can_attrs_break(&expr.attrs)
1562 || expr.fields.iter().any(|expr| can_expr_break(&expr.expr))
1563 }
1564 syn::Expr::Try(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.expr),
1565 syn::Expr::TryBlock(expr) => can_attrs_break(&expr.attrs) || can_block_break(&expr.block),
1566 syn::Expr::Tuple(expr) => {
1567 can_attrs_break(&expr.attrs) || expr.elems.iter().any(can_expr_break)
1568 }
1569 syn::Expr::Unary(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.expr),
1570 syn::Expr::Unsafe(expr) => can_attrs_break(&expr.attrs) || can_block_break(&expr.block),
1571 syn::Expr::Verbatim(_) => true,
1572 syn::Expr::While(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.cond),
1573 syn::Expr::Yield(expr) => {
1574 can_attrs_break(&expr.attrs)
1575 || expr.expr.as_ref().is_some_and(|expr| can_expr_break(expr))
1576 }
1577 _ => true,
1578 }
1579}
1580
1581fn can_block_break(block: &syn::Block) -> bool {
1582 block.stmts.iter().any(|stmt| match stmt {
1583 syn::Stmt::Item(_) => false,
1584 syn::Stmt::Local(syn::Local { attrs, init, .. }) => {
1585 can_attrs_break(attrs)
1586 || init
1587 .as_ref()
1588 .is_some_and(|syn::LocalInit { expr, diverge, .. }| {
1589 can_expr_break(expr)
1590 || diverge
1591 .as_ref()
1592 .is_some_and(|(_, expr)| can_expr_break(expr))
1593 })
1594 }
1595 syn::Stmt::Macro(syn::StmtMacro { attrs, mac, .. }) => {
1596 can_attrs_break(attrs) || can_macro_break(mac)
1597 }
1598 syn::Stmt::Expr(expr, _) => can_expr_break(expr),
1599 })
1600}
1601
1602fn try_stringify_expr(expr: &syn::Expr, can_break: bool) -> Option<String> {
1603 if can_break && can_expr_break(expr) {
1604 return None;
1605 }
1606 match expr {
1607 syn::Expr::Lit(syn::ExprLit { attrs, lit }) if attrs.is_empty() => match lit {
1608 syn::Lit::Str(s) => Some(s.value()),
1609 syn::Lit::Byte(byte) => Some(byte.value().to_string()),
1610 syn::Lit::Char(ch) => Some(ch.value().to_string()),
1611 syn::Lit::Int(int) => Some(int.to_string()),
1612 syn::Lit::Float(float) => Some(float.to_string()),
1613 syn::Lit::Bool(bool) => Some(bool.value().to_string()),
1614 _ => None,
1615 },
1616 syn::Expr::Paren(syn::ExprParen { expr, attrs, .. }) if attrs.is_empty() => {
1617 try_stringify_expr(expr, false)
1618 }
1619 syn::Expr::Block(syn::ExprBlock {
1620 block,
1621 attrs,
1622 label: None,
1623 ..
1624 }) if attrs.is_empty() => try_stringify_block(block, false),
1625 _ => None,
1626 }
1627}
1628
1629fn try_stringify_block(block: &syn::Block, can_break: bool) -> Option<String> {
1630 if can_break && can_block_break(block) {
1631 return None;
1632 }
1633 let Some(syn::Stmt::Expr(expr, None)) = block.stmts.iter().rfind(|stmt| {
1634 matches!(
1635 stmt,
1636 syn::Stmt::Expr(_, _) | syn::Stmt::Macro(_) | syn::Stmt::Local(_),
1637 )
1638 }) else {
1639 return None;
1640 };
1641 try_stringify_expr(expr, false)
1642}
1643
1644fn flush_buffer(tokens: &mut TokenStream, buf: &mut String, span: Span) {
1645 if !buf.is_empty() {
1646 let writer = Ident::new("__writer", Span::mixed_site());
1647
1648 tokens.append_all(quote_spanned! { span =>
1649 ::core::fmt::Write::write_str(#writer, #buf)?;
1650 });
1651 buf.clear();
1652 }
1653}
1654
1655fn isolate_block(tokens: impl ToTokens) -> TokenStream {
1657 quote_spanned! { tokens.span() =>
1658 loop {
1659 #[allow(unreachable_code)]
1660 break {
1661 #[warn(unreachable_code)]
1662 { #tokens }
1663 };
1664 }
1665 }
1666}
1667
1668struct TmplBodyNode<'a> {
1669 size: &'a mut usize,
1670 buf: &'a mut String,
1671 node: &'a Node,
1672 item_span: Span,
1673}
1674
1675impl<'a> TmplBodyNode<'a> {
1676 pub fn new(node: &'a Node, size: &'a mut usize, buf: &'a mut String, item_span: Span) -> Self {
1677 Self {
1678 node,
1679 size,
1680 buf,
1681 item_span,
1682 }
1683 }
1684
1685 fn write_escaped_str(&mut self, value: impl Display) {
1686 pub struct EscapeWriter<'a>(&'a mut String);
1687
1688 impl Write for EscapeWriter<'_> {
1689 #[inline]
1690 fn write_str(&mut self, s: &str) -> std::fmt::Result {
1691 askama_escape::Html.write_escaped(&mut *self.0, s)
1692 }
1693 }
1694
1695 write!(EscapeWriter(self.buf), "{value}").unwrap();
1696 }
1697
1698 #[inline]
1699 fn flush_buffer(&mut self, tokens: &mut TokenStream) {
1700 *self.size += self.buf.len();
1701 flush_buffer(tokens, &mut *self.buf, self.item_span)
1702 }
1703
1704 fn write_displayable(&mut self, tokens: &mut TokenStream, displayable: &impl ToTokens) {
1705 let span = displayable.span();
1706 let crate_path = crate_path(span);
1707
1708 self.flush_buffer(tokens);
1709 *self.size += EST_EXPR_SIZE;
1710
1711 let displayable = isolate_block(displayable);
1712 let writer = Ident::new("__writer", Span::mixed_site());
1713 tokens.append_all(quote_spanned! { span =>
1714 #crate_path::__write_escaped(
1715 #writer,
1716 &#displayable,
1717 )?;
1718 });
1719 }
1720
1721 fn write_block(&mut self, tokens: &mut TokenStream, block: &syn::Block) {
1722 match try_stringify_block(block, true) {
1723 Some(s) => {
1724 tokens.append_all(isolate_block(block));
1725 tokens.append_all(quote_spanned! { block.span() => ; });
1726 self.write_escaped_str(&s);
1727 }
1728 None => {
1729 match block.stmts.iter().rposition(|stmt| {
1730 matches!(
1731 stmt,
1732 syn::Stmt::Expr(_, _) | syn::Stmt::Macro(_) | syn::Stmt::Local(_),
1733 )
1734 }) {
1735 Some(i)
1736 if matches!(block.stmts[i], syn::Stmt::Expr(_, None))
1737 && !can_block_break(block) =>
1738 {
1739 let (before, after) = block.stmts.split_at(i);
1740 let (expr, after) = after.split_first().unwrap();
1741
1742 let block_span = block.span();
1743 let crate_path = crate_path(block_span);
1744
1745 self.flush_buffer(tokens);
1746 *self.size += EST_EXPR_SIZE;
1747
1748 let writer = Ident::new("__writer", Span::mixed_site());
1749 tokens.append_all(quote_spanned! { block_span => {
1751 #(#before)*
1752 #crate_path::__write_escaped(#writer, &(#expr))?;
1753 #(#after)*
1754 } });
1755 }
1756 _ => self.write_displayable(tokens, block),
1757 }
1758 }
1759 }
1760 }
1761
1762 fn write_node_block(&mut self, tokens: &mut TokenStream, block: &rstml::node::NodeBlock) {
1763 match block {
1764 rstml::node::NodeBlock::ValidBlock(block) => self.write_block(tokens, block),
1765 rstml::node::NodeBlock::Invalid { .. } => self.write_displayable(tokens, block),
1766 }
1767 }
1768
1769 fn write_expr(&mut self, tokens: &mut TokenStream, expr: &syn::Expr) {
1770 match try_stringify_expr(expr, true) {
1771 Some(s) => {
1772 tokens.append_all(isolate_block(expr));
1773 tokens.append_all(quote_spanned! { expr.span() => ; });
1774 self.write_escaped_str(&s);
1775 }
1776 None if !can_expr_break(expr) => {
1777 let expr_span = expr.span();
1778 let crate_path = crate_path(expr_span);
1779
1780 self.flush_buffer(tokens);
1781 *self.size += EST_EXPR_SIZE;
1782
1783 let writer = Ident::new("__writer", Span::mixed_site());
1784 tokens.append_all(quote_spanned! { expr_span =>
1786 #crate_path::__write_escaped(#writer, &(#expr))?;
1787 });
1788 }
1789 None => self.write_displayable(tokens, expr),
1790 }
1791 }
1792
1793 fn generate(&mut self, tokens: &mut TokenStream) {
1794 match self.node {
1795 Node::Comment(comment) => {
1796 self.buf.push_str("<!-- ");
1797 self.write_escaped_str(comment.value.value());
1798 self.buf.push_str(" -->");
1799 }
1800 Node::Doctype(doctype) => {
1801 write!(
1802 self.buf,
1803 "<!{} {}>",
1804 doctype.token_doctype,
1805 doctype.value.to_string_best(),
1806 )
1807 .unwrap();
1808 }
1809 Node::Fragment(fragment) => {
1810 let mut block_tokens = TokenStream::new();
1811 for child in &fragment.children {
1812 TmplBodyNode::new(child, &mut *self.size, &mut *self.buf, self.item_span)
1813 .generate(&mut block_tokens);
1814 }
1815 tokens.append_all(quote_spanned! { block_tokens.span() => { #block_tokens } });
1816 }
1817 Node::DiscardElement(DiscardNodeElement { value, .. }) => {
1818 tokens.append_all(isolate_block(value));
1819 tokens.append_all(quote_spanned! { self.node.span() => ; });
1820 }
1821 Node::TrustElement(TrustNodeElement { value, .. }) => {
1822 fn write_block(value: impl ToTokens) -> TokenStream {
1823 let writer = Ident::new("__writer", Span::mixed_site());
1824
1825 quote_spanned! { value.span() =>
1826 match #value {
1828 __value => {
1829 use ::core::fmt::Write;
1830 ::core::write!(#writer, "{}", __value)?;
1831 }
1832 }
1833 }
1834 }
1835 match value {
1836 NodeBlock::ValidBlock(value) => match try_stringify_block(value, true) {
1837 Some(s) => {
1838 tokens.append_all(isolate_block(value));
1839 tokens.append_all(quote_spanned! { value.span() => ; });
1840 self.buf.push_str(&s);
1841 }
1842 None => {
1843 match value.stmts.iter().rposition(|stmt| {
1844 matches!(
1845 stmt,
1846 syn::Stmt::Expr(_, _)
1847 | syn::Stmt::Macro(_)
1848 | syn::Stmt::Local(_),
1849 )
1850 }) {
1851 Some(i)
1852 if matches!(value.stmts[i], syn::Stmt::Expr(_, None))
1853 && !can_block_break(value) =>
1854 {
1855 let (before, after) = value.stmts.split_at(i);
1856 let (stmt, after) = after.split_first().unwrap();
1857
1858 self.flush_buffer(tokens);
1859 *self.size += EST_EXPR_SIZE;
1860
1861 let show_stmt =
1862 write_block(quote_spanned! { stmt.span() => &(#stmt) });
1863 tokens.append_all(quote_spanned! { value.span() => {
1865 #(#before)*
1866 #show_stmt
1867 #(#after)*
1868 } });
1869 }
1870 _ => tokens.append_all(write_block(value)),
1871 }
1872 }
1873 },
1874 NodeBlock::Invalid { .. } => tokens.append_all(write_block(value)),
1875 }
1876 }
1877 Node::LetElement(LetNodeElement {
1878 let_token,
1879 binding,
1880 value,
1881 ..
1882 }) => tokens.append_all(quote_spanned! {
1883 self.node.span() => #let_token #binding = #value;
1884 }),
1885 Node::ForElement(ForNodeElement {
1886 open_tag:
1887 ForOpenTag {
1888 for_token,
1889 binding,
1890 in_token,
1891 iter,
1892 ..
1893 },
1894 children,
1895 ..
1896 }) => {
1897 self.flush_buffer(tokens);
1898 let mut block_tokens = TokenStream::new();
1899 let init_size = *self.size;
1900 for child in children {
1901 TmplBodyNode::new(child, &mut *self.size, &mut *self.buf, self.item_span)
1902 .generate(&mut block_tokens);
1903 }
1904 self.buf.push(' ');
1905 self.flush_buffer(&mut block_tokens);
1906 *self.size = 6 * *self.size - 5 * init_size;
1907
1908 let iter = isolate_block(iter);
1909 tokens.append_all(quote_spanned! { self.node.span() =>
1910 #for_token #binding #in_token #iter {
1912 #block_tokens
1913 }
1914 });
1915 }
1916 Node::MatchElement(MatchNodeElement {
1917 open_tag:
1918 MatchOpenTag {
1919 match_token, value, ..
1920 },
1921 arms,
1922 close_tag: _,
1923 }) => {
1924 self.flush_buffer(tokens);
1925 let mut block_tokens = TokenStream::new();
1926 let init_size = *self.size;
1927
1928 self.flush_buffer(&mut block_tokens);
1929
1930 let mut max_arm_size = init_size;
1931 for (
1932 MatchArmTag {
1933 binding,
1934 guard,
1935 on_token,
1936 ..
1937 },
1938 children,
1939 ) in arms
1940 {
1941 let mut subblock_tokens = TokenStream::new();
1942
1943 for child in children {
1944 TmplBodyNode::new(child, &mut *self.size, &mut *self.buf, self.item_span)
1945 .generate(&mut subblock_tokens);
1946 }
1947 self.flush_buffer(&mut subblock_tokens);
1948
1949 let guard = guard.as_ref().map(|(if_token, cond)| {
1950 let cond = isolate_block(cond);
1951 quote! { #if_token #cond }
1952 });
1953
1954 block_tokens.append_all(quote_spanned! { on_token.span() =>
1955 #binding #guard => { #subblock_tokens }
1956 });
1957
1958 max_arm_size = max_arm_size.max(*self.size);
1959 *self.size = init_size;
1960 }
1961 *self.size = max_arm_size;
1962
1963 let value = isolate_block(value);
1964 tokens.append_all(quote_spanned! { match_token.span() =>
1965 #match_token #value {
1967 #block_tokens
1968 }
1969 });
1970
1971 self.buf.push(' ');
1972 }
1973 Node::IfElement(IfNodeElement {
1974 open_tag:
1975 IfOpenTag {
1976 if_token,
1977 let_binding,
1978 value,
1979 ..
1980 },
1981 if_section,
1982 else_if_sections,
1983 else_section,
1984 close_tag: _,
1985 }) => {
1986 self.flush_buffer(tokens);
1987 let mut block_tokens = TokenStream::new();
1988 let init_size = *self.size;
1989 for child in if_section {
1990 TmplBodyNode::new(child, &mut *self.size, &mut *self.buf, self.item_span)
1991 .generate(&mut block_tokens);
1992 }
1993
1994 let let_binding = let_binding.as_ref().map(
1995 |IfLetBinding {
1996 let_token, binding, ..
1997 }| quote_spanned! { let_token.span() => #let_token #binding = },
1998 );
1999
2000 self.flush_buffer(&mut block_tokens);
2001 let value = isolate_block(value);
2002 tokens.append_all(quote_spanned! { if_token.span() =>
2003 #if_token #let_binding #value { #block_tokens }
2005 });
2006 let mut new_size = *self.size;
2007
2008 for (
2009 ElseIfTag {
2010 else_token,
2011 if_token,
2012 let_binding,
2013 value,
2014 ..
2015 },
2016 children,
2017 ) in else_if_sections
2018 {
2019 *self.size = init_size;
2020 let mut block_tokens = TokenStream::new();
2021 for child in children {
2022 TmplBodyNode::new(child, &mut *self.size, &mut *self.buf, self.item_span)
2023 .generate(&mut block_tokens);
2024 }
2025
2026 let let_binding = let_binding.as_ref().map(
2027 |IfLetBinding {
2028 let_token, binding, ..
2029 }| quote_spanned! { let_token.span() => #let_token #binding = },
2030 );
2031
2032 self.flush_buffer(&mut block_tokens);
2033 let value = isolate_block(value);
2034 tokens.append_all(quote_spanned! { else_token.span() =>
2035 #else_token #if_token #let_binding #value { #block_tokens }
2036 });
2037
2038 new_size = new_size.max(*self.size);
2039 }
2040
2041 if let Some((ElseTag { else_token, .. }, children)) = else_section {
2042 *self.size = init_size;
2043 let mut block_tokens = TokenStream::new();
2044 for child in children {
2045 TmplBodyNode::new(child, &mut *self.size, &mut *self.buf, self.item_span)
2046 .generate(&mut block_tokens);
2047 }
2048 self.flush_buffer(&mut block_tokens);
2049 tokens.append_all(quote_spanned! { else_token.span =>
2050 #else_token { #block_tokens }
2051 });
2052
2053 new_size = new_size.max(*self.size);
2054 }
2055 *self.size = new_size;
2056
2057 self.buf.push(' ');
2058 }
2059 Node::HtmlElement(el) => {
2060 if matches!(el.open_tag.name, HtmlNodeName::Ident(_)) {
2061 let open = &el.open_tag.name;
2062 let close = el.close_tag.iter().map(|tag| &tag.name);
2063 tokens.append_all(quote_spanned! { open.span() => {
2064 let #open = ();
2065 #(_ = #close;)*
2066 _ = #open;
2067 } });
2068 }
2069
2070 self.buf.push('<');
2071
2072 self.write_escaped_str(&el.open_tag.name);
2073
2074 for attr in &el.open_tag.attributes {
2075 match attr {
2076 HtmlNodeAttribute::Keyed(attr) => {
2077 self.buf.push(' ');
2078 self.write_escaped_str(&attr.key);
2079 if let Some(value) = &attr.value {
2080 self.buf.push_str("=\"");
2081 self.write_expr(tokens, &value.value);
2082 self.buf.push('"');
2083 }
2084 }
2085 HtmlNodeAttribute::Block(block) => {
2086 let block_span = block.span();
2087 let crate_path = crate_path(block_span);
2088 let writer = Ident::new("__writer", Span::mixed_site());
2089 let block = isolate_block(block);
2090 tokens.append_all(quote_spanned! { block_span =>
2091 #crate_path::Attributes::render_into(#block, #writer)?;
2092 });
2093 }
2094 }
2095 }
2096
2097 if el.open_tag.end.token_solidus.is_some() {
2098 self.buf.push_str(" />");
2099 } else {
2100 self.buf.push('>');
2101 }
2102 let mut block_tokens = TokenStream::new();
2105 for child in &el.children {
2106 TmplBodyNode::new(child, &mut *self.size, &mut *self.buf, self.item_span)
2107 .generate(&mut block_tokens);
2108 }
2109 if !block_tokens.is_empty() {
2110 tokens.append_all(quote_spanned! { block_tokens.span() => { #block_tokens } });
2111 }
2112
2113 if let Some(_close_tag) = &el.close_tag {
2114 self.buf.push_str("</");
2115
2116 self.write_escaped_str(&el.open_tag.name);
2117 self.buf.push('>');
2118 }
2119 self.buf.push(' ');
2120 }
2121 Node::DynTmplElement(DynTmplNodeElement { name_block, .. }) => {
2122 let block_span = name_block.span();
2123 let crate_path = crate_path(block_span);
2124
2125 self.flush_buffer(tokens);
2126 let writer = Ident::new("__writer", Span::mixed_site());
2127 let block = isolate_block(name_block);
2128 tokens.append_all(quote_spanned! { block_span =>
2129 #crate_path::Template::render_into(
2131 #block,
2132 #writer,
2133 )?;
2134 });
2135 self.buf.push(' ');
2136 }
2137 Node::StaticTmplElement(StaticTmplNodeElement {
2138 open_tag:
2139 StaticTmplOpenTag {
2140 name, attributes, ..
2141 },
2142 children,
2143 prop_children,
2144 close_tag: _,
2145 }) => {
2146 let self_span = self.node.span();
2147 self.flush_buffer(tokens);
2148
2149 let children_value = {
2150 assert!(self.buf.is_empty());
2151 let init_size = *self.size;
2152 let mut block_tokens = TokenStream::new();
2153 for child in children {
2154 TmplBodyNode::new(child, &mut *self.size, &mut *self.buf, self.item_span)
2155 .generate(&mut block_tokens);
2156 }
2157 self.flush_buffer(&mut block_tokens);
2158 let children_size = *self.size - init_size;
2159
2160 if block_tokens.is_empty() {
2161 None
2162 } else {
2163 let writer = Ident::new("__writer", Span::mixed_site());
2164 let crate_path = crate_path(self_span);
2165
2166 Some(quote_spanned! { self_span =>
2167 #crate_path::TemplateFn::new(#children_size, |#writer| {
2168 #block_tokens
2169 #crate_path::Result::Ok(())
2170 })
2171 })
2172 }
2173 };
2174
2175 let attr_values = attributes.iter().map(
2176 |attr @ StaticTmplNodeAttribute { value, .. }| match value {
2177 Some(value) if !can_expr_break(&value.value) => {
2178 value.value.to_token_stream()
2179 }
2180 Some(value) => isolate_block(&value.value),
2181 None => quote_spanned!(attr.span() => ()),
2182 },
2183 );
2184
2185 let prop_children_values =
2186 prop_children
2187 .iter()
2188 .map(|el @ PropNodeElement { children, .. }| {
2189 let el_span = el.span();
2190 let crate_path = crate_path(el_span);
2191
2192 assert!(self.buf.is_empty());
2193 let init_size = *self.size;
2194 let mut block_tokens = TokenStream::new();
2195 for child in children {
2196 TmplBodyNode::new(
2197 child,
2198 &mut *self.size,
2199 &mut *self.buf,
2200 self.item_span,
2201 )
2202 .generate(&mut block_tokens);
2203 }
2204 self.flush_buffer(&mut block_tokens);
2205 let prop_size = *self.size - init_size;
2206
2207 let writer = Ident::new("__writer", Span::mixed_site());
2208 quote_spanned! { el_span =>
2209 #crate_path::TemplateFn::new(#prop_size, |#writer| {
2210 #block_tokens
2211 #crate_path::Result::Ok(())
2212 })
2213 }
2214 });
2215
2216 let apply_attrs = attributes.iter().enumerate().map(
2217 |(i, attr @ StaticTmplNodeAttribute { key, value })| {
2218 let arg = match value {
2219 Some(_) => Some(format_ident!("__arg{i}", span = Span::mixed_site())),
2220 None => None,
2221 };
2222 quote_spanned! { attr.span() => #key(#arg) }
2223 },
2224 );
2225 let apply_prop_children = (attributes.len()..).zip(prop_children).map(|(i, el)| {
2226 let name = &el.open_tag.name;
2227 let arg = format_ident!("__arg{i}", span = Span::mixed_site());
2228 quote_spanned! { el.span() => #name(#arg) }
2229 });
2230 let apply_children = match children_value {
2231 Some(_) => {
2232 let i = attributes.len() + prop_children.len();
2233 let arg = format_ident!("__arg{i}", span = Span::mixed_site());
2234 Some(quote_spanned! { self_span => children(#arg) })
2235 }
2236 None => None,
2237 }
2238 .into_iter();
2239
2240 let vars =
2241 (0..attributes.len() + prop_children.len() + children_value.is_some() as usize)
2242 .map(|i| format_ident!("__arg{i}", span = Span::mixed_site()));
2243 let children_value = children_value.into_iter();
2244
2245 let writer = Ident::new("__writer", Span::mixed_site());
2246 let crate_path = crate_path(self_span);
2247 tokens.append_all(quote_spanned! { self_span => {
2248 let (#(#vars,)*) = (
2249 #(#attr_values,)*
2250 #(#prop_children_values,)*
2251 #(#children_value,)*
2252 );
2253 #crate_path::Template::render_into(
2254 #name::Props::builder()
2255 #(.#apply_attrs)*
2256 #(.#apply_prop_children)*
2257 #(.#apply_children)*
2258 .build(),
2259 #writer,
2260 )?;
2261 } });
2262 self.buf.push(' ');
2263 }
2264 Node::Block(block) => {
2265 self.write_node_block(tokens, block);
2266 self.buf.push(' ');
2267 }
2268 Node::Text(text) => {
2269 self.write_escaped_str(text.value_string());
2270 self.buf.push(' ');
2271 }
2272 Node::RawText(text) => {
2273 self.write_escaped_str(text.to_string_best());
2274 self.buf.push(' ');
2275 }
2276 }
2277 }
2278}
2279
2280impl ItemTmpl {
2281 fn generate(&self, tokens: &mut TokenStream) {
2282 let span = self.span();
2283
2284 let crate_path = crate_path(span);
2285 let slf = Ident::new("self", Span::mixed_site());
2286 let writer = Ident::new("__writer", Span::mixed_site());
2287 let generator = Ident::new("__generator", Span::mixed_site());
2288
2289 let Self {
2290 attrs,
2291 vis,
2292 fn_token,
2293 ident,
2294 generics,
2295 paren_token: _,
2296 inputs,
2297 brace_token: _,
2298 body,
2299 } = self;
2300
2301 let mut body_tokens = TokenStream::new();
2303 let mut size = 0;
2304
2305 {
2306 let mut buf = String::new();
2307 for node in &body.0 {
2308 TmplBodyNode::new(node, &mut size, &mut buf, span).generate(&mut body_tokens);
2309 }
2310 size += buf.len();
2311 flush_buffer(&mut body_tokens, &mut buf, span);
2312 }
2313
2314 let props_fields =
2315 inputs.iter().map(|arg| {
2316 let attrs = &arg.attrs;
2317 let ident = &arg.pat.ident;
2318 let colon_token = arg.colon_token;
2319 let ty = &arg.ty;
2320 let builders = arg.prop_attrs.iter().flat_map(|attr| &attr.meta_inner).map(
2321 |prop| match &prop {
2322 TmplPropMeta::Default { default_token } => quote_spanned! { prop.span() =>
2323 #[builder(#default_token)]
2324 },
2325 TmplPropMeta::Into { into_token } => quote_spanned! { prop.span() =>
2326 #[builder(setter(#into_token))]
2327 },
2328 TmplPropMeta::DefaultValue {
2329 default_token,
2330 eq_token,
2331 value,
2332 } => quote_spanned! { prop.span() => #[builder(#default_token #eq_token #value)] },
2333 TmplPropMeta::Optional { .. } => quote_spanned! { prop.span() =>
2334 #[builder(default, setter(strip_option))]
2335 },
2336 TmplPropMeta::Toggle { .. } => quote_spanned! { prop.span() =>
2337 #[builder(setter(strip_bool))]
2338 },
2339 },
2340 );
2341 quote_spanned! { arg.span() =>
2342 #(#attrs)*
2343 #(#builders)*
2344 pub #ident #colon_token #ty,
2345 }
2346 });
2347
2348 let fn_inputs = inputs.iter().map(
2349 |TmplArg {
2350 prop_attrs: _,
2351 attrs: _,
2352 pat: syn::PatIdent { ident, .. },
2353 colon_token,
2354 ty,
2355 }| {
2356 quote! {
2357 #ident #colon_token #ty
2358 }
2359 },
2360 );
2361
2362 let generate_inputs = inputs.iter().map(
2363 |TmplArg {
2364 prop_attrs: _,
2365 attrs: _,
2366 pat,
2367 colon_token,
2368 ty,
2369 }| {
2370 quote! {
2371 #pat #colon_token #ty
2372 }
2373 },
2374 );
2375
2376 let instance_fields = inputs.iter().map(
2377 |TmplArg {
2378 pat: syn::PatIdent { ident, .. },
2379 ..
2380 }| ident,
2381 );
2382 let call_generate_args = inputs.iter().map(
2383 |arg @ TmplArg {
2384 pat: syn::PatIdent { ident, .. },
2385 ..
2386 }| quote_spanned! { arg.span() => #slf.#ident },
2387 );
2388
2389 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
2390
2391 let into_generics = syn::Generics {
2392 lt_token: generics
2393 .lt_token
2394 .or_else(|| Some(parse_quote_spanned! { span => < })),
2395 params: {
2396 let params = &generics.params;
2397 parse_quote_spanned! { Span::mixed_site() => '__self, #params }
2398 },
2399 gt_token: generics
2400 .gt_token
2401 .or_else(|| Some(parse_quote_spanned! { span => > })),
2402 where_clause: match &generics.where_clause {
2403 Some(syn::WhereClause {
2404 where_token,
2405 predicates,
2406 }) => {
2407 parse_quote_spanned! { Span::mixed_site() => #where_token self::Props #ty_generics: '__self, #predicates }
2408 }
2409 None => Some(parse_quote_spanned! { Span::mixed_site() =>
2410 where self::Props #ty_generics: '__self
2411 }),
2412 },
2413 };
2414 let (impl_into_generics, _, into_where_clause) = into_generics.split_for_impl();
2415
2416 #[cfg(not(feature = "axum"))]
2417 let axum_impl = TokenStream::new();
2418
2419 #[cfg(feature = "axum")]
2420 let axum_impl = quote_spanned! { span =>
2421 impl #impl_generics #crate_path::__axum::IntoResponse for self::Props #ty_generics
2422 #where_clause
2423 {
2424 fn into_response(self) -> #crate_path::__axum::Response {
2425 #crate_path::__axum::into_response(self)
2426 }
2427 }
2428 };
2429
2430 #[cfg(not(feature = "actix-web"))]
2431 let actix_web_impl = TokenStream::new();
2432
2433 #[cfg(feature = "actix-web")]
2434 let actix_web_impl = quote_spanned! { span =>
2435 impl #impl_generics #crate_path::__actix_web::Responder for self::Props #ty_generics
2436 #where_clause
2437 {
2438 type Body = #crate_path::__actix_web::BoxBody;
2439
2440 fn respond_to(
2441 self,
2442 _req: &#crate_path::__actix_web::HttpRequest,
2443 ) -> #crate_path::__actix_web::HttpResponse {
2444 #crate_path::__actix_web::respond_to(self)
2445 }
2446 }
2447 };
2448
2449 #[cfg(not(feature = "hyper"))]
2450 let hyper_impl = TokenStream::new();
2451
2452 #[cfg(feature = "hyper")]
2453 let hyper_impl = quote_spanned! { span =>
2454 impl #impl_generics ::core::convert::From<self::Props #ty_generics>
2455 for #crate_path::__hyper::Response
2456 #where_clause
2457 {
2458 fn from(slf: self::Props #ty_generics) -> Self {
2459 #crate_path::__hyper::respond(slf)
2460 }
2461 }
2462
2463 };
2474
2475 #[cfg(not(feature = "warp"))]
2476 let warp_impl = TokenStream::new();
2477
2478 #[cfg(feature = "warp")]
2479 let warp_impl = quote_spanned! { span =>
2480 impl #impl_generics #crate_path::__warp::Reply for self::Props #ty_generics
2481 #where_clause
2482 {
2483 fn into_response(self) -> #crate_path::__warp::Response {
2484 #crate_path::__warp::reply(self)
2485 }
2486 }
2487 };
2488
2489 #[cfg(not(feature = "tide"))]
2490 let tide_impl = TokenStream::new();
2491
2492 #[cfg(feature = "tide")]
2493 let tide_impl = quote_spanned! { span =>
2494 impl #impl_generics ::core::convert::TryFrom<self::Props #ty_generics>
2495 for #crate_path::__tide::Body
2496 #where_clause
2497 {
2498 type Error = #crate_path::Error;
2499 fn try_from(slf: self::Props #ty_generics) -> #crate_path::Result<Self> {
2500 #crate_path::__tide::try_into_body(slf)
2501 }
2502 }
2503
2504 impl #impl_generics ::core::convert::From<self::Props #ty_generics>
2505 for #crate_path::__tide::Response
2506 #where_clause
2507 {
2508 fn from(slf: self::Props #ty_generics) -> Self {
2509 #crate_path::__tide::into_response(slf)
2510 }
2511 }
2512 };
2513
2514 #[cfg(not(feature = "gotham"))]
2515 let gotham_impl = TokenStream::new();
2516
2517 #[cfg(feature = "gotham")]
2518 let gotham_impl = quote_spanned! { span =>
2519 impl #impl_generics #crate_path::__gotham::IntoResponse for self::Props #ty_generics
2520 #where_clause
2521 {
2522 fn into_response(
2523 self,
2524 _state: &#crate_path::__gotham::State,
2525 ) -> #crate_path::__gotham::Response {
2526 #crate_path::__gotham::respond(self)
2527 }
2528 }
2529 };
2530
2531 #[cfg(not(feature = "rocket"))]
2532 let rocket_impl = TokenStream::new();
2533
2534 #[cfg(feature = "rocket")]
2535 let rocket_impl = {
2536 let into_generics = syn::Generics {
2537 lt_token: generics
2538 .lt_token
2539 .clone()
2540 .or_else(|| Some(parse_quote_spanned! { span => < })),
2541 params: {
2542 let params = &generics.params;
2543 parse_quote_spanned! { span => '__r, '__o: '__r, #params }
2544 },
2545 gt_token: generics
2546 .gt_token
2547 .clone()
2548 .or_else(|| Some(parse_quote_spanned! { span => > })),
2549 where_clause: None,
2550 };
2551 let (impl_into_generics, _, _) = into_generics.split_for_impl();
2552
2553 quote_spanned! { span =>
2554 impl #impl_into_generics #crate_path::__rocket::Responder<'__r, '__o>
2555 for self::Props #ty_generics
2556 #where_clause
2557 {
2558 fn respond_to(
2559 self,
2560 _req: &'__r #crate_path::__rocket::Request<'_>,
2561 ) -> #crate_path::__rocket::Result<'__o> {
2562 #crate_path::__rocket::respond(self)
2563 }
2564 }
2565 }
2566 };
2567
2568 let inner_vis = match &vis {
2569 syn::Visibility::Inherited => parse_quote_spanned! { fn_token.span() => pub(super) },
2570 syn::Visibility::Public(_) => vis.clone(),
2571 syn::Visibility::Restricted(syn::VisRestricted {
2572 pub_token,
2573 paren_token: _,
2574 in_token,
2575 path,
2576 }) => {
2577 let in_token =
2578 in_token.unwrap_or_else(|| parse_quote_spanned! { path.span() => in });
2579
2580 match path.segments.first() {
2581 Some(_) if path.leading_colon.is_some() => vis.clone(),
2582 Some(syn::PathSegment { ident, .. }) if ident == "crate" => vis.clone(),
2583 Some(syn::PathSegment { ident, .. }) if ident == "super" => {
2584 parse_quote_spanned! { vis.span() => #pub_token(#in_token super::#path) }
2585 }
2586 Some(syn::PathSegment { ident, .. }) if ident == "self" => {
2587 let segs = path.segments.iter().skip(1);
2588 parse_quote_spanned! { vis.span() =>
2589 #pub_token(#in_token super #(::#segs)*)
2590 }
2591 }
2592 Some(_) => parse_quote_spanned! { vis.span() =>
2593 #pub_token(#in_token super::#path)
2594 },
2595 None => unreachable!(),
2596 }
2597 }
2598 };
2599
2600 tokens.append_all(quote_spanned! { span =>
2601 #(#attrs)*
2602 #vis mod #ident {
2603 #[allow(unused_imports)]
2604 use ::core::{clone::Clone, convert::Into, panic};
2605
2606 #[allow(unused_imports)]
2607 use super::*;
2608
2609 #(#attrs)*
2610 #[derive(#crate_path::__typed_builder::TypedBuilder)]
2611 #[builder(crate_module_path = #crate_path::__typed_builder)]
2612 #inner_vis struct Props #generics #where_clause {
2614 #(#props_fields)*
2615 }
2616
2617 impl #impl_generics #crate_path::Template for self::Props #ty_generics #where_clause {
2618 const SIZE_HINT: usize = #size;
2619
2620 fn render_into(
2621 #slf,
2622 #writer: &mut dyn ::core::fmt::Write,
2623 ) -> #crate_path::Result<()> {
2624 #[inline]
2625 #[doc(hidden)]
2626 #[allow(unused_braces)]
2627 fn #generator #generics (
2628 #writer: &mut dyn ::core::fmt::Write,
2629 #(#generate_inputs,)*
2630 ) -> #crate_path::Result<()>
2631 #where_clause
2632 {
2633 #body_tokens
2634 #crate_path::Result::Ok(())
2635 }
2636
2637 #generator(#writer, #(#call_generate_args),*)
2638 }
2639 }
2640
2641 impl #impl_into_generics ::core::convert::From<self::Props #ty_generics>
2642 for #crate_path::TemplateFn<'__self>
2643 #into_where_clause
2644 {
2645 fn from(slf: self::Props #ty_generics) -> Self {
2646 Self::new(
2647 #crate_path::Template::size_hint(&slf),
2648 |writer| #crate_path::Template::render_into(slf, writer),
2649 )
2650 }
2651 }
2652
2653 #axum_impl
2654 #actix_web_impl
2655 #hyper_impl
2656 #warp_impl
2657 #tide_impl
2658 #gotham_impl
2659 #rocket_impl
2660 }
2661 #(#attrs)*
2662 #vis #fn_token #ident #generics (#(#fn_inputs),*) -> #ident::Props #ty_generics #where_clause {
2663 #ident::Props {
2664 #(#instance_fields,)*
2665 }
2666 }
2667 });
2668 }
2669}
2670
2671fn parse_templates(input: ParseStream) -> syn::Result<Vec<ItemTmpl>> {
2672 let mut items = vec![];
2673 while !input.is_empty() {
2674 items.push(input.parse()?);
2675 }
2676 Ok(items)
2677}
2678
2679#[proc_macro]
2985pub fn templates(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
2986 let items = parse_macro_input!(input with parse_templates);
2987
2988 let mut tokens = TokenStream::new();
2989
2990 for item in items {
2991 item.generate(&mut tokens);
2992 }
2993
2994 tokens.into()
2995}
2996
2997#[proc_macro]
3011pub fn tmpl(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
3012 let body = parse_macro_input!(input as NodeBody);
3013
3014 let crate_path = crate_path(Span::call_site());
3015
3016 let mut buf = String::new();
3017
3018 let mut size = 0;
3019 let mut block_tokens = TokenStream::new();
3020 for child in &body.0 {
3021 TmplBodyNode::new(child, &mut size, &mut buf, Span::call_site())
3022 .generate(&mut block_tokens);
3023 }
3024 size += buf.len();
3025 flush_buffer(&mut block_tokens, &mut buf, Span::call_site());
3026
3027 let writer = Ident::new("__writer", Span::mixed_site());
3028
3029 quote! {
3030 #crate_path::TemplateFn::new(#size, |#writer| {
3031 #[allow(unused_braces)]
3032 {
3033 #block_tokens
3034 }
3035 #crate_path::Result::Ok(())
3036 })
3037 }
3038 .into()
3039}