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_for;
16mod html_if;
17mod html_iterable;
18mod html_list;
19mod html_node;
20mod lint;
21mod tag;
22
23use html_block::HtmlBlock;
24use html_component::HtmlComponent;
25pub use html_dashed_name::HtmlDashedName;
26use html_element::HtmlElement;
27use html_if::HtmlIf;
28use html_iterable::HtmlIterable;
29use html_list::HtmlList;
30use html_node::HtmlNode;
31use tag::TagTokens;
32
33use self::html_block::BlockContent;
34use self::html_for::HtmlFor;
35
36pub enum HtmlType {
37 Block,
38 Component,
39 List,
40 Element,
41 If,
42 For,
43 Empty,
44}
45
46pub enum HtmlTree {
47 Block(Box<HtmlBlock>),
48 Component(Box<HtmlComponent>),
49 List(Box<HtmlList>),
50 Element(Box<HtmlElement>),
51 If(Box<HtmlIf>),
52 For(Box<HtmlFor>),
53 Empty,
54}
55
56impl Parse for HtmlTree {
57 fn parse(input: ParseStream) -> syn::Result<Self> {
58 let html_type = Self::peek_html_type(input)
59 .ok_or_else(|| input.error("expected a valid html element"))?;
60 Ok(match html_type {
61 HtmlType::Empty => Self::Empty,
62 HtmlType::Component => Self::Component(Box::new(input.parse()?)),
63 HtmlType::Element => Self::Element(Box::new(input.parse()?)),
64 HtmlType::Block => Self::Block(Box::new(input.parse()?)),
65 HtmlType::List => Self::List(Box::new(input.parse()?)),
66 HtmlType::If => Self::If(Box::new(input.parse()?)),
67 HtmlType::For => Self::For(Box::new(input.parse()?)),
68 })
69 }
70}
71
72impl HtmlTree {
73 fn peek_html_type(input: ParseStream) -> Option<HtmlType> {
78 let input = input.fork(); let cursor = input.cursor();
80
81 if input.is_empty() {
82 Some(HtmlType::Empty)
83 } else if HtmlBlock::peek(cursor).is_some() {
84 Some(HtmlType::Block)
85 } else if HtmlIf::peek(cursor).is_some() {
86 Some(HtmlType::If)
87 } else if HtmlFor::peek(cursor).is_some() {
88 Some(HtmlType::For)
89 } else if input.peek(Token![<]) {
90 let _lt: Token![<] = input.parse().ok()?;
91
92 let _slash: Option<Token![/]> = input.parse().ok();
94
95 if input.peek(Token![>]) {
96 Some(HtmlType::List)
97 } else if input.peek(Token![@]) {
98 Some(HtmlType::Element) } else if input.peek(Token![::]) {
100 Some(HtmlType::Component)
101 } else if input.peek(Ident::peek_any) {
102 let ident = Ident::parse_any(&input).ok()?;
103 let ident_str = ident.to_string();
104
105 if input.peek(Token![=]) || (input.peek(Token![?]) && input.peek2(Token![=])) {
106 Some(HtmlType::List)
107 } else if ident_str.chars().next().unwrap().is_ascii_uppercase()
108 || input.peek(Token![::])
109 || is_ide_completion() && ident_str.chars().any(|c| c.is_ascii_uppercase())
110 {
111 Some(HtmlType::Component)
112 } else {
113 Some(HtmlType::Element)
114 }
115 } else {
116 None
117 }
118 } else {
119 None
120 }
121 }
122}
123
124impl ToTokens for HtmlTree {
125 fn to_tokens(&self, tokens: &mut TokenStream) {
126 lint::lint_all(self);
127 match self {
128 Self::Empty => tokens.extend(quote! {
129 <::yew::virtual_dom::VNode as ::std::default::Default>::default()
130 }),
131 Self::Component(comp) => comp.to_tokens(tokens),
132 Self::Element(tag) => tag.to_tokens(tokens),
133 Self::List(list) => list.to_tokens(tokens),
134 Self::Block(block) => block.to_tokens(tokens),
135 Self::If(block) => block.to_tokens(tokens),
136 Self::For(block) => block.to_tokens(tokens),
137 }
138 }
139}
140
141pub enum HtmlRoot {
142 Tree(HtmlTree),
143 Node(Box<HtmlNode>),
144}
145
146impl Parse for HtmlRoot {
147 fn parse(input: ParseStream) -> syn::Result<Self> {
148 let html_root = if HtmlTree::peek_html_type(input).is_some() {
149 Self::Tree(input.parse()?)
150 } else {
151 Self::Node(Box::new(input.parse()?))
152 };
153
154 if !input.is_empty() {
155 let stream: TokenStream = input.parse()?;
156 Err(syn::Error::new_spanned(
157 stream,
158 "only one root html element is allowed (hint: you can wrap multiple html elements \
159 in a fragment `<></>`)",
160 ))
161 } else {
162 Ok(html_root)
163 }
164 }
165}
166
167impl ToTokens for HtmlRoot {
168 fn to_tokens(&self, tokens: &mut TokenStream) {
169 match self {
170 Self::Tree(tree) => tree.to_tokens(tokens),
171 Self::Node(node) => node.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 fn is_singular(&self) -> bool;
207}
208
209impl ToNodeIterator for HtmlTree {
210 fn to_node_iterator_stream(&self) -> Option<TokenStream> {
211 match self {
212 Self::Block(block) => block.to_node_iterator_stream(),
213 _ => None,
215 }
216 }
217
218 fn is_singular(&self) -> bool {
219 match self {
220 Self::Block(block) => block.is_singular(),
221 _ => true,
222 }
223 }
224}
225
226pub struct HtmlChildrenTree(pub Vec<HtmlTree>);
227
228impl HtmlChildrenTree {
229 pub fn new() -> Self {
230 Self(Vec::new())
231 }
232
233 pub fn parse_child(&mut self, input: ParseStream) -> syn::Result<()> {
234 self.0.push(input.parse()?);
235 Ok(())
236 }
237
238 pub fn is_empty(&self) -> bool {
239 self.0.is_empty()
240 }
241
242 fn only_single_node_children(&self) -> bool {
245 self.0.iter().all(HtmlTree::is_singular)
246 }
247
248 pub fn to_build_vec_token_stream(&self) -> TokenStream {
249 let Self(children) = self;
250
251 if self.only_single_node_children() {
252 let children_into = children
255 .iter()
256 .map(|child| quote_spanned! {child.span()=> ::std::convert::Into::into(#child) });
257 return quote! {
258 [#(#children_into),*].to_vec()
259 };
260 }
261
262 let vec_ident = Ident::new("__yew_v", Span::mixed_site());
263 let add_children_streams = children.iter().map(|child| {
264 if let Some(node_iterator_stream) = child.to_node_iterator_stream() {
265 quote! {
266 ::std::iter::Extend::extend(&mut #vec_ident, #node_iterator_stream);
267 }
268 } else {
269 quote_spanned! {child.span()=>
270 #vec_ident.push(::std::convert::Into::into(#child));
271 }
272 }
273 });
274
275 quote! {
276 {
277 let mut #vec_ident = ::std::vec::Vec::new();
278 #(#add_children_streams)*
279 #vec_ident
280 }
281 }
282 }
283
284 fn parse_delimited(input: ParseStream) -> syn::Result<Self> {
285 let mut children = HtmlChildrenTree::new();
286
287 while !input.is_empty() {
288 children.parse_child(input)?;
289 }
290
291 Ok(children)
292 }
293
294 pub fn to_children_renderer_tokens(&self) -> Option<TokenStream> {
295 match self.0[..] {
296 [] => None,
297 [HtmlTree::Component(ref children)] => Some(quote! { #children }),
298 [HtmlTree::Element(ref children)] => Some(quote! { #children }),
299 [HtmlTree::Block(ref m)] => {
300 if let HtmlBlock {
304 content: BlockContent::Node(children),
305 ..
306 } = m.as_ref()
307 {
308 Some(quote! { #children })
309 } else {
310 Some(quote! { ::yew::html::ChildrenRenderer::new(#self) })
311 }
312 }
313 _ => Some(quote! { ::yew::html::ChildrenRenderer::new(#self) }),
314 }
315 }
316
317 pub fn to_vnode_tokens(&self) -> TokenStream {
318 match self.0[..] {
319 [] => quote! {::std::default::Default::default() },
320 [HtmlTree::Component(ref children)] => {
321 quote! { ::yew::html::IntoPropValue::<::yew::virtual_dom::VNode>::into_prop_value(#children) }
322 }
323 [HtmlTree::Element(ref children)] => {
324 quote! { ::yew::html::IntoPropValue::<::yew::virtual_dom::VNode>::into_prop_value(#children) }
325 }
326 [HtmlTree::Block(ref m)] => {
327 if let HtmlBlock {
331 content: BlockContent::Node(children),
332 ..
333 } = m.as_ref()
334 {
335 quote! { ::yew::html::IntoPropValue::<::yew::virtual_dom::VNode>::into_prop_value(#children) }
336 } else {
337 quote! {
338 ::yew::html::IntoPropValue::<::yew::virtual_dom::VNode>::into_prop_value(
339 ::yew::html::ChildrenRenderer::new(#self)
340 )
341 }
342 }
343 }
344 _ => quote! {
345 ::yew::html::IntoPropValue::<::yew::virtual_dom::VNode>::into_prop_value(
346 ::yew::html::ChildrenRenderer::new(#self)
347 )
348 },
349 }
350 }
351
352 pub fn size_hint(&self) -> Option<usize> {
353 self.only_single_node_children().then_some(self.0.len())
354 }
355
356 pub fn fully_keyed(&self) -> Option<bool> {
357 for child in self.0.iter() {
358 match child {
359 HtmlTree::Block(block) => {
360 return if let BlockContent::Node(node) = &block.content {
361 matches!(&**node, HtmlNode::Literal(_)).then_some(false)
362 } else {
363 None
364 }
365 }
366 HtmlTree::Component(comp) => {
367 if comp.props.props.special.key.is_none() {
368 return Some(false);
369 }
370 }
371 HtmlTree::List(list) => {
372 if list.open.props.key.is_none() {
373 return Some(false);
374 }
375 }
376 HtmlTree::Element(element) => {
377 if element.props.special.key.is_none() {
378 return Some(false);
379 }
380 }
381 HtmlTree::If(_) | HtmlTree::For(_) | HtmlTree::Empty => return Some(false),
382 }
383 }
384 Some(true)
385 }
386}
387
388impl ToTokens for HtmlChildrenTree {
389 fn to_tokens(&self, tokens: &mut TokenStream) {
390 tokens.extend(self.to_build_vec_token_stream());
391 }
392}
393
394pub struct HtmlRootBraced {
395 brace: token::Brace,
396 children: HtmlChildrenTree,
397}
398
399impl PeekValue<()> for HtmlRootBraced {
400 fn peek(cursor: Cursor) -> Option<()> {
401 cursor.group(Delimiter::Brace).map(|_| ())
402 }
403}
404
405impl Parse for HtmlRootBraced {
406 fn parse(input: ParseStream) -> syn::Result<Self> {
407 let content;
408 let brace = braced!(content in input);
409 let children = HtmlChildrenTree::parse_delimited(&content)?;
410
411 Ok(HtmlRootBraced { brace, children })
412 }
413}
414
415impl ToTokens for HtmlRootBraced {
416 fn to_tokens(&self, tokens: &mut TokenStream) {
417 let Self { brace, children } = self;
418
419 tokens.extend(quote_spanned! {brace.span.span()=>
420 {
421 ::yew::virtual_dom::VNode::VList(::std::rc::Rc::new(
422 ::yew::virtual_dom::VList::with_children(#children, ::std::option::Option::None)
423 ))
424 }
425 });
426 }
427}