use quote::ToTokens;
use crate::prop_type;
pub(crate) fn hash<I: IntoIterator<Item = String>>(declarations: I) -> u64 {
let mut declarations: Vec<String> = declarations.into_iter().collect();
declarations.sort();
prop_type::fnv1a_hash(&declarations.join("\u{1f}"))
}
pub(crate) fn declaration(
attributes: &[syn::Attribute],
attrs: &[&str],
item: &impl ToTokens,
) -> String {
let mut out = String::new();
for attr in attributes
.iter()
.filter(|a| attrs.iter().any(|name| a.path().is_ident(name)))
{
out.push_str(&attr.to_token_stream().to_string());
out.push(' ');
}
out.push_str(&item.to_token_stream().to_string());
out
}
pub(crate) fn field(field: &syn::Field, attrs: &[&str]) -> Option<String> {
if !field
.attrs
.iter()
.any(|a| attrs.iter().any(|name| a.path().is_ident(name)))
{
return None;
}
let ident = field.ident.as_ref()?;
let ty = &field.ty;
Some(declaration(
&field.attrs,
attrs,
"e::quote! { #ident: #ty },
))
}
#[cfg(test)]
mod tests {
use super::*;
use syn::parse_quote;
fn fields(item: syn::ItemStruct) -> Vec<String> {
item.fields
.iter()
.filter_map(|f| field(f, &["uproperty", "component"]))
.collect()
}
#[test]
fn ue_fields_make_the_shape() {
let before = fields(parse_quote! {
struct Platform {
#[uproperty(EditAnywhere)]
speed: f32,
timer: f64,
}
});
let same = fields(parse_quote! {
struct Platform {
#[doc = "A comment changes nothing."]
#[uproperty(EditAnywhere)]
speed: f32,
timer: f32,
elapsed: f64,
}
});
let added = fields(parse_quote! {
struct Platform {
#[uproperty(EditAnywhere)]
speed: f32,
#[uproperty(EditAnywhere)]
distance: f32,
}
});
let flags = fields(parse_quote! {
struct Platform {
#[uproperty(VisibleAnywhere)]
speed: f32,
}
});
assert_eq!(hash(before.clone()), hash(same));
assert_ne!(hash(before.clone()), hash(added));
assert_ne!(hash(before), hash(flags));
}
#[test]
fn order_does_not_matter() {
let a = vec!["a".to_string(), "b".to_string()];
let b = vec!["b".to_string(), "a".to_string()];
assert_eq!(hash(a), hash(b));
}
#[test]
fn a_method_body_is_not_part_of_it() {
let shape =
|method: syn::ImplItemFn| declaration(&method.attrs, &["ufunction"], &method.sig);
let before = shape(parse_quote! {
#[ufunction(BlueprintCallable)]
fn jump(&mut self, height: f32) { let _ = height; }
});
let body = shape(parse_quote! {
#[ufunction(BlueprintCallable)]
fn jump(&mut self, height: f32) { println!("{height}"); }
});
let params = shape(parse_quote! {
#[ufunction(BlueprintCallable)]
fn jump(&mut self, height: f64) {}
});
assert_eq!(before, body);
assert_ne!(before, params);
}
}