leptos_mview_core/
lib.rs

1#![warn(clippy::pedantic, clippy::nursery)]
2#![allow(
3    clippy::option_if_let_else,
4    clippy::or_fun_call,
5    clippy::module_name_repetitions
6)]
7
8mod ast;
9mod error_ext;
10mod expand;
11mod kw;
12mod parse;
13mod span;
14
15use ast::{Child, Children};
16use expand::root_children_tokens;
17use proc_macro2::{Span, TokenStream};
18use proc_macro_error2::abort;
19use quote::quote;
20use syn::spanned::Spanned;
21
22#[must_use]
23pub fn mview_impl(input: TokenStream) -> TokenStream {
24    // return () in case of any errors, to avoid "unexpected end of macro
25    // invocation" e.g. when assigning `let res = mview! { ... };`
26    proc_macro_error2::set_dummy(quote! { () });
27
28    let children = match syn::parse2::<Children>(input) {
29        Ok(tree) => tree,
30        Err(e) => return e.to_compile_error(),
31    };
32
33    // If there's a single top level component, can just expand like
34    // div().attr(...).child(...)...
35    // If there are multiple top-level children, need to use the fragment.
36    if children.len() == 1 {
37        let child = children.into_vec().remove(0);
38        match child {
39            Child::Node(node) => quote! {
40                { #[allow(unused_braces)] #node }
41            },
42            Child::Slot(slot, _) => abort!(
43                slot.span(),
44                "slots should be inside a parent that supports slots"
45            ),
46        }
47    } else {
48        // look for any slots
49        if let Some(slot) = children.slot_children().next() {
50            abort!(
51                slot.tag().span(),
52                "slots should be inside a parent that supports slots"
53            );
54        };
55
56        let fragment = root_children_tokens(children.node_children(), Span::call_site());
57        quote! {
58            {
59                #[allow(unused_braces)]
60                #fragment
61            }
62        }
63    }
64}