mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 06:34:20 +00:00
Feat: reject invalid keys
This commit is contained in:
parent
6faa51a4a9
commit
ae352f8958
6 changed files with 40 additions and 23 deletions
|
@ -6,7 +6,7 @@ fn basic_syntax_is_a_template() -> Element {
|
||||||
let var = 123;
|
let var = 123;
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
div { key: "12345", class: "asd", class: "{asd}", class: if true {
|
div { key: "{asd}", class: "asd", class: "{asd}", class: if true {
|
||||||
"{asd}"
|
"{asd}"
|
||||||
}, class: if false {
|
}, class: if false {
|
||||||
"{asd}"
|
"{asd}"
|
||||||
|
|
|
@ -141,6 +141,7 @@ pub fn collect_svgs(children: &mut [BodyNode], out: &mut Vec<BodyNode>) {
|
||||||
fields: vec![],
|
fields: vec![],
|
||||||
children: vec![],
|
children: vec![],
|
||||||
manual_props: None,
|
manual_props: None,
|
||||||
|
key: None,
|
||||||
brace: Default::default(),
|
brace: Default::default(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ use syn::{
|
||||||
pub struct Component {
|
pub struct Component {
|
||||||
pub name: syn::Path,
|
pub name: syn::Path,
|
||||||
pub prop_gen_args: Option<AngleBracketedGenericArguments>,
|
pub prop_gen_args: Option<AngleBracketedGenericArguments>,
|
||||||
|
pub key: Option<IfmtInput>,
|
||||||
pub fields: Vec<ComponentField>,
|
pub fields: Vec<ComponentField>,
|
||||||
pub children: Vec<BodyNode>,
|
pub children: Vec<BodyNode>,
|
||||||
pub manual_props: Option<Expr>,
|
pub manual_props: Option<Expr>,
|
||||||
|
@ -47,6 +48,7 @@ impl Parse for Component {
|
||||||
let mut fields = Vec::new();
|
let mut fields = Vec::new();
|
||||||
let mut children = Vec::new();
|
let mut children = Vec::new();
|
||||||
let mut manual_props = None;
|
let mut manual_props = None;
|
||||||
|
let mut key = None;
|
||||||
|
|
||||||
while !content.is_empty() {
|
while !content.is_empty() {
|
||||||
// if we splat into a component then we're merging properties
|
// if we splat into a component then we're merging properties
|
||||||
|
@ -56,14 +58,26 @@ impl Parse for Component {
|
||||||
} else if
|
} else if
|
||||||
// Named fields
|
// Named fields
|
||||||
(content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]))
|
(content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]))
|
||||||
// shorthand struct initialization
|
// shorthand struct initialization
|
||||||
// Not a div {}, mod::Component {}, or web-component {}
|
// Not a div {}, mod::Component {}, or web-component {}
|
||||||
|| (content.peek(Ident)
|
|| (content.peek(Ident)
|
||||||
&& !content.peek2(Brace)
|
&& !content.peek2(Brace)
|
||||||
&& !content.peek2(Token![:])
|
&& !content.peek2(Token![:])
|
||||||
&& !content.peek2(Token![-]))
|
&& !content.peek2(Token![-]))
|
||||||
{
|
{
|
||||||
fields.push(content.parse()?);
|
// If it
|
||||||
|
if content.fork().parse::<Ident>()? == "key" {
|
||||||
|
_ = content.parse::<Ident>()?;
|
||||||
|
_ = content.parse::<Token![:]>()?;
|
||||||
|
|
||||||
|
let _key: IfmtInput = content.parse()?;
|
||||||
|
if _key.is_static() {
|
||||||
|
invalid_key!(_key);
|
||||||
|
}
|
||||||
|
key = Some(_key);
|
||||||
|
} else {
|
||||||
|
fields.push(content.parse()?);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
children.push(content.parse()?);
|
children.push(content.parse()?);
|
||||||
}
|
}
|
||||||
|
@ -80,6 +94,7 @@ impl Parse for Component {
|
||||||
children,
|
children,
|
||||||
manual_props,
|
manual_props,
|
||||||
brace,
|
brace,
|
||||||
|
key,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -137,15 +152,7 @@ impl Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn key(&self) -> Option<&IfmtInput> {
|
pub fn key(&self) -> Option<&IfmtInput> {
|
||||||
match self
|
self.key.as_ref()
|
||||||
.fields
|
|
||||||
.iter()
|
|
||||||
.find(|f| f.name == "key")
|
|
||||||
.map(|f| &f.content)
|
|
||||||
{
|
|
||||||
Some(ContentField::Formatted(fmt)) => Some(fmt),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_manual_props(&self, manual_props: &Expr) -> TokenStream2 {
|
fn collect_manual_props(&self, manual_props: &Expr) -> TokenStream2 {
|
||||||
|
@ -169,10 +176,7 @@ impl Component {
|
||||||
None => quote! { fc_to_builder(#name) },
|
None => quote! { fc_to_builder(#name) },
|
||||||
};
|
};
|
||||||
for field in &self.fields {
|
for field in &self.fields {
|
||||||
match field.name.to_string().as_str() {
|
toks.append_all(quote! {#field})
|
||||||
"key" => {}
|
|
||||||
_ => toks.append_all(quote! {#field}),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if !self.children.is_empty() {
|
if !self.children.is_empty() {
|
||||||
let renderer: TemplateRenderer = TemplateRenderer {
|
let renderer: TemplateRenderer = TemplateRenderer {
|
||||||
|
@ -218,10 +222,6 @@ impl ContentField {
|
||||||
return Ok(ContentField::OnHandlerRaw(input.parse()?));
|
return Ok(ContentField::OnHandlerRaw(input.parse()?));
|
||||||
}
|
}
|
||||||
|
|
||||||
if *name == "key" {
|
|
||||||
return Ok(ContentField::Formatted(input.parse()?));
|
|
||||||
}
|
|
||||||
|
|
||||||
if input.peek(LitStr) {
|
if input.peek(LitStr) {
|
||||||
let forked = input.fork();
|
let forked = input.fork();
|
||||||
let t: LitStr = forked.parse()?;
|
let t: LitStr = forked.parse()?;
|
||||||
|
|
|
@ -169,7 +169,13 @@ impl Parse for Element {
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
} else if name_str == "key" {
|
} else if name_str == "key" {
|
||||||
key = Some(content.parse()?);
|
let _key: IfmtInput = content.parse()?;
|
||||||
|
|
||||||
|
if _key.is_static() {
|
||||||
|
invalid_key!(_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
key = Some(_key);
|
||||||
} else {
|
} else {
|
||||||
let value = content.parse::<ElementAttrValue>()?;
|
let value = content.parse::<ElementAttrValue>()?;
|
||||||
attributes.push(attribute::AttributeType::Named(ElementAttrNamed {
|
attributes.push(attribute::AttributeType::Named(ElementAttrNamed {
|
||||||
|
|
|
@ -24,3 +24,13 @@ macro_rules! invalid_component_path {
|
||||||
return Err(Error::new($span, "Invalid component path syntax"));
|
return Err(Error::new($span, "Invalid component path syntax"));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! invalid_key {
|
||||||
|
($_key:ident) => {
|
||||||
|
let val = $_key.to_static().unwrap();
|
||||||
|
return Err(syn::Error::new(
|
||||||
|
$_key.span(),
|
||||||
|
format!("Element keys must be a dynamic value. Considering using `key: {{{val}}}` instead.\nStatic keys will result in every element using the same key which will cause rendering issues or panics."),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -179,8 +179,8 @@ impl<'a> ToTokens for TemplateRenderer<'a> {
|
||||||
let mut context = DynamicContext::default();
|
let mut context = DynamicContext::default();
|
||||||
|
|
||||||
let key = match self.roots.first() {
|
let key = match self.roots.first() {
|
||||||
Some(BodyNode::Element(el)) if self.roots.len() == 1 => el.key.clone(),
|
Some(BodyNode::Element(el)) if self.roots.len() >= 1 => el.key.clone(),
|
||||||
Some(BodyNode::Component(comp)) if self.roots.len() == 1 => comp.key().cloned(),
|
Some(BodyNode::Component(comp)) if self.roots.len() >= 1 => comp.key().cloned(),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue