yew_macro/html_tree/
mod.rs1use proc_macro2::{Delimiter, Ident, Span, TokenStream};
2use quote::{quote, quote_spanned, ToTokens};
3use syn::buffer::Cursor;
4use syn::ext::IdentExt;
5use syn::parse::{Parse, ParseStream};
6use syn::spanned::Spanned;
7use syn::{braced, token, Token};
8
9use crate::{is_ide_completion, PeekValue};
10
11mod html_block;
12mod html_component;
13mod html_dashed_name;
14mod html_element;
15mod html_if;
16mod html_iterable;
17mod html_list;
18mod html_node;
19mod lint;
20mod tag;
21
22use html_block::HtmlBlock;
23use html_component::HtmlComponent;
24pub use html_dashed_name::HtmlDashedName;
25use html_element::HtmlElement;
26use html_if::HtmlIf;
27use html_iterable::HtmlIterable;
28use html_list::HtmlList;
29use html_node::HtmlNode;
30use tag::TagTokens;
31
32use self::html_block::BlockContent;
33
34pub enum HtmlType {
35 Block,
36 Component,
37 List,
38 Element,
39 If,
40 Empty,
41}
42
43pub enum HtmlTree {
44 Block(Box<HtmlBlock>),
45 Component(Box<HtmlComponent>),
46 List(Box<HtmlList>),
47 Element(Box<HtmlElement>),
48 If(Box<HtmlIf>),
49 Empty,
50}
51
52impl Parse for HtmlTree {
53 fn parse(input: ParseStream) -> syn::Result<Self> {
54 let html_type = Self::peek_html_type(input)
55 .ok_or_else(|| input.error("expected a valid html element"))?;
56 let html_tree = match html_type {
57 HtmlType::Empty => HtmlTree::Empty,
58 HtmlType::Component => HtmlTree::Component(Box::new(input.parse()?)),
59 HtmlType::Element => HtmlTree::Element(Box::new(input.parse()?)),
60 HtmlType::Block => HtmlTree::Block(Box::new(input.parse()?)),
61 HtmlType::List => HtmlTree::List(Box::new(input.parse()?)),
62 HtmlType::If => HtmlTree::If(Box::new(input.parse()?)),
63 };
64 Ok(html_tree)
65 }
66}
67
68impl HtmlTree {
69 fn peek_html_type(input: ParseStream) -> Option<HtmlType> {
74 let input = input.fork(); if input.is_empty() {
77 Some(HtmlType::Empty)
78 } else if input
79 .cursor()
80 .group(proc_macro2::Delimiter::Brace)
81 .is_some()
82 {
83 Some(HtmlType::Block)
84 } else if HtmlIf::peek(input.cursor()).is_some() {
85 Some(HtmlType::If)
86 } else if input.peek(Token![<]) {
87 let _lt: Token![<] = input.parse().ok()?;
88
89 let _slash: Option<Token![/]> = input.parse().ok();
91
92 if input.peek(Token![>]) {
93 Some(HtmlType::List)
94 } else if input.peek(Token![@]) {
95 Some(HtmlType::Element) } else if input.peek(Token![::]) {
97 Some(HtmlType::Component)
98 } else if input.peek(Ident::peek_any) {
99 let ident = Ident::parse_any(&input).ok()?;
100 let ident_str = ident.to_string();
101
102 if input.peek(Token![=]) || (input.peek(Token![?]) && input.peek2(Token![=])) {
103 Some(HtmlType::List)
104 } else if ident_str.chars().next().unwrap().is_ascii_uppercase()
105 || input.peek(Token![::])
106 || is_ide_completion() && ident_str.chars().any(|c| c.is_ascii_uppercase())
107 {
108 Some(HtmlType::Component)
109 } else {
110 Some(HtmlType::Element)
111 }
112 } else {
113 None
114 }
115 } else {
116 None
117 }
118 }
119}
120
121impl ToTokens for HtmlTree {
122 fn to_tokens(&self, tokens: &mut TokenStream) {
123 lint::lint_all(self);
124 match self {
125 HtmlTree::Empty => tokens.extend(quote! {
126 <::yew::virtual_dom::VNode as ::std::default::Default>::default()
127 }),
128 HtmlTree::Component(comp) => comp.to_tokens(tokens),
129 HtmlTree::Element(tag) => tag.to_tokens(tokens),
130 HtmlTree::List(list) => list.to_tokens(tokens),
131 HtmlTree::Block(block) => block.to_tokens(tokens),
132 HtmlTree::If(block) => block.to_tokens(tokens),
133 }
134 }
135}
136
137pub enum HtmlRoot {
138 Tree(HtmlTree),
139 Iterable(Box<HtmlIterable>),
140 Node(Box<HtmlNode>),
141}
142
143impl Parse for HtmlRoot {
144 fn parse(input: ParseStream) -> syn::Result<Self> {
145 let html_root = if HtmlTree::peek_html_type(input).is_some() {
146 Self::Tree(input.parse()?)
147 } else if HtmlIterable::peek(input.cursor()).is_some() {
148 Self::Iterable(Box::new(input.parse()?))
149 } else {
150 Self::Node(Box::new(input.parse()?))
151 };
152
153 if !input.is_empty() {
154 let stream: TokenStream = input.parse()?;
155 Err(syn::Error::new_spanned(
156 stream,
157 "only one root html element is allowed (hint: you can wrap multiple html elements \
158 in a fragment `<></>`)",
159 ))
160 } else {
161 Ok(html_root)
162 }
163 }
164}
165
166impl ToTokens for HtmlRoot {
167 fn to_tokens(&self, tokens: &mut TokenStream) {
168 match self {
169 Self::Tree(tree) => tree.to_tokens(tokens),
170 Self::Node(node) => node.to_tokens(tokens),
171 Self::Iterable(iterable) => iterable.to_tokens(tokens),
172 }
173 }
174}
175
176pub struct HtmlRootVNode(HtmlRoot);
178impl Parse for HtmlRootVNode {
179 fn parse(input: ParseStream) -> syn::Result<Self> {
180 input.parse().map(Self)
181 }
182}
183
184impl ToTokens for HtmlRootVNode {
185 fn to_tokens(&self, tokens: &mut TokenStream) {
186 let new_tokens = self.0.to_token_stream();
187 tokens.extend(
188 quote_spanned! {self.0.span().resolved_at(Span::mixed_site())=> {
189 #[allow(clippy::useless_conversion)]
190 <::yew::virtual_dom::VNode as ::std::convert::From<_>>::from(#new_tokens)
191 }},
192 );
193 }
194}
195
196pub trait ToNodeIterator {
198 fn to_node_iterator_stream(&self) -> Option<TokenStream>;
203}
204
205impl ToNodeIterator for HtmlTree {
206 fn to_node_iterator_stream(&self) -> Option<TokenStream> {
207 match self {
208 HtmlTree::Block(block) => block.to_node_iterator_stream(),
209 _ => None,
211 }
212 }
213}
214
215pub struct HtmlChildrenTree(pub Vec<HtmlTree>);
216
217impl HtmlChildrenTree {
218 pub fn new() -> Self {
219 Self(Vec::new())
220 }
221
222 pub fn parse_child(&mut self, input: ParseStream) -> syn::Result<()> {
223 self.0.push(input.parse()?);
224 Ok(())
225 }
226
227 pub fn is_empty(&self) -> bool {
228 self.0.is_empty()
229 }
230
231 fn only_single_node_children(&self) -> bool {
234 self.0
235 .iter()
236 .map(ToNodeIterator::to_node_iterator_stream)
237 .all(|s| s.is_none())
238 }
239
240 pub fn to_build_vec_token_stream(&self) -> TokenStream {
241 let Self(children) = self;
242
243 if self.only_single_node_children() {
244 let children_into = children
247 .iter()
248 .map(|child| quote_spanned! {child.span()=> ::std::convert::Into::into(#child) });
249 return quote! {
250 ::std::vec![#(#children_into),*]
251 };
252 }
253
254 let vec_ident = Ident::new("__yew_v", Span::mixed_site());
255 let add_children_streams = children.iter().map(|child| {
256 if let Some(node_iterator_stream) = child.to_node_iterator_stream() {
257 quote! {
258 ::std::iter::Extend::extend(&mut #vec_ident, #node_iterator_stream);
259 }
260 } else {
261 quote_spanned! {child.span()=>
262 #vec_ident.push(::std::convert::Into::into(#child));
263 }
264 }
265 });
266
267 quote! {
268 {
269 let mut #vec_ident = ::std::vec::Vec::new();
270 #(#add_children_streams)*
271 #vec_ident
272 }
273 }
274 }
275
276 fn parse_delimited(input: ParseStream) -> syn::Result<Self> {
277 let mut children = HtmlChildrenTree::new();
278
279 while !input.is_empty() {
280 children.parse_child(input)?;
281 }
282
283 Ok(children)
284 }
285
286 pub fn to_children_renderer_tokens(&self) -> Option<TokenStream> {
287 match self.0[..] {
288 [] => None,
289 [HtmlTree::Component(ref children)] => Some(quote! { #children }),
290 [HtmlTree::Element(ref children)] => Some(quote! { #children }),
291 [HtmlTree::Block(ref m)] => {
292 if let HtmlBlock {
296 content: BlockContent::Node(children),
297 ..
298 } = m.as_ref()
299 {
300 Some(quote! { #children })
301 } else {
302 Some(quote! { ::yew::html::ChildrenRenderer::new(#self) })
303 }
304 }
305 _ => Some(quote! { ::yew::html::ChildrenRenderer::new(#self) }),
306 }
307 }
308
309 pub fn to_vnode_tokens(&self) -> TokenStream {
310 match self.0[..] {
311 [] => quote! {::std::default::Default::default() },
312 [HtmlTree::Component(ref children)] => {
313 quote! { ::yew::html::IntoPropValue::<::yew::virtual_dom::VNode>::into_prop_value(#children) }
314 }
315 [HtmlTree::Element(ref children)] => {
316 quote! { ::yew::html::IntoPropValue::<::yew::virtual_dom::VNode>::into_prop_value(#children) }
317 }
318 [HtmlTree::Block(ref m)] => {
319 if let HtmlBlock {
323 content: BlockContent::Node(children),
324 ..
325 } = m.as_ref()
326 {
327 quote! { ::yew::html::IntoPropValue::<::yew::virtual_dom::VNode>::into_prop_value(#children) }
328 } else {
329 quote! {
330 ::yew::html::IntoPropValue::<::yew::virtual_dom::VNode>::into_prop_value(
331 ::yew::html::ChildrenRenderer::new(#self)
332 )
333 }
334 }
335 }
336 _ => quote! {
337 ::yew::html::IntoPropValue::<::yew::virtual_dom::VNode>::into_prop_value(
338 ::yew::html::ChildrenRenderer::new(#self)
339 )
340 },
341 }
342 }
343}
344
345impl ToTokens for HtmlChildrenTree {
346 fn to_tokens(&self, tokens: &mut TokenStream) {
347 tokens.extend(self.to_build_vec_token_stream());
348 }
349}
350
351pub struct HtmlRootBraced {
352 brace: token::Brace,
353 children: HtmlChildrenTree,
354}
355
356impl PeekValue<()> for HtmlRootBraced {
357 fn peek(cursor: Cursor) -> Option<()> {
358 cursor.group(Delimiter::Brace).map(|_| ())
359 }
360}
361
362impl Parse for HtmlRootBraced {
363 fn parse(input: ParseStream) -> syn::Result<Self> {
364 let content;
365 let brace = braced!(content in input);
366 let children = HtmlChildrenTree::parse_delimited(&content)?;
367
368 Ok(HtmlRootBraced { brace, children })
369 }
370}
371
372impl ToTokens for HtmlRootBraced {
373 fn to_tokens(&self, tokens: &mut TokenStream) {
374 let Self { brace, children } = self;
375
376 tokens.extend(quote_spanned! {brace.span.span()=>
377 {
378 ::yew::virtual_dom::VNode::VList(::std::rc::Rc::new(
379 ::yew::virtual_dom::VList::with_children(#children, ::std::option::Option::None)
380 ))
381 }
382 });
383 }
384}