russx_macros/
lib.rs

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
197// fn write_escaped(
198//     writer: &mut (impl fmt::Write + ?Sized),
199//     value: &(impl fmt::Display + ?Sized),
200// ) -> fmt::Result {
201// }
202
203fn 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                                // return Err(syn::Error::new_spanned(
856                                //     block,
857                                //     "unexpected block attribute, NOTE: HTML elements' keys cannot be blocks",
858                                // ));
859                            }
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", // html
1101                            "else", "on", "let", "_", "trust", // control-flow
1102                        ]
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                // Conditional compilation
1400                "cfg", "cfg_attr",
1401                // Testing
1402                "test", "ignore", "should_panic",
1403                // Derive
1404                "derive", "automatically_derived",
1405                // Macros
1406                "macro_export", "macro_use", "proc_macro", "proc_macro_derive",
1407                "proc_macro_attribute",
1408                // Diagnostics
1409                "allow", "warn", "deny", "forbid", "deprecated", "must_use",
1410                // ABI, linking, symbols, and FFI
1411                "link", "link_name", "link_ordinal", "no_link", "repr", "crate_type",
1412                "no_main", "export_name", "link_section", "no_mangle", "used", "crate_name",
1413                // Code generation
1414                "inline", "cold", "no_builtins", "target_feature", "track_caller",
1415                "instruction_set",
1416                // Documentation
1417                "doc",
1418                // Preludes
1419                "no_std", "no_implicit_prelude",
1420                // Modules
1421                "path",
1422                // Limits
1423                "recursion_limit", "type_length_limit",
1424                // Runtime
1425                "panic_handler", "global_allocator", "windows_subsystem",
1426                // Features
1427                "feature",
1428                // Type System
1429                "non_exhaustive",
1430                // Debugger
1431                "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
1655/// Try to isolate `tokens` from `break`.
1656fn 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                        // if macros lie, this can break...
1750                        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                // if macros lie, this can break...
1785                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                        // #[allow(unused_braces)]
1827                        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                                    // if macros lie, this can break...
1864                                    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                    // #[allow(unused_braces)]
1911                    #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                    // #[allow(unused_braces)]
1966                    #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                    // #[allow(unused_braces)]
2004                    #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                // pub end: OpenTagEnd,
2103
2104                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                    // #[allow(unused_braces)]
2130                    #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 body = TmplBody { nodes: &body.0 };
2302        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            // impl #impl_generics ::core::convert::TryFrom<self::Props #ty_generics>
2464            // for #crate_path::__hyper::Body
2465            // #where_clause
2466            // {
2467            //     type Error = #crate_path::Error;
2468            //     fn try_from(slf: self::Props #ty_generics) -> #crate_path::Result<Self> {
2469            //         #crate_path::Template::render(slf)
2470            //             .map(::core::convert::Into::into)
2471            //     }
2472            // }
2473        };
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                // #[builder(doc)]
2613                #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/// Define static templates.
2680///
2681/// Each static template is defined in a similar manner to a function definition.
2682/// The major differences are:
2683/// * Templates cannot be `const`, `async`, or `unsafe`.
2684/// * No return type is specified, if called as a function it will return `{name}::Props` struct that
2685///   implements `Template`.
2686/// * Implicit lifetimes aren't supported, as a template item's arguments are used to generate the
2687///   `{name}::Props` struct, and structs don't currently support them.
2688/// * `impl trait` arguments aren't currently because Rust doesn't yet support `impl trait` fields
2689///   in the generated struct. This may change in the future.
2690/// * All of a template item's arguments must be named, you can still destructure using the
2691///   `field_name @ pat` pattern.
2692/// * Finally the function body uses a special syntax instead of regular rust code, as is the
2693///   entire point.
2694///
2695/// Here's an example for how to make a simple template:
2696/// ```rust
2697/// templates! {
2698///     pub fn show<T: fmt::Display>(x: T) {
2699///         Here is: <span style="color: red">{x}</span>
2700///     }
2701///
2702///     pub fn greet<'a>(
2703///         name: &'a str,
2704///         age: u8,
2705///     ) {
2706///         <p>
2707///             "Your name is" {name} "and you are" {age} "years old."
2708///         </p>
2709///     }
2710/// }
2711///
2712/// println!("{}", greet("Jake", 40).render()?);
2713/// // something like: <p>Your name is Jake and you are 40 years old.</p>
2714/// ```
2715///
2716/// # Text
2717/// Text may be quoted or unquoted. Separate text blocks will have a space automatically inserted
2718/// between them. Unquoted text follows the [caveats specified here](https://docs.rs/rstml/latest/rstml/node/struct.RawText.html).
2719///
2720/// # HTML Element
2721/// A cannonical HTML element can be defined using its name.
2722/// Custom names are supported but must be prefixed with `raw:` to avoid confusion with static
2723/// templates. `raw:` will be strippd from the element's name and the element will be treated as an
2724/// HTML element.
2725///
2726/// HTML elements may have attributes which will be parsed as Rust expressions, though the macro
2727/// will optimize any literal attributes. Attribute names may also be prefixed with `raw:` and
2728/// follow the same rules as the element name.
2729///
2730/// If you want to have dynamic attributes you can surround your attributes with braces.
2731/// Then it will call [`Attributes::render_into`] to write your attributes. If you want to iterate over
2732/// multiple attributes just use the "range to" operator, `..iter`.
2733///
2734/// Any close tag may be substituted for an underscore, `_`, and it will be automatically replaced
2735/// with the appropriate closing tag. Also some HTML elements such as `<input>` may identify as
2736/// self closing, this library respects their descision and will treat them as they desire.
2737///
2738/// ```rust
2739/// templates! {
2740///     pub fn greet<'a>(
2741///         id: &'a str,
2742///         name: &'a str,
2743///         age: u8,
2744///     ) {
2745///         <label for=id>"Name: "</label>
2746///         <input id=id type="text" name="name" value=name>
2747///     }
2748///
2749///     pub fn text_input<'a>(attrs: &'a [(&'a str, &'a str)]) {
2750///         <input type="text" {..attrs}>
2751///     }
2752/// }
2753/// ```
2754///
2755/// # Substitutions
2756/// Braces, `{}`, will be interpreted as substitutions. These will write a rust block expression
2757/// into the file. Substitutions are HTML escaped so it is mostly safe to put user provided strings
2758/// there.
2759///
2760/// To disable escaping use the trust tag (`<trust {...}>`), this tag will display whatever it
2761/// consumes without escaping it.
2762///
2763/// You can also choose not to show a subsitution using the discard tag (`<_ {...}>`). This tag
2764/// allows you to write mutating operations in a nice manner.
2765///
2766/// Internally subsitution, and any block, are wrapped by `loop { break {...} }`. You can use this
2767/// to break early from the block which works nicely with `let-else` statements.
2768///
2769/// ```rust
2770/// templates! {
2771///     pub fn safe<'a>(name: &'a str) {
2772///         <p>{name}</p>
2773///     }
2774///     pub fn naughty<'a>(name: &'a str) {
2775///         <trust {format!("<p>{name}</p>")}>
2776///     }
2777///
2778///     pub fn add2(mut n: f32) {
2779///         {n}
2780///         "+ 2 ="
2781///         <_ {n += 2}>
2782///         {n}
2783///     }
2784/// }
2785/// ```
2786///
2787/// # `<script>` & `<style>`
2788/// Everything inside these elements will be treated as raw text, even quotes. To get around this
2789/// you can prefix them with `raw:`, though this is not recommended and may lead to XSS if not done
2790/// carefully.
2791///
2792/// ```rust
2793/// templates! {
2794///     pub fn js() {
2795///         <script>
2796///             const names = ["Jeff", "Jake", "James", "Jonathan"];
2797///             for (const name of names) {
2798///                 console.log(name)
2799///             }
2800///         </script>
2801///     }
2802/// }
2803/// ```
2804///
2805/// # Control flow
2806/// There are multiple control-flow operations.
2807///
2808/// ## Let-Var
2809/// Let-var elements allow you to define a variable local to the current element.
2810/// `<let var(pattern: OptionalType) {value}>`
2811///
2812/// ```rust
2813/// templates! {
2814///     pub fn sum_up(n: u32, m: u32) {
2815///         <let var(sum) {n + m}>
2816///         {n} "and" {m} "sum up to" {sum}
2817///     }
2818/// }
2819/// ```
2820///
2821/// # If-Else
2822/// If-else statements are supported, and so are if-let statements.
2823/// Else elements are put inside an if element and are self-closed.
2824///
2825/// ```rust
2826/// templates! {
2827///     pub fn show_number(n: u32) {
2828///         <if {n == 0}>
2829///             nought
2830///         <else if {n == 1}>
2831///             singleton
2832///         <else if let(n @ 2..=9) {n}>
2833///             Single digit {n}
2834///         <else>
2835///             BIG {n}
2836///         </if>
2837///     }
2838/// }
2839/// ```
2840///
2841/// # For
2842/// For loops are present and function like they do in rust.
2843///
2844/// ```rust
2845/// templates! {
2846///     pub fn greet_everyone<'a>(names: &'a [&'a str]) {
2847///         <for each(name) in {names}>
2848///             "Hello" {name}
2849///         </for>
2850///     }
2851/// }
2852/// ```
2853///
2854/// # Match
2855/// To provide feature parity with Askama, it was decided to include a `match` element as well.
2856/// This element contains different cases for what to match against, these on-case elements are
2857/// self-closed and segment the `match` body similarly to if-else elements, `<on case(pattern)>`.
2858/// They also support guards `<on case(pattern) if {condition}>`.
2859///
2860/// ```rust
2861/// const FIRE_REASONS: &[&str] = &["laziness", "evilness", "badness", "deadness"];
2862/// templates! {
2863///     pub fn greet<'a>(name_result: Result<&'a str, &'a str>) {
2864///         <match {name_result}>
2865///         <on case(Ok("President John"))>
2866///             "Oh, hello John, how are you?"
2867///         <on case(Ok(name))>
2868///             "Hello" {name}
2869///         <on case(Err(reason)) if {FIRE_REASONS.contains(&reason)}>
2870///             "YOU ARE FIRED!"
2871///         <on case(Err("sleepiness"))>
2872///             "WAKE UP!"
2873///         <on case(Err(reason))>
2874///             "Getting name failed because" {reason}
2875///         </match>
2876///     }
2877/// }
2878/// ```
2879///
2880/// # Properties
2881/// You can tag properties to alter them during static instantiation of the template.
2882/// There are multiple different tags you can use:
2883/// * `#[prop(into)]` This will call `Into::into()` on any value passed into the property during
2884///    static instantiation.
2885/// * `#[prop(default)]` If the user does not specify this property when they statically
2886///   instantiate the template, it will be set to its default value.
2887/// * `#[prop(optional)]` Like `default`, but if the user does specify this property it will be set
2888///   to Some(value).
2889/// * `#[prop(default = value_expr)]` Like `default`, except instead of `Default::default()`
2890///   `value_expr` will be assigned.
2891/// * `#[prop(toggle)]` If not present it will be set to `false`. To turn it on, this property must
2892///   be present but not assign a value, then it will be set to `true`.
2893///
2894/// # Template Instantiation
2895/// There are two types of instantiation: static and dynamic.
2896///
2897/// ## Dynamic Instantiation
2898/// Dynamic instantiation is extremely simple. This just calls `Template::render_into()` on its
2899/// argument. You perform it by writing an element whose name is a block, `<{template} />`.
2900/// This element must self close, with `/>`, and therefore it cannot take children.
2901/// It also doesn't accept any attributes.
2902///
2903/// ```rust
2904/// templates! {
2905///     pub fn box<'a>(
2906///         #[prop(into, default)]
2907///         children: TemplateFn<'a>,
2908///     ) {
2909///         <div style="border: 1px solid black">
2910///             <{children} />
2911///         </div>
2912///     }
2913/// }
2914/// ```
2915///
2916/// ## Static Instantiation
2917/// Static instantiation is instantiation of templates defined using `templates! {}`. You do this by
2918/// defining an element whose name is the path to the template. If the path collides with an HTML
2919/// element you may prefix it with `self::` to disambiguate it. You can assign to properties using
2920/// attributes. You can toggle a property by putting an attribute without assigning it a value.
2921///
2922/// You can create and assign a dynamic template to a property by either putting a prop element
2923/// directly inside the instantiation (`<prop property_name>children</prop>`),
2924/// or by just putting children inside the instantiation and therefore implicitly assigning them to
2925/// the `children` property. Because `children` can be empty it's recommended to mark it as
2926/// `#[prop(into, default)]` to allow callers to not put any children inside.
2927///
2928/// Static instantiation doesn't currently support turbofish syntax, so you have to use type
2929/// inference for generics, so be careful.
2930///
2931/// ```rust
2932/// templates! {
2933///     pub fn greet<'a>(
2934///         name: &'a str,
2935///         #[prop(toggle)]
2936///         politely: bool,
2937///     ) {
2938///         <p>
2939///             <if {politely}>
2940///                 "Thank you for gracing us with your presense"
2941///                 {name}
2942///             <else>
2943///                 "Hi" {name}
2944///             </if>
2945///         </p>
2946///     }
2947///
2948///     pub fn greet_john() {
2949///         <greet politely name="Mr. John" />
2950///     }
2951///
2952///     pub fn base<'a>(
2953///         #[prop(into, default)]
2954///         head: TemplateFn<'a>,
2955///         #[prop(into, default)]
2956///         children: TemplateFn<'a>,
2957///     ) {
2958///         <!DOCTYPE html>
2959///         <html>
2960///             <head>
2961///                 <{head} />
2962///             </head>
2963///             <body>
2964///                 <{children} />
2965///             </body>
2966///         </html>
2967///     }
2968///
2969///     pub fn home_page() {
2970///         <self::base>
2971///             <prop head>
2972///                 <title>"Awesome"</title>
2973///             </prop>
2974///
2975///             <greet politely name="Mr. John" />
2976///         </self::base>
2977///     }
2978/// }
2979/// ```
2980///
2981/// [`TemplateFn`]: struct.TemplateFn.html
2982/// [`Attributes`]: trait.Attributes.html
2983/// [`Attributes::render_into`]: trait.Attributes.html#tymethod.render_into
2984#[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/// Define dynamic templates, basically the content of a template funcation inside [`templates`]
2998/// This returns [`TemplateFn`]. To see the syntax and usage information check [`templates`].
2999///
3000/// ```rust
3001/// let name = "world";
3002/// let html = tmpl! {
3003///     <p>Hello, {name}!</p>
3004/// }.render()?;
3005/// println!("{html}");
3006/// ```
3007///
3008/// [`templates`]: macro.templates.html
3009/// [`TemplateFn`]: struct.TemplateFn.html
3010#[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}