use syn::visit_mut::VisitMut;
use syn::{File, Macro};
#[derive(Debug)]
pub struct ChangedRsx {
pub old: Macro,
pub new: Macro,
}
pub fn diff_rsx(new: &File, old: &File) -> Option<Vec<ChangedRsx>> {
let mut old = old.clone();
let mut new = new.clone();
let old_macros = collect_from_file(&mut old);
let new_macros = collect_from_file(&mut new);
if old_macros.len() != new_macros.len() {
return None;
}
if old != new {
return None;
}
Some(
old_macros
.into_iter()
.zip(new_macros)
.map(|(old, new)| ChangedRsx { old, new })
.collect(),
)
}
pub fn collect_from_file(file: &mut File) -> Vec<Macro> {
struct MacroCollector(Vec<Macro>);
impl VisitMut for MacroCollector {
fn visit_macro_mut(&mut self, dest: &mut syn::Macro) {
let name = &dest.path.segments.last().map(|i| i.ident.to_string());
if let Some("rsx" | "render") = name.as_deref() {
let mut default: syn::Macro = syn::parse_quote! { rsx! {} };
std::mem::swap(dest, &mut default);
self.0.push(default)
}
}
fn visit_attribute_mut(&mut self, i: &mut syn::Attribute) {
if i.path().is_ident("doc") {
*i = syn::parse_quote! { #[doc = ""] };
}
}
}
let mut macros = MacroCollector(vec![]);
macros.visit_file_mut(file);
macros.0
}
#[test]
fn changing_files() {
let old = r#"
use dioxus::prelude::*;
/// some comment
pub fn CoolChild() -> Element {
let a = 123;
rsx! {
div {
{some_expr()}
}
}
}"#;
let new = r#"
use dioxus::prelude::*;
/// some comment
pub fn CoolChild() -> Element {
rsx! {
div {
{some_expr()}
}
}
}"#;
let same = r#"
use dioxus::prelude::*;
/// some comment!!!!!
pub fn CoolChild() -> Element {
let a = 123;
rsx! {
div {
{some_expr()}
}
}
}"#;
let old = syn::parse_file(old).unwrap();
let new = syn::parse_file(new).unwrap();
let same = syn::parse_file(same).unwrap();
assert!(
diff_rsx(&old, &new).is_none(),
"Files with different expressions should not be hotreloadable"
);
assert!(
diff_rsx(&new, &new).is_some(),
"The same file should be reloadable with itself"
);
assert!(
diff_rsx(&old, &same).is_some(),
"Files with changed comments should be hotreloadable"
);
}