Fix key parse bug for components (#1338)

This commit is contained in:
Justin Starry 2020-06-21 17:15:07 +08:00 committed by GitHub
parent 125812da4f
commit f56020fff4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 96 additions and 107 deletions

View File

@ -87,8 +87,8 @@ impl ToTokens for HtmlComponent {
children,
} = self;
let validate_props = if let Props::List(list_props) = props {
let check_props = list_props.props.iter().map(|HtmlProp { label, .. }| {
let validate_props = if let PropType::List(list_props) = &props.prop_type {
let check_props = list_props.iter().map(|HtmlProp { label, .. }| {
quote! { props.#label; }
});
@ -116,9 +116,9 @@ impl ToTokens for HtmlComponent {
quote! {}
};
let init_props = match props {
Props::List(list_props) => {
let set_props = list_props.props.iter().map(|HtmlProp { label, value }| {
let init_props = match &props.prop_type {
PropType::List(list_props) => {
let set_props = list_props.iter().map(|HtmlProp { label, value }| {
quote_spanned! { value.span()=> .#label(
<::yew::virtual_dom::VComp as ::yew::virtual_dom::Transformer<_, _>>::transform(
#value
@ -133,24 +133,23 @@ impl ToTokens for HtmlComponent {
.build()
}
}
Props::With(with_props) => {
let props = &with_props.props;
PropType::With(props) => {
quote! { #props }
}
Props::None => quote! {
PropType::None => quote! {
<<#ty as ::yew::html::Component>::Properties as ::yew::html::Properties>::builder()
#set_children
.build()
},
};
let node_ref = if let Some(node_ref) = props.node_ref() {
let node_ref = if let Some(node_ref) = &props.node_ref {
quote_spanned! { node_ref.span()=> #node_ref }
} else {
quote! { ::yew::html::NodeRef::default() }
};
let key = if let Some(key) = props.key() {
let key = if let Some(key) = &props.key {
quote_spanned! { key.span()=> Some(#key) }
} else {
quote! {None}
@ -336,70 +335,39 @@ impl ToTokens for HtmlComponentClose {
}
}
enum Props {
List(Box<ListProps>),
With(Box<WithProps>),
enum PropType {
List(Vec<HtmlProp>),
With(Ident),
None,
}
struct ListProps {
props: Vec<HtmlProp>,
struct Props {
node_ref: Option<Expr>,
key: Option<Expr>,
prop_type: PropType,
}
struct WithProps {
props: Ident,
node_ref: Option<Expr>,
key: Option<Expr>,
}
impl Props {
fn node_ref(&self) -> Option<&Expr> {
match self {
Props::List(list_props) => list_props.node_ref.as_ref(),
Props::With(with_props) => with_props.node_ref.as_ref(),
Props::None => None,
}
}
fn key(&self) -> Option<&Expr> {
match self {
Props::List(list_props) => list_props.key.as_ref(),
Props::With(with_props) => with_props.key.as_ref(),
Props::None => None,
}
}
fn collision_message() -> &'static str {
"Using the `with props` syntax in combination with named props is not allowed (note: this does not apply to the `ref` prop)."
}
}
const COLLISION_MSG: &str = "Using the `with props` syntax in combination with named props is not allowed (note: this does not apply to the `ref` prop).";
impl Parse for Props {
fn parse(input: ParseStream) -> ParseResult<Self> {
let mut props = Props::None;
let mut node_ref: Option<Expr> = None;
let mut key: Option<Expr> = None;
let mut props = Props {
node_ref: None,
key: None,
prop_type: PropType::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()))
}
match props.prop_type {
PropType::None => Ok(()),
PropType::With(_) => Err(input.error("too many `with` tokens used")),
PropType::List(_) => Err(syn::Error::new_spanned(&token, COLLISION_MSG)),
}?;
input.parse::<Ident>()?;
props = Props::With(Box::new(WithProps {
props: input.parse::<Ident>().map_err(|_| {
syn::Error::new_spanned(&token, "`with` must be followed by an identifier")
})?,
node_ref: None,
key: None,
}));
props.prop_type = PropType::With(input.parse::<Ident>().map_err(|_| {
syn::Error::new_spanned(&token, "`with` must be followed by an identifier")
})?);
// Handle optional comma
let _ = input.parse::<Token![,]>();
@ -412,21 +380,21 @@ impl Parse for Props {
let prop = input.parse::<HtmlProp>()?;
if prop.label.to_string() == "ref" {
match node_ref {
match props.node_ref {
None => Ok(()),
Some(_) => Err(syn::Error::new_spanned(&prop.label, "too many refs set")),
}?;
node_ref = Some(prop.value);
props.node_ref = Some(prop.value);
continue;
}
if prop.label.to_string() == "key" {
match key {
match props.key {
None => Ok(()),
Some(_) => Err(syn::Error::new_spanned(&prop.label, "too many keys set")),
}?;
key = Some(prop.value);
props.key = Some(prop.value);
continue;
}
@ -438,50 +406,34 @@ impl Parse for Props {
return Err(syn::Error::new_spanned(&prop.label, "expected identifier"));
}
match props {
ref mut props @ Props::None => {
*props = Props::List(Box::new(ListProps {
props: vec![prop],
node_ref: None,
key: None,
}));
match props.prop_type {
ref mut prop_type @ PropType::None => {
*prop_type = PropType::List(vec![prop]);
}
Props::With(_) => {
return Err(syn::Error::new_spanned(&token, Props::collision_message()))
}
Props::List(ref mut list) => {
list.props.push(prop);
PropType::With(_) => return Err(syn::Error::new_spanned(&token, COLLISION_MSG)),
PropType::List(ref mut list) => {
list.push(prop);
}
};
}
match props {
Props::None => {}
Props::With(ref mut p) => {
p.node_ref = node_ref;
p.key = key
}
Props::List(ref mut p) => {
p.node_ref = node_ref;
p.key = key;
// sort alphabetically
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()
}
});
}
};
if let PropType::List(list) = &mut props.prop_type {
// sort alphabetically
list.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(props)
}

View File

@ -279,7 +279,7 @@ impl<COMP: Component> fmt::Debug for VChild<COMP> {
#[cfg(test)]
mod tests {
use super::VChild;
use super::*;
use crate::macros::Properties;
use crate::{html, Children, Component, ComponentLink, Html, NodeRef, ShouldRender};
#[cfg(feature = "wasm_test")]
@ -347,6 +347,47 @@ mod tests {
};
}
#[test]
fn set_component_key() {
let test_key = "test".to_string();
let check_key = |vnode: VNode| {
assert_eq!(vnode.key().as_ref(), Some(&test_key));
};
let props = Props {
field_1: 1,
field_2: 1,
};
let props_2 = props.clone();
check_key(html! { <Comp key=test_key.clone() /> });
check_key(html! { <Comp key=test_key.clone() field_1=1 /> });
check_key(html! { <Comp field_1=1 key=test_key.clone() /> });
check_key(html! { <Comp with props key=test_key.clone() /> });
check_key(html! { <Comp key=test_key.clone() with props_2 /> });
}
#[test]
fn set_component_node_ref() {
let test_node: Node = document().create_text_node("test").into();
let test_node_ref = NodeRef::new(test_node);
let check_node_ref = |vnode: VNode| {
assert_eq!(vnode.first_node(), test_node_ref.get().unwrap());
};
let props = Props {
field_1: 1,
field_2: 1,
};
let props_2 = props.clone();
check_node_ref(html! { <Comp ref=test_node_ref.clone() /> });
check_node_ref(html! { <Comp ref=test_node_ref.clone() field_1=1 /> });
check_node_ref(html! { <Comp field_1=1 ref=test_node_ref.clone() /> });
check_node_ref(html! { <Comp with props ref=test_node_ref.clone() /> });
check_node_ref(html! { <Comp ref=test_node_ref.clone() with props_2 /> });
}
#[test]
fn vchild_partialeq() {
let vchild1: VChild<Comp> = VChild::new(
@ -412,8 +453,6 @@ mod tests {
#[cfg(feature = "web_sys")]
fn setup_parent() -> (AnyScope, Element) {
use crate::utils::document;
let scope = AnyScope {
type_id: std::any::TypeId::of::<()>(),
parent: None,
@ -428,8 +467,6 @@ mod tests {
#[cfg(feature = "web_sys")]
fn get_html(mut node: Html, scope: &AnyScope, parent: &Element) -> String {
use super::VDiff;
// clear parent
parent.set_inner_html("");