use syn::{Attribute, FnArg, ItemFn, LitStr, Meta, Type};
use crate::component::utils::extract_generic_type;
#[derive(Debug, Clone)]
pub struct DependencyInfo {
pub component_type: Type,
pub explicit_plugin_name: Option<String>,
pub inferred_plugin_name: String,
}
pub fn analyze_dependencies(func: &ItemFn) -> Vec<DependencyInfo> {
let mut dependencies = Vec::new();
for param in &func.sig.inputs {
if let FnArg::Typed(pat_type) = param {
let ty = &*pat_type.ty;
if is_component_type(ty) {
if let Some(component_type) = extract_generic_type(ty) {
let explicit_name = extract_inject_attr(&pat_type.attrs);
let inferred_name = infer_plugin_name(&component_type);
dependencies.push(DependencyInfo {
component_type,
explicit_plugin_name: explicit_name,
inferred_plugin_name: inferred_name,
});
}
}
}
}
dependencies
}
fn is_component_type(ty: &Type) -> bool {
if let Type::Path(type_path) = ty {
if let Some(segment) = type_path.path.segments.last() {
return segment.ident == "Component";
}
}
false
}
fn extract_inject_attr(attrs: &[Attribute]) -> Option<String> {
for attr in attrs {
if attr.path().is_ident("inject") {
if let Meta::List(meta_list) = &attr.meta {
if let Ok(lit) = syn::parse2::<LitStr>(meta_list.tokens.clone()) {
return Some(lit.value());
}
}
}
}
None
}
fn infer_plugin_name(component_type: &Type) -> String {
let type_name = extract_type_name(component_type);
format!("__Create{}Plugin", type_name)
}
fn extract_type_name(ty: &Type) -> String {
match ty {
Type::Path(type_path) => {
if let Some(segment) = type_path.path.segments.last() {
segment.ident.to_string()
} else {
"Unknown".to_string()
}
}
_ => "Unknown".to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use quote::quote;
use syn::parse2;
#[test]
fn test_analyze_dependencies_no_deps() {
let input = quote! {
fn create_component(Config(config): Config<MyConfig>) -> MyComponent {
MyComponent::new(config)
}
};
let func = parse2::<ItemFn>(input).unwrap();
let deps = analyze_dependencies(&func);
assert_eq!(deps.len(), 0);
}
#[test]
fn test_analyze_dependencies_with_component() {
let input = quote! {
fn create_service(
Component(db): Component<DbConnection>,
) -> MyService {
MyService::new(db)
}
};
let func = parse2::<ItemFn>(input).unwrap();
let deps = analyze_dependencies(&func);
assert_eq!(deps.len(), 1);
assert_eq!(deps[0].inferred_plugin_name, "__CreateDbConnectionPlugin");
}
#[test]
fn test_infer_plugin_name() {
let ty: Type = parse2(quote! { UserRepository }).unwrap();
let name = infer_plugin_name(&ty);
assert_eq!(name, "__CreateUserRepositoryPlugin");
}
#[test]
fn test_extract_type_name() {
let ty: Type = parse2(quote! { DbConnection }).unwrap();
let name = extract_type_name(&ty);
assert_eq!(name, "DbConnection");
}
}