use anyhow::Result;
use beet_rsx::prelude::RstmlRustToHash;
use proc_macro2::TokenStream;
use proc_macro2::TokenTree;
use quote::ToTokens;
use rapidhash::RapidHasher;
use std::hash::Hash;
use std::hash::Hasher;
use std::path::Path;
use sweet::prelude::ReadFile;
pub struct HashRsxFile {
hasher: RapidHasher,
}
impl Default for HashRsxFile {
fn default() -> Self {
Self {
hasher: RapidHasher::default_const(),
}
}
}
impl HashRsxFile {
pub fn file_to_hash(path: &Path) -> Result<u64> {
let file = ReadFile::to_string(path)?;
let file = syn::parse_file(&file)?;
let mut this = Self::default();
this.walk_tokens(file.to_token_stream())?;
Ok(this.hasher.finish())
}
pub fn walk_tokens(&mut self, tokens: TokenStream) -> Result<()> {
let mut iter = tokens.into_iter().peekable();
while let Some(tree) = iter.next() {
match &tree {
TokenTree::Ident(ident) if ident.to_string() == "rsx" => {
if let Some(TokenTree::Punct(punct)) = iter.peek() {
if punct.as_char() == '!' {
iter.next(); if let Some(TokenTree::Group(group)) = iter.next() {
RstmlRustToHash::visit_and_hash(
&mut self.hasher,
group.stream(),
);
continue;
}
}
} else {
ident
.to_string()
.replace(" ", "")
.hash(&mut self.hasher);
}
}
TokenTree::Group(group) => {
self.walk_tokens(group.stream())?;
}
tree => {
tree.to_string().replace(" ", "").hash(&mut self.hasher);
}
}
}
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
use quote::quote;
use sweet::prelude::*;
#[test]
#[rustfmt::skip]
fn works() {
fn hash(tokens: TokenStream) -> u64 {
let mut hasher = HashRsxFile::default();
hasher.walk_tokens(tokens).unwrap();
hasher.hasher.finish()
}
expect(hash(quote! {rsx!{<el1/>}}))
.to_be(hash(quote! {rsx!{<el2/>}}));
expect(hash(quote! {rsx!{<el key="lit"/>}}))
.to_be(hash(quote! {rsx!{<el key=28/>}}));
expect(hash(quote! {rsx!{<el foo={7}/>}}))
.to_be(hash(quote! {rsx!{<el bar={7}>}}));
expect(hash(quote! {rsx!{<el>{7}</el>}}))
.to_be(hash(quote! {rsx!{<el><el>{7}</el></el>}}));
expect(hash(quote! {rsx!{<el><Component></el>}}))
.to_be(hash(quote! {rsx!{<el><el><Component></el></el>}}));
expect(hash(quote! {let foo = rsx!{<el/>}}))
.not()
.to_be(hash(quote! {let bar = rsx!{<el/>}}));
expect(hash(quote! {rsx!{<el/>}foo}))
.not()
.to_be(hash(quote! {rsx!{<el/>}bar}));
expect(hash(quote! {rsx!{<el>{7}{8}</el>}}))
.not()
.to_be(hash(quote! {rsx!{<el>{8}{7}</el>}}));
expect(hash(quote! {rsx!{<el foo=bar/>}}))
.not()
.to_be(hash(quote! {rsx!{<el foo=bazz>}}));
expect(hash(quote! {rsx!{<el {7}/>}}))
.not()
.to_be(hash(quote! {rsx!{<el {8}>}}));
expect(hash(quote! {rsx!{<el>{7}</el>}}))
.not()
.to_be(hash(quote! {rsx!{<el>{8}</el>}}));
expect(hash(quote! {{v1}}))
.not()
.to_be(hash(quote! {{v2}}));
expect(hash(quote! {{rsx!{<el1/>}}}))
.to_be(hash(quote! {{rsx!{<el2/>}}}));
}
}