Feat: reject invalid keys

This commit is contained in:
Jonathan Kelley 2024-03-07 16:03:00 -08:00
parent 6faa51a4a9
commit ae352f8958
No known key found for this signature in database
GPG key ID: 1FBB50F7EB0A08BE
6 changed files with 40 additions and 23 deletions

View file

@ -6,7 +6,7 @@ fn basic_syntax_is_a_template() -> Element {
let var = 123;
rsx! {
div { key: "12345", class: "asd", class: "{asd}", class: if true {
div { key: "{asd}", class: "asd", class: "{asd}", class: if true {
"{asd}"
}, class: if false {
"{asd}"

View file

@ -141,6 +141,7 @@ pub fn collect_svgs(children: &mut [BodyNode], out: &mut Vec<BodyNode>) {
fields: vec![],
children: vec![],
manual_props: None,
key: None,
brace: Default::default(),
});

View file

@ -27,6 +27,7 @@ use syn::{
pub struct Component {
pub name: syn::Path,
pub prop_gen_args: Option<AngleBracketedGenericArguments>,
pub key: Option<IfmtInput>,
pub fields: Vec<ComponentField>,
pub children: Vec<BodyNode>,
pub manual_props: Option<Expr>,
@ -47,6 +48,7 @@ impl Parse for Component {
let mut fields = Vec::new();
let mut children = Vec::new();
let mut manual_props = None;
let mut key = None;
while !content.is_empty() {
// if we splat into a component then we're merging properties
@ -63,7 +65,19 @@ impl Parse for Component {
&& !content.peek2(Token![:])
&& !content.peek2(Token![-]))
{
// 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 {
children.push(content.parse()?);
}
@ -80,6 +94,7 @@ impl Parse for Component {
children,
manual_props,
brace,
key,
})
}
}
@ -137,15 +152,7 @@ impl Component {
}
pub fn key(&self) -> Option<&IfmtInput> {
match self
.fields
.iter()
.find(|f| f.name == "key")
.map(|f| &f.content)
{
Some(ContentField::Formatted(fmt)) => Some(fmt),
_ => None,
}
self.key.as_ref()
}
fn collect_manual_props(&self, manual_props: &Expr) -> TokenStream2 {
@ -169,10 +176,7 @@ impl Component {
None => quote! { fc_to_builder(#name) },
};
for field in &self.fields {
match field.name.to_string().as_str() {
"key" => {}
_ => toks.append_all(quote! {#field}),
}
toks.append_all(quote! {#field})
}
if !self.children.is_empty() {
let renderer: TemplateRenderer = TemplateRenderer {
@ -218,10 +222,6 @@ impl ContentField {
return Ok(ContentField::OnHandlerRaw(input.parse()?));
}
if *name == "key" {
return Ok(ContentField::Formatted(input.parse()?));
}
if input.peek(LitStr) {
let forked = input.fork();
let t: LitStr = forked.parse()?;

View file

@ -169,7 +169,13 @@ impl Parse for Element {
},
}));
} 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 {
let value = content.parse::<ElementAttrValue>()?;
attributes.push(attribute::AttributeType::Named(ElementAttrNamed {

View file

@ -24,3 +24,13 @@ macro_rules! invalid_component_path {
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."),
));
};
}

View file

@ -179,8 +179,8 @@ impl<'a> ToTokens for TemplateRenderer<'a> {
let mut context = DynamicContext::default();
let key = match self.roots.first() {
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::Element(el)) if self.roots.len() >= 1 => el.key.clone(),
Some(BodyNode::Component(comp)) if self.roots.len() >= 1 => comp.key().cloned(),
_ => None,
};