From b336704fcc55bcc3e4755a1f6e6089a40c8f346b Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Wed, 8 May 2019 14:29:27 -0700 Subject: [PATCH 01/46] cargo new --lib yew-html --- .gitignore | 3 +++ Cargo.toml | 7 +++++++ src/lib.rs | 7 +++++++ 3 files changed, 17 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000000..2f88dbac54e --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000000..441bdd4bba4 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "yew-html" +version = "0.1.0" +authors = ["Justin Starry "] +edition = "2018" + +[dependencies] diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000000..31e1bb209f9 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,7 @@ +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} From 3464e00dba7c6db9598b7b78a573442e77e0a2d0 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Wed, 8 May 2019 14:43:45 -0700 Subject: [PATCH 02/46] Add proc macro dependencies --- Cargo.toml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 441bdd4bba4..b0f782fa8fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,15 @@ [package] name = "yew-html" version = "0.1.0" -authors = ["Justin Starry "] +authors = ["Justin Starry"] +license = "MIT/Apache-2.0" +repository = "https://github.com/jstarry/yew-html" edition = "2018" [dependencies] +syn = "0.15" +quote = "0.6" +proc-macro2 = "0.4" + +[lib] +proc-macro = true From f28bd905d5be0c195da4dbee7e35713b4319eca1 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Wed, 15 May 2019 22:08:15 -0700 Subject: [PATCH 03/46] Create macro for parsing lists --- Cargo.toml | 21 +++---- html-impl/Cargo.toml | 13 ++++ html-impl/src/html_tree/html_list.rs | 94 ++++++++++++++++++++++++++++ html-impl/src/html_tree/mod.rs | 24 +++++++ html-impl/src/lib.rs | 20 ++++++ html/Cargo.toml | 16 +++++ html/src/lib.rs | 5 ++ html/tests/cases.rs | 5 ++ html/tests/parse-list.rs | 7 +++ main.rs | 7 +++ src/lib.rs | 7 --- 11 files changed, 201 insertions(+), 18 deletions(-) create mode 100644 html-impl/Cargo.toml create mode 100644 html-impl/src/html_tree/html_list.rs create mode 100644 html-impl/src/html_tree/mod.rs create mode 100644 html-impl/src/lib.rs create mode 100644 html/Cargo.toml create mode 100644 html/src/lib.rs create mode 100644 html/tests/cases.rs create mode 100644 html/tests/parse-list.rs create mode 100644 main.rs delete mode 100644 src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index b0f782fa8fe..c2ecce40bf1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,15 +1,14 @@ [package] -name = "yew-html" -version = "0.1.0" -authors = ["Justin Starry"] -license = "MIT/Apache-2.0" -repository = "https://github.com/jstarry/yew-html" +name = "yew-macros" +version = "0.0.1" edition = "2018" -[dependencies] -syn = "0.15" -quote = "0.6" -proc-macro2 = "0.4" +[workspace] + +[[bin]] +name = "macros" +path = "main.rs" -[lib] -proc-macro = true +[dependencies] +yew-html = { path = "html" } +yew-html-impl = { path = "html-impl" } diff --git a/html-impl/Cargo.toml b/html-impl/Cargo.toml new file mode 100644 index 00000000000..f96d5071082 --- /dev/null +++ b/html-impl/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "yew-html-impl" +version = "0.0.1" +edition = "2018" + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "0.15", features = ["full"] } +quote = "0.6" +proc-macro-hack = "0.5" +proc-macro2 = "0.4" diff --git a/html-impl/src/html_tree/html_list.rs b/html-impl/src/html_tree/html_list.rs new file mode 100644 index 00000000000..d66bfc027f6 --- /dev/null +++ b/html-impl/src/html_tree/html_list.rs @@ -0,0 +1,94 @@ +use super::HtmlTree; +use crate::Peek; +use proc_macro2::TokenTree; +use quote::{quote, ToTokens}; +use syn::parse::{Parse, ParseStream, Result}; +use syn::token; + +struct HtmlListChildren(Vec); +pub struct HtmlList { + children: HtmlListChildren, +} + +struct HtmlListOpen { + lt_token: token::Lt, + gt_token: token::Gt, +} + +impl Peek for HtmlListOpen { + fn peek(input: &ParseStream) -> bool { + input.peek(token::Lt) && input.peek2(token::Gt) + } +} + +impl Parse for HtmlListOpen { + fn parse(input: ParseStream) -> Result { + Ok(HtmlListOpen { + lt_token: input.parse()?, + gt_token: input.parse()?, + }) + } +} + +impl ToTokens for HtmlListOpen { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let HtmlListOpen { lt_token, gt_token } = self; + tokens.extend(quote! {#lt_token#gt_token}); + } +} + +struct HtmlListClose {} + +impl Parse for HtmlListClose { + fn parse(input: ParseStream) -> Result { + input.parse::()?; + input.parse::()?; + input.parse::()?; + Ok(HtmlListClose {}) + } +} + +impl Parse for HtmlListChildren { + fn parse(input: ParseStream) -> Result { + let mut children: Vec = vec![]; + while !input.is_empty() { + children.push(input.parse::()?); + } + + Ok(HtmlListChildren(children)) + } +} + +impl Peek for HtmlList { + fn peek(input: &ParseStream) -> bool { + HtmlListOpen::peek(input) + } +} + +impl Parse for HtmlList { + fn parse(input: ParseStream) -> Result { + let open = input.parse::()?; + + let mut content: Vec = vec![]; + while !input.is_empty() { + content.push(input.parse::()?); + } + + let split = if content.len() < 3 { + 0 + } else { + content.len() - 3 + }; + + let last_tokens = content.split_off(split); + let token_stream: proc_macro2::TokenStream = last_tokens.into_iter().collect(); + syn::parse::(token_stream.into()).map_err(|_| { + syn::Error::new_spanned(open, "this open tag has no corresponding close tag") + })?; + + let token_stream: proc_macro2::TokenStream = content.into_iter().collect(); + let children = syn::parse::(token_stream.into())?; + + Ok(HtmlList { children }) + } +} diff --git a/html-impl/src/html_tree/mod.rs b/html-impl/src/html_tree/mod.rs new file mode 100644 index 00000000000..6962d101003 --- /dev/null +++ b/html-impl/src/html_tree/mod.rs @@ -0,0 +1,24 @@ +pub mod html_list; + +use crate::Peek; +use html_list::HtmlList; +use syn::parse::{Parse, ParseStream, Result}; + +pub enum HtmlTree { + List(HtmlList), + Empty, +} + +impl Parse for HtmlTree { + fn parse(input: ParseStream) -> Result { + if HtmlList::peek(&input) { + return Ok(HtmlTree::List(input.parse()?)); + } + + if input.is_empty() { + Ok(HtmlTree::Empty) + } else { + Err(input.error("expected some kind of html element")) + } + } +} diff --git a/html-impl/src/lib.rs b/html-impl/src/lib.rs new file mode 100644 index 00000000000..ee5684162b0 --- /dev/null +++ b/html-impl/src/lib.rs @@ -0,0 +1,20 @@ +extern crate proc_macro; + +mod html_tree; + +use html_tree::HtmlTree; +use proc_macro::TokenStream; +use proc_macro_hack::proc_macro_hack; +use quote::quote; +use syn::parse::ParseStream; +use syn::parse_macro_input; + +trait Peek: Sized { + fn peek(input: &ParseStream) -> bool; +} + +#[proc_macro_hack] +pub fn html(input: TokenStream) -> TokenStream { + parse_macro_input!(input as HtmlTree); + TokenStream::from(quote! { 42 }) +} diff --git a/html/Cargo.toml b/html/Cargo.toml new file mode 100644 index 00000000000..3974bddb36c --- /dev/null +++ b/html/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "yew-html" +version = "0.0.1" +edition = "2018" +autotests = false + +[[test]] +name = "tests" +path = "tests/cases.rs" + +[dev-dependencies] +trybuild = "1.0" + +[dependencies] +proc-macro-hack = "0.5" +yew-html-impl = { path = "../html-impl" } diff --git a/html/src/lib.rs b/html/src/lib.rs new file mode 100644 index 00000000000..47c66e21483 --- /dev/null +++ b/html/src/lib.rs @@ -0,0 +1,5 @@ +use proc_macro_hack::proc_macro_hack; + +/// Generate html tree +#[proc_macro_hack] +pub use yew_html_impl::html; diff --git a/html/tests/cases.rs b/html/tests/cases.rs new file mode 100644 index 00000000000..7e5d8616b47 --- /dev/null +++ b/html/tests/cases.rs @@ -0,0 +1,5 @@ +#[test] +fn tests() { + let t = trybuild::TestCases::new(); + t.pass("tests/parse-list.rs"); +} diff --git a/html/tests/parse-list.rs b/html/tests/parse-list.rs new file mode 100644 index 00000000000..721f07cf771 --- /dev/null +++ b/html/tests/parse-list.rs @@ -0,0 +1,7 @@ +use yew_html::html; + +fn main() { + html! { + <> + }; +} diff --git a/main.rs b/main.rs new file mode 100644 index 00000000000..721f07cf771 --- /dev/null +++ b/main.rs @@ -0,0 +1,7 @@ +use yew_html::html; + +fn main() { + html! { + <> + }; +} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 31e1bb209f9..00000000000 --- a/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); - } -} From 6c94426286991dcef23806f342d28f2fe2861738 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Wed, 15 May 2019 22:50:09 -0700 Subject: [PATCH 04/46] Expand html list tests --- html-impl/src/html_tree/html_list.rs | 26 ++++++++++++------- html-impl/src/html_tree/mod.rs | 2 +- html/tests/cases.rs | 3 ++- html/tests/html-list-fail.rs | 15 +++++++++++ html/tests/html-list-fail.stderr | 17 ++++++++++++ .../{parse-list.rs => html-list-pass.rs} | 4 +++ 6 files changed, 56 insertions(+), 11 deletions(-) create mode 100644 html/tests/html-list-fail.rs create mode 100644 html/tests/html-list-fail.stderr rename html/tests/{parse-list.rs => html-list-pass.rs} (60%) diff --git a/html-impl/src/html_tree/html_list.rs b/html-impl/src/html_tree/html_list.rs index d66bfc027f6..227237e6895 100644 --- a/html-impl/src/html_tree/html_list.rs +++ b/html-impl/src/html_tree/html_list.rs @@ -39,6 +39,12 @@ impl ToTokens for HtmlListOpen { struct HtmlListClose {} +impl Peek for HtmlListClose { + fn peek(input: &ParseStream) -> bool { + input.peek(token::Lt) && input.peek2(token::Div) && input.peek3(token::Gt) + } +} + impl Parse for HtmlListClose { fn parse(input: ParseStream) -> Result { input.parse::()?; @@ -70,19 +76,21 @@ impl Parse for HtmlList { let open = input.parse::()?; let mut content: Vec = vec![]; + let mut list_stack_count = 0; while !input.is_empty() { + if HtmlListOpen::peek(&input) { + list_stack_count += 1; + } else if HtmlListClose::peek(&input) { + if list_stack_count == 0 { + break; + } else { + list_stack_count -= 1; + } + } content.push(input.parse::()?); } - let split = if content.len() < 3 { - 0 - } else { - content.len() - 3 - }; - - let last_tokens = content.split_off(split); - let token_stream: proc_macro2::TokenStream = last_tokens.into_iter().collect(); - syn::parse::(token_stream.into()).map_err(|_| { + input.parse::().map_err(|_| { syn::Error::new_spanned(open, "this open tag has no corresponding close tag") })?; diff --git a/html-impl/src/html_tree/mod.rs b/html-impl/src/html_tree/mod.rs index 6962d101003..0a2f0622b6a 100644 --- a/html-impl/src/html_tree/mod.rs +++ b/html-impl/src/html_tree/mod.rs @@ -18,7 +18,7 @@ impl Parse for HtmlTree { if input.is_empty() { Ok(HtmlTree::Empty) } else { - Err(input.error("expected some kind of html element")) + Err(input.error("expected valid html element")) } } } diff --git a/html/tests/cases.rs b/html/tests/cases.rs index 7e5d8616b47..ad12c5e3ab8 100644 --- a/html/tests/cases.rs +++ b/html/tests/cases.rs @@ -1,5 +1,6 @@ #[test] fn tests() { let t = trybuild::TestCases::new(); - t.pass("tests/parse-list.rs"); + t.pass("tests/html-list-pass.rs"); + t.compile_fail("tests/html-list-fail.rs"); } diff --git a/html/tests/html-list-fail.rs b/html/tests/html-list-fail.rs new file mode 100644 index 00000000000..9a918a85946 --- /dev/null +++ b/html/tests/html-list-fail.rs @@ -0,0 +1,15 @@ +use yew_html::html; + +fn main() { + html! { + <><> + }; + + html! { + + }; + + html! { + <><> + }; +} diff --git a/html/tests/html-list-fail.stderr b/html/tests/html-list-fail.stderr new file mode 100644 index 00000000000..0aedabdfa2b --- /dev/null +++ b/html/tests/html-list-fail.stderr @@ -0,0 +1,17 @@ +error: this open tag has no corresponding close tag + --> $DIR/html-list-fail.rs:5:9 + | +5 | <><> + | ^^ + +error: expected valid html element + --> $DIR/html-list-fail.rs:9:9 + | +9 | + | ^ + +error: this open tag has no corresponding close tag + --> $DIR/html-list-fail.rs:13:9 + | +13 | <><> + | ^^ diff --git a/html/tests/parse-list.rs b/html/tests/html-list-pass.rs similarity index 60% rename from html/tests/parse-list.rs rename to html/tests/html-list-pass.rs index 721f07cf771..c6d99c61575 100644 --- a/html/tests/parse-list.rs +++ b/html/tests/html-list-pass.rs @@ -4,4 +4,8 @@ fn main() { html! { <> }; + + html! { + <><><> + }; } From dec10ed2f6dbc115cfdeab6a606991c0edc0092d Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Thu, 16 May 2019 09:55:49 -0700 Subject: [PATCH 05/46] Only allow one root html element --- html-impl/src/html_tree/mod.rs | 18 ++++++++++++++---- html-impl/src/lib.rs | 4 ++-- html/tests/html-list-fail.rs | 5 +++++ html/tests/html-list-fail.stderr | 6 ++++++ html/tests/html-list-pass.rs | 5 ++++- 5 files changed, 31 insertions(+), 7 deletions(-) diff --git a/html-impl/src/html_tree/mod.rs b/html-impl/src/html_tree/mod.rs index 0a2f0622b6a..1fc16e93edc 100644 --- a/html-impl/src/html_tree/mod.rs +++ b/html-impl/src/html_tree/mod.rs @@ -9,13 +9,23 @@ pub enum HtmlTree { Empty, } -impl Parse for HtmlTree { +pub struct HtmlRoot(HtmlTree); +impl Parse for HtmlRoot { fn parse(input: ParseStream) -> Result { - if HtmlList::peek(&input) { - return Ok(HtmlTree::List(input.parse()?)); + let html_tree = input.parse::()?; + if !input.is_empty() { + Err(input.error("only one root html element allowed")) + } else { + Ok(HtmlRoot(html_tree)) } + } +} - if input.is_empty() { +impl Parse for HtmlTree { + fn parse(input: ParseStream) -> Result { + if HtmlList::peek(&input) { + Ok(HtmlTree::List(input.parse()?)) + } else if input.is_empty() { Ok(HtmlTree::Empty) } else { Err(input.error("expected valid html element")) diff --git a/html-impl/src/lib.rs b/html-impl/src/lib.rs index ee5684162b0..ff72d77123d 100644 --- a/html-impl/src/lib.rs +++ b/html-impl/src/lib.rs @@ -2,7 +2,7 @@ extern crate proc_macro; mod html_tree; -use html_tree::HtmlTree; +use html_tree::HtmlRoot; use proc_macro::TokenStream; use proc_macro_hack::proc_macro_hack; use quote::quote; @@ -15,6 +15,6 @@ trait Peek: Sized { #[proc_macro_hack] pub fn html(input: TokenStream) -> TokenStream { - parse_macro_input!(input as HtmlTree); + parse_macro_input!(input as HtmlRoot); TokenStream::from(quote! { 42 }) } diff --git a/html/tests/html-list-fail.rs b/html/tests/html-list-fail.rs index 9a918a85946..294873a00b1 100644 --- a/html/tests/html-list-fail.rs +++ b/html/tests/html-list-fail.rs @@ -12,4 +12,9 @@ fn main() { html! { <><> }; + + html! { + <> + <> + }; } diff --git a/html/tests/html-list-fail.stderr b/html/tests/html-list-fail.stderr index 0aedabdfa2b..494f137ee71 100644 --- a/html/tests/html-list-fail.stderr +++ b/html/tests/html-list-fail.stderr @@ -15,3 +15,9 @@ error: this open tag has no corresponding close tag | 13 | <><> | ^^ + +error: only one root html element allowed + --> $DIR/html-list-fail.rs:18:9 + | +18 | <> + | ^ diff --git a/html/tests/html-list-pass.rs b/html/tests/html-list-pass.rs index c6d99c61575..77417246763 100644 --- a/html/tests/html-list-pass.rs +++ b/html/tests/html-list-pass.rs @@ -6,6 +6,9 @@ fn main() { }; html! { - <><><> + <> + <> + <> + }; } From 1d810c13451c80736b112ad07f9fa1b0ba498ba9 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Thu, 16 May 2019 10:33:05 -0700 Subject: [PATCH 06/46] Add html-common and tokenize --- Cargo.toml | 1 + html-common/Cargo.toml | 10 ++++++++ .../src/html_tree/html_list.rs | 25 +++++++++++++++++-- .../src/html_tree/mod.rs | 23 +++++++++++++++++ html-common/src/lib.rs | 7 ++++++ html-impl/Cargo.toml | 1 + html-impl/src/lib.rs | 13 +++------- html/Cargo.toml | 1 + html/tests/html-list-pass.rs | 2 ++ main.rs | 4 ++- 10 files changed, 74 insertions(+), 13 deletions(-) create mode 100644 html-common/Cargo.toml rename {html-impl => html-common}/src/html_tree/html_list.rs (77%) rename {html-impl => html-common}/src/html_tree/mod.rs (55%) create mode 100644 html-common/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index c2ecce40bf1..cd992f40e39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,3 +12,4 @@ path = "main.rs" [dependencies] yew-html = { path = "html" } yew-html-impl = { path = "html-impl" } +yew-html-common = { path = "html-common" } diff --git a/html-common/Cargo.toml b/html-common/Cargo.toml new file mode 100644 index 00000000000..39f195e7cda --- /dev/null +++ b/html-common/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "yew-html-common" +version = "0.0.1" +edition = "2018" + +[dependencies] +syn = { version = "0.15", features = ["full"] } +quote = "0.6" +proc-macro-hack = "0.5" +proc-macro2 = "0.4" diff --git a/html-impl/src/html_tree/html_list.rs b/html-common/src/html_tree/html_list.rs similarity index 77% rename from html-impl/src/html_tree/html_list.rs rename to html-common/src/html_tree/html_list.rs index 227237e6895..18a17261169 100644 --- a/html-impl/src/html_tree/html_list.rs +++ b/html-common/src/html_tree/html_list.rs @@ -5,9 +5,19 @@ use quote::{quote, ToTokens}; use syn::parse::{Parse, ParseStream, Result}; use syn::token; -struct HtmlListChildren(Vec); +pub struct HtmlListChildren(pub Vec); +impl ToTokens for HtmlListChildren { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let HtmlListChildren(html_trees) = self; + let html_trees = html_trees.iter().map(|html_tree| quote! { #html_tree }); + tokens.extend(quote! { + ::yew_html_common::html_tree::html_list::HtmlListChildren(vec![#(#html_trees,)*]) + }); + } +} + pub struct HtmlList { - children: HtmlListChildren, + pub children: HtmlListChildren, } struct HtmlListOpen { @@ -100,3 +110,14 @@ impl Parse for HtmlList { Ok(HtmlList { children }) } } + +impl ToTokens for HtmlList { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let HtmlList { children } = self; + tokens.extend(quote! { + ::yew_html_common::html_tree::html_list::HtmlList { + children: #children, + } + }); + } +} diff --git a/html-impl/src/html_tree/mod.rs b/html-common/src/html_tree/mod.rs similarity index 55% rename from html-impl/src/html_tree/mod.rs rename to html-common/src/html_tree/mod.rs index 1fc16e93edc..b46f8c37d41 100644 --- a/html-impl/src/html_tree/mod.rs +++ b/html-common/src/html_tree/mod.rs @@ -2,6 +2,7 @@ pub mod html_list; use crate::Peek; use html_list::HtmlList; +use quote::{quote, ToTokens}; use syn::parse::{Parse, ParseStream, Result}; pub enum HtmlTree { @@ -21,6 +22,13 @@ impl Parse for HtmlRoot { } } +impl ToTokens for HtmlRoot { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let HtmlRoot(html_tree) = self; + tokens.extend(quote! { #html_tree }); + } +} + impl Parse for HtmlTree { fn parse(input: ParseStream) -> Result { if HtmlList::peek(&input) { @@ -32,3 +40,18 @@ impl Parse for HtmlTree { } } } + +impl ToTokens for HtmlTree { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let token_stream = match self { + HtmlTree::Empty => quote! { + ::yew_html_common::html_tree::HtmlTree::Empty + }, + HtmlTree::List(list) => quote! { + ::yew_html_common::html_tree::HtmlTree::List(#list) + }, + }; + + tokens.extend(token_stream); + } +} diff --git a/html-common/src/lib.rs b/html-common/src/lib.rs new file mode 100644 index 00000000000..65c8845df25 --- /dev/null +++ b/html-common/src/lib.rs @@ -0,0 +1,7 @@ +pub mod html_tree; + +use syn::parse::ParseStream; + +pub trait Peek: Sized { + fn peek(input: &ParseStream) -> bool; +} diff --git a/html-impl/Cargo.toml b/html-impl/Cargo.toml index f96d5071082..d8d72f4aa1f 100644 --- a/html-impl/Cargo.toml +++ b/html-impl/Cargo.toml @@ -11,3 +11,4 @@ syn = { version = "0.15", features = ["full"] } quote = "0.6" proc-macro-hack = "0.5" proc-macro2 = "0.4" +yew-html-common = { path = "../html-common" } diff --git a/html-impl/src/lib.rs b/html-impl/src/lib.rs index ff72d77123d..ada5c339118 100644 --- a/html-impl/src/lib.rs +++ b/html-impl/src/lib.rs @@ -1,20 +1,13 @@ extern crate proc_macro; -mod html_tree; - -use html_tree::HtmlRoot; use proc_macro::TokenStream; use proc_macro_hack::proc_macro_hack; use quote::quote; -use syn::parse::ParseStream; use syn::parse_macro_input; - -trait Peek: Sized { - fn peek(input: &ParseStream) -> bool; -} +use yew_html_common::html_tree::HtmlRoot; #[proc_macro_hack] pub fn html(input: TokenStream) -> TokenStream { - parse_macro_input!(input as HtmlRoot); - TokenStream::from(quote! { 42 }) + let root = parse_macro_input!(input as HtmlRoot); + TokenStream::from(quote! { #root }) } diff --git a/html/Cargo.toml b/html/Cargo.toml index 3974bddb36c..2c1b3cae362 100644 --- a/html/Cargo.toml +++ b/html/Cargo.toml @@ -14,3 +14,4 @@ trybuild = "1.0" [dependencies] proc-macro-hack = "0.5" yew-html-impl = { path = "../html-impl" } +yew-html-common = { path = "../html-common" } diff --git a/html/tests/html-list-pass.rs b/html/tests/html-list-pass.rs index 77417246763..1667ebf3210 100644 --- a/html/tests/html-list-pass.rs +++ b/html/tests/html-list-pass.rs @@ -1,6 +1,8 @@ use yew_html::html; fn main() { + html! {}; + html! { <> }; diff --git a/main.rs b/main.rs index 721f07cf771..8e166291679 100644 --- a/main.rs +++ b/main.rs @@ -2,6 +2,8 @@ use yew_html::html; fn main() { html! { - <> + <> + <> + }; } From 6e59a798b140b5298becd7659ba93bbc034ee46c Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Thu, 16 May 2019 11:44:33 -0700 Subject: [PATCH 07/46] Support html blocks --- html-common/src/html_tree/html_block.rs | 52 +++++++++++++++++++++++++ html-common/src/html_tree/mod.rs | 8 ++++ html/src/lib.rs | 2 + html/tests/cases.rs | 2 + html/tests/html-block-fail.rs | 20 ++++++++++ html/tests/html-block-fail.stderr | 19 +++++++++ html/tests/html-block-pass.rs | 19 +++++++++ main.rs | 9 ++++- 8 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 html-common/src/html_tree/html_block.rs create mode 100644 html/tests/html-block-fail.rs create mode 100644 html/tests/html-block-fail.stderr create mode 100644 html/tests/html-block-pass.rs diff --git a/html-common/src/html_tree/html_block.rs b/html-common/src/html_tree/html_block.rs new file mode 100644 index 00000000000..53753dd91cb --- /dev/null +++ b/html-common/src/html_tree/html_block.rs @@ -0,0 +1,52 @@ +use super::HtmlTree; +use crate::Peek; +use proc_macro2::{Ident, Span, TokenStream}; +use quote::{quote, quote_spanned, ToTokens}; +use syn::parse::{Parse, ParseStream, Result}; +use syn::spanned::Spanned; +use syn::{braced, token}; + +pub struct HtmlBlock { + pub tree: Box, + content: TokenStream, +} + +impl HtmlBlock { + pub fn new(tree: HtmlTree) -> Self { + HtmlBlock { + tree: Box::new(tree), + content: TokenStream::new(), + } + } +} + +impl Peek for HtmlBlock { + fn peek(input: &ParseStream) -> bool { + input.peek(token::Brace) + } +} + +impl Parse for HtmlBlock { + fn parse(input: ParseStream) -> Result { + let content; + braced!(content in input); + Ok(HtmlBlock { + tree: Box::new(HtmlTree::Empty), + content: content.parse()?, + }) + } +} + +impl ToTokens for HtmlBlock { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let HtmlBlock { content, .. } = self; + let tree = Ident::new("__yew_html_tree", Span::call_site()); + let init_tree = quote_spanned! {content.span()=> + let #tree: ::yew_html_common::html_tree::HtmlTree = {#content}; + }; + tokens.extend(quote! {{ + #init_tree + ::yew_html_common::html_tree::html_block::HtmlBlock::new(#tree) + }}); + } +} diff --git a/html-common/src/html_tree/mod.rs b/html-common/src/html_tree/mod.rs index b46f8c37d41..0314d296ffd 100644 --- a/html-common/src/html_tree/mod.rs +++ b/html-common/src/html_tree/mod.rs @@ -1,11 +1,14 @@ +pub mod html_block; pub mod html_list; use crate::Peek; +use html_block::HtmlBlock; use html_list::HtmlList; use quote::{quote, ToTokens}; use syn::parse::{Parse, ParseStream, Result}; pub enum HtmlTree { + Block(HtmlBlock), List(HtmlList), Empty, } @@ -33,6 +36,8 @@ impl Parse for HtmlTree { fn parse(input: ParseStream) -> Result { if HtmlList::peek(&input) { Ok(HtmlTree::List(input.parse()?)) + } else if HtmlBlock::peek(&input) { + Ok(HtmlTree::Block(input.parse()?)) } else if input.is_empty() { Ok(HtmlTree::Empty) } else { @@ -50,6 +55,9 @@ impl ToTokens for HtmlTree { HtmlTree::List(list) => quote! { ::yew_html_common::html_tree::HtmlTree::List(#list) }, + HtmlTree::Block(block) => quote! { + ::yew_html_common::html_tree::HtmlTree::Block(#block) + }, }; tokens.extend(token_stream); diff --git a/html/src/lib.rs b/html/src/lib.rs index 47c66e21483..a2ce9bf6e5e 100644 --- a/html/src/lib.rs +++ b/html/src/lib.rs @@ -1,5 +1,7 @@ use proc_macro_hack::proc_macro_hack; +pub use yew_html_common::html_tree::HtmlTree; + /// Generate html tree #[proc_macro_hack] pub use yew_html_impl::html; diff --git a/html/tests/cases.rs b/html/tests/cases.rs index ad12c5e3ab8..ca1baf33bee 100644 --- a/html/tests/cases.rs +++ b/html/tests/cases.rs @@ -1,6 +1,8 @@ #[test] fn tests() { let t = trybuild::TestCases::new(); + t.pass("tests/html-block-pass.rs"); + t.compile_fail("tests/html-block-fail.rs"); t.pass("tests/html-list-pass.rs"); t.compile_fail("tests/html-list-fail.rs"); } diff --git a/html/tests/html-block-fail.rs b/html/tests/html-block-fail.rs new file mode 100644 index 00000000000..6480d804612 --- /dev/null +++ b/html/tests/html-block-fail.rs @@ -0,0 +1,20 @@ +use yew_html::html; + +struct NotHtmlTree {} + +fn not_tree() -> NotHtmlTree { + NotHtmlTree {} +} + +fn main() { + html! { + { not_tree() } + }; + + html! { + { + let not_a_tree = not_tree(); + not_a_tree + } + }; +} diff --git a/html/tests/html-block-fail.stderr b/html/tests/html-block-fail.stderr new file mode 100644 index 00000000000..aa5f3078db7 --- /dev/null +++ b/html/tests/html-block-fail.stderr @@ -0,0 +1,19 @@ +error[E0308]: mismatched types + --> $DIR/html-block-fail.rs:11:11 + | +11 | { not_tree() } + | ^^^^^^^^^^ expected enum `yew_html_common::html_tree::HtmlTree`, found struct `NotHtmlTree` + | + = note: expected type `yew_html_common::html_tree::HtmlTree` + found type `NotHtmlTree` + +error[E0308]: mismatched types + --> $DIR/html-block-fail.rs:17:13 + | +17 | not_a_tree + | ^^^^^^^^^^ expected enum `yew_html_common::html_tree::HtmlTree`, found struct `NotHtmlTree` + | + = note: expected type `yew_html_common::html_tree::HtmlTree` + found type `NotHtmlTree` + +For more information about this error, try `rustc --explain E0308`. diff --git a/html/tests/html-block-pass.rs b/html/tests/html-block-pass.rs new file mode 100644 index 00000000000..bdb4f58a8ed --- /dev/null +++ b/html/tests/html-block-pass.rs @@ -0,0 +1,19 @@ +use yew_html::html; +use yew_html::HtmlTree; + +fn tree_block() -> HtmlTree { + html! {} +} + +fn main() { + html! { + { tree_block() } + }; + + html! { + { + let stmt = tree_block(); + stmt + } + }; +} diff --git a/main.rs b/main.rs index 8e166291679..db17d157201 100644 --- a/main.rs +++ b/main.rs @@ -1,9 +1,16 @@ use yew_html::html; +use yew_html::HtmlTree; + +fn nested_list() -> HtmlTree { + html! { + <> + } +} fn main() { html! { <> - <> + { nested_list() } }; } From 17f975940df7decc335e27ee0bcd224231d6d765 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Thu, 16 May 2019 13:35:29 -0700 Subject: [PATCH 08/46] Use cursor for peeking and remove HtmlListChildren --- html-common/Cargo.toml | 1 + html-common/src/html_tree/html_block.rs | 9 ++- html-common/src/html_tree/html_list.rs | 99 ++++++++++++------------- html-common/src/html_tree/mod.rs | 4 +- html-common/src/lib.rs | 6 +- 5 files changed, 60 insertions(+), 59 deletions(-) diff --git a/html-common/Cargo.toml b/html-common/Cargo.toml index 39f195e7cda..4bec44196e0 100644 --- a/html-common/Cargo.toml +++ b/html-common/Cargo.toml @@ -4,6 +4,7 @@ version = "0.0.1" edition = "2018" [dependencies] +boolinator = "2.4.0" syn = { version = "0.15", features = ["full"] } quote = "0.6" proc-macro-hack = "0.5" diff --git a/html-common/src/html_tree/html_block.rs b/html-common/src/html_tree/html_block.rs index 53753dd91cb..7bc2e7e314f 100644 --- a/html-common/src/html_tree/html_block.rs +++ b/html-common/src/html_tree/html_block.rs @@ -1,10 +1,11 @@ use super::HtmlTree; use crate::Peek; -use proc_macro2::{Ident, Span, TokenStream}; +use proc_macro2::{Delimiter, Ident, Span, TokenStream}; use quote::{quote, quote_spanned, ToTokens}; +use syn::braced; +use syn::buffer::Cursor; use syn::parse::{Parse, ParseStream, Result}; use syn::spanned::Spanned; -use syn::{braced, token}; pub struct HtmlBlock { pub tree: Box, @@ -21,8 +22,8 @@ impl HtmlBlock { } impl Peek for HtmlBlock { - fn peek(input: &ParseStream) -> bool { - input.peek(token::Brace) + fn peek(cursor: Cursor) -> Option<()> { + cursor.group(Delimiter::Brace).map(|_| ()) } } diff --git a/html-common/src/html_tree/html_list.rs b/html-common/src/html_tree/html_list.rs index 18a17261169..d7e01c20fbf 100644 --- a/html-common/src/html_tree/html_list.rs +++ b/html-common/src/html_tree/html_list.rs @@ -1,24 +1,12 @@ use super::HtmlTree; use crate::Peek; -use proc_macro2::TokenTree; +use boolinator::Boolinator; use quote::{quote, ToTokens}; +use syn::buffer::Cursor; use syn::parse::{Parse, ParseStream, Result}; use syn::token; -pub struct HtmlListChildren(pub Vec); -impl ToTokens for HtmlListChildren { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let HtmlListChildren(html_trees) = self; - let html_trees = html_trees.iter().map(|html_tree| quote! { #html_tree }); - tokens.extend(quote! { - ::yew_html_common::html_tree::html_list::HtmlListChildren(vec![#(#html_trees,)*]) - }); - } -} - -pub struct HtmlList { - pub children: HtmlListChildren, -} +pub struct HtmlList(pub Vec); struct HtmlListOpen { lt_token: token::Lt, @@ -26,8 +14,12 @@ struct HtmlListOpen { } impl Peek for HtmlListOpen { - fn peek(input: &ParseStream) -> bool { - input.peek(token::Lt) && input.peek2(token::Gt) + fn peek(cursor: Cursor) -> Option<()> { + let (punct, cursor) = cursor.punct()?; + (punct.as_char() == '<').as_option()?; + + let (punct, _) = cursor.punct()?; + (punct.as_char() == '>').as_option() } } @@ -50,8 +42,15 @@ impl ToTokens for HtmlListOpen { struct HtmlListClose {} impl Peek for HtmlListClose { - fn peek(input: &ParseStream) -> bool { - input.peek(token::Lt) && input.peek2(token::Div) && input.peek3(token::Gt) + fn peek(cursor: Cursor) -> Option<()> { + let (punct, cursor) = cursor.punct()?; + (punct.as_char() == '<').as_option()?; + + let (punct, cursor) = cursor.punct()?; + (punct.as_char() == '/').as_option()?; + + let (punct, _) = cursor.punct()?; + (punct.as_char() == '>').as_option() } } @@ -64,20 +63,9 @@ impl Parse for HtmlListClose { } } -impl Parse for HtmlListChildren { - fn parse(input: ParseStream) -> Result { - let mut children: Vec = vec![]; - while !input.is_empty() { - children.push(input.parse::()?); - } - - Ok(HtmlListChildren(children)) - } -} - impl Peek for HtmlList { - fn peek(input: &ParseStream) -> bool { - HtmlListOpen::peek(input) + fn peek(cursor: Cursor) -> Option<()> { + HtmlListOpen::peek(cursor) } } @@ -85,39 +73,50 @@ impl Parse for HtmlList { fn parse(input: ParseStream) -> Result { let open = input.parse::()?; - let mut content: Vec = vec![]; - let mut list_stack_count = 0; - while !input.is_empty() { - if HtmlListOpen::peek(&input) { + let mut cursor = input.cursor(); + let mut list_stack_count = 1; + loop { + if HtmlListOpen::peek(cursor).is_some() { list_stack_count += 1; - } else if HtmlListClose::peek(&input) { + } else if HtmlListClose::peek(cursor).is_some() { + list_stack_count -= 1; if list_stack_count == 0 { break; - } else { - list_stack_count -= 1; } } - content.push(input.parse::()?); + if let Some((_, next)) = cursor.token_tree() { + cursor = next; + } else { + break; + } } - input.parse::().map_err(|_| { - syn::Error::new_spanned(open, "this open tag has no corresponding close tag") - })?; + if list_stack_count > 0 { + return Err(syn::Error::new_spanned( + open, + "this open tag has no corresponding close tag", + )); + } + + let mut children: Vec = vec![]; + while let Ok(html_tree) = input.parse::() { + children.push(html_tree); + } - let token_stream: proc_macro2::TokenStream = content.into_iter().collect(); - let children = syn::parse::(token_stream.into())?; + input.parse::()?; - Ok(HtmlList { children }) + Ok(HtmlList(children)) } } impl ToTokens for HtmlList { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let HtmlList { children } = self; + let HtmlList(html_trees) = self; + let html_trees = html_trees.iter().map(|html_tree| quote! { #html_tree }); tokens.extend(quote! { - ::yew_html_common::html_tree::html_list::HtmlList { - children: #children, - } + ::yew_html_common::html_tree::html_list::HtmlList( + vec![#(#html_trees,)*] + ) }); } } diff --git a/html-common/src/html_tree/mod.rs b/html-common/src/html_tree/mod.rs index 0314d296ffd..802ab4867dd 100644 --- a/html-common/src/html_tree/mod.rs +++ b/html-common/src/html_tree/mod.rs @@ -34,9 +34,9 @@ impl ToTokens for HtmlRoot { impl Parse for HtmlTree { fn parse(input: ParseStream) -> Result { - if HtmlList::peek(&input) { + if HtmlList::peek(input.cursor()).is_some() { Ok(HtmlTree::List(input.parse()?)) - } else if HtmlBlock::peek(&input) { + } else if HtmlBlock::peek(input.cursor()).is_some() { Ok(HtmlTree::Block(input.parse()?)) } else if input.is_empty() { Ok(HtmlTree::Empty) diff --git a/html-common/src/lib.rs b/html-common/src/lib.rs index 65c8845df25..7259d56ff03 100644 --- a/html-common/src/lib.rs +++ b/html-common/src/lib.rs @@ -1,7 +1,7 @@ pub mod html_tree; -use syn::parse::ParseStream; +use syn::buffer::Cursor; -pub trait Peek: Sized { - fn peek(input: &ParseStream) -> bool; +pub trait Peek { + fn peek(cursor: Cursor) -> Option<()>; } From b1deca6b8446713c8d8d6baba5b5d47385787990 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Thu, 16 May 2019 13:37:48 -0700 Subject: [PATCH 09/46] Reorder html list file --- html-common/src/html_tree/html_list.rs | 110 ++++++++++++------------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/html-common/src/html_tree/html_list.rs b/html-common/src/html_tree/html_list.rs index d7e01c20fbf..383729c1329 100644 --- a/html-common/src/html_tree/html_list.rs +++ b/html-common/src/html_tree/html_list.rs @@ -8,61 +8,6 @@ use syn::token; pub struct HtmlList(pub Vec); -struct HtmlListOpen { - lt_token: token::Lt, - gt_token: token::Gt, -} - -impl Peek for HtmlListOpen { - fn peek(cursor: Cursor) -> Option<()> { - let (punct, cursor) = cursor.punct()?; - (punct.as_char() == '<').as_option()?; - - let (punct, _) = cursor.punct()?; - (punct.as_char() == '>').as_option() - } -} - -impl Parse for HtmlListOpen { - fn parse(input: ParseStream) -> Result { - Ok(HtmlListOpen { - lt_token: input.parse()?, - gt_token: input.parse()?, - }) - } -} - -impl ToTokens for HtmlListOpen { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let HtmlListOpen { lt_token, gt_token } = self; - tokens.extend(quote! {#lt_token#gt_token}); - } -} - -struct HtmlListClose {} - -impl Peek for HtmlListClose { - fn peek(cursor: Cursor) -> Option<()> { - let (punct, cursor) = cursor.punct()?; - (punct.as_char() == '<').as_option()?; - - let (punct, cursor) = cursor.punct()?; - (punct.as_char() == '/').as_option()?; - - let (punct, _) = cursor.punct()?; - (punct.as_char() == '>').as_option() - } -} - -impl Parse for HtmlListClose { - fn parse(input: ParseStream) -> Result { - input.parse::()?; - input.parse::()?; - input.parse::()?; - Ok(HtmlListClose {}) - } -} - impl Peek for HtmlList { fn peek(cursor: Cursor) -> Option<()> { HtmlListOpen::peek(cursor) @@ -120,3 +65,58 @@ impl ToTokens for HtmlList { }); } } + +struct HtmlListOpen { + lt_token: token::Lt, + gt_token: token::Gt, +} + +impl Peek for HtmlListOpen { + fn peek(cursor: Cursor) -> Option<()> { + let (punct, cursor) = cursor.punct()?; + (punct.as_char() == '<').as_option()?; + + let (punct, _) = cursor.punct()?; + (punct.as_char() == '>').as_option() + } +} + +impl Parse for HtmlListOpen { + fn parse(input: ParseStream) -> Result { + Ok(HtmlListOpen { + lt_token: input.parse()?, + gt_token: input.parse()?, + }) + } +} + +impl ToTokens for HtmlListOpen { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let HtmlListOpen { lt_token, gt_token } = self; + tokens.extend(quote! {#lt_token#gt_token}); + } +} + +struct HtmlListClose {} + +impl Peek for HtmlListClose { + fn peek(cursor: Cursor) -> Option<()> { + let (punct, cursor) = cursor.punct()?; + (punct.as_char() == '<').as_option()?; + + let (punct, cursor) = cursor.punct()?; + (punct.as_char() == '/').as_option()?; + + let (punct, _) = cursor.punct()?; + (punct.as_char() == '>').as_option() + } +} + +impl Parse for HtmlListClose { + fn parse(input: ParseStream) -> Result { + input.parse::()?; + input.parse::()?; + input.parse::()?; + Ok(HtmlListClose {}) + } +} From 72f3007f98caeb9b45cbf16d02b74011695a3631 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Thu, 16 May 2019 15:27:36 -0700 Subject: [PATCH 10/46] Implement FromIterator for HtmlTree --- html-common/src/html_tree/html_block.rs | 11 +++++++---- html-common/src/html_tree/mod.rs | 11 +++++++++++ html-common/src/lib.rs | 2 ++ html/tests/html-block-fail.rs | 6 ++++++ html/tests/html-block-fail.stderr | 9 +++++++++ html/tests/html-block-pass.rs | 4 ++++ 6 files changed, 39 insertions(+), 4 deletions(-) diff --git a/html-common/src/html_tree/html_block.rs b/html-common/src/html_tree/html_block.rs index 7bc2e7e314f..a87511848d5 100644 --- a/html-common/src/html_tree/html_block.rs +++ b/html-common/src/html_tree/html_block.rs @@ -5,11 +5,12 @@ use quote::{quote, quote_spanned, ToTokens}; use syn::braced; use syn::buffer::Cursor; use syn::parse::{Parse, ParseStream, Result}; -use syn::spanned::Spanned; +use syn::token; pub struct HtmlBlock { pub tree: Box, content: TokenStream, + brace: Option, } impl HtmlBlock { @@ -17,6 +18,7 @@ impl HtmlBlock { HtmlBlock { tree: Box::new(tree), content: TokenStream::new(), + brace: None, } } } @@ -30,9 +32,9 @@ impl Peek for HtmlBlock { impl Parse for HtmlBlock { fn parse(input: ParseStream) -> Result { let content; - braced!(content in input); Ok(HtmlBlock { tree: Box::new(HtmlTree::Empty), + brace: Some(braced!(content in input)), content: content.parse()?, }) } @@ -40,11 +42,12 @@ impl Parse for HtmlBlock { impl ToTokens for HtmlBlock { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let HtmlBlock { content, .. } = self; + let HtmlBlock { content, brace, .. } = self; let tree = Ident::new("__yew_html_tree", Span::call_site()); - let init_tree = quote_spanned! {content.span()=> + let init_tree = quote_spanned! {brace.unwrap().span=> let #tree: ::yew_html_common::html_tree::HtmlTree = {#content}; }; + tokens.extend(quote! {{ #init_tree ::yew_html_common::html_tree::html_block::HtmlBlock::new(#tree) diff --git a/html-common/src/html_tree/mod.rs b/html-common/src/html_tree/mod.rs index 802ab4867dd..e83f195173d 100644 --- a/html-common/src/html_tree/mod.rs +++ b/html-common/src/html_tree/mod.rs @@ -5,6 +5,7 @@ use crate::Peek; use html_block::HtmlBlock; use html_list::HtmlList; use quote::{quote, ToTokens}; +use std::iter::FromIterator; use syn::parse::{Parse, ParseStream, Result}; pub enum HtmlTree { @@ -13,6 +14,16 @@ pub enum HtmlTree { Empty, } +impl FromIterator for HtmlTree { + fn from_iter>(iter: I) -> Self { + let mut trees = vec![]; + for tree in iter { + trees.push(tree); + } + HtmlTree::List(HtmlList(trees)) + } +} + pub struct HtmlRoot(HtmlTree); impl Parse for HtmlRoot { fn parse(input: ParseStream) -> Result { diff --git a/html-common/src/lib.rs b/html-common/src/lib.rs index 7259d56ff03..aac31b9280e 100644 --- a/html-common/src/lib.rs +++ b/html-common/src/lib.rs @@ -1,3 +1,5 @@ +#![recursion_limit = "128"] + pub mod html_tree; use syn::buffer::Cursor; diff --git a/html/tests/html-block-fail.rs b/html/tests/html-block-fail.rs index 6480d804612..811afe8af98 100644 --- a/html/tests/html-block-fail.rs +++ b/html/tests/html-block-fail.rs @@ -17,4 +17,10 @@ fn main() { not_a_tree } }; + + html! { + <> + { (0..3).map(|_| not_tree()) } + + }; } diff --git a/html/tests/html-block-fail.stderr b/html/tests/html-block-fail.stderr index aa5f3078db7..bb866373cd1 100644 --- a/html/tests/html-block-fail.stderr +++ b/html/tests/html-block-fail.stderr @@ -16,4 +16,13 @@ error[E0308]: mismatched types = note: expected type `yew_html_common::html_tree::HtmlTree` found type `NotHtmlTree` +error[E0308]: mismatched types + --> $DIR/html-block-fail.rs:23:15 + | +23 | { (0..3).map(|_| not_tree()) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected enum `yew_html_common::html_tree::HtmlTree`, found struct `std::iter::Map` + | + = note: expected type `yew_html_common::html_tree::HtmlTree` + found type `std::iter::Map, [closure@$DIR/tests/html-block-fail.rs:23:26: 23:40]>` + For more information about this error, try `rustc --explain E0308`. diff --git a/html/tests/html-block-pass.rs b/html/tests/html-block-pass.rs index bdb4f58a8ed..db1a57b63be 100644 --- a/html/tests/html-block-pass.rs +++ b/html/tests/html-block-pass.rs @@ -16,4 +16,8 @@ fn main() { stmt } }; + + html! { + { (0..3).map(|_| tree_block()).collect() } + }; } From 4ce800574c929cc12e21e30a823f2adff05bc35e Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Thu, 16 May 2019 16:27:00 -0700 Subject: [PATCH 11/46] Allow peek inside and add rudimentary tag support --- html-common/src/html_tree/html_block.rs | 2 +- html-common/src/html_tree/html_list.rs | 6 +- html-common/src/html_tree/html_tag.rs | 149 ++++++++++++++++++++++++ html-common/src/html_tree/mod.rs | 15 ++- html-common/src/lib.rs | 4 +- html/tests/cases.rs | 2 + html/tests/html-list-fail.rs | 8 ++ html/tests/html-list-fail.stderr | 22 +++- html/tests/html-tag-fail.rs | 28 +++++ html/tests/html-tag-fail.stderr | 35 ++++++ html/tests/html-tag-pass.rs | 14 +++ main.rs | 16 ++- 12 files changed, 280 insertions(+), 21 deletions(-) create mode 100644 html-common/src/html_tree/html_tag.rs create mode 100644 html/tests/html-tag-fail.rs create mode 100644 html/tests/html-tag-fail.stderr create mode 100644 html/tests/html-tag-pass.rs diff --git a/html-common/src/html_tree/html_block.rs b/html-common/src/html_tree/html_block.rs index a87511848d5..9f230f511b2 100644 --- a/html-common/src/html_tree/html_block.rs +++ b/html-common/src/html_tree/html_block.rs @@ -23,7 +23,7 @@ impl HtmlBlock { } } -impl Peek for HtmlBlock { +impl Peek<()> for HtmlBlock { fn peek(cursor: Cursor) -> Option<()> { cursor.group(Delimiter::Brace).map(|_| ()) } diff --git a/html-common/src/html_tree/html_list.rs b/html-common/src/html_tree/html_list.rs index 383729c1329..2810cb0bdee 100644 --- a/html-common/src/html_tree/html_list.rs +++ b/html-common/src/html_tree/html_list.rs @@ -8,7 +8,7 @@ use syn::token; pub struct HtmlList(pub Vec); -impl Peek for HtmlList { +impl Peek<()> for HtmlList { fn peek(cursor: Cursor) -> Option<()> { HtmlListOpen::peek(cursor) } @@ -71,7 +71,7 @@ struct HtmlListOpen { gt_token: token::Gt, } -impl Peek for HtmlListOpen { +impl Peek<()> for HtmlListOpen { fn peek(cursor: Cursor) -> Option<()> { let (punct, cursor) = cursor.punct()?; (punct.as_char() == '<').as_option()?; @@ -99,7 +99,7 @@ impl ToTokens for HtmlListOpen { struct HtmlListClose {} -impl Peek for HtmlListClose { +impl Peek<()> for HtmlListClose { fn peek(cursor: Cursor) -> Option<()> { let (punct, cursor) = cursor.punct()?; (punct.as_char() == '<').as_option()?; diff --git a/html-common/src/html_tree/html_tag.rs b/html-common/src/html_tree/html_tag.rs new file mode 100644 index 00000000000..d36b77c573c --- /dev/null +++ b/html-common/src/html_tree/html_tag.rs @@ -0,0 +1,149 @@ +use super::HtmlTree; +use crate::Peek; +use boolinator::Boolinator; +use quote::{quote, ToTokens}; +use syn::buffer::Cursor; +use syn::parse::{Parse, ParseStream, Result}; +use syn::token; +use syn::Ident; + +pub struct HtmlTag { + pub tree: Box, +} + +impl HtmlTag { + pub fn new(tree: HtmlTree) -> Self { + HtmlTag { + tree: Box::new(tree), + } + } +} + +impl Peek<()> for HtmlTag { + fn peek(cursor: Cursor) -> Option<()> { + HtmlTagOpen::peek(cursor).map(|_| ()) + } +} + +impl Parse for HtmlTag { + fn parse(input: ParseStream) -> Result { + let open = input.parse::()?; + + let mut cursor = input.cursor(); + let mut tag_stack_count = 1; + loop { + if let Some(next_open_ident) = HtmlTagOpen::peek(cursor) { + if open.ident.to_string() == next_open_ident.to_string() { + tag_stack_count += 1; + } + } else if let Some(next_close_ident) = HtmlTagClose::peek(cursor) { + if open.ident.to_string() == next_close_ident.to_string() { + tag_stack_count -= 1; + if tag_stack_count == 0 { + break; + } + } + } + if let Some((_, next)) = cursor.token_tree() { + cursor = next; + } else { + break; + } + } + + if tag_stack_count > 0 { + return Err(syn::Error::new_spanned( + open, + "this open tag has no corresponding close tag", + )); + } + + let mut children: Vec = vec![]; + while let Ok(html_tree) = input.parse::() { + children.push(html_tree); + } + + input.parse::()?; + + Ok(HtmlTag { + tree: Box::new(children.into_iter().collect()), + }) + } +} + +impl ToTokens for HtmlTag { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let HtmlTag { tree, .. } = self; + tokens.extend(quote! { + ::yew_html_common::html_tree::html_tag::HtmlTag::new(#tree) + }); + } +} + +struct HtmlTagOpen { + lt: token::Lt, + ident: Ident, + gt: token::Gt, +} + +impl Peek for HtmlTagOpen { + fn peek(cursor: Cursor) -> Option { + let (punct, cursor) = cursor.punct()?; + (punct.as_char() == '<').as_option()?; + + let (ident, cursor) = cursor.ident()?; + (ident.to_string().to_lowercase() == ident.to_string()).as_option()?; + + let (punct, _) = cursor.punct()?; + (punct.as_char() == '>').as_option()?; + + Some(ident) + } +} + +impl Parse for HtmlTagOpen { + fn parse(input: ParseStream) -> Result { + Ok(HtmlTagOpen { + lt: input.parse()?, + ident: input.parse()?, + gt: input.parse()?, + }) + } +} + +impl ToTokens for HtmlTagOpen { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let HtmlTagOpen { lt, ident, gt } = self; + tokens.extend(quote! {#lt#ident#gt}); + } +} + +struct HtmlTagClose {} + +impl Peek for HtmlTagClose { + fn peek(cursor: Cursor) -> Option { + let (punct, cursor) = cursor.punct()?; + (punct.as_char() == '<').as_option()?; + + let (punct, cursor) = cursor.punct()?; + (punct.as_char() == '/').as_option()?; + + let (ident, cursor) = cursor.ident()?; + (ident.to_string().to_lowercase() == ident.to_string()).as_option()?; + + let (punct, _) = cursor.punct()?; + (punct.as_char() == '>').as_option()?; + + Some(ident) + } +} + +impl Parse for HtmlTagClose { + fn parse(input: ParseStream) -> Result { + input.parse::()?; + input.parse::()?; + input.parse::()?; + input.parse::()?; + Ok(HtmlTagClose {}) + } +} diff --git a/html-common/src/html_tree/mod.rs b/html-common/src/html_tree/mod.rs index e83f195173d..62bb050651d 100644 --- a/html-common/src/html_tree/mod.rs +++ b/html-common/src/html_tree/mod.rs @@ -1,9 +1,11 @@ pub mod html_block; pub mod html_list; +pub mod html_tag; use crate::Peek; use html_block::HtmlBlock; use html_list::HtmlList; +use html_tag::HtmlTag; use quote::{quote, ToTokens}; use std::iter::FromIterator; use syn::parse::{Parse, ParseStream, Result}; @@ -11,6 +13,7 @@ use syn::parse::{Parse, ParseStream, Result}; pub enum HtmlTree { Block(HtmlBlock), List(HtmlList), + Tag(HtmlTag), Empty, } @@ -20,7 +23,12 @@ impl FromIterator for HtmlTree { for tree in iter { trees.push(tree); } - HtmlTree::List(HtmlList(trees)) + + match trees.len() { + 0 => HtmlTree::Empty, + 1 => trees.remove(0), + _ => HtmlTree::List(HtmlList(trees)), + } } } @@ -49,6 +57,8 @@ impl Parse for HtmlTree { Ok(HtmlTree::List(input.parse()?)) } else if HtmlBlock::peek(input.cursor()).is_some() { Ok(HtmlTree::Block(input.parse()?)) + } else if HtmlTag::peek(input.cursor()).is_some() { + Ok(HtmlTree::Tag(input.parse()?)) } else if input.is_empty() { Ok(HtmlTree::Empty) } else { @@ -63,6 +73,9 @@ impl ToTokens for HtmlTree { HtmlTree::Empty => quote! { ::yew_html_common::html_tree::HtmlTree::Empty }, + HtmlTree::Tag(tag) => quote! { + ::yew_html_common::html_tree::HtmlTree::Tag(#tag) + }, HtmlTree::List(list) => quote! { ::yew_html_common::html_tree::HtmlTree::List(#list) }, diff --git a/html-common/src/lib.rs b/html-common/src/lib.rs index aac31b9280e..64859b6446c 100644 --- a/html-common/src/lib.rs +++ b/html-common/src/lib.rs @@ -4,6 +4,6 @@ pub mod html_tree; use syn::buffer::Cursor; -pub trait Peek { - fn peek(cursor: Cursor) -> Option<()>; +pub trait Peek { + fn peek(cursor: Cursor) -> Option; } diff --git a/html/tests/cases.rs b/html/tests/cases.rs index ca1baf33bee..c3f63b76feb 100644 --- a/html/tests/cases.rs +++ b/html/tests/cases.rs @@ -5,4 +5,6 @@ fn tests() { t.compile_fail("tests/html-block-fail.rs"); t.pass("tests/html-list-pass.rs"); t.compile_fail("tests/html-list-fail.rs"); + t.pass("tests/html-tag-pass.rs"); + t.compile_fail("tests/html-tag-fail.rs"); } diff --git a/html/tests/html-list-fail.rs b/html/tests/html-list-fail.rs index 294873a00b1..c979487d116 100644 --- a/html/tests/html-list-fail.rs +++ b/html/tests/html-list-fail.rs @@ -1,6 +1,14 @@ use yew_html::html; fn main() { + html! { + <> + }; + + html! { + + }; + html! { <><> }; diff --git a/html/tests/html-list-fail.stderr b/html/tests/html-list-fail.stderr index 494f137ee71..52a5dc2cf7f 100644 --- a/html/tests/html-list-fail.stderr +++ b/html/tests/html-list-fail.stderr @@ -1,23 +1,35 @@ error: this open tag has no corresponding close tag --> $DIR/html-list-fail.rs:5:9 | -5 | <><> +5 | <> | ^^ error: expected valid html element --> $DIR/html-list-fail.rs:9:9 | -9 | +9 | | ^ error: this open tag has no corresponding close tag --> $DIR/html-list-fail.rs:13:9 | -13 | <><> +13 | <><> + | ^^ + +error: expected valid html element + --> $DIR/html-list-fail.rs:17:9 + | +17 | + | ^ + +error: this open tag has no corresponding close tag + --> $DIR/html-list-fail.rs:21:9 + | +21 | <><> | ^^ error: only one root html element allowed - --> $DIR/html-list-fail.rs:18:9 + --> $DIR/html-list-fail.rs:26:9 | -18 | <> +26 | <> | ^ diff --git a/html/tests/html-tag-fail.rs b/html/tests/html-tag-fail.rs new file mode 100644 index 00000000000..e11c5e88a5a --- /dev/null +++ b/html/tests/html-tag-fail.rs @@ -0,0 +1,28 @@ +use yew_html::html; + +fn main() { + html! { +
+ }; + + html! { +
+ }; + + html! { +
+ }; + + html! { +
+ }; + + html! { +
+
+ }; + + html! { +
+ }; +} diff --git a/html/tests/html-tag-fail.stderr b/html/tests/html-tag-fail.stderr new file mode 100644 index 00000000000..cf75346f036 --- /dev/null +++ b/html/tests/html-tag-fail.stderr @@ -0,0 +1,35 @@ +error: this open tag has no corresponding close tag + --> $DIR/html-tag-fail.rs:5:9 + | +5 |
+ | ^^^^^ + +error: this open tag has no corresponding close tag + --> $DIR/html-tag-fail.rs:9:9 + | +9 |
+ | ^^^^^ + +error: expected valid html element + --> $DIR/html-tag-fail.rs:13:9 + | +13 |
+ | ^ + +error: this open tag has no corresponding close tag + --> $DIR/html-tag-fail.rs:17:9 + | +17 |
+ | ^^^^^ + +error: only one root html element allowed + --> $DIR/html-tag-fail.rs:22:9 + | +22 |
+ | ^ + +error: this open tag has no corresponding close tag + --> $DIR/html-tag-fail.rs:26:9 + | +26 |
+ | ^^^^^ diff --git a/html/tests/html-tag-pass.rs b/html/tests/html-tag-pass.rs new file mode 100644 index 00000000000..95e410df6cd --- /dev/null +++ b/html/tests/html-tag-pass.rs @@ -0,0 +1,14 @@ +use yew_html::html; + +fn main() { + html! { +
+ }; + + html! { +
+
+
+
+ }; +} diff --git a/main.rs b/main.rs index db17d157201..95e410df6cd 100644 --- a/main.rs +++ b/main.rs @@ -1,16 +1,14 @@ use yew_html::html; -use yew_html::HtmlTree; -fn nested_list() -> HtmlTree { +fn main() { html! { - <> - } -} +
+ }; -fn main() { html! { - <> - { nested_list() } - +
+
+
+
}; } From 6d0ffab9bfc740655d381fedf3e0721b77354b6f Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Thu, 16 May 2019 17:21:18 -0700 Subject: [PATCH 12/46] Support self closing tags --- html-common/src/html_tree/html_block.rs | 2 +- html-common/src/html_tree/html_tag.rs | 22 +++++++++++++++++++--- html/tests/html-tag-fail.rs | 4 ++++ html/tests/html-tag-fail.stderr | 6 ++++++ html/tests/html-tag-pass.rs | 4 ++++ 5 files changed, 34 insertions(+), 4 deletions(-) diff --git a/html-common/src/html_tree/html_block.rs b/html-common/src/html_tree/html_block.rs index 9f230f511b2..b5ad82376eb 100644 --- a/html-common/src/html_tree/html_block.rs +++ b/html-common/src/html_tree/html_block.rs @@ -8,7 +8,7 @@ use syn::parse::{Parse, ParseStream, Result}; use syn::token; pub struct HtmlBlock { - pub tree: Box, + tree: Box, content: TokenStream, brace: Option, } diff --git a/html-common/src/html_tree/html_tag.rs b/html-common/src/html_tree/html_tag.rs index d36b77c573c..e9732e713cc 100644 --- a/html-common/src/html_tree/html_tag.rs +++ b/html-common/src/html_tree/html_tag.rs @@ -28,6 +28,11 @@ impl Peek<()> for HtmlTag { impl Parse for HtmlTag { fn parse(input: ParseStream) -> Result { let open = input.parse::()?; + if open.div.is_some() { + return Ok(HtmlTag { + tree: Box::new(HtmlTree::Empty), + }); + } let mut cursor = input.cursor(); let mut tag_stack_count = 1; @@ -83,6 +88,7 @@ impl ToTokens for HtmlTag { struct HtmlTagOpen { lt: token::Lt, ident: Ident, + div: Option, gt: token::Gt, } @@ -94,7 +100,12 @@ impl Peek for HtmlTagOpen { let (ident, cursor) = cursor.ident()?; (ident.to_string().to_lowercase() == ident.to_string()).as_option()?; - let (punct, _) = cursor.punct()?; + let (mut punct, cursor) = cursor.punct()?; + if punct.as_char() == '/' { + let extra_punct = cursor.punct()?; + punct = extra_punct.0; + } + (punct.as_char() == '>').as_option()?; Some(ident) @@ -106,6 +117,7 @@ impl Parse for HtmlTagOpen { Ok(HtmlTagOpen { lt: input.parse()?, ident: input.parse()?, + div: input.parse().ok(), gt: input.parse()?, }) } @@ -113,8 +125,12 @@ impl Parse for HtmlTagOpen { impl ToTokens for HtmlTagOpen { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let HtmlTagOpen { lt, ident, gt } = self; - tokens.extend(quote! {#lt#ident#gt}); + let HtmlTagOpen { lt, ident, div, gt } = self; + let open_tag = match div { + Some(div) => quote! {#lt#ident#div#gt}, + None => quote! {#lt#ident#gt}, + }; + tokens.extend(open_tag); } } diff --git a/html/tests/html-tag-fail.rs b/html/tests/html-tag-fail.rs index e11c5e88a5a..04d4da6ee49 100644 --- a/html/tests/html-tag-fail.rs +++ b/html/tests/html-tag-fail.rs @@ -25,4 +25,8 @@ fn main() { html! {
}; + + html! { + + }; } diff --git a/html/tests/html-tag-fail.stderr b/html/tests/html-tag-fail.stderr index cf75346f036..a4c3e41fb71 100644 --- a/html/tests/html-tag-fail.stderr +++ b/html/tests/html-tag-fail.stderr @@ -33,3 +33,9 @@ error: this open tag has no corresponding close tag | 26 |
| ^^^^^ + +error: only one root html element allowed + --> $DIR/html-tag-fail.rs:30:16 + | +30 | + | ^ diff --git a/html/tests/html-tag-pass.rs b/html/tests/html-tag-pass.rs index 95e410df6cd..6e260eca17e 100644 --- a/html/tests/html-tag-pass.rs +++ b/html/tests/html-tag-pass.rs @@ -11,4 +11,8 @@ fn main() {
}; + + html! { + + }; } From 83746b13e343d009bf5f6a1a95b1b7f93c85611c Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Wed, 22 May 2019 14:46:04 -0400 Subject: [PATCH 13/46] Improve tag and list error messages --- html-common/Cargo.toml | 4 +- html-common/src/html_tree/html_block.rs | 4 +- html-common/src/html_tree/html_list.rs | 58 +++++++++++++++++-------- html-common/src/html_tree/html_tag.rs | 57 ++++++++++++++++++------ html-common/src/html_tree/mod.rs | 6 ++- html/tests/html-list-fail.stderr | 19 +++++--- html/tests/html-tag-fail.rs | 8 ++++ html/tests/html-tag-fail.stderr | 37 ++++++++++++---- 8 files changed, 143 insertions(+), 50 deletions(-) diff --git a/html-common/Cargo.toml b/html-common/Cargo.toml index 4bec44196e0..978ba08fa9f 100644 --- a/html-common/Cargo.toml +++ b/html-common/Cargo.toml @@ -5,7 +5,7 @@ edition = "2018" [dependencies] boolinator = "2.4.0" -syn = { version = "0.15", features = ["full"] } -quote = "0.6" proc-macro-hack = "0.5" proc-macro2 = "0.4" +quote = "0.6" +syn = { version = "0.15", features = ["full"] } diff --git a/html-common/src/html_tree/html_block.rs b/html-common/src/html_tree/html_block.rs index b5ad82376eb..d38ccfa301c 100644 --- a/html-common/src/html_tree/html_block.rs +++ b/html-common/src/html_tree/html_block.rs @@ -4,7 +4,7 @@ use proc_macro2::{Delimiter, Ident, Span, TokenStream}; use quote::{quote, quote_spanned, ToTokens}; use syn::braced; use syn::buffer::Cursor; -use syn::parse::{Parse, ParseStream, Result}; +use syn::parse::{Parse, ParseStream, Result as ParseResult}; use syn::token; pub struct HtmlBlock { @@ -30,7 +30,7 @@ impl Peek<()> for HtmlBlock { } impl Parse for HtmlBlock { - fn parse(input: ParseStream) -> Result { + fn parse(input: ParseStream) -> ParseResult { let content; Ok(HtmlBlock { tree: Box::new(HtmlTree::Empty), diff --git a/html-common/src/html_tree/html_list.rs b/html-common/src/html_tree/html_list.rs index 2810cb0bdee..001a5566e13 100644 --- a/html-common/src/html_tree/html_list.rs +++ b/html-common/src/html_tree/html_list.rs @@ -3,7 +3,7 @@ use crate::Peek; use boolinator::Boolinator; use quote::{quote, ToTokens}; use syn::buffer::Cursor; -use syn::parse::{Parse, ParseStream, Result}; +use syn::parse::{Parse, ParseStream, Result as ParseResult}; use syn::token; pub struct HtmlList(pub Vec); @@ -11,11 +11,23 @@ pub struct HtmlList(pub Vec); impl Peek<()> for HtmlList { fn peek(cursor: Cursor) -> Option<()> { HtmlListOpen::peek(cursor) + .or(HtmlListClose::peek(cursor)) + .map(|_| ()) } } impl Parse for HtmlList { - fn parse(input: ParseStream) -> Result { + fn parse(input: ParseStream) -> ParseResult { + if HtmlListClose::peek(input.cursor()).is_some() { + return match input.parse::() { + Ok(close) => Err(syn::Error::new_spanned( + close, + "this close tag has no corresponding open tag", + )), + Err(err) => Err(err), + }; + } + let open = input.parse::()?; let mut cursor = input.cursor(); @@ -44,8 +56,8 @@ impl Parse for HtmlList { } let mut children: Vec = vec![]; - while let Ok(html_tree) = input.parse::() { - children.push(html_tree); + while HtmlListClose::peek(input.cursor()).is_none() { + children.push(input.parse()?); } input.parse::()?; @@ -67,8 +79,8 @@ impl ToTokens for HtmlList { } struct HtmlListOpen { - lt_token: token::Lt, - gt_token: token::Gt, + lt: token::Lt, + gt: token::Gt, } impl Peek<()> for HtmlListOpen { @@ -82,22 +94,26 @@ impl Peek<()> for HtmlListOpen { } impl Parse for HtmlListOpen { - fn parse(input: ParseStream) -> Result { + fn parse(input: ParseStream) -> ParseResult { Ok(HtmlListOpen { - lt_token: input.parse()?, - gt_token: input.parse()?, + lt: input.parse()?, + gt: input.parse()?, }) } } impl ToTokens for HtmlListOpen { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let HtmlListOpen { lt_token, gt_token } = self; - tokens.extend(quote! {#lt_token#gt_token}); + let HtmlListOpen { lt, gt } = self; + tokens.extend(quote! {#lt#gt}); } } -struct HtmlListClose {} +struct HtmlListClose { + lt: token::Lt, + div: token::Div, + gt: token::Gt, +} impl Peek<()> for HtmlListClose { fn peek(cursor: Cursor) -> Option<()> { @@ -113,10 +129,18 @@ impl Peek<()> for HtmlListClose { } impl Parse for HtmlListClose { - fn parse(input: ParseStream) -> Result { - input.parse::()?; - input.parse::()?; - input.parse::()?; - Ok(HtmlListClose {}) + fn parse(input: ParseStream) -> ParseResult { + Ok(HtmlListClose { + lt: input.parse()?, + div: input.parse()?, + gt: input.parse()?, + }) + } +} + +impl ToTokens for HtmlListClose { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let HtmlListClose { lt, div, gt } = self; + tokens.extend(quote! {#lt#div#gt}); } } diff --git a/html-common/src/html_tree/html_tag.rs b/html-common/src/html_tree/html_tag.rs index e9732e713cc..b3f6788e1ae 100644 --- a/html-common/src/html_tree/html_tag.rs +++ b/html-common/src/html_tree/html_tag.rs @@ -3,7 +3,7 @@ use crate::Peek; use boolinator::Boolinator; use quote::{quote, ToTokens}; use syn::buffer::Cursor; -use syn::parse::{Parse, ParseStream, Result}; +use syn::parse::{Parse, ParseStream, Result as ParseResult}; use syn::token; use syn::Ident; @@ -21,12 +21,24 @@ impl HtmlTag { impl Peek<()> for HtmlTag { fn peek(cursor: Cursor) -> Option<()> { - HtmlTagOpen::peek(cursor).map(|_| ()) + HtmlTagOpen::peek(cursor) + .or(HtmlTagClose::peek(cursor)) + .map(|_| ()) } } impl Parse for HtmlTag { - fn parse(input: ParseStream) -> Result { + fn parse(input: ParseStream) -> ParseResult { + if HtmlTagClose::peek(input.cursor()).is_some() { + return match input.parse::() { + Ok(close) => Err(syn::Error::new_spanned( + close, + "this close tag has no corresponding open tag", + )), + Err(err) => Err(err), + }; + } + let open = input.parse::()?; if open.div.is_some() { return Ok(HtmlTag { @@ -64,8 +76,14 @@ impl Parse for HtmlTag { } let mut children: Vec = vec![]; - while let Ok(html_tree) = input.parse::() { - children.push(html_tree); + loop { + if let Some(next_close_ident) = HtmlTagClose::peek(input.cursor()) { + if open.ident.to_string() == next_close_ident.to_string() { + break; + } + } + + children.push(input.parse()?); } input.parse::()?; @@ -113,7 +131,7 @@ impl Peek for HtmlTagOpen { } impl Parse for HtmlTagOpen { - fn parse(input: ParseStream) -> Result { + fn parse(input: ParseStream) -> ParseResult { Ok(HtmlTagOpen { lt: input.parse()?, ident: input.parse()?, @@ -134,7 +152,12 @@ impl ToTokens for HtmlTagOpen { } } -struct HtmlTagClose {} +struct HtmlTagClose { + lt: token::Lt, + div: Option, + ident: Ident, + gt: token::Gt, +} impl Peek for HtmlTagClose { fn peek(cursor: Cursor) -> Option { @@ -155,11 +178,19 @@ impl Peek for HtmlTagClose { } impl Parse for HtmlTagClose { - fn parse(input: ParseStream) -> Result { - input.parse::()?; - input.parse::()?; - input.parse::()?; - input.parse::()?; - Ok(HtmlTagClose {}) + fn parse(input: ParseStream) -> ParseResult { + Ok(HtmlTagClose { + lt: input.parse()?, + div: input.parse()?, + ident: input.parse()?, + gt: input.parse()?, + }) + } +} + +impl ToTokens for HtmlTagClose { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let HtmlTagClose { lt, div, ident, gt } = self; + tokens.extend(quote! {#lt#div#ident#gt}); } } diff --git a/html-common/src/html_tree/mod.rs b/html-common/src/html_tree/mod.rs index 62bb050651d..5a8e788203a 100644 --- a/html-common/src/html_tree/mod.rs +++ b/html-common/src/html_tree/mod.rs @@ -6,6 +6,7 @@ use crate::Peek; use html_block::HtmlBlock; use html_list::HtmlList; use html_tag::HtmlTag; +use proc_macro2::Span; use quote::{quote, ToTokens}; use std::iter::FromIterator; use syn::parse::{Parse, ParseStream, Result}; @@ -37,7 +38,10 @@ impl Parse for HtmlRoot { fn parse(input: ParseStream) -> Result { let html_tree = input.parse::()?; if !input.is_empty() { - Err(input.error("only one root html element allowed")) + Err(syn::Error::new( + Span::call_site(), + "only one root html element allowed", + )) } else { Ok(HtmlRoot(html_tree)) } diff --git a/html/tests/html-list-fail.stderr b/html/tests/html-list-fail.stderr index 52a5dc2cf7f..60739199662 100644 --- a/html/tests/html-list-fail.stderr +++ b/html/tests/html-list-fail.stderr @@ -4,11 +4,11 @@ error: this open tag has no corresponding close tag 5 | <> | ^^ -error: expected valid html element +error: this close tag has no corresponding open tag --> $DIR/html-list-fail.rs:9:9 | 9 | - | ^ + | ^^^ error: this open tag has no corresponding close tag --> $DIR/html-list-fail.rs:13:9 @@ -16,11 +16,11 @@ error: this open tag has no corresponding close tag 13 | <><> | ^^ -error: expected valid html element +error: this close tag has no corresponding open tag --> $DIR/html-list-fail.rs:17:9 | 17 | - | ^ + | ^^^ error: this open tag has no corresponding close tag --> $DIR/html-list-fail.rs:21:9 @@ -29,7 +29,12 @@ error: this open tag has no corresponding close tag | ^^ error: only one root html element allowed - --> $DIR/html-list-fail.rs:26:9 + --> $DIR/html-list-fail.rs:24:5 | -26 | <> - | ^ +24 | / html! { +25 | | <> +26 | | <> +27 | | }; + | |______^ + | + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) diff --git a/html/tests/html-tag-fail.rs b/html/tests/html-tag-fail.rs index 04d4da6ee49..da55ba2e68f 100644 --- a/html/tests/html-tag-fail.rs +++ b/html/tests/html-tag-fail.rs @@ -29,4 +29,12 @@ fn main() { html! { }; + + html! { +
Invalid
+ }; + + html! { +
+ }; } diff --git a/html/tests/html-tag-fail.stderr b/html/tests/html-tag-fail.stderr index a4c3e41fb71..03afe289d33 100644 --- a/html/tests/html-tag-fail.stderr +++ b/html/tests/html-tag-fail.stderr @@ -10,11 +10,11 @@ error: this open tag has no corresponding close tag 9 |
| ^^^^^ -error: expected valid html element +error: this close tag has no corresponding open tag --> $DIR/html-tag-fail.rs:13:9 | 13 |
- | ^ + | ^^^^^^ error: this open tag has no corresponding close tag --> $DIR/html-tag-fail.rs:17:9 @@ -23,10 +23,15 @@ error: this open tag has no corresponding close tag | ^^^^^ error: only one root html element allowed - --> $DIR/html-tag-fail.rs:22:9 + --> $DIR/html-tag-fail.rs:20:5 + | +20 | / html! { +21 | |
+22 | |
+23 | | }; + | |______^ | -22 |
- | ^ + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) error: this open tag has no corresponding close tag --> $DIR/html-tag-fail.rs:26:9 @@ -35,7 +40,23 @@ error: this open tag has no corresponding close tag | ^^^^^ error: only one root html element allowed - --> $DIR/html-tag-fail.rs:30:16 + --> $DIR/html-tag-fail.rs:29:5 + | +29 | / html! { +30 | | +31 | | }; + | |______^ + | + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +error: expected valid html element + --> $DIR/html-tag-fail.rs:34:14 + | +34 |
Invalid
+ | ^^^^^^^ + +error: this close tag has no corresponding open tag + --> $DIR/html-tag-fail.rs:38:14 | -30 | - | ^ +38 |
+ | ^^^^^^^ From 00de9bbf8dbf57044abeceb6e9701a37cfeca98e Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Wed, 22 May 2019 18:09:15 -0400 Subject: [PATCH 14/46] Support root literal nodes and braced literals --- html-common/src/html_tree/html_block.rs | 42 ++++++++++++++----- html-common/src/html_tree/html_text.rs | 56 +++++++++++++++++++++++++ html-common/src/html_tree/mod.rs | 55 +++++++++++++++++++----- html/tests/cases.rs | 6 +++ html/tests/html-text-fail.rs | 14 +++++++ html/tests/html-text-fail.stderr | 31 ++++++++++++++ html/tests/html-text-pass.rs | 34 +++++++++++++++ main.rs | 11 +---- 8 files changed, 217 insertions(+), 32 deletions(-) create mode 100644 html-common/src/html_tree/html_text.rs create mode 100644 html/tests/html-text-fail.rs create mode 100644 html/tests/html-text-fail.stderr create mode 100644 html/tests/html-text-pass.rs diff --git a/html-common/src/html_tree/html_block.rs b/html-common/src/html_tree/html_block.rs index d38ccfa301c..9253ec2aaf8 100644 --- a/html-common/src/html_tree/html_block.rs +++ b/html-common/src/html_tree/html_block.rs @@ -1,3 +1,4 @@ +use super::html_text::HtmlText; use super::HtmlTree; use crate::Peek; use proc_macro2::{Delimiter, Ident, Span, TokenStream}; @@ -9,18 +10,13 @@ use syn::token; pub struct HtmlBlock { tree: Box, - content: TokenStream, + content: BlockContent, brace: Option, } -impl HtmlBlock { - pub fn new(tree: HtmlTree) -> Self { - HtmlBlock { - tree: Box::new(tree), - content: TokenStream::new(), - brace: None, - } - } +enum BlockContent { + Text(HtmlText), + Stream(TokenStream), } impl Peek<()> for HtmlBlock { @@ -32,18 +28,42 @@ impl Peek<()> for HtmlBlock { impl Parse for HtmlBlock { fn parse(input: ParseStream) -> ParseResult { let content; + let brace = braced!(content in input); + let content = if HtmlText::peek(content.cursor()).is_some() { + BlockContent::Text(content.parse()?) + } else { + BlockContent::Stream(content.parse()?) + }; + Ok(HtmlBlock { tree: Box::new(HtmlTree::Empty), - brace: Some(braced!(content in input)), - content: content.parse()?, + brace: Some(brace), + content, }) } } +impl HtmlBlock { + pub fn new(tree: HtmlTree) -> Self { + HtmlBlock { + tree: Box::new(tree), + content: BlockContent::Stream(TokenStream::new()), + brace: None, + } + } +} + impl ToTokens for HtmlBlock { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let HtmlBlock { content, brace, .. } = self; let tree = Ident::new("__yew_html_tree", Span::call_site()); + let content: Box = match content { + BlockContent::Text(html_text) => Box::new(quote! { + ::yew_html_common::html_tree::HtmlTree::Text(#html_text) + }), + BlockContent::Stream(stream) => Box::new(stream), + }; + let init_tree = quote_spanned! {brace.unwrap().span=> let #tree: ::yew_html_common::html_tree::HtmlTree = {#content}; }; diff --git a/html-common/src/html_tree/html_text.rs b/html-common/src/html_tree/html_text.rs new file mode 100644 index 00000000000..b06b5fd61c9 --- /dev/null +++ b/html-common/src/html_tree/html_text.rs @@ -0,0 +1,56 @@ +use crate::Peek; +use quote::{quote, ToTokens}; +use syn::buffer::Cursor; +use syn::parse::{Parse, ParseStream, Result}; +use syn::spanned::Spanned; +use syn::Lit; + +pub struct HtmlText { + text: String, + literal: Option, +} + +impl HtmlText { + pub fn new(text: String) -> Self { + HtmlText { + text, + literal: None, + } + } +} + +impl Parse for HtmlText { + fn parse(input: ParseStream) -> Result { + let lit: Lit = input.parse()?; + match lit { + Lit::Str(_) | Lit::Char(_) | Lit::Int(_) | Lit::Float(_) | Lit::Bool(_) => {} + _ => return Err(syn::Error::new(lit.span(), "unsupported type")), + }; + + Ok(HtmlText { + text: String::from(""), + literal: Some(lit), + }) + } +} + +impl Peek<()> for HtmlText { + fn peek(cursor: Cursor) -> Option<()> { + cursor.literal().map(|_| ()).or_else(|| { + let (ident, _) = cursor.ident()?; + match ident.to_string().as_str() { + "true" | "false" => Some(()), + _ => None, + } + }) + } +} + +impl ToTokens for HtmlText { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let literal = self.literal.clone().unwrap(); + tokens.extend(quote! {{ + ::yew_html_common::html_tree::html_text::HtmlText::new(#literal.to_string()) + }}); + } +} diff --git a/html-common/src/html_tree/mod.rs b/html-common/src/html_tree/mod.rs index 5a8e788203a..7d6ef81161e 100644 --- a/html-common/src/html_tree/mod.rs +++ b/html-common/src/html_tree/mod.rs @@ -1,20 +1,31 @@ pub mod html_block; pub mod html_list; pub mod html_tag; +pub mod html_text; use crate::Peek; use html_block::HtmlBlock; use html_list::HtmlList; use html_tag::HtmlTag; +use html_text::HtmlText; use proc_macro2::Span; use quote::{quote, ToTokens}; use std::iter::FromIterator; +use syn::buffer::Cursor; use syn::parse::{Parse, ParseStream, Result}; +pub enum HtmlType { + Block, + List, + Tag, + Empty, +} + pub enum HtmlTree { Block(HtmlBlock), List(HtmlList), Tag(HtmlTag), + Text(HtmlText), Empty, } @@ -36,14 +47,19 @@ impl FromIterator for HtmlTree { pub struct HtmlRoot(HtmlTree); impl Parse for HtmlRoot { fn parse(input: ParseStream) -> Result { - let html_tree = input.parse::()?; + let html_root = if HtmlTree::peek(input.cursor()).is_some() { + HtmlRoot(input.parse()?) + } else { + HtmlRoot(HtmlTree::Text(input.parse()?)) + }; + if !input.is_empty() { Err(syn::Error::new( Span::call_site(), "only one root html element allowed", )) } else { - Ok(HtmlRoot(html_tree)) + Ok(html_root) } } } @@ -57,16 +73,30 @@ impl ToTokens for HtmlRoot { impl Parse for HtmlTree { fn parse(input: ParseStream) -> Result { - if HtmlList::peek(input.cursor()).is_some() { - Ok(HtmlTree::List(input.parse()?)) - } else if HtmlBlock::peek(input.cursor()).is_some() { - Ok(HtmlTree::Block(input.parse()?)) - } else if HtmlTag::peek(input.cursor()).is_some() { - Ok(HtmlTree::Tag(input.parse()?)) - } else if input.is_empty() { - Ok(HtmlTree::Empty) + let html_type = + HtmlTree::peek(input.cursor()).ok_or(input.error("expected valid html element"))?; + let html_tree = match html_type { + HtmlType::Empty => HtmlTree::Empty, + HtmlType::Tag => HtmlTree::Tag(input.parse()?), + HtmlType::Block => HtmlTree::Block(input.parse()?), + HtmlType::List => HtmlTree::List(input.parse()?), + }; + Ok(html_tree) + } +} + +impl Peek for HtmlTree { + fn peek(cursor: Cursor) -> Option { + if cursor.eof() { + Some(HtmlType::Empty) + } else if HtmlTag::peek(cursor).is_some() { + Some(HtmlType::Tag) + } else if HtmlBlock::peek(cursor).is_some() { + Some(HtmlType::Block) + } else if HtmlList::peek(cursor).is_some() { + Some(HtmlType::List) } else { - Err(input.error("expected valid html element")) + None } } } @@ -77,6 +107,9 @@ impl ToTokens for HtmlTree { HtmlTree::Empty => quote! { ::yew_html_common::html_tree::HtmlTree::Empty }, + HtmlTree::Text(text) => quote! { + ::yew_html_common::html_tree::HtmlTree::Text(#text) + }, HtmlTree::Tag(tag) => quote! { ::yew_html_common::html_tree::HtmlTree::Tag(#tag) }, diff --git a/html/tests/cases.rs b/html/tests/cases.rs index c3f63b76feb..688b5f78681 100644 --- a/html/tests/cases.rs +++ b/html/tests/cases.rs @@ -1,10 +1,16 @@ #[test] fn tests() { let t = trybuild::TestCases::new(); + t.pass("tests/html-block-pass.rs"); t.compile_fail("tests/html-block-fail.rs"); + t.pass("tests/html-list-pass.rs"); t.compile_fail("tests/html-list-fail.rs"); + t.pass("tests/html-tag-pass.rs"); t.compile_fail("tests/html-tag-fail.rs"); + + t.pass("tests/html-text-pass.rs"); + t.compile_fail("tests/html-text-fail.rs"); } diff --git a/html/tests/html-text-fail.rs b/html/tests/html-text-fail.rs new file mode 100644 index 00000000000..cfc54d340e9 --- /dev/null +++ b/html/tests/html-text-fail.rs @@ -0,0 +1,14 @@ +use yew_html::html; + +fn main() { + html! { "valid" "invalid" }; + + html! { + { "valid" "invalid" } + }; + + // unsupported literals + html! { b'a' }; + html! { b"str" }; + html! { 1111111111111111111111111111111111111111111111111111111111111111111111111111 }; +} diff --git a/html/tests/html-text-fail.stderr b/html/tests/html-text-fail.stderr new file mode 100644 index 00000000000..29507b6aa03 --- /dev/null +++ b/html/tests/html-text-fail.stderr @@ -0,0 +1,31 @@ +error: only one root html element allowed + --> $DIR/html-text-fail.rs:4:5 + | +4 | html! { "valid" "invalid" }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +error: unexpected token + --> $DIR/html-text-fail.rs:7:25 + | +7 | { "valid" "invalid" } + | ^^^^^^^^^ + +error: unsupported type + --> $DIR/html-text-fail.rs:11:13 + | +11 | html! { b'a' }; + | ^^^^ + +error: unsupported type + --> $DIR/html-text-fail.rs:12:13 + | +12 | html! { b"str" }; + | ^^^^^^ + +error: unsupported type + --> $DIR/html-text-fail.rs:13:13 + | +13 | html! { 1111111111111111111111111111111111111111111111111111111111111111111111111111 }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/html/tests/html-text-pass.rs b/html/tests/html-text-pass.rs new file mode 100644 index 00000000000..203ca030aa0 --- /dev/null +++ b/html/tests/html-text-pass.rs @@ -0,0 +1,34 @@ +use yew_html::html; + +fn main() { + html! { "" }; + html! { 'a' }; + html! { "hello" }; + html! { 42 }; + html! { 1.234 }; + html! { true }; + + html! { + { "" } + }; + + html! { + { 'a' } + }; + + html! { + { "hello" } + }; + + html! { + { 42 } + }; + + html! { + { 1.234 } + }; + + html! { + { true } + }; +} diff --git a/main.rs b/main.rs index 95e410df6cd..c0b4752e33e 100644 --- a/main.rs +++ b/main.rs @@ -1,14 +1,5 @@ use yew_html::html; fn main() { - html! { -
- }; - - html! { -
-
-
-
- }; + html! { "str" }; } From e082380812f4687ad83b477b4a937772e67bc2ea Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Thu, 23 May 2019 21:52:35 -0400 Subject: [PATCH 15/46] Integrate with yew virtual dom library --- html-common/src/html_tree/html_block.rs | 80 ++++++++++++--------- html-common/src/html_tree/html_list.rs | 9 ++- html-common/src/html_tree/html_tag.rs | 29 ++++---- html-common/src/html_tree/html_text.rs | 27 ++----- html-common/src/html_tree/mod.rs | 36 +++------- html/Cargo.toml | 2 + html/src/helpers.rs | 39 +++++++++++ html/src/lib.rs | 5 +- html/tests/html-block-fail.rs | 33 ++++----- html/tests/html-block-fail.stderr | 60 ++++++++++------ html/tests/html-block-pass.rs | 44 ++++++++---- html/tests/html-list-fail.rs | 34 +++------ html/tests/html-list-fail.stderr | 55 +++++++-------- html/tests/html-list-pass.rs | 21 +++--- html/tests/html-tag-fail.rs | 53 ++++---------- html/tests/html-tag-fail.stderr | 93 ++++++++++++------------- html/tests/html-tag-pass.rs | 28 ++++---- html/tests/html-text-fail.rs | 19 +++-- html/tests/html-text-fail.stderr | 48 ++++++------- html/tests/html-text-pass.rs | 45 ++++-------- main.rs | 6 +- 21 files changed, 368 insertions(+), 398 deletions(-) create mode 100644 html/src/helpers.rs diff --git a/html-common/src/html_tree/html_block.rs b/html-common/src/html_tree/html_block.rs index 9253ec2aaf8..ee88c4353dc 100644 --- a/html-common/src/html_tree/html_block.rs +++ b/html-common/src/html_tree/html_block.rs @@ -1,21 +1,22 @@ use super::html_text::HtmlText; -use super::HtmlTree; use crate::Peek; -use proc_macro2::{Delimiter, Ident, Span, TokenStream}; +use boolinator::Boolinator; +use proc_macro2::{Delimiter, TokenStream}; use quote::{quote, quote_spanned, ToTokens}; use syn::braced; use syn::buffer::Cursor; use syn::parse::{Parse, ParseStream, Result as ParseResult}; use syn::token; +use syn::Token; pub struct HtmlBlock { - tree: Box, content: BlockContent, - brace: Option, + brace: token::Brace, } enum BlockContent { Text(HtmlText), + Iterable(HtmlIterable), Stream(TokenStream), } @@ -31,46 +32,61 @@ impl Parse for HtmlBlock { let brace = braced!(content in input); let content = if HtmlText::peek(content.cursor()).is_some() { BlockContent::Text(content.parse()?) + } else if HtmlIterable::peek(content.cursor()).is_some() { + BlockContent::Iterable(content.parse()?) } else { BlockContent::Stream(content.parse()?) }; - Ok(HtmlBlock { - tree: Box::new(HtmlTree::Empty), - brace: Some(brace), - content, - }) - } -} - -impl HtmlBlock { - pub fn new(tree: HtmlTree) -> Self { - HtmlBlock { - tree: Box::new(tree), - content: BlockContent::Stream(TokenStream::new()), - brace: None, - } + Ok(HtmlBlock { brace, content }) } } impl ToTokens for HtmlBlock { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let HtmlBlock { content, brace, .. } = self; - let tree = Ident::new("__yew_html_tree", Span::call_site()); - let content: Box = match content { - BlockContent::Text(html_text) => Box::new(quote! { - ::yew_html_common::html_tree::HtmlTree::Text(#html_text) - }), - BlockContent::Stream(stream) => Box::new(stream), + let HtmlBlock { content, brace } = self; + let new_tokens = match content { + BlockContent::Text(html_text) => quote! {#html_text}, + BlockContent::Iterable(html_iterable) => quote! {#html_iterable}, + BlockContent::Stream(stream) => quote! { + ::yew::virtual_dom::VNode::from({#stream}) + }, }; - let init_tree = quote_spanned! {brace.unwrap().span=> - let #tree: ::yew_html_common::html_tree::HtmlTree = {#content}; + tokens.extend(quote_spanned! {brace.span=> #new_tokens}); + } +} + +struct HtmlIterable(TokenStream); + +impl Peek<()> for HtmlIterable { + fn peek(cursor: Cursor) -> Option<()> { + let (ident, _) = cursor.ident()?; + (ident.to_string() == "for").as_option() + } +} + +impl Parse for HtmlIterable { + fn parse(input: ParseStream) -> ParseResult { + input.parse::()?; + Ok(HtmlIterable(input.parse()?)) + } +} + +impl ToTokens for HtmlIterable { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let stream = &self.0; + let new_tokens = quote! { + { + let mut __yew_vlist = ::yew::virtual_dom::VList::new(); + for __yew_node in {#stream} { + let __yew_vnode = ::yew::virtual_dom::VNode::from(__yew_node); + __yew_vlist.add_child(__yew_vnode); + } + ::yew::virtual_dom::VNode::from(__yew_vlist) + } }; - tokens.extend(quote! {{ - #init_tree - ::yew_html_common::html_tree::html_block::HtmlBlock::new(#tree) - }}); + tokens.extend(new_tokens); } } diff --git a/html-common/src/html_tree/html_list.rs b/html-common/src/html_tree/html_list.rs index 001a5566e13..f6b5c51a47d 100644 --- a/html-common/src/html_tree/html_list.rs +++ b/html-common/src/html_tree/html_list.rs @@ -68,12 +68,11 @@ impl Parse for HtmlList { impl ToTokens for HtmlList { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let HtmlList(html_trees) = self; - let html_trees = html_trees.iter().map(|html_tree| quote! { #html_tree }); + let html_trees = &self.0; tokens.extend(quote! { - ::yew_html_common::html_tree::html_list::HtmlList( - vec![#(#html_trees,)*] - ) + ::yew::virtual_dom::vlist::VList { + childs: vec![#(#html_trees,)*], + } }); } } diff --git a/html-common/src/html_tree/html_tag.rs b/html-common/src/html_tree/html_tag.rs index b3f6788e1ae..0ad705f784e 100644 --- a/html-common/src/html_tree/html_tag.rs +++ b/html-common/src/html_tree/html_tag.rs @@ -8,15 +8,8 @@ use syn::token; use syn::Ident; pub struct HtmlTag { - pub tree: Box, -} - -impl HtmlTag { - pub fn new(tree: HtmlTree) -> Self { - HtmlTag { - tree: Box::new(tree), - } - } + open: HtmlTagOpen, + children: Vec, } impl Peek<()> for HtmlTag { @@ -42,7 +35,8 @@ impl Parse for HtmlTag { let open = input.parse::()?; if open.div.is_some() { return Ok(HtmlTag { - tree: Box::new(HtmlTree::Empty), + open, + children: Vec::new(), }); } @@ -88,18 +82,19 @@ impl Parse for HtmlTag { input.parse::()?; - Ok(HtmlTag { - tree: Box::new(children.into_iter().collect()), - }) + Ok(HtmlTag { open, children }) } } impl ToTokens for HtmlTag { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let HtmlTag { tree, .. } = self; - tokens.extend(quote! { - ::yew_html_common::html_tree::html_tag::HtmlTag::new(#tree) - }); + let HtmlTag { open, children } = self; + let tag_name = open.ident.to_string(); + tokens.extend(quote! {{ + let mut __yew_vtag = ::yew::virtual_dom::vtag::VTag::new(#tag_name); + #(__yew_vtag.add_child(#children);)* + __yew_vtag + }}); } } diff --git a/html-common/src/html_tree/html_text.rs b/html-common/src/html_tree/html_text.rs index b06b5fd61c9..0b155bf16e8 100644 --- a/html-common/src/html_tree/html_text.rs +++ b/html-common/src/html_tree/html_text.rs @@ -5,19 +5,7 @@ use syn::parse::{Parse, ParseStream, Result}; use syn::spanned::Spanned; use syn::Lit; -pub struct HtmlText { - text: String, - literal: Option, -} - -impl HtmlText { - pub fn new(text: String) -> Self { - HtmlText { - text, - literal: None, - } - } -} +pub struct HtmlText(Lit); impl Parse for HtmlText { fn parse(input: ParseStream) -> Result { @@ -27,10 +15,7 @@ impl Parse for HtmlText { _ => return Err(syn::Error::new(lit.span(), "unsupported type")), }; - Ok(HtmlText { - text: String::from(""), - literal: Some(lit), - }) + Ok(HtmlText(lit)) } } @@ -48,9 +33,9 @@ impl Peek<()> for HtmlText { impl ToTokens for HtmlText { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let literal = self.literal.clone().unwrap(); - tokens.extend(quote! {{ - ::yew_html_common::html_tree::html_text::HtmlText::new(#literal.to_string()) - }}); + let literal = &self.0; + tokens.extend(quote! {::yew::virtual_dom::VNode::VText( + ::yew::virtual_dom::vtext::VText::new(#literal.to_string()) + )}); } } diff --git a/html-common/src/html_tree/mod.rs b/html-common/src/html_tree/mod.rs index 7d6ef81161e..77a601d5b28 100644 --- a/html-common/src/html_tree/mod.rs +++ b/html-common/src/html_tree/mod.rs @@ -10,7 +10,6 @@ use html_tag::HtmlTag; use html_text::HtmlText; use proc_macro2::Span; use quote::{quote, ToTokens}; -use std::iter::FromIterator; use syn::buffer::Cursor; use syn::parse::{Parse, ParseStream, Result}; @@ -29,28 +28,15 @@ pub enum HtmlTree { Empty, } -impl FromIterator for HtmlTree { - fn from_iter>(iter: I) -> Self { - let mut trees = vec![]; - for tree in iter { - trees.push(tree); - } - - match trees.len() { - 0 => HtmlTree::Empty, - 1 => trees.remove(0), - _ => HtmlTree::List(HtmlList(trees)), - } - } -} - pub struct HtmlRoot(HtmlTree); impl Parse for HtmlRoot { fn parse(input: ParseStream) -> Result { let html_root = if HtmlTree::peek(input.cursor()).is_some() { HtmlRoot(input.parse()?) - } else { + } else if HtmlText::peek(input.cursor()).is_some() { HtmlRoot(HtmlTree::Text(input.parse()?)) + } else { + return Err(input.error("invalid root html element")); }; if !input.is_empty() { @@ -105,20 +91,18 @@ impl ToTokens for HtmlTree { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let token_stream = match self { HtmlTree::Empty => quote! { - ::yew_html_common::html_tree::HtmlTree::Empty - }, - HtmlTree::Text(text) => quote! { - ::yew_html_common::html_tree::HtmlTree::Text(#text) + ::yew::virtual_dom::VNode::VList( + ::yew::virtual_dom::vlist::VList::new() + ) }, HtmlTree::Tag(tag) => quote! { - ::yew_html_common::html_tree::HtmlTree::Tag(#tag) + ::yew::virtual_dom::VNode::VTag(#tag) }, HtmlTree::List(list) => quote! { - ::yew_html_common::html_tree::HtmlTree::List(#list) - }, - HtmlTree::Block(block) => quote! { - ::yew_html_common::html_tree::HtmlTree::Block(#block) + ::yew::virtual_dom::VNode::VList(#list) }, + HtmlTree::Text(text) => quote! {#text}, + HtmlTree::Block(block) => quote! {#block}, }; tokens.extend(token_stream); diff --git a/html/Cargo.toml b/html/Cargo.toml index 2c1b3cae362..226e4ca4c50 100644 --- a/html/Cargo.toml +++ b/html/Cargo.toml @@ -13,5 +13,7 @@ trybuild = "1.0" [dependencies] proc-macro-hack = "0.5" +proc-macro-nested = "0.1" +yew = "0.6" yew-html-impl = { path = "../html-impl" } yew-html-common = { path = "../html-common" } diff --git a/html/src/helpers.rs b/html/src/helpers.rs new file mode 100644 index 00000000000..080865c1d87 --- /dev/null +++ b/html/src/helpers.rs @@ -0,0 +1,39 @@ +#[macro_export] +macro_rules! test_html_block { + ( |$tc:ident| $($view:tt)* ) => { + test_html! { @ gen $tc { $($view)* } } + }; +} + +#[macro_export] +macro_rules! test_html { + ( |$tc:ident| $($view:tt)* ) => { + test_html! { @ gen $tc { html! { $($view)* } } } + }; + ( @gen $tc:ident $view:block ) => { + mod $tc { + use ::yew::prelude::*; + use ::yew_html::html; + + struct TestComponent {} + impl Component for TestComponent { + type Message = (); + type Properties = (); + + fn create(_: Self::Properties, _: ComponentLink) -> Self { + TestComponent {} + } + + fn update(&mut self, _: Self::Message) -> ShouldRender { + true + } + } + + impl Renderable for TestComponent { + fn view(&self) -> Html { + $view + } + } + } + }; +} diff --git a/html/src/lib.rs b/html/src/lib.rs index a2ce9bf6e5e..449ea8373e6 100644 --- a/html/src/lib.rs +++ b/html/src/lib.rs @@ -1,7 +1,8 @@ use proc_macro_hack::proc_macro_hack; -pub use yew_html_common::html_tree::HtmlTree; +#[macro_use] +pub mod helpers; /// Generate html tree -#[proc_macro_hack] +#[proc_macro_hack(support_nested)] pub use yew_html_impl::html; diff --git a/html/tests/html-block-fail.rs b/html/tests/html-block-fail.rs index 811afe8af98..cdeaad45553 100644 --- a/html/tests/html-block-fail.rs +++ b/html/tests/html-block-fail.rs @@ -1,26 +1,23 @@ -use yew_html::html; +use yew_html::{test_html, test_html_block}; -struct NotHtmlTree {} - -fn not_tree() -> NotHtmlTree { - NotHtmlTree {} +test_html! { |t1| + { () } } -fn main() { - html! { - { not_tree() } - }; +test_html_block! { |t2| + let not_tree = || (); html! { - { - let not_a_tree = not_tree(); - not_a_tree - } - }; +
{ not_tree() }
+ } +} + +test_html_block! { |t3| + let not_tree = || (); html! { - <> - { (0..3).map(|_| not_tree()) } - - }; + <>{ for (0..3).map(|_| not_tree()) } + } } + +fn main() {} diff --git a/html/tests/html-block-fail.stderr b/html/tests/html-block-fail.stderr index bb866373cd1..e4f83dd4352 100644 --- a/html/tests/html-block-fail.stderr +++ b/html/tests/html-block-fail.stderr @@ -1,28 +1,46 @@ -error[E0308]: mismatched types - --> $DIR/html-block-fail.rs:11:11 +error[E0277]: `()` doesn't implement `std::fmt::Display` + --> $DIR/html-block-fail.rs:10:5 | -11 | { not_tree() } - | ^^^^^^^^^^ expected enum `yew_html_common::html_tree::HtmlTree`, found struct `NotHtmlTree` +10 | / html! { +11 | |
{ not_tree() }
+12 | | } + | |_____^ `()` cannot be formatted with the default formatter | - = note: expected type `yew_html_common::html_tree::HtmlTree` - found type `NotHtmlTree` + = help: the trait `std::fmt::Display` is not implemented for `()` + = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead + = note: required because of the requirements on the impl of `std::string::ToString` for `()` + = note: required because of the requirements on the impl of `std::convert::From<()>` for `yew::virtual_dom::vnode::VNode<_>` + = note: required by `std::convert::From::from` + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) -error[E0308]: mismatched types - --> $DIR/html-block-fail.rs:17:13 +error[E0277]: `()` doesn't implement `std::fmt::Display` + --> $DIR/html-block-fail.rs:18:5 | -17 | not_a_tree - | ^^^^^^^^^^ expected enum `yew_html_common::html_tree::HtmlTree`, found struct `NotHtmlTree` +18 | / html! { +19 | | <>{ for (0..3).map(|_| not_tree()) } +20 | | } + | |_____^ `()` cannot be formatted with the default formatter | - = note: expected type `yew_html_common::html_tree::HtmlTree` - found type `NotHtmlTree` + = help: the trait `std::fmt::Display` is not implemented for `()` + = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead + = note: required because of the requirements on the impl of `std::string::ToString` for `()` + = note: required because of the requirements on the impl of `std::convert::From<()>` for `yew::virtual_dom::vnode::VNode<_>` + = note: required by `std::convert::From::from` + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) -error[E0308]: mismatched types - --> $DIR/html-block-fail.rs:23:15 - | -23 | { (0..3).map(|_| not_tree()) } - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected enum `yew_html_common::html_tree::HtmlTree`, found struct `std::iter::Map` - | - = note: expected type `yew_html_common::html_tree::HtmlTree` - found type `std::iter::Map, [closure@$DIR/tests/html-block-fail.rs:23:26: 23:40]>` +error[E0277]: `()` doesn't implement `std::fmt::Display` + --> $DIR/html-block-fail.rs:3:1 + | +3 | / test_html! { |t1| +4 | | { () } +5 | | } + | |_^ `()` cannot be formatted with the default formatter + | + = help: the trait `std::fmt::Display` is not implemented for `()` + = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead + = note: required because of the requirements on the impl of `std::string::ToString` for `()` + = note: required because of the requirements on the impl of `std::convert::From<()>` for `yew::virtual_dom::vnode::VNode<_>` + = note: required by `std::convert::From::from` + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) -For more information about this error, try `rustc --explain E0308`. +For more information about this error, try `rustc --explain E0277`. diff --git a/html/tests/html-block-pass.rs b/html/tests/html-block-pass.rs index db1a57b63be..4e9e1148dfd 100644 --- a/html/tests/html-block-pass.rs +++ b/html/tests/html-block-pass.rs @@ -1,23 +1,39 @@ -use yew_html::html; -use yew_html::HtmlTree; +use yew_html::{test_html, test_html_block}; -fn tree_block() -> HtmlTree { - html! {} +test_html! { |t0| + <>{ "Hi" } } -fn main() { +test_html_block! { |t1| + let subview = || html! { "subview!" }; + html! { - { tree_block() } - }; +
{ subview() }
+ } +} + +test_html_block! { |t2| + let subview = html! { "subview!" }; html! { - { - let stmt = tree_block(); - stmt - } - }; +
{ subview }
+ } +} + +test_html_block! { |t3| + let item = |num| html! {
  • {format!("item {}!", num)}
  • }; html! { - { (0..3).map(|_| tree_block()).collect() } - }; +
      + { for (0..3).map(item) } +
    + } +} + +test_html! { |t4| +
      + { for (0..3).map(|num| { html! { {num} }}) } +
    } + +fn main() {} diff --git a/html/tests/html-list-fail.rs b/html/tests/html-list-fail.rs index c979487d116..b1a062adf4d 100644 --- a/html/tests/html-list-fail.rs +++ b/html/tests/html-list-fail.rs @@ -1,28 +1,10 @@ -use yew_html::html; +use yew_html::test_html; -fn main() { - html! { - <> - }; +test_html! { |t1| <> } +test_html! { |t2| } +test_html! { |t3| <><> } +test_html! { |t4| } +test_html! { |t5| <><> } +test_html! { |t6| <><> } - html! { - - }; - - html! { - <><> - }; - - html! { - - }; - - html! { - <><> - }; - - html! { - <> - <> - }; -} +fn main() {} diff --git a/html/tests/html-list-fail.stderr b/html/tests/html-list-fail.stderr index 60739199662..45a5e64ce29 100644 --- a/html/tests/html-list-fail.stderr +++ b/html/tests/html-list-fail.stderr @@ -1,40 +1,37 @@ +error: only one root html element allowed + --> $DIR/html-list-fail.rs:8:1 + | +8 | test_html! { |t6| <><> } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + error: this open tag has no corresponding close tag - --> $DIR/html-list-fail.rs:5:9 + --> $DIR/html-list-fail.rs:7:19 | -5 | <> - | ^^ +7 | test_html! { |t5| <><> } + | ^^ error: this close tag has no corresponding open tag - --> $DIR/html-list-fail.rs:9:9 + --> $DIR/html-list-fail.rs:6:19 | -9 | - | ^^^ +6 | test_html! { |t4| } + | ^^^ error: this open tag has no corresponding close tag - --> $DIR/html-list-fail.rs:13:9 - | -13 | <><> - | ^^ + --> $DIR/html-list-fail.rs:5:19 + | +5 | test_html! { |t3| <><> } + | ^^ error: this close tag has no corresponding open tag - --> $DIR/html-list-fail.rs:17:9 - | -17 | - | ^^^ + --> $DIR/html-list-fail.rs:4:19 + | +4 | test_html! { |t2| } + | ^^^ error: this open tag has no corresponding close tag - --> $DIR/html-list-fail.rs:21:9 - | -21 | <><> - | ^^ - -error: only one root html element allowed - --> $DIR/html-list-fail.rs:24:5 - | -24 | / html! { -25 | | <> -26 | | <> -27 | | }; - | |______^ - | - = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + --> $DIR/html-list-fail.rs:3:19 + | +3 | test_html! { |t1| <> } + | ^^ diff --git a/html/tests/html-list-pass.rs b/html/tests/html-list-pass.rs index 1667ebf3210..7f4fca5fe31 100644 --- a/html/tests/html-list-pass.rs +++ b/html/tests/html-list-pass.rs @@ -1,16 +1,11 @@ -use yew_html::html; +use yew_html::test_html; -fn main() { - html! {}; - - html! { +test_html! { |t1| <> } +test_html! { |t2| + <> <> - }; - - html! { - <> - <> - <> - - }; + <> + } + +fn main() {} diff --git a/html/tests/html-tag-fail.rs b/html/tests/html-tag-fail.rs index da55ba2e68f..20317989223 100644 --- a/html/tests/html-tag-fail.rs +++ b/html/tests/html-tag-fail.rs @@ -1,40 +1,13 @@ -use yew_html::html; - -fn main() { - html! { -
    - }; - - html! { -
    - }; - - html! { -
    - }; - - html! { -
    - }; - - html! { -
    -
    - }; - - html! { -
    - }; - - html! { - - }; - - html! { -
    Invalid
    - }; - - html! { -
    - }; -} +use yew_html::test_html; + +test_html! { |t1|
    } +test_html! { |t2|
    } +test_html! { |t3|
    } +test_html! { |t4|
    } +test_html! { |t5|
    } +test_html! { |t6|
    } +test_html! { |t7|
    } +test_html! { |t8| } +test_html! { |t9|
    Invalid
    } + +fn main() {} diff --git a/html/tests/html-tag-fail.stderr b/html/tests/html-tag-fail.stderr index 03afe289d33..64dfdb31748 100644 --- a/html/tests/html-tag-fail.stderr +++ b/html/tests/html-tag-fail.stderr @@ -1,62 +1,57 @@ -error: this open tag has no corresponding close tag - --> $DIR/html-tag-fail.rs:5:9 - | -5 |
    - | ^^^^^ - -error: this open tag has no corresponding close tag - --> $DIR/html-tag-fail.rs:9:9 - | -9 |
    - | ^^^^^ - -error: this close tag has no corresponding open tag - --> $DIR/html-tag-fail.rs:13:9 - | -13 |
    - | ^^^^^^ - -error: this open tag has no corresponding close tag - --> $DIR/html-tag-fail.rs:17:9 +error: expected valid html element + --> $DIR/html-tag-fail.rs:11:24 | -17 |
    - | ^^^^^ +11 | test_html! { |t9|
    Invalid
    } + | ^^^^^^^ error: only one root html element allowed - --> $DIR/html-tag-fail.rs:20:5 + --> $DIR/html-tag-fail.rs:10:1 | -20 | / html! { -21 | |
    -22 | |
    -23 | | }; - | |______^ +10 | test_html! { |t8| } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) +error: this close tag has no corresponding open tag + --> $DIR/html-tag-fail.rs:9:24 + | +9 | test_html! { |t7|
    } + | ^^^^^^^ + error: this open tag has no corresponding close tag - --> $DIR/html-tag-fail.rs:26:9 - | -26 |
    - | ^^^^^ + --> $DIR/html-tag-fail.rs:8:19 + | +8 | test_html! { |t6|
    } + | ^^^^^ error: only one root html element allowed - --> $DIR/html-tag-fail.rs:29:5 - | -29 | / html! { -30 | | -31 | | }; - | |______^ - | - = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + --> $DIR/html-tag-fail.rs:7:1 + | +7 | test_html! { |t5|
    } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) -error: expected valid html element - --> $DIR/html-tag-fail.rs:34:14 - | -34 |
    Invalid
    - | ^^^^^^^ +error: this open tag has no corresponding close tag + --> $DIR/html-tag-fail.rs:6:19 + | +6 | test_html! { |t4|
    } + | ^^^^^ error: this close tag has no corresponding open tag - --> $DIR/html-tag-fail.rs:38:14 - | -38 |
    - | ^^^^^^^ + --> $DIR/html-tag-fail.rs:5:19 + | +5 | test_html! { |t3|
    } + | ^^^^^^ + +error: this open tag has no corresponding close tag + --> $DIR/html-tag-fail.rs:4:19 + | +4 | test_html! { |t2|
    } + | ^^^^^ + +error: this open tag has no corresponding close tag + --> $DIR/html-tag-fail.rs:3:19 + | +3 | test_html! { |t1|
    } + | ^^^^^ diff --git a/html/tests/html-tag-pass.rs b/html/tests/html-tag-pass.rs index 6e260eca17e..6781b0f4f47 100644 --- a/html/tests/html-tag-pass.rs +++ b/html/tests/html-tag-pass.rs @@ -1,18 +1,18 @@ -use yew_html::html; +use yew_html::test_html; -fn main() { - html! { -
    - }; +test_html! { |t1| +
    +} - html! { -
    -
    -
    -
    - }; +test_html! { |t2| +
    +
    +
    +
    +} - html! { - - }; +test_html! { |t3| + } + +fn main() {} diff --git a/html/tests/html-text-fail.rs b/html/tests/html-text-fail.rs index cfc54d340e9..78ed423b4f1 100644 --- a/html/tests/html-text-fail.rs +++ b/html/tests/html-text-fail.rs @@ -1,14 +1,11 @@ -use yew_html::html; +use yew_html::test_html; -fn main() { - html! { "valid" "invalid" }; +test_html! { |t1| "valid" "invalid" } +test_html! { |t2| { "valid" "invalid" } } - html! { - { "valid" "invalid" } - }; +// unsupported literals +test_html! { |t10| b'a' } +test_html! { |t11| b"str" } +test_html! { |t12| 1111111111111111111111111111111111111111111111111111111111111111111111111111 } - // unsupported literals - html! { b'a' }; - html! { b"str" }; - html! { 1111111111111111111111111111111111111111111111111111111111111111111111111111 }; -} +fn main() {} diff --git a/html/tests/html-text-fail.stderr b/html/tests/html-text-fail.stderr index 29507b6aa03..09cc897f686 100644 --- a/html/tests/html-text-fail.stderr +++ b/html/tests/html-text-fail.stderr @@ -1,31 +1,31 @@ -error: only one root html element allowed - --> $DIR/html-text-fail.rs:4:5 - | -4 | html! { "valid" "invalid" }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +error: unsupported type + --> $DIR/html-text-fail.rs:9:20 | - = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) +9 | test_html! { |t12| 1111111111111111111111111111111111111111111111111111111111111111111111111111 } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: unexpected token - --> $DIR/html-text-fail.rs:7:25 +error: unsupported type + --> $DIR/html-text-fail.rs:8:20 | -7 | { "valid" "invalid" } - | ^^^^^^^^^ +8 | test_html! { |t11| b"str" } + | ^^^^^^ error: unsupported type - --> $DIR/html-text-fail.rs:11:13 - | -11 | html! { b'a' }; - | ^^^^ + --> $DIR/html-text-fail.rs:7:20 + | +7 | test_html! { |t10| b'a' } + | ^^^^ -error: unsupported type - --> $DIR/html-text-fail.rs:12:13 - | -12 | html! { b"str" }; - | ^^^^^^ +error: unexpected token + --> $DIR/html-text-fail.rs:4:35 + | +4 | test_html! { |t2| { "valid" "invalid" } } + | ^^^^^^^^^ -error: unsupported type - --> $DIR/html-text-fail.rs:13:13 - | -13 | html! { 1111111111111111111111111111111111111111111111111111111111111111111111111111 }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +error: only one root html element allowed + --> $DIR/html-text-fail.rs:3:1 + | +3 | test_html! { |t1| "valid" "invalid" } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) diff --git a/html/tests/html-text-pass.rs b/html/tests/html-text-pass.rs index 203ca030aa0..13251125c0f 100644 --- a/html/tests/html-text-pass.rs +++ b/html/tests/html-text-pass.rs @@ -1,34 +1,17 @@ -use yew_html::html; +use yew_html::test_html; -fn main() { - html! { "" }; - html! { 'a' }; - html! { "hello" }; - html! { 42 }; - html! { 1.234 }; - html! { true }; +test_html! { |t1| "" } +test_html! { |t2| 'a' } +test_html! { |t3| "hello" } +test_html! { |t4| 42 } +test_html! { |t5| 1.234 } +test_html! { |t6| true } - html! { - { "" } - }; +test_html! { |t10| { "" } } +test_html! { |t11| { 'a' } } +test_html! { |t12| { "hello" } } +test_html! { |t13| { 42 } } +test_html! { |t14| { 1.234 } } +test_html! { |t15| { true } } - html! { - { 'a' } - }; - - html! { - { "hello" } - }; - - html! { - { 42 } - }; - - html! { - { 1.234 } - }; - - html! { - { true } - }; -} +fn main() {} diff --git a/main.rs b/main.rs index c0b4752e33e..f328e4d9d04 100644 --- a/main.rs +++ b/main.rs @@ -1,5 +1 @@ -use yew_html::html; - -fn main() { - html! { "str" }; -} +fn main() {} From 16821eb2e88e293b8582e7acec2103ca7a22238a Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Thu, 23 May 2019 21:56:50 -0400 Subject: [PATCH 16/46] Add example runnable from main crate --- Cargo.toml | 1 + main.rs | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index cd992f40e39..43b8b48de4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ name = "macros" path = "main.rs" [dependencies] +yew = "0.6" yew-html = { path = "html" } yew-html-impl = { path = "html-impl" } yew-html-common = { path = "html-common" } diff --git a/main.rs b/main.rs index f328e4d9d04..61cf6f08e0f 100644 --- a/main.rs +++ b/main.rs @@ -1 +1,30 @@ +use yew::prelude::*; +use yew_html::html; + +struct TestComponent {} +impl Component for TestComponent { + type Message = (); + type Properties = (); + + fn create(_: Self::Properties, _: ComponentLink) -> Self { + TestComponent {} + } + + fn update(&mut self, _: Self::Message) -> ShouldRender { + true + } +} + +impl Renderable for TestComponent { + fn view(&self) -> Html { + let item = |num| html! {
  • {format!("item {}!", num)}
  • }; + + html! { +
      + { for (0..3).map(item) } +
    + } + } +} + fn main() {} From 92d7a0b1f88f39fd538a61fdbbf176f9b9cb90da Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Thu, 23 May 2019 22:01:56 -0400 Subject: [PATCH 17/46] Add empty list test case --- html/tests/html-list-pass.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/html/tests/html-list-pass.rs b/html/tests/html-list-pass.rs index 7f4fca5fe31..c44dc15bee5 100644 --- a/html/tests/html-list-pass.rs +++ b/html/tests/html-list-pass.rs @@ -1,5 +1,6 @@ use yew_html::test_html; +test_html! { |t0| } test_html! { |t1| <> } test_html! { |t2| <> From 14b58e3f3c97cb1a3d4f8745a9b0aec5cc2d41f7 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Thu, 23 May 2019 22:39:36 -0400 Subject: [PATCH 18/46] Allow root level iterables --- html-common/src/html_tree/html_block.rs | 37 +------------------ html-common/src/html_tree/html_iterable.rs | 42 ++++++++++++++++++++++ html-common/src/html_tree/mod.rs | 6 ++++ html/tests/cases.rs | 3 ++ html/tests/html-block-fail.stderr | 16 ++++----- html/tests/html-iterable-fail.rs | 13 +++++++ html/tests/html-iterable-fail.stderr | 33 +++++++++++++++++ html/tests/html-iterable-pass.rs | 11 ++++++ main.rs | 8 ++--- 9 files changed, 119 insertions(+), 50 deletions(-) create mode 100644 html-common/src/html_tree/html_iterable.rs create mode 100644 html/tests/html-iterable-fail.rs create mode 100644 html/tests/html-iterable-fail.stderr create mode 100644 html/tests/html-iterable-pass.rs diff --git a/html-common/src/html_tree/html_block.rs b/html-common/src/html_tree/html_block.rs index ee88c4353dc..ea511aa9d24 100644 --- a/html-common/src/html_tree/html_block.rs +++ b/html-common/src/html_tree/html_block.rs @@ -1,13 +1,12 @@ +use super::html_iterable::HtmlIterable; use super::html_text::HtmlText; use crate::Peek; -use boolinator::Boolinator; use proc_macro2::{Delimiter, TokenStream}; use quote::{quote, quote_spanned, ToTokens}; use syn::braced; use syn::buffer::Cursor; use syn::parse::{Parse, ParseStream, Result as ParseResult}; use syn::token; -use syn::Token; pub struct HtmlBlock { content: BlockContent, @@ -56,37 +55,3 @@ impl ToTokens for HtmlBlock { tokens.extend(quote_spanned! {brace.span=> #new_tokens}); } } - -struct HtmlIterable(TokenStream); - -impl Peek<()> for HtmlIterable { - fn peek(cursor: Cursor) -> Option<()> { - let (ident, _) = cursor.ident()?; - (ident.to_string() == "for").as_option() - } -} - -impl Parse for HtmlIterable { - fn parse(input: ParseStream) -> ParseResult { - input.parse::()?; - Ok(HtmlIterable(input.parse()?)) - } -} - -impl ToTokens for HtmlIterable { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let stream = &self.0; - let new_tokens = quote! { - { - let mut __yew_vlist = ::yew::virtual_dom::VList::new(); - for __yew_node in {#stream} { - let __yew_vnode = ::yew::virtual_dom::VNode::from(__yew_node); - __yew_vlist.add_child(__yew_vnode); - } - ::yew::virtual_dom::VNode::from(__yew_vlist) - } - }; - - tokens.extend(new_tokens); - } -} diff --git a/html-common/src/html_tree/html_iterable.rs b/html-common/src/html_tree/html_iterable.rs new file mode 100644 index 00000000000..6e40697d751 --- /dev/null +++ b/html-common/src/html_tree/html_iterable.rs @@ -0,0 +1,42 @@ +use crate::Peek; +use boolinator::Boolinator; +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::buffer::Cursor; +use syn::parse::{Parse, ParseStream, Result as ParseResult}; +use syn::Token; + +pub struct HtmlIterable(TokenStream); + +impl Peek<()> for HtmlIterable { + fn peek(cursor: Cursor) -> Option<()> { + let (ident, _) = cursor.ident()?; + (ident.to_string() == "for").as_option() + } +} + +impl Parse for HtmlIterable { + fn parse(input: ParseStream) -> ParseResult { + input.parse::()?; + Ok(HtmlIterable(input.parse()?)) + } +} + +impl ToTokens for HtmlIterable { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let stream = &self.0; + let new_tokens = quote! { + { + let mut __yew_vlist = ::yew::virtual_dom::VList::new(); + let __yew_nodes: ::std::boxed::Box>> = ::std::boxed::Box::new({#stream}); + for __yew_node in __yew_nodes { + let __yew_vnode = ::yew::virtual_dom::VNode::from(__yew_node); + __yew_vlist.add_child(__yew_vnode); + } + ::yew::virtual_dom::VNode::from(__yew_vlist) + } + }; + + tokens.extend(new_tokens); + } +} diff --git a/html-common/src/html_tree/mod.rs b/html-common/src/html_tree/mod.rs index 77a601d5b28..6f5cb448a72 100644 --- a/html-common/src/html_tree/mod.rs +++ b/html-common/src/html_tree/mod.rs @@ -1,10 +1,12 @@ pub mod html_block; +pub mod html_iterable; pub mod html_list; pub mod html_tag; pub mod html_text; use crate::Peek; use html_block::HtmlBlock; +use html_iterable::HtmlIterable; use html_list::HtmlList; use html_tag::HtmlTag; use html_text::HtmlText; @@ -22,6 +24,7 @@ pub enum HtmlType { pub enum HtmlTree { Block(HtmlBlock), + Iterable(HtmlIterable), List(HtmlList), Tag(HtmlTag), Text(HtmlText), @@ -35,6 +38,8 @@ impl Parse for HtmlRoot { HtmlRoot(input.parse()?) } else if HtmlText::peek(input.cursor()).is_some() { HtmlRoot(HtmlTree::Text(input.parse()?)) + } else if HtmlIterable::peek(input.cursor()).is_some() { + HtmlRoot(HtmlTree::Iterable(input.parse()?)) } else { return Err(input.error("invalid root html element")); }; @@ -102,6 +107,7 @@ impl ToTokens for HtmlTree { ::yew::virtual_dom::VNode::VList(#list) }, HtmlTree::Text(text) => quote! {#text}, + HtmlTree::Iterable(iterable) => quote! {#iterable}, HtmlTree::Block(block) => quote! {#block}, }; diff --git a/html/tests/cases.rs b/html/tests/cases.rs index 688b5f78681..a4ec89200ae 100644 --- a/html/tests/cases.rs +++ b/html/tests/cases.rs @@ -5,6 +5,9 @@ fn tests() { t.pass("tests/html-block-pass.rs"); t.compile_fail("tests/html-block-fail.rs"); + t.pass("tests/html-iterable-pass.rs"); + t.compile_fail("tests/html-iterable-fail.rs"); + t.pass("tests/html-list-pass.rs"); t.compile_fail("tests/html-list-fail.rs"); diff --git a/html/tests/html-block-fail.stderr b/html/tests/html-block-fail.stderr index e4f83dd4352..43d155e885c 100644 --- a/html/tests/html-block-fail.stderr +++ b/html/tests/html-block-fail.stderr @@ -13,19 +13,18 @@ error[E0277]: `()` doesn't implement `std::fmt::Display` = note: required by `std::convert::From::from` = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) -error[E0277]: `()` doesn't implement `std::fmt::Display` +error[E0271]: type mismatch resolving `<[closure@$DIR/tests/html-block-fail.rs:19:28: 19:42 not_tree:_] as std::ops::FnOnce<({integer},)>>::Output == yew::virtual_dom::vnode::VNode<_>` --> $DIR/html-block-fail.rs:18:5 | 18 | / html! { 19 | | <>{ for (0..3).map(|_| not_tree()) } 20 | | } - | |_____^ `()` cannot be formatted with the default formatter + | |_____^ expected (), found enum `yew::virtual_dom::vnode::VNode` | - = help: the trait `std::fmt::Display` is not implemented for `()` - = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead - = note: required because of the requirements on the impl of `std::string::ToString` for `()` - = note: required because of the requirements on the impl of `std::convert::From<()>` for `yew::virtual_dom::vnode::VNode<_>` - = note: required by `std::convert::From::from` + = note: expected type `()` + found type `yew::virtual_dom::vnode::VNode<_>` + = note: required because of the requirements on the impl of `std::iter::Iterator` for `std::iter::Map, [closure@$DIR/tests/html-block-fail.rs:19:28: 19:42 not_tree:_]>` + = note: required for the cast to the object type `dyn std::iter::Iterator>` = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) error[E0277]: `()` doesn't implement `std::fmt::Display` @@ -43,4 +42,5 @@ error[E0277]: `()` doesn't implement `std::fmt::Display` = note: required by `std::convert::From::from` = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) -For more information about this error, try `rustc --explain E0277`. +Some errors occurred: E0271, E0277. +For more information about an error, try `rustc --explain E0271`. diff --git a/html/tests/html-iterable-fail.rs b/html/tests/html-iterable-fail.rs new file mode 100644 index 00000000000..969aa49c706 --- /dev/null +++ b/html/tests/html-iterable-fail.rs @@ -0,0 +1,13 @@ +use yew_html::{test_html, test_html_block}; + +test_html! { |t1| for } +test_html! { |t2| for () } + +// unsupported +test_html_block! { |t3| + let empty: Vec> = Vec::new(); + + html! { for empty.iter() } +} + +fn main() {} diff --git a/html/tests/html-iterable-fail.stderr b/html/tests/html-iterable-fail.stderr new file mode 100644 index 00000000000..65e6c727742 --- /dev/null +++ b/html/tests/html-iterable-fail.stderr @@ -0,0 +1,33 @@ +error[E0277]: `()` is not an iterator + --> $DIR/html-iterable-fail.rs:3:1 + | +3 | test_html! { |t1| for } + | ^^^^^^^^^^^^^^^^^^^^^^^ `()` is not an iterator + | + = help: the trait `std::iter::Iterator` is not implemented for `()` + = note: required for the cast to the object type `dyn std::iter::Iterator>` + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +error[E0277]: `()` is not an iterator + --> $DIR/html-iterable-fail.rs:4:1 + | +4 | test_html! { |t2| for () } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ `()` is not an iterator + | + = help: the trait `std::iter::Iterator` is not implemented for `()` + = note: required for the cast to the object type `dyn std::iter::Iterator>` + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +error[E0271]: type mismatch resolving `> as std::iter::Iterator>::Item == yew::virtual_dom::vnode::VNode<_>` + --> $DIR/html-iterable-fail.rs:10:5 + | +10 | html! { for empty.iter() } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected reference, found enum `yew::virtual_dom::vnode::VNode` + | + = note: expected type `&yew::virtual_dom::vnode::VNode` + found type `yew::virtual_dom::vnode::VNode<_>` + = note: required for the cast to the object type `dyn std::iter::Iterator>` + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +Some errors occurred: E0271, E0277. +For more information about an error, try `rustc --explain E0271`. diff --git a/html/tests/html-iterable-pass.rs b/html/tests/html-iterable-pass.rs new file mode 100644 index 00000000000..73e75757220 --- /dev/null +++ b/html/tests/html-iterable-pass.rs @@ -0,0 +1,11 @@ +use yew_html::{test_html, test_html_block}; + +test_html_block! { |t1| + let empty: Vec> = Vec::new(); + + html! { for empty.into_iter() } +} + +test_html! { |t2| for (0..3).map(|num| { html! { {num} } }) } + +fn main() {} diff --git a/main.rs b/main.rs index 61cf6f08e0f..3e9cf2ea822 100644 --- a/main.rs +++ b/main.rs @@ -17,13 +17,9 @@ impl Component for TestComponent { impl Renderable for TestComponent { fn view(&self) -> Html { - let item = |num| html! {
  • {format!("item {}!", num)}
  • }; + let empty: Vec> = Vec::new(); - html! { -
      - { for (0..3).map(item) } -
    - } + html! { for empty.iter() } } } From 4f11996c9dd712f6174a7c62af04df4ccecebe8d Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Thu, 23 May 2019 23:13:23 -0400 Subject: [PATCH 19/46] Support root level expressions --- html-common/src/html_tree/html_block.rs | 18 +++--- html-common/src/html_tree/html_node.rs | 47 ++++++++++++++++ html-common/src/html_tree/html_text.rs | 41 -------------- html-common/src/html_tree/mod.rs | 17 +++--- html/tests/cases.rs | 6 +- html/tests/html-block-pass.rs | 34 +++++++----- .../{html-text-fail.rs => html-node-fail.rs} | 3 + html/tests/html-node-fail.stderr | 55 +++++++++++++++++++ .../{html-text-pass.rs => html-node-pass.rs} | 9 ++- html/tests/html-text-fail.stderr | 31 ----------- 10 files changed, 154 insertions(+), 107 deletions(-) create mode 100644 html-common/src/html_tree/html_node.rs delete mode 100644 html-common/src/html_tree/html_text.rs rename html/tests/{html-text-fail.rs => html-node-fail.rs} (60%) create mode 100644 html/tests/html-node-fail.stderr rename html/tests/{html-text-pass.rs => html-node-pass.rs} (68%) delete mode 100644 html/tests/html-text-fail.stderr diff --git a/html-common/src/html_tree/html_block.rs b/html-common/src/html_tree/html_block.rs index ea511aa9d24..1c2a855a543 100644 --- a/html-common/src/html_tree/html_block.rs +++ b/html-common/src/html_tree/html_block.rs @@ -1,7 +1,7 @@ use super::html_iterable::HtmlIterable; -use super::html_text::HtmlText; +use super::html_node::HtmlNode; use crate::Peek; -use proc_macro2::{Delimiter, TokenStream}; +use proc_macro2::Delimiter; use quote::{quote, quote_spanned, ToTokens}; use syn::braced; use syn::buffer::Cursor; @@ -14,9 +14,8 @@ pub struct HtmlBlock { } enum BlockContent { - Text(HtmlText), + Node(HtmlNode), Iterable(HtmlIterable), - Stream(TokenStream), } impl Peek<()> for HtmlBlock { @@ -29,12 +28,10 @@ impl Parse for HtmlBlock { fn parse(input: ParseStream) -> ParseResult { let content; let brace = braced!(content in input); - let content = if HtmlText::peek(content.cursor()).is_some() { - BlockContent::Text(content.parse()?) - } else if HtmlIterable::peek(content.cursor()).is_some() { + let content = if HtmlIterable::peek(content.cursor()).is_some() { BlockContent::Iterable(content.parse()?) } else { - BlockContent::Stream(content.parse()?) + BlockContent::Node(content.parse()?) }; Ok(HtmlBlock { brace, content }) @@ -45,10 +42,9 @@ impl ToTokens for HtmlBlock { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let HtmlBlock { content, brace } = self; let new_tokens = match content { - BlockContent::Text(html_text) => quote! {#html_text}, BlockContent::Iterable(html_iterable) => quote! {#html_iterable}, - BlockContent::Stream(stream) => quote! { - ::yew::virtual_dom::VNode::from({#stream}) + BlockContent::Node(html_node) => quote! { + ::yew::virtual_dom::VNode::from({#html_node}) }, }; diff --git a/html-common/src/html_tree/html_node.rs b/html-common/src/html_tree/html_node.rs new file mode 100644 index 00000000000..32eaf7c3b62 --- /dev/null +++ b/html-common/src/html_tree/html_node.rs @@ -0,0 +1,47 @@ +use crate::Peek; +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::buffer::Cursor; +use syn::parse::{Parse, ParseStream, Result}; +use syn::spanned::Spanned; +use syn::Lit; + +pub struct HtmlNode(TokenStream); + +impl Parse for HtmlNode { + fn parse(input: ParseStream) -> Result { + let stream = if HtmlNode::peek(input.cursor()).is_some() { + let lit: Lit = input.parse()?; + match lit { + Lit::Str(_) | Lit::Char(_) | Lit::Int(_) | Lit::Float(_) | Lit::Bool(_) => {} + _ => return Err(syn::Error::new(lit.span(), "unsupported type")), + }; + let mut stream = TokenStream::new(); + stream.extend(quote! {#lit}); + stream + } else { + input.parse()? + }; + + Ok(HtmlNode(stream)) + } +} + +impl Peek<()> for HtmlNode { + fn peek(cursor: Cursor) -> Option<()> { + cursor.literal().map(|_| ()).or_else(|| { + let (ident, _) = cursor.ident()?; + match ident.to_string().as_str() { + "true" | "false" => Some(()), + _ => None, + } + }) + } +} + +impl ToTokens for HtmlNode { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let stream = &self.0; + tokens.extend(quote! { ::yew::virtual_dom::VNode::from({#stream}) }); + } +} diff --git a/html-common/src/html_tree/html_text.rs b/html-common/src/html_tree/html_text.rs deleted file mode 100644 index 0b155bf16e8..00000000000 --- a/html-common/src/html_tree/html_text.rs +++ /dev/null @@ -1,41 +0,0 @@ -use crate::Peek; -use quote::{quote, ToTokens}; -use syn::buffer::Cursor; -use syn::parse::{Parse, ParseStream, Result}; -use syn::spanned::Spanned; -use syn::Lit; - -pub struct HtmlText(Lit); - -impl Parse for HtmlText { - fn parse(input: ParseStream) -> Result { - let lit: Lit = input.parse()?; - match lit { - Lit::Str(_) | Lit::Char(_) | Lit::Int(_) | Lit::Float(_) | Lit::Bool(_) => {} - _ => return Err(syn::Error::new(lit.span(), "unsupported type")), - }; - - Ok(HtmlText(lit)) - } -} - -impl Peek<()> for HtmlText { - fn peek(cursor: Cursor) -> Option<()> { - cursor.literal().map(|_| ()).or_else(|| { - let (ident, _) = cursor.ident()?; - match ident.to_string().as_str() { - "true" | "false" => Some(()), - _ => None, - } - }) - } -} - -impl ToTokens for HtmlText { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let literal = &self.0; - tokens.extend(quote! {::yew::virtual_dom::VNode::VText( - ::yew::virtual_dom::vtext::VText::new(#literal.to_string()) - )}); - } -} diff --git a/html-common/src/html_tree/mod.rs b/html-common/src/html_tree/mod.rs index 6f5cb448a72..b7b687c7636 100644 --- a/html-common/src/html_tree/mod.rs +++ b/html-common/src/html_tree/mod.rs @@ -1,15 +1,15 @@ pub mod html_block; pub mod html_iterable; pub mod html_list; +pub mod html_node; pub mod html_tag; -pub mod html_text; use crate::Peek; use html_block::HtmlBlock; use html_iterable::HtmlIterable; use html_list::HtmlList; +use html_node::HtmlNode; use html_tag::HtmlTag; -use html_text::HtmlText; use proc_macro2::Span; use quote::{quote, ToTokens}; use syn::buffer::Cursor; @@ -27,7 +27,7 @@ pub enum HtmlTree { Iterable(HtmlIterable), List(HtmlList), Tag(HtmlTag), - Text(HtmlText), + Node(HtmlNode), Empty, } @@ -36,12 +36,15 @@ impl Parse for HtmlRoot { fn parse(input: ParseStream) -> Result { let html_root = if HtmlTree::peek(input.cursor()).is_some() { HtmlRoot(input.parse()?) - } else if HtmlText::peek(input.cursor()).is_some() { - HtmlRoot(HtmlTree::Text(input.parse()?)) } else if HtmlIterable::peek(input.cursor()).is_some() { HtmlRoot(HtmlTree::Iterable(input.parse()?)) + } else if let Ok(html_node) = input.parse::() { + HtmlRoot(HtmlTree::Node(html_node)) } else { - return Err(input.error("invalid root html element")); + return Err(syn::Error::new( + Span::call_site(), + "invalid root html element", + )); }; if !input.is_empty() { @@ -106,7 +109,7 @@ impl ToTokens for HtmlTree { HtmlTree::List(list) => quote! { ::yew::virtual_dom::VNode::VList(#list) }, - HtmlTree::Text(text) => quote! {#text}, + HtmlTree::Node(node) => quote! {#node}, HtmlTree::Iterable(iterable) => quote! {#iterable}, HtmlTree::Block(block) => quote! {#block}, }; diff --git a/html/tests/cases.rs b/html/tests/cases.rs index a4ec89200ae..ff396a7ebe5 100644 --- a/html/tests/cases.rs +++ b/html/tests/cases.rs @@ -11,9 +11,9 @@ fn tests() { t.pass("tests/html-list-pass.rs"); t.compile_fail("tests/html-list-fail.rs"); + t.pass("tests/html-node-pass.rs"); + t.compile_fail("tests/html-node-fail.rs"); + t.pass("tests/html-tag-pass.rs"); t.compile_fail("tests/html-tag-fail.rs"); - - t.pass("tests/html-text-pass.rs"); - t.compile_fail("tests/html-text-fail.rs"); } diff --git a/html/tests/html-block-pass.rs b/html/tests/html-block-pass.rs index 4e9e1148dfd..1e25f851be1 100644 --- a/html/tests/html-block-pass.rs +++ b/html/tests/html-block-pass.rs @@ -1,18 +1,18 @@ use yew_html::{test_html, test_html_block}; -test_html! { |t0| - <>{ "Hi" } -} +test_html! { |t1| <>{ "Hi" } } +test_html! { |t2| <>{ format!("Hello") } } +test_html! { |t3| <>{ String::from("Hello") } } -test_html_block! { |t1| - let subview = || html! { "subview!" }; +test_html_block! { |t10| + let msg = "Hello"; html! { -
    { subview() }
    +
    { msg }
    } } -test_html_block! { |t2| +test_html_block! { |t11| let subview = html! { "subview!" }; html! { @@ -20,20 +20,28 @@ test_html_block! { |t2| } } -test_html_block! { |t3| - let item = |num| html! {
  • {format!("item {}!", num)}
  • }; +test_html_block! { |t12| + let subview = || html! { "subview!" }; html! { -
      - { for (0..3).map(item) } -
    +
    { subview() }
    } } -test_html! { |t4| +test_html! { |t20|
      { for (0..3).map(|num| { html! { {num} }}) }
    } +test_html_block! { |t21| + let item = |num| html! {
  • {format!("item {}!", num)}
  • }; + + html! { +
      + { for (0..3).map(item) } +
    + } +} + fn main() {} diff --git a/html/tests/html-text-fail.rs b/html/tests/html-node-fail.rs similarity index 60% rename from html/tests/html-text-fail.rs rename to html/tests/html-node-fail.rs index 78ed423b4f1..9b37be3a1d5 100644 --- a/html/tests/html-text-fail.rs +++ b/html/tests/html-node-fail.rs @@ -7,5 +7,8 @@ test_html! { |t2| { "valid" "invalid" } } test_html! { |t10| b'a' } test_html! { |t11| b"str" } test_html! { |t12| 1111111111111111111111111111111111111111111111111111111111111111111111111111 } +test_html! { |t13| { b'a' } } +test_html! { |t14| { b"str" } } +test_html! { |t15| { 1111111111111111111111111111111111111111111111111111111111111111111111111111 } } fn main() {} diff --git a/html/tests/html-node-fail.stderr b/html/tests/html-node-fail.stderr new file mode 100644 index 00000000000..a37cad88c9f --- /dev/null +++ b/html/tests/html-node-fail.stderr @@ -0,0 +1,55 @@ +error: unsupported type + --> $DIR/html-node-fail.rs:12:28 + | +12 | test_html! { |t15| { 1111111111111111111111111111111111111111111111111111111111111111111111111111 } } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: unsupported type + --> $DIR/html-node-fail.rs:11:28 + | +11 | test_html! { |t14| { b"str" } } + | ^^^^^^ + +error: unsupported type + --> $DIR/html-node-fail.rs:10:28 + | +10 | test_html! { |t13| { b'a' } } + | ^^^^ + +error: invalid root html element + --> $DIR/html-node-fail.rs:9:1 + | +9 | test_html! { |t12| 1111111111111111111111111111111111111111111111111111111111111111111111111111 } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +error: invalid root html element + --> $DIR/html-node-fail.rs:8:1 + | +8 | test_html! { |t11| b"str" } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +error: invalid root html element + --> $DIR/html-node-fail.rs:7:1 + | +7 | test_html! { |t10| b'a' } + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +error: unexpected token + --> $DIR/html-node-fail.rs:4:35 + | +4 | test_html! { |t2| { "valid" "invalid" } } + | ^^^^^^^^^ + +error: only one root html element allowed + --> $DIR/html-node-fail.rs:3:1 + | +3 | test_html! { |t1| "valid" "invalid" } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) diff --git a/html/tests/html-text-pass.rs b/html/tests/html-node-pass.rs similarity index 68% rename from html/tests/html-text-pass.rs rename to html/tests/html-node-pass.rs index 13251125c0f..08039823fe7 100644 --- a/html/tests/html-text-pass.rs +++ b/html/tests/html-node-pass.rs @@ -1,4 +1,4 @@ -use yew_html::test_html; +use yew_html::{test_html, test_html_block}; test_html! { |t1| "" } test_html! { |t2| 'a' } @@ -14,4 +14,11 @@ test_html! { |t13| { 42 } } test_html! { |t14| { 1.234 } } test_html! { |t15| { true } } +test_html! { |t20| format!("Hello") } +test_html! { |t21| String::from("Hello") } +test_html_block! { |t22| + let msg = "Hello"; + html! { msg } +} + fn main() {} diff --git a/html/tests/html-text-fail.stderr b/html/tests/html-text-fail.stderr deleted file mode 100644 index 09cc897f686..00000000000 --- a/html/tests/html-text-fail.stderr +++ /dev/null @@ -1,31 +0,0 @@ -error: unsupported type - --> $DIR/html-text-fail.rs:9:20 - | -9 | test_html! { |t12| 1111111111111111111111111111111111111111111111111111111111111111111111111111 } - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: unsupported type - --> $DIR/html-text-fail.rs:8:20 - | -8 | test_html! { |t11| b"str" } - | ^^^^^^ - -error: unsupported type - --> $DIR/html-text-fail.rs:7:20 - | -7 | test_html! { |t10| b'a' } - | ^^^^ - -error: unexpected token - --> $DIR/html-text-fail.rs:4:35 - | -4 | test_html! { |t2| { "valid" "invalid" } } - | ^^^^^^^^^ - -error: only one root html element allowed - --> $DIR/html-text-fail.rs:3:1 - | -3 | test_html! { |t1| "valid" "invalid" } - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) From 1246ad5bbfbaa13b01f09a3a5a5e42824cdba04c Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Fri, 24 May 2019 10:43:35 -0400 Subject: [PATCH 20/46] Basic support for components --- html-common/src/html_tree/html_component.rs | 82 +++++++++++++++++++++ html-common/src/html_tree/mod.rs | 10 +++ html/src/helpers.rs | 1 + html/tests/cases.rs | 3 + html/tests/html-component-fail.rs | 7 ++ html/tests/html-component-fail.stderr | 23 ++++++ html/tests/html-component-pass.rs | 44 +++++++++++ 7 files changed, 170 insertions(+) create mode 100644 html-common/src/html_tree/html_component.rs create mode 100644 html/tests/html-component-fail.rs create mode 100644 html/tests/html-component-fail.stderr create mode 100644 html/tests/html-component-pass.rs diff --git a/html-common/src/html_tree/html_component.rs b/html-common/src/html_tree/html_component.rs new file mode 100644 index 00000000000..8e0fd389805 --- /dev/null +++ b/html-common/src/html_tree/html_component.rs @@ -0,0 +1,82 @@ +use crate::Peek; +use boolinator::Boolinator; +use quote::{quote, ToTokens}; +use syn::buffer::Cursor; +use syn::parse::{Parse, ParseStream, Result as ParseResult}; +use syn::Token; +use syn::Type; + +pub struct HtmlComponent { + ty: Type, +} + +impl Peek<()> for HtmlComponent { + fn peek(cursor: Cursor) -> Option<()> { + let (punct, cursor) = cursor.punct()?; + (punct.as_char() == '<').as_option()?; + + let (type_str, cursor) = HtmlComponent::type_str(cursor)?; + (type_str.to_lowercase() != type_str).as_option()?; + + let (punct, cursor) = cursor.punct()?; + (punct.as_char() == '/').as_option()?; + + let (punct, _) = cursor.punct()?; + (punct.as_char() == '>').as_option() + } +} + +impl Parse for HtmlComponent { + fn parse(input: ParseStream) -> ParseResult { + input.parse::()?; + let comp = HtmlComponent { ty: input.parse()? }; + // backwards compatibility + let _ = input.parse::(); + input.parse::()?; + input.parse::]>()?; + Ok(comp) + } +} + +impl ToTokens for HtmlComponent { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let HtmlComponent { ty } = self; + tokens.extend(quote! {{ + ::yew::virtual_dom::VComp::lazy::<#ty>().1 + }}); + } +} + +impl HtmlComponent { + fn type_str(cursor: Cursor) -> Option<(String, Cursor)> { + let mut cursor = cursor; + let mut type_str: String = "".to_owned(); + let mut progress = true; + while progress { + progress = false; + match cursor.ident() { + Some((ident, c)) => { + type_str += &ident.to_string(); + cursor = c; + progress = true; + } + None => {} + } + + match cursor.punct() { + Some((punct, c)) => match punct.as_char() { + ':' => { + type_str += ":"; + cursor = c; + progress = true; + } + '/' => {} + _ => return None, + }, + None => {} + } + } + + Some((type_str, cursor)) + } +} diff --git a/html-common/src/html_tree/mod.rs b/html-common/src/html_tree/mod.rs index b7b687c7636..3b922fbb7b8 100644 --- a/html-common/src/html_tree/mod.rs +++ b/html-common/src/html_tree/mod.rs @@ -1,4 +1,5 @@ pub mod html_block; +pub mod html_component; pub mod html_iterable; pub mod html_list; pub mod html_node; @@ -6,6 +7,7 @@ pub mod html_tag; use crate::Peek; use html_block::HtmlBlock; +use html_component::HtmlComponent; use html_iterable::HtmlIterable; use html_list::HtmlList; use html_node::HtmlNode; @@ -17,6 +19,7 @@ use syn::parse::{Parse, ParseStream, Result}; pub enum HtmlType { Block, + Component, List, Tag, Empty, @@ -24,6 +27,7 @@ pub enum HtmlType { pub enum HtmlTree { Block(HtmlBlock), + Component(HtmlComponent), Iterable(HtmlIterable), List(HtmlList), Tag(HtmlTag), @@ -71,6 +75,7 @@ impl Parse for HtmlTree { HtmlTree::peek(input.cursor()).ok_or(input.error("expected valid html element"))?; let html_tree = match html_type { HtmlType::Empty => HtmlTree::Empty, + HtmlType::Component => HtmlTree::Component(input.parse()?), HtmlType::Tag => HtmlTree::Tag(input.parse()?), HtmlType::Block => HtmlTree::Block(input.parse()?), HtmlType::List => HtmlTree::List(input.parse()?), @@ -83,6 +88,8 @@ impl Peek for HtmlTree { fn peek(cursor: Cursor) -> Option { if cursor.eof() { Some(HtmlType::Empty) + } else if HtmlComponent::peek(cursor).is_some() { + Some(HtmlType::Component) } else if HtmlTag::peek(cursor).is_some() { Some(HtmlType::Tag) } else if HtmlBlock::peek(cursor).is_some() { @@ -103,6 +110,9 @@ impl ToTokens for HtmlTree { ::yew::virtual_dom::vlist::VList::new() ) }, + HtmlTree::Component(comp) => quote! { + ::yew::virtual_dom::VNode::VComp(#comp) + }, HtmlTree::Tag(tag) => quote! { ::yew::virtual_dom::VNode::VTag(#tag) }, diff --git a/html/src/helpers.rs b/html/src/helpers.rs index 080865c1d87..980c7ffafe3 100644 --- a/html/src/helpers.rs +++ b/html/src/helpers.rs @@ -14,6 +14,7 @@ macro_rules! test_html { mod $tc { use ::yew::prelude::*; use ::yew_html::html; + use super::*; struct TestComponent {} impl Component for TestComponent { diff --git a/html/tests/cases.rs b/html/tests/cases.rs index ff396a7ebe5..b483baa8964 100644 --- a/html/tests/cases.rs +++ b/html/tests/cases.rs @@ -5,6 +5,9 @@ fn tests() { t.pass("tests/html-block-pass.rs"); t.compile_fail("tests/html-block-fail.rs"); + t.pass("tests/html-component-pass.rs"); + t.compile_fail("tests/html-component-fail.rs"); + t.pass("tests/html-iterable-pass.rs"); t.compile_fail("tests/html-iterable-fail.rs"); diff --git a/html/tests/html-component-fail.rs b/html/tests/html-component-fail.rs new file mode 100644 index 00000000000..8d95d027678 --- /dev/null +++ b/html/tests/html-component-fail.rs @@ -0,0 +1,7 @@ +use yew_html::test_html; + +test_html! { |t1| + +} + +fn main() {} diff --git a/html/tests/html-component-fail.stderr b/html/tests/html-component-fail.stderr new file mode 100644 index 00000000000..584b9ee9fc5 --- /dev/null +++ b/html/tests/html-component-fail.stderr @@ -0,0 +1,23 @@ +error[E0277]: the trait bound `std::string::String: yew::html::Component` is not satisfied + --> $DIR/html-component-fail.rs:3:1 + | +3 | / test_html! { |t1| +4 | | +5 | | } + | |_^ the trait `yew::html::Component` is not implemented for `std::string::String` + | + = note: required by `>::lazy` + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +error[E0277]: the trait bound `std::string::String: yew::html::Renderable` is not satisfied + --> $DIR/html-component-fail.rs:3:1 + | +3 | / test_html! { |t1| +4 | | +5 | | } + | |_^ the trait `yew::html::Renderable` is not implemented for `std::string::String` + | + = note: required by `>::lazy` + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +For more information about this error, try `rustc --explain E0277`. diff --git a/html/tests/html-component-pass.rs b/html/tests/html-component-pass.rs new file mode 100644 index 00000000000..6deceeaabc7 --- /dev/null +++ b/html/tests/html-component-pass.rs @@ -0,0 +1,44 @@ +use yew::prelude::*; +use yew_html::html; +use yew_html::test_html; + +pub struct ChildComponent {} +impl Component for ChildComponent { + type Message = (); + type Properties = (); + + fn create(_: Self::Properties, _: ComponentLink) -> Self { + ChildComponent {} + } + + fn update(&mut self, _: Self::Message) -> ShouldRender { + true + } +} + +impl Renderable for ChildComponent { + fn view(&self) -> Html { + html! { + { "child" } + } + } +} + +mod scoped { + pub use super::ChildComponent; +} + +test_html! { |t1| + <> + + + + + // backwards compat + + + + +} + +fn main() {} From 22ec126264eb2b5cc0e0869d63e1e015e6ae6c4b Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Sat, 25 May 2019 09:55:46 -0400 Subject: [PATCH 21/46] Merge html-common and html-impl crates --- Cargo.toml | 1 - crates/html-common/Cargo.toml | 11 ----------- crates/html-common/src/lib.rs | 9 --------- crates/html-impl/Cargo.toml | 6 +++--- .../src/html_tree/html_block.rs | 0 .../src/html_tree/html_component.rs | 0 .../src/html_tree/html_iterable.rs | 0 .../src/html_tree/html_list.rs | 0 .../src/html_tree/html_node.rs | 0 .../src/html_tree/html_tag.rs | 0 .../{html-common => html-impl}/src/html_tree/mod.rs | 0 crates/html-impl/src/lib.rs | 10 +++++++++- crates/html/Cargo.toml | 1 - 13 files changed, 12 insertions(+), 26 deletions(-) delete mode 100644 crates/html-common/Cargo.toml delete mode 100644 crates/html-common/src/lib.rs rename crates/{html-common => html-impl}/src/html_tree/html_block.rs (100%) rename crates/{html-common => html-impl}/src/html_tree/html_component.rs (100%) rename crates/{html-common => html-impl}/src/html_tree/html_iterable.rs (100%) rename crates/{html-common => html-impl}/src/html_tree/html_list.rs (100%) rename crates/{html-common => html-impl}/src/html_tree/html_node.rs (100%) rename crates/{html-common => html-impl}/src/html_tree/html_tag.rs (100%) rename crates/{html-common => html-impl}/src/html_tree/mod.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index e69623666bc..697c6133500 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,5 +46,4 @@ cbor = ["serde_cbor"] members = [ "crates/html", "crates/html-impl", - "crates/html-common", ] diff --git a/crates/html-common/Cargo.toml b/crates/html-common/Cargo.toml deleted file mode 100644 index 978ba08fa9f..00000000000 --- a/crates/html-common/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "yew-html-common" -version = "0.0.1" -edition = "2018" - -[dependencies] -boolinator = "2.4.0" -proc-macro-hack = "0.5" -proc-macro2 = "0.4" -quote = "0.6" -syn = { version = "0.15", features = ["full"] } diff --git a/crates/html-common/src/lib.rs b/crates/html-common/src/lib.rs deleted file mode 100644 index 64859b6446c..00000000000 --- a/crates/html-common/src/lib.rs +++ /dev/null @@ -1,9 +0,0 @@ -#![recursion_limit = "128"] - -pub mod html_tree; - -use syn::buffer::Cursor; - -pub trait Peek { - fn peek(cursor: Cursor) -> Option; -} diff --git a/crates/html-impl/Cargo.toml b/crates/html-impl/Cargo.toml index d8d72f4aa1f..8f19de2a6d9 100644 --- a/crates/html-impl/Cargo.toml +++ b/crates/html-impl/Cargo.toml @@ -7,8 +7,8 @@ edition = "2018" proc-macro = true [dependencies] -syn = { version = "0.15", features = ["full"] } -quote = "0.6" +boolinator = "2.4.0" proc-macro-hack = "0.5" proc-macro2 = "0.4" -yew-html-common = { path = "../html-common" } +quote = "0.6" +syn = { version = "0.15", features = ["full"] } diff --git a/crates/html-common/src/html_tree/html_block.rs b/crates/html-impl/src/html_tree/html_block.rs similarity index 100% rename from crates/html-common/src/html_tree/html_block.rs rename to crates/html-impl/src/html_tree/html_block.rs diff --git a/crates/html-common/src/html_tree/html_component.rs b/crates/html-impl/src/html_tree/html_component.rs similarity index 100% rename from crates/html-common/src/html_tree/html_component.rs rename to crates/html-impl/src/html_tree/html_component.rs diff --git a/crates/html-common/src/html_tree/html_iterable.rs b/crates/html-impl/src/html_tree/html_iterable.rs similarity index 100% rename from crates/html-common/src/html_tree/html_iterable.rs rename to crates/html-impl/src/html_tree/html_iterable.rs diff --git a/crates/html-common/src/html_tree/html_list.rs b/crates/html-impl/src/html_tree/html_list.rs similarity index 100% rename from crates/html-common/src/html_tree/html_list.rs rename to crates/html-impl/src/html_tree/html_list.rs diff --git a/crates/html-common/src/html_tree/html_node.rs b/crates/html-impl/src/html_tree/html_node.rs similarity index 100% rename from crates/html-common/src/html_tree/html_node.rs rename to crates/html-impl/src/html_tree/html_node.rs diff --git a/crates/html-common/src/html_tree/html_tag.rs b/crates/html-impl/src/html_tree/html_tag.rs similarity index 100% rename from crates/html-common/src/html_tree/html_tag.rs rename to crates/html-impl/src/html_tree/html_tag.rs diff --git a/crates/html-common/src/html_tree/mod.rs b/crates/html-impl/src/html_tree/mod.rs similarity index 100% rename from crates/html-common/src/html_tree/mod.rs rename to crates/html-impl/src/html_tree/mod.rs diff --git a/crates/html-impl/src/lib.rs b/crates/html-impl/src/lib.rs index ada5c339118..865a02bd389 100644 --- a/crates/html-impl/src/lib.rs +++ b/crates/html-impl/src/lib.rs @@ -1,10 +1,18 @@ +#![recursion_limit = "128"] extern crate proc_macro; +mod html_tree; + +use html_tree::HtmlRoot; use proc_macro::TokenStream; use proc_macro_hack::proc_macro_hack; use quote::quote; +use syn::buffer::Cursor; use syn::parse_macro_input; -use yew_html_common::html_tree::HtmlRoot; + +trait Peek { + fn peek(cursor: Cursor) -> Option; +} #[proc_macro_hack] pub fn html(input: TokenStream) -> TokenStream { diff --git a/crates/html/Cargo.toml b/crates/html/Cargo.toml index 1a6b7653b20..140b71ec253 100644 --- a/crates/html/Cargo.toml +++ b/crates/html/Cargo.toml @@ -16,4 +16,3 @@ yew = { path = "../.."} proc-macro-hack = "0.5" proc-macro-nested = "0.1" yew-html-impl = { path = "../html-impl" } -yew-html-common = { path = "../html-common" } From 1605c15f35b8de9573e7239f88a125d6f80cd9f1 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Sat, 25 May 2019 15:51:47 -0400 Subject: [PATCH 22/46] Rename macro crates, add proc_macro feature switch, split off shared crate --- Cargo.toml | 34 +++++++++++++++---- crates/html/tests/html-component-fail.stderr | 23 ------------- crates/{html-impl => macro-impl}/Cargo.toml | 4 +-- .../src/html_tree/html_block.rs | 2 +- .../src/html_tree/html_component.rs | 2 +- .../src/html_tree/html_iterable.rs | 8 ++--- .../src/html_tree/html_list.rs | 2 +- .../src/html_tree/html_node.rs | 2 +- .../src/html_tree/html_tag.rs | 2 +- .../src/html_tree/mod.rs | 10 +++--- crates/{html-impl => macro-impl}/src/lib.rs | 0 crates/{html => macro}/Cargo.toml | 9 ++--- crates/{html => macro}/src/helpers.rs | 2 +- crates/{html => macro}/src/lib.rs | 4 ++- crates/{html => macro}/tests/cases.rs | 0 .../{html => macro}/tests/html-block-fail.rs | 2 +- .../tests/html-block-fail.stderr | 12 +++---- .../{html => macro}/tests/html-block-pass.rs | 2 +- .../tests/html-component-fail.rs | 2 +- crates/macro/tests/html-component-fail.stderr | 23 +++++++++++++ .../tests/html-component-pass.rs | 3 +- .../tests/html-iterable-fail.rs | 2 +- .../tests/html-iterable-fail.stderr | 14 ++++---- .../tests/html-iterable-pass.rs | 2 +- .../{html => macro}/tests/html-list-fail.rs | 2 +- .../tests/html-list-fail.stderr | 0 .../{html => macro}/tests/html-list-pass.rs | 2 +- .../{html => macro}/tests/html-node-fail.rs | 2 +- .../tests/html-node-fail.stderr | 0 .../{html => macro}/tests/html-node-pass.rs | 2 +- crates/{html => macro}/tests/html-tag-fail.rs | 2 +- .../tests/html-tag-fail.stderr | 0 crates/{html => macro}/tests/html-tag-pass.rs | 2 +- crates/shared/Cargo.toml | 14 ++++++++ {src => crates/shared/src}/agent.rs | 0 {src => crates/shared/src}/app.rs | 0 {src => crates/shared/src}/callback.rs | 0 {src => crates/shared/src}/html.rs | 0 crates/shared/src/lib.rs | 17 ++++++++++ {src => crates/shared/src}/scheduler.rs | 0 {src => crates/shared/src}/virtual_dom/mod.rs | 0 .../shared/src}/virtual_dom/vcomp.rs | 0 .../shared/src}/virtual_dom/vlist.rs | 0 .../shared/src}/virtual_dom/vnode.rs | 0 .../shared/src}/virtual_dom/vtag.rs | 0 .../shared/src}/virtual_dom/vtext.rs | 0 {tests => crates/shared/tests}/vcomp_test.rs | 0 {tests => crates/shared/tests}/vlist_test.rs | 0 {tests => crates/shared/tests}/vtag_test.rs | 0 {tests => crates/shared/tests}/vtext_test.rs | 0 main.rs | 27 --------------- src/lib.rs | 19 +++++------ 52 files changed, 140 insertions(+), 115 deletions(-) delete mode 100644 crates/html/tests/html-component-fail.stderr rename crates/{html-impl => macro-impl}/Cargo.toml (82%) rename crates/{html-impl => macro-impl}/src/html_tree/html_block.rs (95%) rename crates/{html-impl => macro-impl}/src/html_tree/html_component.rs (97%) rename crates/{html-impl => macro-impl}/src/html_tree/html_iterable.rs (77%) rename crates/{html-impl => macro-impl}/src/html_tree/html_list.rs (98%) rename crates/{html-impl => macro-impl}/src/html_tree/html_node.rs (94%) rename crates/{html-impl => macro-impl}/src/html_tree/html_tag.rs (98%) rename crates/{html-impl => macro-impl}/src/html_tree/mod.rs (92%) rename crates/{html-impl => macro-impl}/src/lib.rs (100%) rename crates/{html => macro}/Cargo.toml (58%) rename crates/{html => macro}/src/helpers.rs (96%) rename crates/{html => macro}/src/lib.rs (66%) rename crates/{html => macro}/tests/cases.rs (100%) rename crates/{html => macro}/tests/html-block-fail.rs (85%) rename crates/{html => macro}/tests/html-block-fail.stderr (84%) rename crates/{html => macro}/tests/html-block-pass.rs (94%) rename crates/{html => macro}/tests/html-component-fail.rs (65%) create mode 100644 crates/macro/tests/html-component-fail.stderr rename crates/{html => macro}/tests/html-component-pass.rs (94%) rename crates/{html => macro}/tests/html-iterable-fail.rs (80%) rename crates/{html => macro}/tests/html-iterable-fail.stderr (73%) rename crates/{html => macro}/tests/html-iterable-pass.rs (81%) rename crates/{html => macro}/tests/html-list-fail.rs (86%) rename crates/{html => macro}/tests/html-list-fail.stderr (100%) rename crates/{html => macro}/tests/html-list-pass.rs (82%) rename crates/{html => macro}/tests/html-node-fail.rs (94%) rename crates/{html => macro}/tests/html-node-fail.stderr (100%) rename crates/{html => macro}/tests/html-node-pass.rs (92%) rename crates/{html => macro}/tests/html-tag-fail.rs (92%) rename crates/{html => macro}/tests/html-tag-fail.stderr (100%) rename crates/{html => macro}/tests/html-tag-pass.rs (86%) create mode 100644 crates/shared/Cargo.toml rename {src => crates/shared/src}/agent.rs (100%) rename {src => crates/shared/src}/app.rs (100%) rename {src => crates/shared/src}/callback.rs (100%) rename {src => crates/shared/src}/html.rs (100%) create mode 100644 crates/shared/src/lib.rs rename {src => crates/shared/src}/scheduler.rs (100%) rename {src => crates/shared/src}/virtual_dom/mod.rs (100%) rename {src => crates/shared/src}/virtual_dom/vcomp.rs (100%) rename {src => crates/shared/src}/virtual_dom/vlist.rs (100%) rename {src => crates/shared/src}/virtual_dom/vnode.rs (100%) rename {src => crates/shared/src}/virtual_dom/vtag.rs (100%) rename {src => crates/shared/src}/virtual_dom/vtext.rs (100%) rename {tests => crates/shared/tests}/vcomp_test.rs (100%) rename {tests => crates/shared/tests}/vlist_test.rs (100%) rename {tests => crates/shared/tests}/vtag_test.rs (100%) rename {tests => crates/shared/tests}/vtext_test.rs (100%) delete mode 100644 main.rs diff --git a/Cargo.toml b/Cargo.toml index 697c6133500..4730eb8b73b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,24 +26,44 @@ toml = { version = "0.4", optional = true } serde_yaml = { version = "0.8.3", optional = true } rmp-serde = { version = "0.13.7", optional = true } serde_cbor = { version = "0.9.0", optional = true } -yew-html = { path = "crates/html" } +yew-macro = { path = "crates/macro", optional = true } +yew-shared = { path = "crates/shared" } [dev-dependencies] serde_derive = "1" -[[bin]] -name = "yew" -path = "main.rs" - [features] default = [] web_test = [] yaml = ["serde_yaml"] msgpack = ["rmp-serde"] cbor = ["serde_cbor"] +proc_macro = ["yew-macro"] [workspace] members = [ - "crates/html", - "crates/html-impl", + "crates/macro", + "crates/macro-impl", + "crates/shared", + "examples/counter", + "examples/crm", + "examples/custom_components", + "examples/dashboard", + "examples/file_upload", + "examples/fragments", + "examples/game_of_life", + "examples/inner_html", + "examples/js_callback", + "examples/large_table", + "examples/minimal", + "examples/mount_point", + "examples/multi_thread", + "examples/npm_and_rest", + "examples/routing", + "examples/server", + "examples/showcase", + "examples/textarea", + "examples/timer", + "examples/todomvc", + "examples/two_apps", ] diff --git a/crates/html/tests/html-component-fail.stderr b/crates/html/tests/html-component-fail.stderr deleted file mode 100644 index 584b9ee9fc5..00000000000 --- a/crates/html/tests/html-component-fail.stderr +++ /dev/null @@ -1,23 +0,0 @@ -error[E0277]: the trait bound `std::string::String: yew::html::Component` is not satisfied - --> $DIR/html-component-fail.rs:3:1 - | -3 | / test_html! { |t1| -4 | | -5 | | } - | |_^ the trait `yew::html::Component` is not implemented for `std::string::String` - | - = note: required by `>::lazy` - = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) - -error[E0277]: the trait bound `std::string::String: yew::html::Renderable` is not satisfied - --> $DIR/html-component-fail.rs:3:1 - | -3 | / test_html! { |t1| -4 | | -5 | | } - | |_^ the trait `yew::html::Renderable` is not implemented for `std::string::String` - | - = note: required by `>::lazy` - = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) - -For more information about this error, try `rustc --explain E0277`. diff --git a/crates/html-impl/Cargo.toml b/crates/macro-impl/Cargo.toml similarity index 82% rename from crates/html-impl/Cargo.toml rename to crates/macro-impl/Cargo.toml index 8f19de2a6d9..c31477f787b 100644 --- a/crates/html-impl/Cargo.toml +++ b/crates/macro-impl/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "yew-html-impl" -version = "0.0.1" +name = "yew-macro-impl" +version = "0.7.0" edition = "2018" [lib] diff --git a/crates/html-impl/src/html_tree/html_block.rs b/crates/macro-impl/src/html_tree/html_block.rs similarity index 95% rename from crates/html-impl/src/html_tree/html_block.rs rename to crates/macro-impl/src/html_tree/html_block.rs index 1c2a855a543..d6886c25e59 100644 --- a/crates/html-impl/src/html_tree/html_block.rs +++ b/crates/macro-impl/src/html_tree/html_block.rs @@ -44,7 +44,7 @@ impl ToTokens for HtmlBlock { let new_tokens = match content { BlockContent::Iterable(html_iterable) => quote! {#html_iterable}, BlockContent::Node(html_node) => quote! { - ::yew::virtual_dom::VNode::from({#html_node}) + $crate::virtual_dom::VNode::from({#html_node}) }, }; diff --git a/crates/html-impl/src/html_tree/html_component.rs b/crates/macro-impl/src/html_tree/html_component.rs similarity index 97% rename from crates/html-impl/src/html_tree/html_component.rs rename to crates/macro-impl/src/html_tree/html_component.rs index 8e0fd389805..b17ae263738 100644 --- a/crates/html-impl/src/html_tree/html_component.rs +++ b/crates/macro-impl/src/html_tree/html_component.rs @@ -42,7 +42,7 @@ impl ToTokens for HtmlComponent { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let HtmlComponent { ty } = self; tokens.extend(quote! {{ - ::yew::virtual_dom::VComp::lazy::<#ty>().1 + $crate::virtual_dom::VComp::lazy::<#ty>().1 }}); } } diff --git a/crates/html-impl/src/html_tree/html_iterable.rs b/crates/macro-impl/src/html_tree/html_iterable.rs similarity index 77% rename from crates/html-impl/src/html_tree/html_iterable.rs rename to crates/macro-impl/src/html_tree/html_iterable.rs index d9c1e6815a3..55ec89eff95 100644 --- a/crates/html-impl/src/html_tree/html_iterable.rs +++ b/crates/macro-impl/src/html_tree/html_iterable.rs @@ -27,13 +27,13 @@ impl ToTokens for HtmlIterable { let stream = &self.0; let new_tokens = quote! { { - let mut __yew_vlist = ::yew::virtual_dom::VList::new(); - let __yew_nodes: ::std::boxed::Box<::std::iter::Iterator>> = ::std::boxed::Box::new({#stream}); + let mut __yew_vlist = $crate::virtual_dom::VList::new(); + let __yew_nodes: ::std::boxed::Box<::std::iter::Iterator>> = ::std::boxed::Box::new({#stream}); for __yew_node in __yew_nodes { - let __yew_vnode = ::yew::virtual_dom::VNode::from(__yew_node); + let __yew_vnode = $crate::virtual_dom::VNode::from(__yew_node); __yew_vlist.add_child(__yew_vnode); } - ::yew::virtual_dom::VNode::from(__yew_vlist) + $crate::virtual_dom::VNode::from(__yew_vlist) } }; diff --git a/crates/html-impl/src/html_tree/html_list.rs b/crates/macro-impl/src/html_tree/html_list.rs similarity index 98% rename from crates/html-impl/src/html_tree/html_list.rs rename to crates/macro-impl/src/html_tree/html_list.rs index f6b5c51a47d..b1cfac00474 100644 --- a/crates/html-impl/src/html_tree/html_list.rs +++ b/crates/macro-impl/src/html_tree/html_list.rs @@ -70,7 +70,7 @@ impl ToTokens for HtmlList { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let html_trees = &self.0; tokens.extend(quote! { - ::yew::virtual_dom::vlist::VList { + $crate::virtual_dom::vlist::VList { childs: vec![#(#html_trees,)*], } }); diff --git a/crates/html-impl/src/html_tree/html_node.rs b/crates/macro-impl/src/html_tree/html_node.rs similarity index 94% rename from crates/html-impl/src/html_tree/html_node.rs rename to crates/macro-impl/src/html_tree/html_node.rs index 32eaf7c3b62..eb6ab6ee5a8 100644 --- a/crates/html-impl/src/html_tree/html_node.rs +++ b/crates/macro-impl/src/html_tree/html_node.rs @@ -42,6 +42,6 @@ impl Peek<()> for HtmlNode { impl ToTokens for HtmlNode { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let stream = &self.0; - tokens.extend(quote! { ::yew::virtual_dom::VNode::from({#stream}) }); + tokens.extend(quote! { $crate::virtual_dom::VNode::from({#stream}) }); } } diff --git a/crates/html-impl/src/html_tree/html_tag.rs b/crates/macro-impl/src/html_tree/html_tag.rs similarity index 98% rename from crates/html-impl/src/html_tree/html_tag.rs rename to crates/macro-impl/src/html_tree/html_tag.rs index 0ad705f784e..f12e1bafc09 100644 --- a/crates/html-impl/src/html_tree/html_tag.rs +++ b/crates/macro-impl/src/html_tree/html_tag.rs @@ -91,7 +91,7 @@ impl ToTokens for HtmlTag { let HtmlTag { open, children } = self; let tag_name = open.ident.to_string(); tokens.extend(quote! {{ - let mut __yew_vtag = ::yew::virtual_dom::vtag::VTag::new(#tag_name); + let mut __yew_vtag = $crate::virtual_dom::vtag::VTag::new(#tag_name); #(__yew_vtag.add_child(#children);)* __yew_vtag }}); diff --git a/crates/html-impl/src/html_tree/mod.rs b/crates/macro-impl/src/html_tree/mod.rs similarity index 92% rename from crates/html-impl/src/html_tree/mod.rs rename to crates/macro-impl/src/html_tree/mod.rs index 3b922fbb7b8..3dcc0a2293d 100644 --- a/crates/html-impl/src/html_tree/mod.rs +++ b/crates/macro-impl/src/html_tree/mod.rs @@ -106,18 +106,18 @@ impl ToTokens for HtmlTree { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let token_stream = match self { HtmlTree::Empty => quote! { - ::yew::virtual_dom::VNode::VList( - ::yew::virtual_dom::vlist::VList::new() + $crate::virtual_dom::VNode::VList( + $crate::virtual_dom::vlist::VList::new() ) }, HtmlTree::Component(comp) => quote! { - ::yew::virtual_dom::VNode::VComp(#comp) + $crate::virtual_dom::VNode::VComp(#comp) }, HtmlTree::Tag(tag) => quote! { - ::yew::virtual_dom::VNode::VTag(#tag) + $crate::virtual_dom::VNode::VTag(#tag) }, HtmlTree::List(list) => quote! { - ::yew::virtual_dom::VNode::VList(#list) + $crate::virtual_dom::VNode::VList(#list) }, HtmlTree::Node(node) => quote! {#node}, HtmlTree::Iterable(iterable) => quote! {#iterable}, diff --git a/crates/html-impl/src/lib.rs b/crates/macro-impl/src/lib.rs similarity index 100% rename from crates/html-impl/src/lib.rs rename to crates/macro-impl/src/lib.rs diff --git a/crates/html/Cargo.toml b/crates/macro/Cargo.toml similarity index 58% rename from crates/html/Cargo.toml rename to crates/macro/Cargo.toml index 140b71ec253..94e586892e8 100644 --- a/crates/html/Cargo.toml +++ b/crates/macro/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "yew-html" -version = "0.0.1" +name = "yew-macro" +version = "0.7.0" edition = "2018" autotests = false @@ -10,9 +10,10 @@ path = "tests/cases.rs" [dev-dependencies] trybuild = "1.0" -yew = { path = "../.."} +yew = { path = "../.." } [dependencies] proc-macro-hack = "0.5" proc-macro-nested = "0.1" -yew-html-impl = { path = "../html-impl" } +yew-macro-impl = { path = "../macro-impl" } +yew-shared = { path = "../shared" } diff --git a/crates/html/src/helpers.rs b/crates/macro/src/helpers.rs similarity index 96% rename from crates/html/src/helpers.rs rename to crates/macro/src/helpers.rs index 980c7ffafe3..6635a5aff9b 100644 --- a/crates/html/src/helpers.rs +++ b/crates/macro/src/helpers.rs @@ -13,7 +13,7 @@ macro_rules! test_html { ( @gen $tc:ident $view:block ) => { mod $tc { use ::yew::prelude::*; - use ::yew_html::html; + use ::yew_macro::html; use super::*; struct TestComponent {} diff --git a/crates/html/src/lib.rs b/crates/macro/src/lib.rs similarity index 66% rename from crates/html/src/lib.rs rename to crates/macro/src/lib.rs index 449ea8373e6..866a5626099 100644 --- a/crates/html/src/lib.rs +++ b/crates/macro/src/lib.rs @@ -3,6 +3,8 @@ use proc_macro_hack::proc_macro_hack; #[macro_use] pub mod helpers; +pub use yew_shared::virtual_dom; + /// Generate html tree #[proc_macro_hack(support_nested)] -pub use yew_html_impl::html; +pub use yew_macro_impl::html; diff --git a/crates/html/tests/cases.rs b/crates/macro/tests/cases.rs similarity index 100% rename from crates/html/tests/cases.rs rename to crates/macro/tests/cases.rs diff --git a/crates/html/tests/html-block-fail.rs b/crates/macro/tests/html-block-fail.rs similarity index 85% rename from crates/html/tests/html-block-fail.rs rename to crates/macro/tests/html-block-fail.rs index cdeaad45553..743d28de0f0 100644 --- a/crates/html/tests/html-block-fail.rs +++ b/crates/macro/tests/html-block-fail.rs @@ -1,4 +1,4 @@ -use yew_html::{test_html, test_html_block}; +use yew_macro::{test_html, test_html_block}; test_html! { |t1| { () } diff --git a/crates/html/tests/html-block-fail.stderr b/crates/macro/tests/html-block-fail.stderr similarity index 84% rename from crates/html/tests/html-block-fail.stderr rename to crates/macro/tests/html-block-fail.stderr index 43d155e885c..a188f2943dc 100644 --- a/crates/html/tests/html-block-fail.stderr +++ b/crates/macro/tests/html-block-fail.stderr @@ -9,22 +9,22 @@ error[E0277]: `()` doesn't implement `std::fmt::Display` = help: the trait `std::fmt::Display` is not implemented for `()` = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead = note: required because of the requirements on the impl of `std::string::ToString` for `()` - = note: required because of the requirements on the impl of `std::convert::From<()>` for `yew::virtual_dom::vnode::VNode<_>` + = note: required because of the requirements on the impl of `std::convert::From<()>` for `yew_shared::virtual_dom::vnode::VNode<_>` = note: required by `std::convert::From::from` = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) -error[E0271]: type mismatch resolving `<[closure@$DIR/tests/html-block-fail.rs:19:28: 19:42 not_tree:_] as std::ops::FnOnce<({integer},)>>::Output == yew::virtual_dom::vnode::VNode<_>` +error[E0271]: type mismatch resolving `<[closure@$DIR/tests/html-block-fail.rs:19:28: 19:42 not_tree:_] as std::ops::FnOnce<({integer},)>>::Output == yew_shared::virtual_dom::vnode::VNode<_>` --> $DIR/html-block-fail.rs:18:5 | 18 | / html! { 19 | | <>{ for (0..3).map(|_| not_tree()) } 20 | | } - | |_____^ expected (), found enum `yew::virtual_dom::vnode::VNode` + | |_____^ expected (), found enum `yew_shared::virtual_dom::vnode::VNode` | = note: expected type `()` - found type `yew::virtual_dom::vnode::VNode<_>` + found type `yew_shared::virtual_dom::vnode::VNode<_>` = note: required because of the requirements on the impl of `std::iter::Iterator` for `std::iter::Map, [closure@$DIR/tests/html-block-fail.rs:19:28: 19:42 not_tree:_]>` - = note: required for the cast to the object type `dyn std::iter::Iterator>` + = note: required for the cast to the object type `dyn std::iter::Iterator>` = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) error[E0277]: `()` doesn't implement `std::fmt::Display` @@ -38,7 +38,7 @@ error[E0277]: `()` doesn't implement `std::fmt::Display` = help: the trait `std::fmt::Display` is not implemented for `()` = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead = note: required because of the requirements on the impl of `std::string::ToString` for `()` - = note: required because of the requirements on the impl of `std::convert::From<()>` for `yew::virtual_dom::vnode::VNode<_>` + = note: required because of the requirements on the impl of `std::convert::From<()>` for `yew_shared::virtual_dom::vnode::VNode<_>` = note: required by `std::convert::From::from` = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) diff --git a/crates/html/tests/html-block-pass.rs b/crates/macro/tests/html-block-pass.rs similarity index 94% rename from crates/html/tests/html-block-pass.rs rename to crates/macro/tests/html-block-pass.rs index 1e25f851be1..22dbd32f8ad 100644 --- a/crates/html/tests/html-block-pass.rs +++ b/crates/macro/tests/html-block-pass.rs @@ -1,4 +1,4 @@ -use yew_html::{test_html, test_html_block}; +use yew_macro::{test_html, test_html_block}; test_html! { |t1| <>{ "Hi" } } test_html! { |t2| <>{ format!("Hello") } } diff --git a/crates/html/tests/html-component-fail.rs b/crates/macro/tests/html-component-fail.rs similarity index 65% rename from crates/html/tests/html-component-fail.rs rename to crates/macro/tests/html-component-fail.rs index 8d95d027678..c75f3eb98a5 100644 --- a/crates/html/tests/html-component-fail.rs +++ b/crates/macro/tests/html-component-fail.rs @@ -1,4 +1,4 @@ -use yew_html::test_html; +use yew_macro::test_html; test_html! { |t1| diff --git a/crates/macro/tests/html-component-fail.stderr b/crates/macro/tests/html-component-fail.stderr new file mode 100644 index 00000000000..970a8a6ac5a --- /dev/null +++ b/crates/macro/tests/html-component-fail.stderr @@ -0,0 +1,23 @@ +error[E0277]: the trait bound `std::string::String: yew_shared::html::Component` is not satisfied + --> $DIR/html-component-fail.rs:3:1 + | +3 | / test_html! { |t1| +4 | | +5 | | } + | |_^ the trait `yew_shared::html::Component` is not implemented for `std::string::String` + | + = note: required by `>::lazy` + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +error[E0277]: the trait bound `std::string::String: yew_shared::html::Renderable` is not satisfied + --> $DIR/html-component-fail.rs:3:1 + | +3 | / test_html! { |t1| +4 | | +5 | | } + | |_^ the trait `yew_shared::html::Renderable` is not implemented for `std::string::String` + | + = note: required by `>::lazy` + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +For more information about this error, try `rustc --explain E0277`. diff --git a/crates/html/tests/html-component-pass.rs b/crates/macro/tests/html-component-pass.rs similarity index 94% rename from crates/html/tests/html-component-pass.rs rename to crates/macro/tests/html-component-pass.rs index 6deceeaabc7..220397e8524 100644 --- a/crates/html/tests/html-component-pass.rs +++ b/crates/macro/tests/html-component-pass.rs @@ -1,6 +1,5 @@ use yew::prelude::*; -use yew_html::html; -use yew_html::test_html; +use yew_macro::{html, test_html}; pub struct ChildComponent {} impl Component for ChildComponent { diff --git a/crates/html/tests/html-iterable-fail.rs b/crates/macro/tests/html-iterable-fail.rs similarity index 80% rename from crates/html/tests/html-iterable-fail.rs rename to crates/macro/tests/html-iterable-fail.rs index 969aa49c706..a7fc8c9a216 100644 --- a/crates/html/tests/html-iterable-fail.rs +++ b/crates/macro/tests/html-iterable-fail.rs @@ -1,4 +1,4 @@ -use yew_html::{test_html, test_html_block}; +use yew_macro::{test_html, test_html_block}; test_html! { |t1| for } test_html! { |t2| for () } diff --git a/crates/html/tests/html-iterable-fail.stderr b/crates/macro/tests/html-iterable-fail.stderr similarity index 73% rename from crates/html/tests/html-iterable-fail.stderr rename to crates/macro/tests/html-iterable-fail.stderr index 65e6c727742..ef057d18dbd 100644 --- a/crates/html/tests/html-iterable-fail.stderr +++ b/crates/macro/tests/html-iterable-fail.stderr @@ -5,7 +5,7 @@ error[E0277]: `()` is not an iterator | ^^^^^^^^^^^^^^^^^^^^^^^ `()` is not an iterator | = help: the trait `std::iter::Iterator` is not implemented for `()` - = note: required for the cast to the object type `dyn std::iter::Iterator>` + = note: required for the cast to the object type `dyn std::iter::Iterator>` = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) error[E0277]: `()` is not an iterator @@ -15,18 +15,18 @@ error[E0277]: `()` is not an iterator | ^^^^^^^^^^^^^^^^^^^^^^^^^^ `()` is not an iterator | = help: the trait `std::iter::Iterator` is not implemented for `()` - = note: required for the cast to the object type `dyn std::iter::Iterator>` + = note: required for the cast to the object type `dyn std::iter::Iterator>` = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) -error[E0271]: type mismatch resolving `> as std::iter::Iterator>::Item == yew::virtual_dom::vnode::VNode<_>` +error[E0271]: type mismatch resolving `> as std::iter::Iterator>::Item == yew_shared::virtual_dom::vnode::VNode<_>` --> $DIR/html-iterable-fail.rs:10:5 | 10 | html! { for empty.iter() } - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected reference, found enum `yew::virtual_dom::vnode::VNode` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected reference, found enum `yew_shared::virtual_dom::vnode::VNode` | - = note: expected type `&yew::virtual_dom::vnode::VNode` - found type `yew::virtual_dom::vnode::VNode<_>` - = note: required for the cast to the object type `dyn std::iter::Iterator>` + = note: expected type `&yew_shared::virtual_dom::vnode::VNode` + found type `yew_shared::virtual_dom::vnode::VNode<_>` + = note: required for the cast to the object type `dyn std::iter::Iterator>` = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) Some errors occurred: E0271, E0277. diff --git a/crates/html/tests/html-iterable-pass.rs b/crates/macro/tests/html-iterable-pass.rs similarity index 81% rename from crates/html/tests/html-iterable-pass.rs rename to crates/macro/tests/html-iterable-pass.rs index 73e75757220..05310bc8185 100644 --- a/crates/html/tests/html-iterable-pass.rs +++ b/crates/macro/tests/html-iterable-pass.rs @@ -1,4 +1,4 @@ -use yew_html::{test_html, test_html_block}; +use yew_macro::{test_html, test_html_block}; test_html_block! { |t1| let empty: Vec> = Vec::new(); diff --git a/crates/html/tests/html-list-fail.rs b/crates/macro/tests/html-list-fail.rs similarity index 86% rename from crates/html/tests/html-list-fail.rs rename to crates/macro/tests/html-list-fail.rs index b1a062adf4d..0fca671668e 100644 --- a/crates/html/tests/html-list-fail.rs +++ b/crates/macro/tests/html-list-fail.rs @@ -1,4 +1,4 @@ -use yew_html::test_html; +use yew_macro::test_html; test_html! { |t1| <> } test_html! { |t2| } diff --git a/crates/html/tests/html-list-fail.stderr b/crates/macro/tests/html-list-fail.stderr similarity index 100% rename from crates/html/tests/html-list-fail.stderr rename to crates/macro/tests/html-list-fail.stderr diff --git a/crates/html/tests/html-list-pass.rs b/crates/macro/tests/html-list-pass.rs similarity index 82% rename from crates/html/tests/html-list-pass.rs rename to crates/macro/tests/html-list-pass.rs index c44dc15bee5..817b4663d24 100644 --- a/crates/html/tests/html-list-pass.rs +++ b/crates/macro/tests/html-list-pass.rs @@ -1,4 +1,4 @@ -use yew_html::test_html; +use yew_macro::test_html; test_html! { |t0| } test_html! { |t1| <> } diff --git a/crates/html/tests/html-node-fail.rs b/crates/macro/tests/html-node-fail.rs similarity index 94% rename from crates/html/tests/html-node-fail.rs rename to crates/macro/tests/html-node-fail.rs index 9b37be3a1d5..0a114bb21bc 100644 --- a/crates/html/tests/html-node-fail.rs +++ b/crates/macro/tests/html-node-fail.rs @@ -1,4 +1,4 @@ -use yew_html::test_html; +use yew_macro::test_html; test_html! { |t1| "valid" "invalid" } test_html! { |t2| { "valid" "invalid" } } diff --git a/crates/html/tests/html-node-fail.stderr b/crates/macro/tests/html-node-fail.stderr similarity index 100% rename from crates/html/tests/html-node-fail.stderr rename to crates/macro/tests/html-node-fail.stderr diff --git a/crates/html/tests/html-node-pass.rs b/crates/macro/tests/html-node-pass.rs similarity index 92% rename from crates/html/tests/html-node-pass.rs rename to crates/macro/tests/html-node-pass.rs index 08039823fe7..cadfcc504ad 100644 --- a/crates/html/tests/html-node-pass.rs +++ b/crates/macro/tests/html-node-pass.rs @@ -1,4 +1,4 @@ -use yew_html::{test_html, test_html_block}; +use yew_macro::{test_html, test_html_block}; test_html! { |t1| "" } test_html! { |t2| 'a' } diff --git a/crates/html/tests/html-tag-fail.rs b/crates/macro/tests/html-tag-fail.rs similarity index 92% rename from crates/html/tests/html-tag-fail.rs rename to crates/macro/tests/html-tag-fail.rs index 20317989223..32648e3dddf 100644 --- a/crates/html/tests/html-tag-fail.rs +++ b/crates/macro/tests/html-tag-fail.rs @@ -1,4 +1,4 @@ -use yew_html::test_html; +use yew_macro::test_html; test_html! { |t1|
    } test_html! { |t2|
    } diff --git a/crates/html/tests/html-tag-fail.stderr b/crates/macro/tests/html-tag-fail.stderr similarity index 100% rename from crates/html/tests/html-tag-fail.stderr rename to crates/macro/tests/html-tag-fail.stderr diff --git a/crates/html/tests/html-tag-pass.rs b/crates/macro/tests/html-tag-pass.rs similarity index 86% rename from crates/html/tests/html-tag-pass.rs rename to crates/macro/tests/html-tag-pass.rs index 6781b0f4f47..fe0a864bae2 100644 --- a/crates/html/tests/html-tag-pass.rs +++ b/crates/macro/tests/html-tag-pass.rs @@ -1,4 +1,4 @@ -use yew_html::test_html; +use yew_macro::test_html; test_html! { |t1|
    diff --git a/crates/shared/Cargo.toml b/crates/shared/Cargo.toml new file mode 100644 index 00000000000..5e138fee8de --- /dev/null +++ b/crates/shared/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "yew-shared" +version = "0.7.0" + +[lib] + +[dependencies] +log = "0.4" +serde = "1.0" +serde_derive = "1.0" +bincode = "=1.0.1" +anymap = "0.12" +slab = "0.4" +stdweb = "^0.4.14" diff --git a/src/agent.rs b/crates/shared/src/agent.rs similarity index 100% rename from src/agent.rs rename to crates/shared/src/agent.rs diff --git a/src/app.rs b/crates/shared/src/app.rs similarity index 100% rename from src/app.rs rename to crates/shared/src/app.rs diff --git a/src/callback.rs b/crates/shared/src/callback.rs similarity index 100% rename from src/callback.rs rename to crates/shared/src/callback.rs diff --git a/src/html.rs b/crates/shared/src/html.rs similarity index 100% rename from src/html.rs rename to crates/shared/src/html.rs diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs new file mode 100644 index 00000000000..b7eb2867dcf --- /dev/null +++ b/crates/shared/src/lib.rs @@ -0,0 +1,17 @@ +#[macro_use] +extern crate log; +extern crate serde; +#[macro_use] +extern crate serde_derive; +extern crate bincode; +extern crate anymap; +extern crate slab; +#[macro_use] +extern crate stdweb; + +pub mod html; +pub mod agent; +pub mod app; +pub mod callback; +pub mod scheduler; +pub mod virtual_dom; diff --git a/src/scheduler.rs b/crates/shared/src/scheduler.rs similarity index 100% rename from src/scheduler.rs rename to crates/shared/src/scheduler.rs diff --git a/src/virtual_dom/mod.rs b/crates/shared/src/virtual_dom/mod.rs similarity index 100% rename from src/virtual_dom/mod.rs rename to crates/shared/src/virtual_dom/mod.rs diff --git a/src/virtual_dom/vcomp.rs b/crates/shared/src/virtual_dom/vcomp.rs similarity index 100% rename from src/virtual_dom/vcomp.rs rename to crates/shared/src/virtual_dom/vcomp.rs diff --git a/src/virtual_dom/vlist.rs b/crates/shared/src/virtual_dom/vlist.rs similarity index 100% rename from src/virtual_dom/vlist.rs rename to crates/shared/src/virtual_dom/vlist.rs diff --git a/src/virtual_dom/vnode.rs b/crates/shared/src/virtual_dom/vnode.rs similarity index 100% rename from src/virtual_dom/vnode.rs rename to crates/shared/src/virtual_dom/vnode.rs diff --git a/src/virtual_dom/vtag.rs b/crates/shared/src/virtual_dom/vtag.rs similarity index 100% rename from src/virtual_dom/vtag.rs rename to crates/shared/src/virtual_dom/vtag.rs diff --git a/src/virtual_dom/vtext.rs b/crates/shared/src/virtual_dom/vtext.rs similarity index 100% rename from src/virtual_dom/vtext.rs rename to crates/shared/src/virtual_dom/vtext.rs diff --git a/tests/vcomp_test.rs b/crates/shared/tests/vcomp_test.rs similarity index 100% rename from tests/vcomp_test.rs rename to crates/shared/tests/vcomp_test.rs diff --git a/tests/vlist_test.rs b/crates/shared/tests/vlist_test.rs similarity index 100% rename from tests/vlist_test.rs rename to crates/shared/tests/vlist_test.rs diff --git a/tests/vtag_test.rs b/crates/shared/tests/vtag_test.rs similarity index 100% rename from tests/vtag_test.rs rename to crates/shared/tests/vtag_test.rs diff --git a/tests/vtext_test.rs b/crates/shared/tests/vtext_test.rs similarity index 100% rename from tests/vtext_test.rs rename to crates/shared/tests/vtext_test.rs diff --git a/main.rs b/main.rs deleted file mode 100644 index acf3d29e72a..00000000000 --- a/main.rs +++ /dev/null @@ -1,27 +0,0 @@ -extern crate yew; - -use yew::prelude::*; - -struct TestComponent {} -impl Component for TestComponent { - type Message = (); - type Properties = (); - - fn create(_: Self::Properties, _: ComponentLink) -> Self { - TestComponent {} - } - - fn update(&mut self, _: Self::Message) -> ShouldRender { - true - } -} - -impl Renderable for TestComponent { - fn view(&self) -> Html { - let empty: Vec> = Vec::new(); - - html! { for empty.into_iter() } - } -} - -fn main() {} diff --git a/src/lib.rs b/src/lib.rs index 3072ff2ca6e..18b33d50b2d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,7 +61,6 @@ #[macro_use] extern crate failure; -#[macro_use] extern crate log; extern crate http; extern crate serde; @@ -71,6 +70,7 @@ extern crate serde_json; extern crate bincode; extern crate anymap; extern crate slab; +extern crate yew_shared; #[macro_use] extern crate stdweb; #[cfg(feature = "toml")] @@ -81,20 +81,18 @@ extern crate serde_yaml; extern crate rmp_serde; #[cfg(feature = "cbor")] extern crate serde_cbor; -extern crate yew_html; +#[cfg(feature = "proc_macro")] +extern crate yew_macro; #[macro_use] +#[cfg(not(feature = "proc_macro"))] pub mod macros; pub mod format; -pub mod html; -pub mod app; pub mod services; -pub mod virtual_dom; -pub mod callback; -pub mod scheduler; -pub mod agent; pub mod components; +pub use yew_shared::*; + /// Initializes yew framework. It should be called first. pub fn initialize() { stdweb::initialize(); @@ -211,8 +209,9 @@ pub mod prelude { Transferable, }; } - - pub use yew_html::html; + + #[cfg(feature = "proc_macro")] + pub use yew_macro::html; } pub use self::prelude::*; From a7291b87a1f5393183480bb6764dec1473e10950 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Sat, 25 May 2019 16:15:56 -0400 Subject: [PATCH 23/46] Add root component tests --- crates/macro/tests/html-component-pass.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/macro/tests/html-component-pass.rs b/crates/macro/tests/html-component-pass.rs index 220397e8524..304bb8a5559 100644 --- a/crates/macro/tests/html-component-pass.rs +++ b/crates/macro/tests/html-component-pass.rs @@ -28,6 +28,15 @@ mod scoped { } test_html! { |t1| + +} + +test_html! { |t2| + // backwards compat + +} + +test_html! { |t3| <> From 3aca0d8c17e2f5ed23b39f4a7e9bff79e1ed1f71 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Sun, 26 May 2019 09:44:10 -0400 Subject: [PATCH 24/46] Support component props --- crates/macro-impl/Cargo.toml | 2 +- .../src/html_tree/html_component.rs | 214 +++++++++++++++--- crates/macro/Cargo.toml | 1 - crates/macro/src/helpers.rs | 2 +- crates/macro/tests/html-component-fail.rs | 87 ++++++- crates/macro/tests/html-component-fail.stderr | 141 ++++++++++-- crates/macro/tests/html-component-pass.rs | 56 ++++- crates/shared/src/lib.rs | 92 +++++++- src/lib.rs | 88 +------ 9 files changed, 530 insertions(+), 153 deletions(-) diff --git a/crates/macro-impl/Cargo.toml b/crates/macro-impl/Cargo.toml index c31477f787b..d533f040808 100644 --- a/crates/macro-impl/Cargo.toml +++ b/crates/macro-impl/Cargo.toml @@ -11,4 +11,4 @@ boolinator = "2.4.0" proc-macro-hack = "0.5" proc-macro2 = "0.4" quote = "0.6" -syn = { version = "0.15", features = ["full"] } +syn = { version = "0.15.34", features = ["full"] } diff --git a/crates/macro-impl/src/html_tree/html_component.rs b/crates/macro-impl/src/html_tree/html_component.rs index b17ae263738..0911a62ebe2 100644 --- a/crates/macro-impl/src/html_tree/html_component.rs +++ b/crates/macro-impl/src/html_tree/html_component.rs @@ -1,13 +1,15 @@ use crate::Peek; use boolinator::Boolinator; -use quote::{quote, ToTokens}; +use quote::{quote, quote_spanned, ToTokens}; +use proc_macro2::Span; use syn::buffer::Cursor; use syn::parse::{Parse, ParseStream, Result as ParseResult}; -use syn::Token; -use syn::Type; +use syn::{Attribute, Ident, Token, Type, Expr, Lit, ExprLit}; +use syn::spanned::Spanned; pub struct HtmlComponent { ty: Type, + props: Option, } impl Peek<()> for HtmlComponent { @@ -15,23 +17,38 @@ impl Peek<()> for HtmlComponent { let (punct, cursor) = cursor.punct()?; (punct.as_char() == '<').as_option()?; - let (type_str, cursor) = HtmlComponent::type_str(cursor)?; - (type_str.to_lowercase() != type_str).as_option()?; - - let (punct, cursor) = cursor.punct()?; - (punct.as_char() == '/').as_option()?; - - let (punct, _) = cursor.punct()?; - (punct.as_char() == '>').as_option() + let (type_str, _) = HtmlComponent::type_str(cursor)?; + (type_str.to_lowercase() != type_str).as_option() } } impl Parse for HtmlComponent { fn parse(input: ParseStream) -> ParseResult { input.parse::()?; - let comp = HtmlComponent { ty: input.parse()? }; + let ty = input.parse::()?; + // backwards compatibility let _ = input.parse::(); + + let props = if let Some(prop_type) = Props::peek(input.cursor()) { + match prop_type { + PropType::List => { + let mut props: Vec = Vec::new(); + while Prop::peek(input.cursor()).is_some() { + props.push(input.parse::()?); + } + Some(Props::List(props)) + } + PropType::With => { + Some(Props::With(input.parse::()?)) + } + } + } else { + None + }; + + let comp = HtmlComponent { ty, props}; + input.parse::()?; input.parse::]>()?; Ok(comp) @@ -40,43 +57,174 @@ impl Parse for HtmlComponent { impl ToTokens for HtmlComponent { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let HtmlComponent { ty } = self; + let HtmlComponent { ty, props } = self; + let vcomp = Ident::new("__yew_vcomp", Span::call_site()); + let vcomp_props = Ident::new("__yew_vcomp_props", Span::call_site()); + let override_props = if let Some(props) = props { + match props { + Props::List(vec_props) => { + let check_props = vec_props.iter().map(|Prop{attr, ..}| { + quote_spanned!{ attr.span()=> let _ = #vcomp_props.#attr; } + }); + + let set_props = vec_props.iter().map(|Prop{attr, value}| { + quote_spanned!{ value.span()=> #vcomp_props.#attr = #value.into(); } + }); + + quote! { + #(#check_props#set_props)* + } + } + Props::With(WithProps(props)) => { + quote_spanned!{ props.span()=> #vcomp_props = #props; } + } + } + } else { + quote!{} + }; + + + // hack because span breaks with $crate inline + let alias_virtual_dom = quote!{ use $crate::virtual_dom as _virtual_dom; }; + let lazy_init = quote_spanned!{ ty.span()=> + #alias_virtual_dom + let (mut #vcomp_props, mut #vcomp) = _virtual_dom::VComp::lazy::<#ty>(); + }; + tokens.extend(quote! {{ - $crate::virtual_dom::VComp::lazy::<#ty>().1 + #lazy_init + #override_props + #vcomp.set_props(#vcomp_props); + #vcomp }}); } } impl HtmlComponent { + fn double_colon(cursor: Cursor) -> Option { + let mut cursor = cursor; + for _ in 0..2 { + let (punct, c) = cursor.punct()?; + (punct.as_char() == ':').as_option()?; + cursor = c; + } + + Some(cursor) + } + fn type_str(cursor: Cursor) -> Option<(String, Cursor)> { let mut cursor = cursor; let mut type_str: String = "".to_owned(); - let mut progress = true; - while progress { - progress = false; - match cursor.ident() { - Some((ident, c)) => { - type_str += &ident.to_string(); + let mut parse_ident_ok = true; + let mut parse_colons_ok = true; + + while parse_ident_ok { + if let Some((ident, c)) = cursor.ident() { + if parse_ident_ok { cursor = c; - progress = true; + type_str += &ident.to_string(); + parse_colons_ok = true; + } else { + break; } - None => {} } + parse_ident_ok = false; - match cursor.punct() { - Some((punct, c)) => match punct.as_char() { - ':' => { - type_str += ":"; - cursor = c; - progress = true; - } - '/' => {} - _ => return None, - }, - None => {} + if let Some(c) = Self::double_colon(cursor) { + if parse_colons_ok { + cursor = c; + type_str += "::"; + parse_ident_ok = true; + } else { + break; + } } + parse_colons_ok = false; } Some((type_str, cursor)) } } + +enum PropType { + List, + With, +} + +enum Props { + List(Vec), + With(WithProps), +} + +struct Prop { + attr: Ident, + value: Expr, +} + +struct WithProps(Ident); + +impl Peek for Props { + fn peek(cursor: Cursor) -> Option { + let (ident, _) = cursor.ident()?; + if ident.to_string() == "with" { + Some(PropType::With) + } else { + Some(PropType::List) + } + } +} + + +impl Peek<()> for Prop { + fn peek(cursor: Cursor) -> Option<()> { + let (_, cursor) = cursor.ident()?; + let (punct, _) = cursor.punct()?; + (punct.as_char() == '=').as_option() + } +} + +struct ExprBlock(syn::ExprBlock); + +impl Parse for ExprBlock { + fn parse(input: ParseStream) -> ParseResult { + Ok(ExprBlock(syn::ExprBlock{ + attrs: input.call(Attribute::parse_outer)?, + label: input.parse().ok(), + block: input.parse()?, + })) + } +} + +impl Parse for Prop { + fn parse(input: ParseStream) -> ParseResult { + let attr = input.parse::()?; + input.parse::()?; + + let err = input.error("expected literal or expression block"); + let value = if let Ok(lit) = input.parse::() { + Expr::Lit(ExprLit{ + attrs: Vec::new(), + lit, + }) + } else if let Ok(ExprBlock(block)) = input.parse::() { + Expr::Block(block) + } else { + return Err(err); + }; + let _ = input.parse::(); + Ok(Prop{attr, value}) + } +} + +impl Parse for WithProps { + fn parse(input: ParseStream) -> ParseResult { + let with = input.parse::()?; + if with.to_string() != "with" { + return Err(input.error("expected to find with token")); + } + + let props = input.parse::()?; + let _ = input.parse::(); + Ok(WithProps(props)) + } +} diff --git a/crates/macro/Cargo.toml b/crates/macro/Cargo.toml index 94e586892e8..68ea3f03c07 100644 --- a/crates/macro/Cargo.toml +++ b/crates/macro/Cargo.toml @@ -10,7 +10,6 @@ path = "tests/cases.rs" [dev-dependencies] trybuild = "1.0" -yew = { path = "../.." } [dependencies] proc-macro-hack = "0.5" diff --git a/crates/macro/src/helpers.rs b/crates/macro/src/helpers.rs index 6635a5aff9b..f41e0392650 100644 --- a/crates/macro/src/helpers.rs +++ b/crates/macro/src/helpers.rs @@ -12,7 +12,7 @@ macro_rules! test_html { }; ( @gen $tc:ident $view:block ) => { mod $tc { - use ::yew::prelude::*; + use ::yew_shared::prelude::*; use ::yew_macro::html; use super::*; diff --git a/crates/macro/tests/html-component-fail.rs b/crates/macro/tests/html-component-fail.rs index c75f3eb98a5..e57acc2f9a3 100644 --- a/crates/macro/tests/html-component-fail.rs +++ b/crates/macro/tests/html-component-fail.rs @@ -1,7 +1,92 @@ -use yew_macro::test_html; +#![recursion_limit = "128"] + +use yew_shared::prelude::*; +use yew_macro::{test_html, test_html_block}; + +#[derive(Clone, Default, PartialEq)] +pub struct ChildProperties { + pub string: String, + pub int: i32, +} + +pub struct ChildComponent; +impl Component for ChildComponent { + type Message = (); + type Properties = ChildProperties; + + fn create(props: Self::Properties, _: ComponentLink) -> Self { + ChildComponent + } + + fn update(&mut self, _: Self::Message) -> ShouldRender { + unimplemented!() + } +} + +impl Renderable for ChildComponent { + fn view(&self) -> Html { + unimplemented!() + } +} test_html! { |t1| + +} + +test_html! { |t2| + +} + +test_html! { |t3| + +} + +test_html! { |t4| + +} + +test_html! { |t5| + +} + +test_html! { |t6| + +} + +test_html_block! { |t7| + let name_expr = "child"; + + html! { + + } +} + +test_html! { |t10| } +test_html! { |t11| + +} + +test_html! { |t12| + +} + +test_html! { |t13| + +} + +test_html! { |t14| + +} + +test_html! { |t15| + +} + +test_html! { |t16| + +} + fn main() {} diff --git a/crates/macro/tests/html-component-fail.stderr b/crates/macro/tests/html-component-fail.stderr index 970a8a6ac5a..240ebeffd98 100644 --- a/crates/macro/tests/html-component-fail.stderr +++ b/crates/macro/tests/html-component-fail.stderr @@ -1,23 +1,124 @@ +error: expected literal or expression block + --> $DIR/html-component-fail.rs:60:32 + | +60 | + | ^^^^^^^^^ + +error: expected identifier + --> $DIR/html-component-fail.rs:53:21 + | +53 | + | ^^^^ + +error: expected literal or expression block + --> $DIR/html-component-fail.rs:49:29 + | +49 | + | ^ + +error: expected `/` + --> $DIR/html-component-fail.rs:45:31 + | +45 | + | ^ + +error: expected `/` + --> $DIR/html-component-fail.rs:41:21 + | +41 | + | ^^^^^ + +error: expected identifier + --> $DIR/html-component-fail.rs:37:26 + | +37 | + | ^ + +error: expected `/` + --> $DIR/html-component-fail.rs:33:20 + | +33 | + | ^ + +error[E0425]: cannot find value `blah` in this scope + --> $DIR/html-component-fail.rs:69:26 + | +69 | + | ^^^^ not found in this scope + error[E0277]: the trait bound `std::string::String: yew_shared::html::Component` is not satisfied - --> $DIR/html-component-fail.rs:3:1 - | -3 | / test_html! { |t1| -4 | | -5 | | } - | |_^ the trait `yew_shared::html::Component` is not implemented for `std::string::String` - | - = note: required by `>::lazy` - = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + --> $DIR/html-component-fail.rs:65:6 + | +65 | + | ^^^^^^ the trait `yew_shared::html::Component` is not implemented for `std::string::String` + | + = note: required by `>::lazy` error[E0277]: the trait bound `std::string::String: yew_shared::html::Renderable` is not satisfied - --> $DIR/html-component-fail.rs:3:1 - | -3 | / test_html! { |t1| -4 | | -5 | | } - | |_^ the trait `yew_shared::html::Renderable` is not implemented for `std::string::String` - | - = note: required by `>::lazy` - = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) - -For more information about this error, try `rustc --explain E0277`. + --> $DIR/html-component-fail.rs:65:6 + | +65 | + | ^^^^^^ the trait `yew_shared::html::Renderable` is not implemented for `std::string::String` + | + = note: required by `>::lazy` + +error[E0609]: no field `unknown` on type `ChildProperties` + --> $DIR/html-component-fail.rs:73:21 + | +73 | + | ^^^^^^^ unknown field + | + = note: available fields are: `string`, `int` + +error[E0277]: the trait bound `std::string::String: std::convert::From<()>` is not satisfied + --> $DIR/html-component-fail.rs:77:28 + | +77 | + | ^^ the trait `std::convert::From<()>` is not implemented for `std::string::String` + | + = help: the following implementations were found: + > + >> + >> + = note: required because of the requirements on the impl of `std::convert::Into` for `()` + +error[E0277]: the trait bound `std::string::String: std::convert::From<{integer}>` is not satisfied + --> $DIR/html-component-fail.rs:81:28 + | +81 | + | ^ the trait `std::convert::From<{integer}>` is not implemented for `std::string::String` + | + = help: the following implementations were found: + > + >> + >> + = note: required because of the requirements on the impl of `std::convert::Into` for `{integer}` + +error[E0277]: the trait bound `std::string::String: std::convert::From<{integer}>` is not satisfied + --> $DIR/html-component-fail.rs:85:28 + | +85 | + | ^^^ the trait `std::convert::From<{integer}>` is not implemented for `std::string::String` + | + = help: the following implementations were found: + > + >> + >> + = note: required because of the requirements on the impl of `std::convert::Into` for `{integer}` + +error[E0277]: the trait bound `i32: std::convert::From` is not satisfied + --> $DIR/html-component-fail.rs:89:25 + | +89 | + | ^^^^ the trait `std::convert::From` is not implemented for `i32` + | + = help: the following implementations were found: + > + > + > + > + and 2 others + = note: required because of the requirements on the impl of `std::convert::Into` for `u32` + +Some errors occurred: E0277, E0425, E0609. +For more information about an error, try `rustc --explain E0277`. diff --git a/crates/macro/tests/html-component-pass.rs b/crates/macro/tests/html-component-pass.rs index 304bb8a5559..fb703835448 100644 --- a/crates/macro/tests/html-component-pass.rs +++ b/crates/macro/tests/html-component-pass.rs @@ -1,13 +1,25 @@ -use yew::prelude::*; -use yew_macro::{html, test_html}; +#![recursion_limit = "128"] + +use yew_shared::prelude::*; +use yew_macro::{html, test_html, test_html_block}; + +#[derive(Clone, Default, PartialEq)] +pub struct ChildProperties { + pub string: String, + pub int: i32, + pub vec: Vec, +} + +pub struct ChildComponent { + props: ChildProperties, +} -pub struct ChildComponent {} impl Component for ChildComponent { type Message = (); - type Properties = (); + type Properties = ChildProperties; - fn create(_: Self::Properties, _: ComponentLink) -> Self { - ChildComponent {} + fn create(props: Self::Properties, _: ComponentLink) -> Self { + ChildComponent { props } } fn update(&mut self, _: Self::Message) -> ShouldRender { @@ -17,8 +29,9 @@ impl Component for ChildComponent { impl Renderable for ChildComponent { fn view(&self) -> Html { + let ChildProperties { string, .. } = &self.props; html! { - { "child" } + { string } } } } @@ -31,8 +44,8 @@ test_html! { |t1| } +// backwards compat test_html! { |t2| - // backwards compat } @@ -49,4 +62,31 @@ test_html! { |t3| } +test_html_block! { |t4| + let props = ::Properties::default(); + let props2 = ::Properties::default(); + + html! { + <> + + + // backwards compat + + + } +} + +test_html! { |t5| + <> + + + + + + + // backwards compat + + +} + fn main() {} diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index b7eb2867dcf..37aa332e24f 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -10,8 +10,98 @@ extern crate slab; extern crate stdweb; pub mod html; -pub mod agent; pub mod app; pub mod callback; +pub mod agent; pub mod scheduler; pub mod virtual_dom; + +/// The module that contains all events available in the framework. +pub mod events { + pub use html::{ + ChangeData, + InputData, + }; + + pub use stdweb::web::event::{ + BlurEvent, + ClickEvent, + ContextMenuEvent, + DoubleClickEvent, + DragDropEvent, + DragEndEvent, + DragEnterEvent, + DragEvent, + DragExitEvent, + DragLeaveEvent, + DragOverEvent, + DragStartEvent, + FocusEvent, + GotPointerCaptureEvent, + IKeyboardEvent, + IMouseEvent, + IPointerEvent, + KeyDownEvent, + KeyPressEvent, + KeyUpEvent, + LostPointerCaptureEvent, + MouseDownEvent, + MouseMoveEvent, + MouseOutEvent, + MouseEnterEvent, + MouseLeaveEvent, + MouseOverEvent, + MouseUpEvent, + MouseWheelEvent, + PointerCancelEvent, + PointerDownEvent, + PointerEnterEvent, + PointerLeaveEvent, + PointerMoveEvent, + PointerOutEvent, + PointerOverEvent, + PointerUpEvent, + ScrollEvent, + SubmitEvent + }; +} + +pub mod prelude { + pub use html::{ + Component, + ComponentLink, + Href, + Html, + Renderable, + ShouldRender, + }; + + pub use app::App; + + pub use callback::Callback; + + pub use agent::{ + Bridge, + Bridged, + Threaded, + }; + + pub use events::*; + + /// Prelude module for creating worker. + pub mod worker { + pub use agent::{ + Agent, + AgentLink, + Bridge, + Bridged, + Context, + Global, + HandlerId, + Job, + Private, + Public, + Transferable, + }; + } +} diff --git a/src/lib.rs b/src/lib.rs index 18b33d50b2d..eb1962428be 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -113,56 +113,6 @@ where run_loop(); } -/// The module that contains all events available in the framework. -pub mod events { - pub use html::{ - ChangeData, - InputData, - }; - - pub use stdweb::web::event::{ - BlurEvent, - ClickEvent, - ContextMenuEvent, - DoubleClickEvent, - DragDropEvent, - DragEndEvent, - DragEnterEvent, - DragEvent, - DragExitEvent, - DragLeaveEvent, - DragOverEvent, - DragStartEvent, - FocusEvent, - GotPointerCaptureEvent, - IKeyboardEvent, - IMouseEvent, - IPointerEvent, - KeyDownEvent, - KeyPressEvent, - KeyUpEvent, - LostPointerCaptureEvent, - MouseDownEvent, - MouseMoveEvent, - MouseOutEvent, - MouseEnterEvent, - MouseLeaveEvent, - MouseOverEvent, - MouseUpEvent, - MouseWheelEvent, - PointerCancelEvent, - PointerDownEvent, - PointerEnterEvent, - PointerLeaveEvent, - PointerMoveEvent, - PointerOutEvent, - PointerOverEvent, - PointerUpEvent, - ScrollEvent, - SubmitEvent - }; -} - /// The Yew Prelude /// /// The purpose of this module is to alleviate imports of many common types: @@ -172,43 +122,7 @@ pub mod events { /// use yew::prelude::*; /// ``` pub mod prelude { - pub use html::{ - Component, - ComponentLink, - Href, - Html, - Renderable, - ShouldRender, - }; - - pub use app::App; - - pub use callback::Callback; - - pub use agent::{ - Bridge, - Bridged, - Threaded, - }; - - pub use events::*; - - /// Prelude module for creating worker. - pub mod worker { - pub use agent::{ - Agent, - AgentLink, - Bridge, - Bridged, - Context, - Global, - HandlerId, - Job, - Private, - Public, - Transferable, - }; - } + pub use yew_shared::prelude::*; #[cfg(feature = "proc_macro")] pub use yew_macro::html; From 26edd6ea6fb654408d02abec87a218fdad05d051 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Sun, 26 May 2019 20:44:20 -0400 Subject: [PATCH 25/46] Fix syn dependency version range --- crates/macro-impl/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/macro-impl/Cargo.toml b/crates/macro-impl/Cargo.toml index d533f040808..2696e3e958b 100644 --- a/crates/macro-impl/Cargo.toml +++ b/crates/macro-impl/Cargo.toml @@ -11,4 +11,4 @@ boolinator = "2.4.0" proc-macro-hack = "0.5" proc-macro2 = "0.4" quote = "0.6" -syn = { version = "0.15.34", features = ["full"] } +syn = { version = "^0.15.34", features = ["full"] } From 284a680ba2112eca00f328cc9efe7c4d18da1e71 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Mon, 27 May 2019 08:51:02 -0400 Subject: [PATCH 26/46] Implement tag attributes and overhaul prop parsing --- .../src/html_tree/html_component.rs | 186 +++++++++--------- crates/macro-impl/src/html_tree/html_prop.rs | 98 +++++++++ crates/macro-impl/src/html_tree/html_tag.rs | 155 ++++++++++++--- crates/macro-impl/src/html_tree/mod.rs | 8 +- crates/macro/tests/html-component-fail.rs | 10 +- crates/macro/tests/html-component-fail.stderr | 56 +++--- crates/macro/tests/html-component-pass.rs | 9 + crates/macro/tests/html-list-fail.stderr | 6 +- crates/macro/tests/html-node-fail.stderr | 6 +- crates/macro/tests/html-tag-fail.rs | 5 + crates/macro/tests/html-tag-fail.stderr | 36 +++- crates/macro/tests/html-tag-pass.rs | 16 +- crates/shared/src/lib.rs | 86 ++------ src/lib.rs | 23 ++- 14 files changed, 430 insertions(+), 270 deletions(-) create mode 100644 crates/macro-impl/src/html_tree/html_prop.rs diff --git a/crates/macro-impl/src/html_tree/html_component.rs b/crates/macro-impl/src/html_tree/html_component.rs index 0911a62ebe2..d7aa6d5dd6f 100644 --- a/crates/macro-impl/src/html_tree/html_component.rs +++ b/crates/macro-impl/src/html_tree/html_component.rs @@ -1,16 +1,16 @@ +use super::HtmlProp; +use super::HtmlPropSuffix; use crate::Peek; use boolinator::Boolinator; -use quote::{quote, quote_spanned, ToTokens}; use proc_macro2::Span; +use quote::{quote, quote_spanned, ToTokens}; use syn::buffer::Cursor; +use syn::parse; use syn::parse::{Parse, ParseStream, Result as ParseResult}; -use syn::{Attribute, Ident, Token, Type, Expr, Lit, ExprLit}; use syn::spanned::Spanned; +use syn::{Ident, Token, Type}; -pub struct HtmlComponent { - ty: Type, - props: Option, -} +pub struct HtmlComponent(HtmlComponentInner); impl Peek<()> for HtmlComponent { fn peek(cursor: Cursor) -> Option<()> { @@ -24,69 +24,56 @@ impl Peek<()> for HtmlComponent { impl Parse for HtmlComponent { fn parse(input: ParseStream) -> ParseResult { - input.parse::()?; - let ty = input.parse::()?; - - // backwards compatibility - let _ = input.parse::(); + let lt = input.parse::()?; + let HtmlPropSuffix { stream, div, gt } = input.parse()?; + if div.is_none() { + return Err(syn::Error::new_spanned( + HtmlComponentTag { lt, gt }, + "expected component tag be of form `< .. />`", + )); + } - let props = if let Some(prop_type) = Props::peek(input.cursor()) { - match prop_type { - PropType::List => { - let mut props: Vec = Vec::new(); - while Prop::peek(input.cursor()).is_some() { - props.push(input.parse::()?); - } - Some(Props::List(props)) - } - PropType::With => { - Some(Props::With(input.parse::()?)) + match parse(stream) { + Ok(comp) => Ok(HtmlComponent(comp)), + Err(err) => { + if err.to_string().starts_with("unexpected end of input") { + Err(syn::Error::new_spanned(div, err.to_string())) + } else { + Err(err) } } - } else { - None - }; - - let comp = HtmlComponent { ty, props}; - - input.parse::()?; - input.parse::]>()?; - Ok(comp) + } } } impl ToTokens for HtmlComponent { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let HtmlComponent { ty, props } = self; + let HtmlComponentInner { ty, props } = &self.0; let vcomp = Ident::new("__yew_vcomp", Span::call_site()); let vcomp_props = Ident::new("__yew_vcomp_props", Span::call_site()); - let override_props = if let Some(props) = props { - match props { - Props::List(vec_props) => { - let check_props = vec_props.iter().map(|Prop{attr, ..}| { - quote_spanned!{ attr.span()=> let _ = #vcomp_props.#attr; } - }); - - let set_props = vec_props.iter().map(|Prop{attr, value}| { - quote_spanned!{ value.span()=> #vcomp_props.#attr = #value.into(); } - }); - - quote! { - #(#check_props#set_props)* - } - } - Props::With(WithProps(props)) => { - quote_spanned!{ props.span()=> #vcomp_props = #props; } + let override_props = match props { + Props::List(vec_props) => { + let check_props = vec_props.0.iter().map(|HtmlProp { name, .. }| { + quote_spanned! { name.span()=> let _ = #vcomp_props.#name; } + }); + + let set_props = vec_props.0.iter().map(|HtmlProp { name, value }| { + quote_spanned! { value.span()=> #vcomp_props.#name = #value.into(); } + }); + + quote! { + #(#check_props#set_props)* } } - } else { - quote!{} + Props::With(WithProps(props)) => { + quote_spanned! { props.span()=> #vcomp_props = #props; } + } + Props::None => quote! {}, }; - // hack because span breaks with $crate inline - let alias_virtual_dom = quote!{ use $crate::virtual_dom as _virtual_dom; }; - let lazy_init = quote_spanned!{ ty.span()=> + let alias_virtual_dom = quote! { use $crate::virtual_dom as _virtual_dom; }; + let lazy_init = quote_spanned! { ty.span()=> #alias_virtual_dom let (mut #vcomp_props, mut #vcomp) = _virtual_dom::VComp::lazy::<#ty>(); }; @@ -146,23 +133,44 @@ impl HtmlComponent { } } +pub struct HtmlComponentInner { + ty: Type, + props: Props, +} + +impl Parse for HtmlComponentInner { + fn parse(input: ParseStream) -> ParseResult { + let ty = input.parse()?; + // backwards compatibility + let _ = input.parse::(); + let props = input.parse()?; + Ok(HtmlComponentInner { ty, props }) + } +} + +struct HtmlComponentTag { + lt: Token![<], + gt: Token![>], +} + +impl ToTokens for HtmlComponentTag { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let HtmlComponentTag { lt, gt, .. } = self; + tokens.extend(quote! {#lt#gt}); + } +} + enum PropType { List, With, } enum Props { - List(Vec), + List(ListProps), With(WithProps), + None, } -struct Prop { - attr: Ident, - value: Expr, -} - -struct WithProps(Ident); - impl Peek for Props { fn peek(cursor: Cursor) -> Option { let (ident, _) = cursor.ident()?; @@ -174,55 +182,39 @@ impl Peek for Props { } } - -impl Peek<()> for Prop { - fn peek(cursor: Cursor) -> Option<()> { - let (_, cursor) = cursor.ident()?; - let (punct, _) = cursor.punct()?; - (punct.as_char() == '=').as_option() - } -} - -struct ExprBlock(syn::ExprBlock); - -impl Parse for ExprBlock { +impl Parse for Props { fn parse(input: ParseStream) -> ParseResult { - Ok(ExprBlock(syn::ExprBlock{ - attrs: input.call(Attribute::parse_outer)?, - label: input.parse().ok(), - block: input.parse()?, - })) + let props = if let Some(prop_type) = Props::peek(input.cursor()) { + match prop_type { + PropType::List => input.parse().map(Props::List)?, + PropType::With => input.parse().map(Props::With)?, + } + } else { + Props::None + }; + + Ok(props) } } -impl Parse for Prop { +struct ListProps(Vec); +impl Parse for ListProps { fn parse(input: ParseStream) -> ParseResult { - let attr = input.parse::()?; - input.parse::()?; - - let err = input.error("expected literal or expression block"); - let value = if let Ok(lit) = input.parse::() { - Expr::Lit(ExprLit{ - attrs: Vec::new(), - lit, - }) - } else if let Ok(ExprBlock(block)) = input.parse::() { - Expr::Block(block) - } else { - return Err(err); - }; - let _ = input.parse::(); - Ok(Prop{attr, value}) + let mut props: Vec = Vec::new(); + while HtmlProp::peek(input.cursor()).is_some() { + props.push(input.parse::()?); + } + Ok(ListProps(props)) } } +struct WithProps(Ident); impl Parse for WithProps { fn parse(input: ParseStream) -> ParseResult { let with = input.parse::()?; if with.to_string() != "with" { return Err(input.error("expected to find with token")); } - let props = input.parse::()?; let _ = input.parse::(); Ok(WithProps(props)) diff --git a/crates/macro-impl/src/html_tree/html_prop.rs b/crates/macro-impl/src/html_tree/html_prop.rs new file mode 100644 index 00000000000..ea11deb1d71 --- /dev/null +++ b/crates/macro-impl/src/html_tree/html_prop.rs @@ -0,0 +1,98 @@ +use crate::Peek; +use boolinator::Boolinator; +use proc_macro::TokenStream; +use proc_macro2::TokenTree; +use syn::buffer::Cursor; +use syn::parse::{Parse, ParseStream, Result as ParseResult}; +use syn::{Attribute, Expr, Ident, Token}; + +pub struct HtmlProp { + pub name: Ident, + pub value: Expr, +} + +impl Peek<()> for HtmlProp { + fn peek(cursor: Cursor) -> Option<()> { + let (_, cursor) = cursor.ident()?; + let (punct, _) = cursor.punct()?; + (punct.as_char() == '=').as_option() + } +} + +struct ExprBlock(syn::ExprBlock); + +impl Parse for ExprBlock { + fn parse(input: ParseStream) -> ParseResult { + Ok(ExprBlock(syn::ExprBlock { + attrs: input.call(Attribute::parse_outer)?, + label: input.parse().ok(), + block: input.parse()?, + })) + } +} + +impl Parse for HtmlProp { + fn parse(input: ParseStream) -> ParseResult { + let name = if let Ok(ty) = input.parse::() { + Ident::new("type", ty.span) + } else { + input.parse::()? + }; + + input.parse::()?; + let value = input.parse::()?; + let _ = input.parse::(); + Ok(HtmlProp { name, value }) + } +} + +pub struct HtmlPropSuffix { + pub div: Option, + pub gt: Token![>], + pub stream: TokenStream, +} + +impl Parse for HtmlPropSuffix { + fn parse(input: ParseStream) -> ParseResult { + let mut trees: Vec = vec![]; + let mut div: Option = None; + let mut gt: Option]> = None; + let mut angle_count = 1; + + loop { + let next = input.parse()?; + match &next { + TokenTree::Punct(punct) => match punct.as_char() { + '>' => { + angle_count -= 1; + if angle_count == 0 { + gt = Some(syn::token::Gt { + spans: [punct.span()], + }); + break; + } + } + '<' => angle_count += 1, + '/' => { + if angle_count == 1 && input.peek(Token![>]) { + div = Some(syn::token::Div { + spans: [punct.span()], + }); + gt = Some(input.parse()?); + break; + } + } + _ => {} + }, + _ => {} + } + trees.push(next); + } + + let gt: Token![>] = gt.ok_or(input.error("missing tag close"))?; + let stream: proc_macro2::TokenStream = trees.into_iter().collect(); + let stream = TokenStream::from(stream); + + Ok(HtmlPropSuffix { div, gt, stream }) + } +} diff --git a/crates/macro-impl/src/html_tree/html_tag.rs b/crates/macro-impl/src/html_tree/html_tag.rs index f12e1bafc09..829a9b73e0d 100644 --- a/crates/macro-impl/src/html_tree/html_tag.rs +++ b/crates/macro-impl/src/html_tree/html_tag.rs @@ -1,11 +1,14 @@ +use super::HtmlProp as TagAttribute; +use super::HtmlPropSuffix as TagSuffix; use super::HtmlTree; use crate::Peek; use boolinator::Boolinator; +use proc_macro2::Span; use quote::{quote, ToTokens}; use syn::buffer::Cursor; +use syn::parse; use syn::parse::{Parse, ParseStream, Result as ParseResult}; -use syn::token; -use syn::Ident; +use syn::{Expr, ExprTuple, Ident, Token}; pub struct HtmlTag { open: HtmlTagOpen, @@ -90,8 +93,17 @@ impl ToTokens for HtmlTag { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let HtmlTag { open, children } = self; let tag_name = open.ident.to_string(); + let TagAttributes { + classes, + attributes, + .. + } = &open.attributes; + let attr_names = attributes.iter().map(|attr| attr.name.to_string()); + let attr_values = attributes.iter().map(|attr| &attr.value); tokens.extend(quote! {{ let mut __yew_vtag = $crate::virtual_dom::vtag::VTag::new(#tag_name); + #(__yew_vtag.add_class(&(#classes));)* + #(__yew_vtag.add_attribute(#attr_names, &(#attr_values));)* #(__yew_vtag.add_child(#children);)* __yew_vtag }}); @@ -99,10 +111,11 @@ impl ToTokens for HtmlTag { } struct HtmlTagOpen { - lt: token::Lt, + lt: Token![<], ident: Ident, - div: Option, - gt: token::Gt, + attributes: TagAttributes, + div: Option, + gt: Token![>], } impl Peek for HtmlTagOpen { @@ -110,48 +123,138 @@ impl Peek for HtmlTagOpen { let (punct, cursor) = cursor.punct()?; (punct.as_char() == '<').as_option()?; - let (ident, cursor) = cursor.ident()?; + let (ident, _) = cursor.ident()?; (ident.to_string().to_lowercase() == ident.to_string()).as_option()?; - let (mut punct, cursor) = cursor.punct()?; - if punct.as_char() == '/' { - let extra_punct = cursor.punct()?; - punct = extra_punct.0; - } - - (punct.as_char() == '>').as_option()?; - Some(ident) } } impl Parse for HtmlTagOpen { fn parse(input: ParseStream) -> ParseResult { + let lt = input.parse::()?; + let ident = input.parse::()?; + let TagSuffix { stream, div, gt } = input.parse()?; + let mut attributes: TagAttributes = parse(stream)?; + + // Don't treat value as special for non input / textarea fields + match ident.to_string().as_str() { + "input" | "textarea" => {} + _ => { + if let Some(value) = attributes.value.take() { + attributes.attributes.push(TagAttribute { + name: Ident::new("value", Span::call_site()), + value, + }); + } + } + } + Ok(HtmlTagOpen { - lt: input.parse()?, - ident: input.parse()?, - div: input.parse().ok(), - gt: input.parse()?, + lt, + ident, + attributes, + div, + gt, }) } } impl ToTokens for HtmlTagOpen { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let HtmlTagOpen { lt, ident, div, gt } = self; - let open_tag = match div { - Some(div) => quote! {#lt#ident#div#gt}, - None => quote! {#lt#ident#gt}, + let HtmlTagOpen { lt, gt, .. } = self; + tokens.extend(quote! {#lt#gt}); + } +} + +struct TagAttributes { + attributes: Vec, + classes: Vec, + value: Option, + kind: Option, + checked: Option, +} + +impl TagAttributes { + fn drain_attr(attrs: &mut Vec, name: &str) -> Vec { + let mut i = 0; + let mut drained = Vec::new(); + while i < attrs.len() { + if attrs[i].name.to_string() == name { + drained.push(attrs.remove(i)); + } else { + i += 1; + } + } + drained + } + + fn remove_attr(attrs: &mut Vec, name: &str) -> ParseResult> { + let drained = TagAttributes::drain_attr(attrs, name); + let attr_expr = if drained.len() == 1 { + Some(drained[0].value.clone()) + } else if drained.len() > 1 { + return Err(syn::Error::new_spanned( + &drained[1].name, + format!("only one {} allowed", name), + )); + } else { + None }; - tokens.extend(open_tag); + + Ok(attr_expr) + } +} + +impl Parse for TagAttributes { + fn parse(input: ParseStream) -> ParseResult { + let mut attributes: Vec = Vec::new(); + while TagAttribute::peek(input.cursor()).is_some() { + attributes.push(input.parse::()?); + } + + let mut classes: Vec = Vec::new(); + TagAttributes::drain_attr(&mut attributes, "class") + .into_iter() + .for_each(|TagAttribute { value, .. }| match value { + Expr::Tuple(ExprTuple { elems, .. }) => { + elems.into_iter().for_each(|expr| classes.push(expr)) + } + expr @ _ => classes.push(expr), + }); + + let value = TagAttributes::remove_attr(&mut attributes, "value")?; + let kind = TagAttributes::remove_attr(&mut attributes, "type")?; + let checked = TagAttributes::remove_attr(&mut attributes, "checked")?; + + attributes.sort_by(|a, b| a.name.to_string().partial_cmp(&b.name.to_string()).unwrap()); + let mut i = 0; + while i + 1 < attributes.len() { + if attributes[i].name.to_string() == attributes[i + 1].name.to_string() { + let name = &attributes[i + 1].name; + return Err(syn::Error::new_spanned( + name, + format!("only one {} allowed", name), + )); + } + i += 1; + } + + Ok(TagAttributes { + attributes, + classes, + value, + kind, + checked, + }) } } struct HtmlTagClose { - lt: token::Lt, - div: Option, + lt: Token![<], + div: Option, ident: Ident, - gt: token::Gt, + gt: Token![>], } impl Peek for HtmlTagClose { diff --git a/crates/macro-impl/src/html_tree/mod.rs b/crates/macro-impl/src/html_tree/mod.rs index 3dcc0a2293d..79a8cf16fb8 100644 --- a/crates/macro-impl/src/html_tree/mod.rs +++ b/crates/macro-impl/src/html_tree/mod.rs @@ -3,6 +3,7 @@ pub mod html_component; pub mod html_iterable; pub mod html_list; pub mod html_node; +pub mod html_prop; pub mod html_tag; use crate::Peek; @@ -11,6 +12,8 @@ use html_component::HtmlComponent; use html_iterable::HtmlIterable; use html_list::HtmlList; use html_node::HtmlNode; +use html_prop::HtmlProp; +use html_prop::HtmlPropSuffix; use html_tag::HtmlTag; use proc_macro2::Span; use quote::{quote, ToTokens}; @@ -52,10 +55,7 @@ impl Parse for HtmlRoot { }; if !input.is_empty() { - Err(syn::Error::new( - Span::call_site(), - "only one root html element allowed", - )) + Err(input.error("only one root html element allowed")) } else { Ok(html_root) } diff --git a/crates/macro/tests/html-component-fail.rs b/crates/macro/tests/html-component-fail.rs index e57acc2f9a3..6ca3221a715 100644 --- a/crates/macro/tests/html-component-fail.rs +++ b/crates/macro/tests/html-component-fail.rs @@ -1,7 +1,7 @@ #![recursion_limit = "128"] use yew_shared::prelude::*; -use yew_macro::{test_html, test_html_block}; +use yew_macro::test_html; #[derive(Clone, Default, PartialEq)] pub struct ChildProperties { @@ -53,14 +53,6 @@ test_html! { |t6| } -test_html_block! { |t7| - let name_expr = "child"; - - html! { - - } -} - test_html! { |t10| } diff --git a/crates/macro/tests/html-component-fail.stderr b/crates/macro/tests/html-component-fail.stderr index 240ebeffd98..857ecd457be 100644 --- a/crates/macro/tests/html-component-fail.stderr +++ b/crates/macro/tests/html-component-fail.stderr @@ -1,79 +1,73 @@ -error: expected literal or expression block - --> $DIR/html-component-fail.rs:60:32 - | -60 | - | ^^^^^^^^^ - error: expected identifier --> $DIR/html-component-fail.rs:53:21 | 53 | | ^^^^ -error: expected literal or expression block +error: unexpected end of input, expected expression --> $DIR/html-component-fail.rs:49:29 | 49 | | ^ -error: expected `/` - --> $DIR/html-component-fail.rs:45:31 +error: expected component tag be of form `< .. />` + --> $DIR/html-component-fail.rs:45:5 | 45 | - | ^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: expected `/` +error: unexpected token --> $DIR/html-component-fail.rs:41:21 | 41 | | ^^^^^ -error: expected identifier +error: unexpected end of input, expected identifier --> $DIR/html-component-fail.rs:37:26 | 37 | | ^ -error: expected `/` - --> $DIR/html-component-fail.rs:33:20 +error: expected component tag be of form `< .. />` + --> $DIR/html-component-fail.rs:33:5 | 33 | - | ^ + | ^^^^^^^^^^^^^^^^ error[E0425]: cannot find value `blah` in this scope - --> $DIR/html-component-fail.rs:69:26 + --> $DIR/html-component-fail.rs:61:26 | -69 | +61 | | ^^^^ not found in this scope error[E0277]: the trait bound `std::string::String: yew_shared::html::Component` is not satisfied - --> $DIR/html-component-fail.rs:65:6 + --> $DIR/html-component-fail.rs:57:6 | -65 | +57 | | ^^^^^^ the trait `yew_shared::html::Component` is not implemented for `std::string::String` | = note: required by `>::lazy` error[E0277]: the trait bound `std::string::String: yew_shared::html::Renderable` is not satisfied - --> $DIR/html-component-fail.rs:65:6 + --> $DIR/html-component-fail.rs:57:6 | -65 | +57 | | ^^^^^^ the trait `yew_shared::html::Renderable` is not implemented for `std::string::String` | = note: required by `>::lazy` error[E0609]: no field `unknown` on type `ChildProperties` - --> $DIR/html-component-fail.rs:73:21 + --> $DIR/html-component-fail.rs:65:21 | -73 | +65 | | ^^^^^^^ unknown field | = note: available fields are: `string`, `int` error[E0277]: the trait bound `std::string::String: std::convert::From<()>` is not satisfied - --> $DIR/html-component-fail.rs:77:28 + --> $DIR/html-component-fail.rs:69:28 | -77 | +69 | | ^^ the trait `std::convert::From<()>` is not implemented for `std::string::String` | = help: the following implementations were found: @@ -83,9 +77,9 @@ error[E0277]: the trait bound `std::string::String: std::convert::From<()>` is n = note: required because of the requirements on the impl of `std::convert::Into` for `()` error[E0277]: the trait bound `std::string::String: std::convert::From<{integer}>` is not satisfied - --> $DIR/html-component-fail.rs:81:28 + --> $DIR/html-component-fail.rs:73:28 | -81 | +73 | | ^ the trait `std::convert::From<{integer}>` is not implemented for `std::string::String` | = help: the following implementations were found: @@ -95,9 +89,9 @@ error[E0277]: the trait bound `std::string::String: std::convert::From<{integer} = note: required because of the requirements on the impl of `std::convert::Into` for `{integer}` error[E0277]: the trait bound `std::string::String: std::convert::From<{integer}>` is not satisfied - --> $DIR/html-component-fail.rs:85:28 + --> $DIR/html-component-fail.rs:77:28 | -85 | +77 | | ^^^ the trait `std::convert::From<{integer}>` is not implemented for `std::string::String` | = help: the following implementations were found: @@ -107,9 +101,9 @@ error[E0277]: the trait bound `std::string::String: std::convert::From<{integer} = note: required because of the requirements on the impl of `std::convert::Into` for `{integer}` error[E0277]: the trait bound `i32: std::convert::From` is not satisfied - --> $DIR/html-component-fail.rs:89:25 + --> $DIR/html-component-fail.rs:81:25 | -89 | +81 | | ^^^^ the trait `std::convert::From` is not implemented for `i32` | = help: the following implementations were found: diff --git a/crates/macro/tests/html-component-pass.rs b/crates/macro/tests/html-component-pass.rs index fb703835448..bd5c5dd62c9 100644 --- a/crates/macro/tests/html-component-pass.rs +++ b/crates/macro/tests/html-component-pass.rs @@ -89,4 +89,13 @@ test_html! { |t5| } +test_html_block! { |t6| + let name_expr = "child"; + + html! { + + } +} + + fn main() {} diff --git a/crates/macro/tests/html-list-fail.stderr b/crates/macro/tests/html-list-fail.stderr index 45a5e64ce29..a772c2924f9 100644 --- a/crates/macro/tests/html-list-fail.stderr +++ b/crates/macro/tests/html-list-fail.stderr @@ -1,10 +1,8 @@ error: only one root html element allowed - --> $DIR/html-list-fail.rs:8:1 + --> $DIR/html-list-fail.rs:8:24 | 8 | test_html! { |t6| <><> } - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + | ^ error: this open tag has no corresponding close tag --> $DIR/html-list-fail.rs:7:19 diff --git a/crates/macro/tests/html-node-fail.stderr b/crates/macro/tests/html-node-fail.stderr index a37cad88c9f..8dd7b408831 100644 --- a/crates/macro/tests/html-node-fail.stderr +++ b/crates/macro/tests/html-node-fail.stderr @@ -47,9 +47,7 @@ error: unexpected token | ^^^^^^^^^ error: only one root html element allowed - --> $DIR/html-node-fail.rs:3:1 + --> $DIR/html-node-fail.rs:3:27 | 3 | test_html! { |t1| "valid" "invalid" } - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + | ^^^^^^^^^ diff --git a/crates/macro/tests/html-tag-fail.rs b/crates/macro/tests/html-tag-fail.rs index 32648e3dddf..cdace8f0075 100644 --- a/crates/macro/tests/html-tag-fail.rs +++ b/crates/macro/tests/html-tag-fail.rs @@ -10,4 +10,9 @@ test_html! { |t7|
    } test_html! { |t8| } test_html! { |t9|
    Invalid
    } +test_html! { |t20| } +test_html! { |t21| } +test_html! { |t22| } +test_html! { |t23| } + fn main() {} diff --git a/crates/macro/tests/html-tag-fail.stderr b/crates/macro/tests/html-tag-fail.stderr index 64dfdb31748..fb09c2d3ec3 100644 --- a/crates/macro/tests/html-tag-fail.stderr +++ b/crates/macro/tests/html-tag-fail.stderr @@ -1,3 +1,27 @@ +error: only one checked allowed + --> $DIR/html-tag-fail.rs:16:40 + | +16 | test_html! { |t23| } + | ^^^^^^^ + +error: only one kind allowed + --> $DIR/html-tag-fail.rs:15:43 + | +15 | test_html! { |t22| } + | ^^^^ + +error: only one value allowed + --> $DIR/html-tag-fail.rs:14:39 + | +14 | test_html! { |t21| } + | ^^^^^ + +error: only one attr allowed + --> $DIR/html-tag-fail.rs:13:34 + | +13 | test_html! { |t20| } + | ^^^^ + error: expected valid html element --> $DIR/html-tag-fail.rs:11:24 | @@ -5,12 +29,10 @@ error: expected valid html element | ^^^^^^^ error: only one root html element allowed - --> $DIR/html-tag-fail.rs:10:1 + --> $DIR/html-tag-fail.rs:10:26 | 10 | test_html! { |t8| } - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + | ^ error: this close tag has no corresponding open tag --> $DIR/html-tag-fail.rs:9:24 @@ -25,12 +47,10 @@ error: this open tag has no corresponding close tag | ^^^^^ error: only one root html element allowed - --> $DIR/html-tag-fail.rs:7:1 + --> $DIR/html-tag-fail.rs:7:30 | 7 | test_html! { |t5|
    } - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + | ^ error: this open tag has no corresponding close tag --> $DIR/html-tag-fail.rs:6:19 diff --git a/crates/macro/tests/html-tag-pass.rs b/crates/macro/tests/html-tag-pass.rs index fe0a864bae2..50fb768a81b 100644 --- a/crates/macro/tests/html-tag-pass.rs +++ b/crates/macro/tests/html-tag-pass.rs @@ -1,18 +1,16 @@ +#![recursion_limit = "128"] + use yew_macro::test_html; test_html! { |t1| -
    -} - -test_html! { |t2|
    -
    +
    + +
    + +
    } -test_html! { |t3| - -} - fn main() {} diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index 37aa332e24f..392a53c464e 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -3,105 +3,53 @@ extern crate log; extern crate serde; #[macro_use] extern crate serde_derive; -extern crate bincode; extern crate anymap; +extern crate bincode; extern crate slab; #[macro_use] extern crate stdweb; -pub mod html; +pub mod agent; pub mod app; pub mod callback; -pub mod agent; +pub mod html; pub mod scheduler; pub mod virtual_dom; /// The module that contains all events available in the framework. pub mod events { - pub use html::{ - ChangeData, - InputData, - }; + pub use html::{ChangeData, InputData}; pub use stdweb::web::event::{ - BlurEvent, - ClickEvent, - ContextMenuEvent, - DoubleClickEvent, - DragDropEvent, - DragEndEvent, - DragEnterEvent, - DragEvent, - DragExitEvent, - DragLeaveEvent, - DragOverEvent, - DragStartEvent, - FocusEvent, - GotPointerCaptureEvent, - IKeyboardEvent, - IMouseEvent, - IPointerEvent, - KeyDownEvent, - KeyPressEvent, - KeyUpEvent, - LostPointerCaptureEvent, - MouseDownEvent, - MouseMoveEvent, - MouseOutEvent, - MouseEnterEvent, - MouseLeaveEvent, - MouseOverEvent, - MouseUpEvent, - MouseWheelEvent, - PointerCancelEvent, - PointerDownEvent, - PointerEnterEvent, - PointerLeaveEvent, - PointerMoveEvent, - PointerOutEvent, - PointerOverEvent, - PointerUpEvent, - ScrollEvent, - SubmitEvent + BlurEvent, ClickEvent, ContextMenuEvent, DoubleClickEvent, DragDropEvent, DragEndEvent, + DragEnterEvent, DragEvent, DragExitEvent, DragLeaveEvent, DragOverEvent, DragStartEvent, + FocusEvent, GotPointerCaptureEvent, IKeyboardEvent, IMouseEvent, IPointerEvent, + KeyDownEvent, KeyPressEvent, KeyUpEvent, LostPointerCaptureEvent, MouseDownEvent, + MouseEnterEvent, MouseLeaveEvent, MouseMoveEvent, MouseOutEvent, MouseOverEvent, + MouseUpEvent, MouseWheelEvent, PointerCancelEvent, PointerDownEvent, PointerEnterEvent, + PointerLeaveEvent, PointerMoveEvent, PointerOutEvent, PointerOverEvent, PointerUpEvent, + ScrollEvent, SubmitEvent, }; } pub mod prelude { - pub use html::{ - Component, - ComponentLink, - Href, - Html, - Renderable, - ShouldRender, - }; + pub use html::{Component, ComponentLink, Href, Html, Renderable, ShouldRender}; pub use app::App; pub use callback::Callback; - pub use agent::{ - Bridge, - Bridged, - Threaded, - }; + pub use agent::{Bridge, Bridged, Threaded}; pub use events::*; /// Prelude module for creating worker. pub mod worker { pub use agent::{ - Agent, - AgentLink, - Bridge, - Bridged, - Context, - Global, - HandlerId, - Job, - Private, - Public, + Agent, AgentLink, Bridge, Bridged, Context, Global, HandlerId, Job, Private, Public, Transferable, }; } } + +pub use self::prelude::*; diff --git a/src/lib.rs b/src/lib.rs index eb1962428be..fc6895bbfbe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,40 +56,45 @@ //! ``` //! -#![deny(missing_docs, bare_trait_objects, anonymous_parameters, elided_lifetimes_in_paths)] +#![deny( + missing_docs, + bare_trait_objects, + anonymous_parameters, + elided_lifetimes_in_paths +)] #![recursion_limit = "512"] #[macro_use] extern crate failure; -extern crate log; extern crate http; +extern crate log; extern crate serde; #[macro_use] extern crate serde_derive; -extern crate serde_json; -extern crate bincode; extern crate anymap; +extern crate bincode; +extern crate serde_json; extern crate slab; extern crate yew_shared; #[macro_use] extern crate stdweb; -#[cfg(feature = "toml")] -extern crate toml; -#[cfg(feature = "yaml")] -extern crate serde_yaml; #[cfg(feature = "msgpack")] extern crate rmp_serde; #[cfg(feature = "cbor")] extern crate serde_cbor; +#[cfg(feature = "yaml")] +extern crate serde_yaml; +#[cfg(feature = "toml")] +extern crate toml; #[cfg(feature = "proc_macro")] extern crate yew_macro; #[macro_use] #[cfg(not(feature = "proc_macro"))] pub mod macros; +pub mod components; pub mod format; pub mod services; -pub mod components; pub use yew_shared::*; From 9a8b0ad06e10c01ec0f4b2b0296714e9cc8b3aed Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Mon, 27 May 2019 09:01:13 -0400 Subject: [PATCH 27/46] Disallow type prop for components --- crates/macro-impl/src/html_tree/html_component.rs | 7 +++++++ crates/macro/tests/html-tag-pass.rs | 1 + 2 files changed, 8 insertions(+) diff --git a/crates/macro-impl/src/html_tree/html_component.rs b/crates/macro-impl/src/html_tree/html_component.rs index d7aa6d5dd6f..92175b56e77 100644 --- a/crates/macro-impl/src/html_tree/html_component.rs +++ b/crates/macro-impl/src/html_tree/html_component.rs @@ -204,6 +204,13 @@ impl Parse for ListProps { while HtmlProp::peek(input.cursor()).is_some() { props.push(input.parse::()?); } + + for prop in &props { + if prop.name.to_string() == "type" { + return Err(syn::Error::new_spanned(&prop.name, "expected identifier")); + } + } + Ok(ListProps(props)) } } diff --git a/crates/macro/tests/html-tag-pass.rs b/crates/macro/tests/html-tag-pass.rs index 50fb768a81b..307a1a320b5 100644 --- a/crates/macro/tests/html-tag-pass.rs +++ b/crates/macro/tests/html-tag-pass.rs @@ -7,6 +7,7 @@ test_html! { |t1|
    +
    From 44775c1abdc3f51c4de64ae4492e01ea1b5b0c0d Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Mon, 27 May 2019 09:34:08 -0400 Subject: [PATCH 28/46] Support special tag attributes --- crates/macro-impl/src/html_tree/html_tag.rs | 41 ++++++++++++- crates/macro/tests/html-tag-fail.rs | 8 +++ crates/macro/tests/html-tag-fail.stderr | 64 +++++++++++++++++++++ crates/macro/tests/html-tag-pass.rs | 10 +++- 4 files changed, 120 insertions(+), 3 deletions(-) diff --git a/crates/macro-impl/src/html_tree/html_tag.rs b/crates/macro-impl/src/html_tree/html_tag.rs index 829a9b73e0d..e129f788a27 100644 --- a/crates/macro-impl/src/html_tree/html_tag.rs +++ b/crates/macro-impl/src/html_tree/html_tag.rs @@ -96,14 +96,47 @@ impl ToTokens for HtmlTag { let TagAttributes { classes, attributes, - .. + kind, + value, + checked, + disabled, + selected, } = &open.attributes; let attr_names = attributes.iter().map(|attr| attr.name.to_string()); let attr_values = attributes.iter().map(|attr| &attr.value); + let set_kind = kind.iter().map(|kind| { + quote! { __yew_vtag.set_kind(&(#kind)); } + }); + let set_value = value.iter().map(|value| { + quote! { __yew_vtag.set_value(&(#value)); } + }); + let set_checked = checked.iter().map(|checked| { + quote! { __yew_vtag.set_checked(#checked); } + }); + let add_disabled = disabled.iter().map(|disabled| { + quote! { + if #disabled { + __yew_vtag.add_attribute("disabled", &"true"); + } + } + }); + let add_selected = selected.iter().map(|selected| { + quote! { + if #selected { + __yew_vtag.add_attribute("selected", &"selected"); + } + } + }); + tokens.extend(quote! {{ let mut __yew_vtag = $crate::virtual_dom::vtag::VTag::new(#tag_name); #(__yew_vtag.add_class(&(#classes));)* #(__yew_vtag.add_attribute(#attr_names, &(#attr_values));)* + #(#set_kind)* + #(#set_value)* + #(#set_checked)* + #(#add_disabled)* + #(#add_selected)* #(__yew_vtag.add_child(#children);)* __yew_vtag }}); @@ -173,6 +206,8 @@ struct TagAttributes { value: Option, kind: Option, checked: Option, + disabled: Option, + selected: Option, } impl TagAttributes { @@ -226,6 +261,8 @@ impl Parse for TagAttributes { let value = TagAttributes::remove_attr(&mut attributes, "value")?; let kind = TagAttributes::remove_attr(&mut attributes, "type")?; let checked = TagAttributes::remove_attr(&mut attributes, "checked")?; + let disabled = TagAttributes::remove_attr(&mut attributes, "disabled")?; + let selected = TagAttributes::remove_attr(&mut attributes, "selected")?; attributes.sort_by(|a, b| a.name.to_string().partial_cmp(&b.name.to_string()).unwrap()); let mut i = 0; @@ -246,6 +283,8 @@ impl Parse for TagAttributes { value, kind, checked, + disabled, + selected, }) } } diff --git a/crates/macro/tests/html-tag-fail.rs b/crates/macro/tests/html-tag-fail.rs index cdace8f0075..e3f7af3926b 100644 --- a/crates/macro/tests/html-tag-fail.rs +++ b/crates/macro/tests/html-tag-fail.rs @@ -14,5 +14,13 @@ test_html! { |t20| } test_html! { |t21| } test_html! { |t22| } test_html! { |t23| } +test_html! { |t24| } +test_html! { |t25|