use crate::adapters::shared::macro_tokens::{
idents_in_call_position, recover_exprs, tokens_reference_ident,
};
use proc_macro2::TokenStream;
use syn::visit::Visit;
fn ts(src: &str) -> TokenStream {
src.parse().expect("token stream must lex")
}
fn call_idents(exprs: &[syn::Expr]) -> Vec<String> {
#[derive(Default)]
struct V(Vec<String>);
impl<'ast> Visit<'ast> for V {
fn visit_expr_call(&mut self, n: &'ast syn::ExprCall) {
if let syn::Expr::Path(p) = &*n.func {
if let Some(seg) = p.path.segments.last() {
self.0.push(seg.ident.to_string());
}
}
syn::visit::visit_expr_call(self, n);
}
}
let mut v = V::default();
exprs.iter().for_each(|e| v.visit_expr(e));
v.0
}
#[test]
fn recover_exprs_comma_list_form() {
let calls = call_idents(&recover_exprs(&ts("a(), b()")));
assert!(
calls.contains(&"a".to_string()) && calls.contains(&"b".to_string()),
"comma form must recover both calls: {calls:?}"
);
}
#[test]
fn recover_exprs_repeat_semicolon_form() {
let calls = call_idents(&recover_exprs(&ts("helper(); 3")));
assert!(
calls.contains(&"helper".to_string()),
"repeat (`;`) form must recover the call via the block fallback: {calls:?}"
);
}
#[test]
fn recover_exprs_repeat_form_recovers_nested_macro_stmt() {
let exprs = recover_exprs(&ts(r#"format!("inner"); 3"#));
let has_format = exprs
.iter()
.any(|e| matches!(e, syn::Expr::Macro(m) if m.mac.path.is_ident("format")));
assert!(has_format, "nested format! in a `;`-body must be recovered");
}
#[test]
fn recover_exprs_recovers_let_else_diverging_branch() {
let calls = call_idents(&recover_exprs(&ts("let Some(x) = opt else { bail() };")));
assert!(
calls.contains(&"bail".to_string()),
"a call in the let-else diverging branch must be recovered: {calls:?}"
);
}
#[test]
fn recover_exprs_single_expr_form() {
let calls = call_idents(&recover_exprs(&ts("only_call()")));
assert!(
calls.contains(&"only_call".to_string()),
"single-expr form must recover the call: {calls:?}"
);
}
#[test]
fn recover_exprs_unparseable_dsl_returns_empty_without_panic() {
let exprs = recover_exprs(&ts("=> => =>"));
assert!(
exprs.is_empty(),
"unparseable DSL must recover nothing, got {} exprs",
exprs.len()
);
}
#[test]
fn tokens_reference_ident_finds_self_in_format_args() {
assert!(
tokens_reference_ident(&ts(r#""[{}]", self.name"#), "self"),
"self inside format! args must be found"
);
}
#[test]
fn tokens_reference_ident_recurses_into_nested_groups() {
assert!(
tokens_reference_ident(&ts("outer(inner(self))"), "self"),
"self nested in groups must be found"
);
}
#[test]
fn tokens_reference_ident_false_when_absent() {
assert!(
!tokens_reference_ident(&ts(r#""[{}]", other.name"#), "self"),
"must not find self when it is not referenced"
);
}
#[test]
fn tokens_reference_ident_ignores_string_literal_and_substring() {
assert!(
!tokens_reference_ident(&ts(r#""self is great""#), "self"),
"self only inside a string literal is not a reference"
);
assert!(
!tokens_reference_ident(&ts("local_self.field"), "self"),
"must not substring-match `self` inside `local_self`"
);
}
#[test]
fn idents_in_call_position_harvests_calls_and_component_renders() {
let ids: Vec<String> = idents_in_call_position(&ts("Component { child: Leaf(x) }")).collect();
for name in ["Component", "Leaf"] {
assert!(
ids.iter().any(|i| i == name),
"call/construction-position ident `{name}` must be collected, got {ids:?}"
);
}
for name in ["child", "x"] {
assert!(
!ids.iter().any(|i| i == name),
"non-call ident `{name}` must be excluded, got {ids:?}"
);
}
}
#[test]
fn idents_in_call_position_excludes_lowercase_dsl_element_tag() {
let ids: Vec<String> =
idents_in_call_position(&ts(r#"div { class: "log", Widget {} }"#)).collect();
assert!(
!ids.iter().any(|i| i == "div"),
"lowercase element tag `div` must NOT be harvested, got {ids:?}"
);
assert!(
ids.iter().any(|i| i == "Widget"),
"UpperCamelCase component `Widget` must still be harvested, got {ids:?}"
);
}
#[test]
fn idents_in_call_position_keeps_lowercase_call_form() {
let ids: Vec<String> = idents_in_call_position(&ts("div { onclick: helper() }")).collect();
assert!(
ids.iter().any(|i| i == "helper"),
"lowercase call `helper()` must still be harvested, got {ids:?}"
);
}
#[test]
fn idents_in_call_position_excludes_prop_named_like_a_function() {
let ids: Vec<String> = idents_in_call_position(&ts(r#"Widget { helper: "x" }"#)).collect();
assert!(ids.iter().any(|i| i == "Widget"), "Widget render harvested");
assert!(
!ids.iter().any(|i| i == "helper"),
"prop key `helper` must NOT be harvested, got {ids:?}"
);
}