diff --git a/crates/macro/src/html_tree/html_component.rs b/crates/macro/src/html_tree/html_component.rs index f2d833e58e1..56b4820b33c 100644 --- a/crates/macro/src/html_tree/html_component.rs +++ b/crates/macro/src/html_tree/html_component.rs @@ -340,17 +340,22 @@ impl ToTokens for HtmlComponentClose { } } -enum PropType { - List, - With, -} - enum Props { List(Box), With(Box), None, } +struct ListProps { + props: Vec, + node_ref: Option, +} + +struct WithProps { + props: Ident, + node_ref: Option, +} + impl Props { fn node_ref(&self) -> Option<&Expr> { match self { @@ -359,102 +364,101 @@ impl Props { Props::None => None, } } -} -impl PeekValue for Props { - fn peek(cursor: Cursor) -> Option { - let (ident, _) = cursor.ident()?; - let prop_type = if ident == "with" { - PropType::With - } else { - PropType::List - }; - - Some(prop_type) + fn collision_message() -> &'static str { + "Using special syntax `with props` along with named prop is not allowed. This rule does not apply to special `ref` prop" } } impl Parse for Props { fn parse(input: ParseStream) -> ParseResult { - match Props::peek(input.cursor()) { - Some(PropType::List) => input.parse().map(|l| Props::List(Box::new(l))), - Some(PropType::With) => input.parse().map(|w| Props::With(Box::new(w))), - None => Ok(Props::None), - } - } -} - -struct ListProps { - props: Vec, - node_ref: Option, -} + let mut props = Props::None; + let mut node_ref: Option = None; + + while let Some((token, _)) = input.cursor().ident() { + if token == "with" { + match props { + Props::None => Ok(()), + Props::With(_) => Err(input.error("too many `with` tokens used")), + Props::List(_) => { + Err(syn::Error::new_spanned(&token, Props::collision_message())) + } + }?; + + input.parse::()?; + props = Props::With(Box::new(WithProps { + props: input.parse::()?, + node_ref: None, + })); + + // Handle optional comma + let _ = input.parse::(); + continue; + } -impl Parse for ListProps { - fn parse(input: ParseStream) -> ParseResult { - let mut props: Vec = Vec::new(); - while HtmlProp::peek(input.cursor()).is_some() { - props.push(input.parse::()?); - } + if (HtmlProp::peek(input.cursor())).is_none() { + break; + } - let ref_position = props.iter().position(|p| p.label.to_string() == "ref"); - let node_ref = ref_position.map(|i| props.remove(i).value); - for prop in &props { + let prop = input.parse::()?; if prop.label.to_string() == "ref" { - return Err(syn::Error::new_spanned(&prop.label, "too many refs set")); + match node_ref { + None => Ok(()), + Some(_) => Err(syn::Error::new_spanned(&prop.label, "too many refs set")), + }?; + + node_ref = Some(prop.value); + continue; } + if prop.label.to_string() == "type" { return Err(syn::Error::new_spanned(&prop.label, "expected identifier")); } + if !prop.label.extended.is_empty() { return Err(syn::Error::new_spanned(&prop.label, "expected identifier")); } - } - - // alphabetize - props.sort_by(|a, b| { - if a.label == b.label { - Ordering::Equal - } else if a.label.to_string() == "children" { - Ordering::Greater - } else if b.label.to_string() == "children" { - Ordering::Less - } else { - a.label - .to_string() - .partial_cmp(&b.label.to_string()) - .unwrap() - } - }); - - Ok(ListProps { props, node_ref }) - } -} - -struct WithProps { - props: Ident, - node_ref: Option, -} -impl Parse for WithProps { - fn parse(input: ParseStream) -> ParseResult { - let with = input.parse::()?; - if with != "with" { - return Err(input.error("expected to find `with` token")); + match props { + ref mut props @ Props::None => { + *props = Props::List(Box::new(ListProps { + props: vec![prop], + node_ref: None, + })); + } + Props::With(_) => { + return Err(syn::Error::new_spanned(&token, Props::collision_message())) + } + Props::List(ref mut list) => { + list.props.push(prop); + } + }; } - let props = input.parse::()?; - let _ = input.parse::(); - // Check for the ref tag after `with` - let mut node_ref = None; - if let Some(ident) = input.cursor().ident() { - let prop = input.parse::()?; - if ident.0 == "ref" { - node_ref = Some(prop.value); - } else { - return Err(syn::Error::new_spanned(&prop.label, "unexpected token")); + match props { + Props::None => {} + Props::With(ref mut p) => p.node_ref = node_ref, + Props::List(ref mut p) => { + p.node_ref = node_ref; + + // alphabetize + p.props.sort_by(|a, b| { + if a.label == b.label { + Ordering::Equal + } else if a.label.to_string() == "children" { + Ordering::Greater + } else if b.label.to_string() == "children" { + Ordering::Less + } else { + a.label + .to_string() + .partial_cmp(&b.label.to_string()) + .unwrap() + } + }); } - } + }; - Ok(WithProps { props, node_ref }) + Ok(props) } } diff --git a/crates/macro/tests/macro/html-component-fail.rs b/crates/macro/tests/macro/html-component-fail.rs index 6a9013d7031..0a42e145740 100644 --- a/crates/macro/tests/macro/html-component-fail.rs +++ b/crates/macro/tests/macro/html-component-fail.rs @@ -73,9 +73,16 @@ fn compile_fail() { html! { }; html! { }; html! { }; - html! { }; + html! { }; + html! { }; + html! { }; + html! { }; + html! { }; + html! { }; html! { }; html! { }; + html! { }; + html! { }; html! { }; html! { }; html! { }; diff --git a/crates/macro/tests/macro/html-component-fail.stderr b/crates/macro/tests/macro/html-component-fail.stderr index 3f792855b44..82de6abf39d 100644 --- a/crates/macro/tests/macro/html-component-fail.stderr +++ b/crates/macro/tests/macro/html-component-fail.stderr @@ -30,107 +30,149 @@ error: this open tag has no corresponding close tag 74 | html! { }; | ^^^^^^^^^^^^^^^^^^^ -error: unexpected token +error: too many refs set --> $DIR/html-component-fail.rs:75:38 | 75 | html! { }; | ^^^ -error: unexpected token - --> $DIR/html-component-fail.rs:76:27 +error: too many refs set + --> $DIR/html-component-fail.rs:76:38 | -76 | html! { }; - | ^^^^ +76 | html! { }; + | ^^^ -error: unexpected token +error: Using special syntax `with props` along with named prop is not allowed. This rule does not apply to special `ref` prop + --> $DIR/html-component-fail.rs:77:38 + | +77 | html! { }; + | ^^^^^ + +error: Using special syntax `with props` along with named prop is not allowed. This rule does not apply to special `ref` prop --> $DIR/html-component-fail.rs:78:31 | -78 | html! { }; +78 | html! { }; + | ^^^^^ + +error: Using special syntax `with props` along with named prop is not allowed. This rule does not apply to special `ref` prop + --> $DIR/html-component-fail.rs:79:28 + | +79 | html! { }; + | ^^^^ + +error: Using special syntax `with props` along with named prop is not allowed. This rule does not apply to special `ref` prop + --> $DIR/html-component-fail.rs:80:35 + | +80 | html! { }; + | ^^^^ + +error: too many refs set + --> $DIR/html-component-fail.rs:81:27 + | +81 | html! { }; + | ^^^ + +error: unexpected token + --> $DIR/html-component-fail.rs:83:31 + | +83 | html! { }; | ^^ +error: Using special syntax `with props` along with named prop is not allowed. This rule does not apply to special `ref` prop + --> $DIR/html-component-fail.rs:84:28 + | +84 | html! { }; + | ^^^^ + +error: Using special syntax `with props` along with named prop is not allowed. This rule does not apply to special `ref` prop + --> $DIR/html-component-fail.rs:85:31 + | +85 | html! { }; + | ^^^^^ + error: expected identifier - --> $DIR/html-component-fail.rs:79:20 + --> $DIR/html-component-fail.rs:86:20 | -79 | html! { }; +86 | html! { }; | ^^^^ error: expected identifier - --> $DIR/html-component-fail.rs:80:20 + --> $DIR/html-component-fail.rs:87:20 | -80 | html! { }; +87 | html! { }; | ^^^^^^^^^^^^^^^^^ error: unexpected end of input, expected expression - --> $DIR/html-component-fail.rs:82:5 + --> $DIR/html-component-fail.rs:89:5 | -82 | html! { }; +89 | html! { }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = 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: too many refs set - --> $DIR/html-component-fail.rs:87:33 + --> $DIR/html-component-fail.rs:94:33 | -87 | html! { }; +94 | html! { }; | ^^^ error: this close tag has no corresponding open tag - --> $DIR/html-component-fail.rs:90:13 + --> $DIR/html-component-fail.rs:97:13 | -90 | html! { }; +97 | html! { }; | ^^^^^^^^ error: this open tag has no corresponding close tag - --> $DIR/html-component-fail.rs:91:13 + --> $DIR/html-component-fail.rs:98:13 | -91 | html! { }; +98 | html! { }; | ^^^^^^^ error: only one root html element allowed - --> $DIR/html-component-fail.rs:92:28 + --> $DIR/html-component-fail.rs:99:28 | -92 | html! { }; +99 | html! { }; | ^^^^^^^^^^^^^^^ error: this close tag has no corresponding open tag - --> $DIR/html-component-fail.rs:101:30 + --> $DIR/html-component-fail.rs:108:30 | -101 | html! { > }; +108 | html! { > }; | ^^^^^^^^^^ error: this close tag has no corresponding open tag - --> $DIR/html-component-fail.rs:102:30 + --> $DIR/html-component-fail.rs:109:30 | -102 | html! { >>> }; +109 | html! { >>> }; | ^^^^^^^^^^^^^^^^^^^^^^^ error[E0425]: cannot find value `blah` in this scope - --> $DIR/html-component-fail.rs:77:25 + --> $DIR/html-component-fail.rs:82:25 | -77 | html! { }; +82 | html! { }; | ^^^^ not found in this scope error[E0609]: no field `unknown` on type `ChildProperties` - --> $DIR/html-component-fail.rs:81:20 + --> $DIR/html-component-fail.rs:88:20 | -81 | html! { }; +88 | html! { }; | ^^^^^^^ unknown field | = note: available fields are: `string`, `int` error[E0599]: no method named `unknown` found for type `ChildPropertiesBuilder` in the current scope - --> $DIR/html-component-fail.rs:81:20 + --> $DIR/html-component-fail.rs:88:20 | 6 | #[derive(Clone, Properties, PartialEq)] | ---------- method `unknown` not found for this ... -81 | html! { }; +88 | html! { }; | ^^^^^^^ method not found in `ChildPropertiesBuilder` error[E0277]: the trait bound `yew::virtual_dom::vcomp::VComp: yew::virtual_dom::Transformer<(), std::string::String>` is not satisfied - --> $DIR/html-component-fail.rs:83:33 + --> $DIR/html-component-fail.rs:90:33 | -83 | html! { }; +90 | html! { }; | ^^ the trait `yew::virtual_dom::Transformer<(), std::string::String>` is not implemented for `yew::virtual_dom::vcomp::VComp` | = help: the following implementations were found: @@ -142,9 +184,9 @@ error[E0277]: the trait bound `yew::virtual_dom::vcomp::VComp: yew::virtual_dom: = note: required by `yew::virtual_dom::Transformer::transform` error[E0277]: the trait bound `yew::virtual_dom::vcomp::VComp: yew::virtual_dom::Transformer<{integer}, std::string::String>` is not satisfied - --> $DIR/html-component-fail.rs:84:33 + --> $DIR/html-component-fail.rs:91:33 | -84 | html! { }; +91 | html! { }; | ^ the trait `yew::virtual_dom::Transformer<{integer}, std::string::String>` is not implemented for `yew::virtual_dom::vcomp::VComp` | = help: the following implementations were found: @@ -156,9 +198,9 @@ error[E0277]: the trait bound `yew::virtual_dom::vcomp::VComp: yew::virtual_dom: = note: required by `yew::virtual_dom::Transformer::transform` error[E0277]: the trait bound `yew::virtual_dom::vcomp::VComp: yew::virtual_dom::Transformer<{integer}, std::string::String>` is not satisfied - --> $DIR/html-component-fail.rs:85:33 + --> $DIR/html-component-fail.rs:92:33 | -85 | html! { }; +92 | html! { }; | ^^^ the trait `yew::virtual_dom::Transformer<{integer}, std::string::String>` is not implemented for `yew::virtual_dom::vcomp::VComp` | = help: the following implementations were found: @@ -170,15 +212,15 @@ error[E0277]: the trait bound `yew::virtual_dom::vcomp::VComp: yew::virtual_dom: = note: required by `yew::virtual_dom::Transformer::transform` error[E0308]: mismatched types - --> $DIR/html-component-fail.rs:86:30 + --> $DIR/html-component-fail.rs:93:30 | -86 | html! { }; +93 | html! { }; | ^^ expected struct `yew::html::NodeRef`, found `()` error[E0277]: the trait bound `yew::virtual_dom::vcomp::VComp: yew::virtual_dom::Transformer` is not satisfied - --> $DIR/html-component-fail.rs:88:24 + --> $DIR/html-component-fail.rs:95:24 | -88 | html! { }; +95 | html! { }; | ^^^^ the trait `yew::virtual_dom::Transformer` is not implemented for `yew::virtual_dom::vcomp::VComp` | = help: the following implementations were found: @@ -190,12 +232,12 @@ error[E0277]: the trait bound `yew::virtual_dom::vcomp::VComp: yew::virtual_dom: = note: required by `yew::virtual_dom::Transformer::transform` error[E0599]: no method named `string` found for type `ChildPropertiesBuilder` in the current scope - --> $DIR/html-component-fail.rs:89:20 + --> $DIR/html-component-fail.rs:96:20 | 6 | #[derive(Clone, Properties, PartialEq)] | ---------- method `string` not found for this ... -89 | html! { }; +96 | html! { }; | ^^^^^^ method not found in `ChildPropertiesBuilder` | = help: items from traits can only be used if the trait is implemented and in scope @@ -203,70 +245,70 @@ error[E0599]: no method named `string` found for type `ChildPropertiesBuilder` in the current scope - --> $DIR/html-component-fail.rs:93:5 - | -6 | #[derive(Clone, Properties, PartialEq)] - | ---------- method `children` not found for this + --> $DIR/html-component-fail.rs:100:5 + | +6 | #[derive(Clone, Properties, PartialEq)] + | ---------- method `children` not found for this ... -93 | html! { { "Not allowed" } }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ method not found in `ChildPropertiesBuilder` - | - = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) +100 | html! { { "Not allowed" } }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ method not found in `ChildPropertiesBuilder` + | + = 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[E0599]: no method named `build` found for type `ChildContainerPropertiesBuilder` in the current scope - --> $DIR/html-component-fail.rs:95:5 - | -29 | #[derive(Clone, Properties)] - | ---------- method `build` not found for this + --> $DIR/html-component-fail.rs:102:5 + | +29 | #[derive(Clone, Properties)] + | ---------- method `build` not found for this ... -95 | html! { }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ method not found in `ChildContainerPropertiesBuilder` - | - = help: items from traits can only be used if the trait is implemented and in scope - = note: the following trait defines an item `build`, perhaps you need to implement it: - candidate #1: `proc_macro::bridge::server::TokenStreamBuilder` - = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) +102 | html! { }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ method not found in `ChildContainerPropertiesBuilder` + | + = help: items from traits can only be used if the trait is implemented and in scope + = note: the following trait defines an item `build`, perhaps you need to implement it: + candidate #1: `proc_macro::bridge::server::TokenStreamBuilder` + = 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[E0599]: no method named `build` found for type `ChildContainerPropertiesBuilder` in the current scope - --> $DIR/html-component-fail.rs:96:5 - | -29 | #[derive(Clone, Properties)] - | ---------- method `build` not found for this + --> $DIR/html-component-fail.rs:103:5 + | +29 | #[derive(Clone, Properties)] + | ---------- method `build` not found for this ... -96 | html! { }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ method not found in `ChildContainerPropertiesBuilder` - | - = help: items from traits can only be used if the trait is implemented and in scope - = note: the following trait defines an item `build`, perhaps you need to implement it: - candidate #1: `proc_macro::bridge::server::TokenStreamBuilder` - = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) +103 | html! { }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ method not found in `ChildContainerPropertiesBuilder` + | + = help: items from traits can only be used if the trait is implemented and in scope + = note: the following trait defines an item `build`, perhaps you need to implement it: + candidate #1: `proc_macro::bridge::server::TokenStreamBuilder` + = 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 `yew::virtual_dom::vcomp::VChild: std::convert::From<&str>` is not satisfied - --> $DIR/html-component-fail.rs:97:5 - | -97 | html! { { "Not allowed" } }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From<&str>` is not implemented for `yew::virtual_dom::vcomp::VChild` - | - = note: required because of the requirements on the impl of `std::convert::Into>` for `&str` - = note: required because of the requirements on the impl of `std::iter::IntoIterator` for `yew::utils::NodeSeq<&str, yew::virtual_dom::vcomp::VChild>` - = 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:104:5 + | +104 | html! { { "Not allowed" } }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From<&str>` is not implemented for `yew::virtual_dom::vcomp::VChild` + | + = note: required because of the requirements on the impl of `std::convert::Into>` for `&str` + = note: required because of the requirements on the impl of `std::iter::IntoIterator` for `yew::utils::NodeSeq<&str, yew::virtual_dom::vcomp::VChild>` + = 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 `yew::virtual_dom::vcomp::VChild: std::convert::From` is not satisfied - --> $DIR/html-component-fail.rs:98:5 - | -98 | html! { <> }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From` is not implemented for `yew::virtual_dom::vcomp::VChild` - | - = note: required because of the requirements on the impl of `std::convert::Into>` for `yew::virtual_dom::vnode::VNode` - = note: required because of the requirements on the impl of `std::iter::IntoIterator` for `yew::utils::NodeSeq>` - = 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:105:5 + | +105 | html! { <> }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From` is not implemented for `yew::virtual_dom::vcomp::VChild` + | + = note: required because of the requirements on the impl of `std::convert::Into>` for `yew::virtual_dom::vnode::VNode` + = note: required because of the requirements on the impl of `std::iter::IntoIterator` for `yew::utils::NodeSeq>` + = 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 `yew::virtual_dom::vcomp::VChild: std::convert::From` is not satisfied - --> $DIR/html-component-fail.rs:99:5 - | -99 | html! { }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From` is not implemented for `yew::virtual_dom::vcomp::VChild` - | - = note: required because of the requirements on the impl of `std::convert::Into>` for `yew::virtual_dom::vnode::VNode` - = note: required because of the requirements on the impl of `std::iter::IntoIterator` for `yew::utils::NodeSeq>` - = 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:106:5 + | +106 | html! { }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From` is not implemented for `yew::virtual_dom::vcomp::VChild` + | + = note: required because of the requirements on the impl of `std::convert::Into>` for `yew::virtual_dom::vnode::VNode` + = note: required because of the requirements on the impl of `std::iter::IntoIterator` for `yew::utils::NodeSeq>` + = 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/macro/html-component-pass.rs b/crates/macro/tests/macro/html-component-pass.rs index 8f1d823960c..cf70b1e3825 100644 --- a/crates/macro/tests/macro/html-component-pass.rs +++ b/crates/macro/tests/macro/html-component-pass.rs @@ -181,12 +181,15 @@ fn compile_pass() { let props = ::Properties::default(); let props2 = ::Properties::default(); + let props3 = ::Properties::default(); + let props4 = ::Properties::default(); + let node_ref = NodeRef::default(); html! { <> - - // backwards compat - + // backwards compat + + };