// tools/src/main.rs use serde::Serialize; use std::env; use std::fs; use std::process; use syn::{ spanned::Spanned, FnArg, Item, ItemEnum, ItemFn, ItemImpl, ItemMod, ItemStruct, ItemTrait, ItemUse, Pat, Type, Visibility, }; #[derive(Serialize)] struct RustDecl { name: String, type_: String, start_line: usize, end_line: usize, visibility: String, is_async: bool, is_unsafe: bool, generics: Vec, traits: Vec, fields: Vec, methods: Vec, return_type: Option, parameters: Vec, } fn main() { let args: Vec = env::args().collect(); if args.len() < 2 { eprintln!("Usage: {} ", args[0]); process::exit(1); } let filename = &args[1]; let content = match fs::read_to_string(filename) { Ok(content) => content, Err(e) => { eprintln!("ERR: Failed to read file {}: {}", filename, e); process::exit(2); } }; let syntax = match syn::parse_file(&content) { Ok(syntax) => syntax, Err(e) => { eprintln!("ERR: Failed to parse Rust file {}: {}", filename, e); process::exit(3); } }; let mut decls = Vec::new(); // Process declarations for item in syntax.items { match item { Item::Fn(item_fn) => { if let Some(decl) = parse_function(&item_fn) { decls.push(decl); } } Item::Struct(item_struct) => { if let Some(decl) = parse_struct(&item_struct) { decls.push(decl); } } Item::Enum(item_enum) => { if let Some(decl) = parse_enum(&item_enum) { decls.push(decl); } } Item::Trait(item_trait) => { if let Some(decl) = parse_trait(&item_trait) { decls.push(decl); } } Item::Impl(item_impl) => { if let Some(decl) = parse_impl(&item_impl) { decls.push(decl); } } Item::Mod(item_mod) => { if let Some(decl) = parse_mod(&item_mod) { decls.push(decl); } } _ => {} } } for item in &syntax.items { if let Item::Use(item_use) = item { let path = use_tree_to_string(&item_use.tree); decls.push(RustDecl { name: path.clone(), type_: "import".to_string(), start_line: 0, end_line: 0, visibility: "".to_string(), is_async: false, is_unsafe: false, generics: vec![], traits: vec![], fields: vec![], methods: vec![], return_type: None, parameters: vec![path], }); } } let output = serde_json::to_string(&decls).unwrap_or_else(|_| "[]".to_string()); println!("{}", output); } fn span_start_end(node: &T) -> (usize, usize) { // Requires proc-macro2 "span-locations" feature. let span = node.span(); let start = span.start().line; let end = span.end().line; (start, end) } fn parse_function(item_fn: &ItemFn) -> Option { let (start_line, end_line) = span_start_end(item_fn); let mut parameters = Vec::new(); for input in &item_fn.sig.inputs { if let FnArg::Typed(pat_type) = input { let name = if let Pat::Ident(pat_ident) = &*pat_type.pat { pat_ident.ident.to_string() } else { format!("{}", quote::ToTokens::to_token_stream(&pat_type.pat)) }; let ty = type_to_string(&*pat_type.ty); parameters.push(format!("{}: {}", name, ty)); } else if let FnArg::Receiver(_) = input { parameters.push("self".to_string()); } } let return_type = match &item_fn.sig.output { syn::ReturnType::Default => None, syn::ReturnType::Type(_, ty) => Some(type_to_string(&*ty)), }; Some(RustDecl { name: item_fn.sig.ident.to_string(), type_: "function".to_string(), start_line, end_line, visibility: visibility_to_string(&item_fn.vis), is_async: item_fn.sig.asyncness.is_some(), is_unsafe: item_fn.sig.unsafety.is_some(), generics: item_fn .sig .generics .params .iter() .map(generic_param_to_string) .collect(), traits: Vec::new(), fields: Vec::new(), methods: Vec::new(), return_type, parameters, }) } fn parse_struct(item_struct: &ItemStruct) -> Option { let (start_line, end_line) = span_start_end(item_struct); let fields: Vec = item_struct .fields .iter() .filter_map(|field| field.ident.as_ref().map(|ident| ident.to_string())) .collect(); Some(RustDecl { name: item_struct.ident.to_string(), type_: "struct".to_string(), start_line, end_line, visibility: visibility_to_string(&item_struct.vis), is_async: false, is_unsafe: false, generics: item_struct .generics .params .iter() .map(generic_param_to_string) .collect(), traits: Vec::new(), fields, methods: Vec::new(), return_type: None, parameters: Vec::new(), }) } fn parse_enum(item_enum: &ItemEnum) -> Option { let (start_line, end_line) = span_start_end(item_enum); let variants: Vec = item_enum .variants .iter() .map(|variant| variant.ident.to_string()) .collect(); Some(RustDecl { name: item_enum.ident.to_string(), type_: "enum".to_string(), start_line, end_line, visibility: visibility_to_string(&item_enum.vis), is_async: false, is_unsafe: false, generics: item_enum .generics .params .iter() .map(generic_param_to_string) .collect(), traits: Vec::new(), fields: variants, methods: Vec::new(), return_type: None, parameters: Vec::new(), }) } fn parse_trait(item_trait: &ItemTrait) -> Option { let (start_line, end_line) = span_start_end(item_trait); let methods: Vec = item_trait .items .iter() .filter_map(|item| { if let syn::TraitItem::Fn(method) = item { Some(method.sig.ident.to_string()) } else { None } }) .collect(); Some(RustDecl { name: item_trait.ident.to_string(), type_: "trait".to_string(), start_line, end_line, visibility: visibility_to_string(&item_trait.vis), is_async: false, is_unsafe: false, generics: item_trait .generics .params .iter() .map(generic_param_to_string) .collect(), traits: Vec::new(), fields: Vec::new(), methods, return_type: None, parameters: Vec::new(), }) } fn parse_impl(item_impl: &ItemImpl) -> Option { let (start_line, end_line) = span_start_end(item_impl); let target = type_to_string(&*item_impl.self_ty); let methods: Vec = item_impl .items .iter() .filter_map(|item| { if let syn::ImplItem::Fn(method) = item { Some(method.sig.ident.to_string()) } else { None } }) .collect(); // If this impl implements a trait, extract its path let implements = item_impl .trait_ .as_ref() .map(|(_, path, _)| path_to_string(path)) .into_iter() .collect::>(); Some(RustDecl { name: format!("impl_{}", target), type_: "impl".to_string(), start_line, end_line, visibility: "".to_string(), is_async: false, is_unsafe: item_impl.unsafety.is_some(), generics: item_impl .generics .params .iter() .map(generic_param_to_string) .collect(), traits: implements.clone(), fields: Vec::new(), methods, return_type: None, parameters: Vec::new(), }) } fn parse_mod(item_mod: &ItemMod) -> Option { let (start_line, end_line) = span_start_end(item_mod); Some(RustDecl { name: item_mod.ident.to_string(), type_: "module".to_string(), start_line, end_line, visibility: visibility_to_string(&item_mod.vis), is_async: false, is_unsafe: false, generics: Vec::new(), traits: Vec::new(), fields: Vec::new(), methods: Vec::new(), return_type: None, parameters: Vec::new(), }) } fn visibility_to_string(vis: &Visibility) -> String { match vis { Visibility::Public(_) => "pub".to_string(), Visibility::Restricted(r) => { // pub(crate) or pub(in ...) format!("pub({})", quote::ToTokens::to_token_stream(r).to_string()) } _ => "private".to_string(), } } fn type_to_string(ty: &Type) -> String { // Simple string representation for common types match ty { Type::Path(type_path) => type_path .path .segments .last() .map(|seg| seg.ident.to_string()) .unwrap_or_else(|| "unknown".to_string()), Type::Reference(type_ref) => { // type_ref.elem is Box format!("&{}", type_to_string(&*type_ref.elem)) } _ => quote::ToTokens::to_token_stream(ty).to_string(), } } fn path_to_string(path: &syn::Path) -> String { path.segments .iter() .map(|seg| seg.ident.to_string()) .collect::>() .join("::") } fn generic_param_to_string(param: &syn::GenericParam) -> String { match param { syn::GenericParam::Type(ty) => ty.ident.to_string(), syn::GenericParam::Lifetime(lf) => lf.lifetime.to_string(), syn::GenericParam::Const(cnst) => cnst.ident.to_string(), } } fn use_tree_to_string(tree: &syn::UseTree) -> String { match tree { syn::UseTree::Path(p) => { let base = p.ident.to_string(); let nested = use_tree_to_string(&*p.tree); format!("{}::{}", base, nested) } syn::UseTree::Name(n) => n.ident.to_string(), syn::UseTree::Rename(r) => r.ident.to_string(), syn::UseTree::Glob(_) => "*".to_string(), syn::UseTree::Group(g) => { let items: Vec = g.items.iter().map(use_tree_to_string).collect(); items.join("::") } } }