1use anyhow::Result;
8use quote::ToTokens;
9use serde::{Deserialize, Serialize};
10use syn::{parse_file, Item, ItemEnum, ItemFn, ItemImpl, ItemMod, ItemStruct, Signature};
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct RustAnalysis {
18 pub file_path: String,
20
21 pub modules: Vec<ModuleInfo>,
23
24 pub functions: Vec<FunctionInfo>,
26
27 pub structs: Vec<StructInfo>,
29
30 pub enums: Vec<EnumInfo>,
32
33 pub implementations: Vec<ImplInfo>,
35
36 pub ast_summary: String,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct ModuleInfo {
43 pub name: String,
45
46 pub visibility: String,
48
49 pub items_count: usize,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct FunctionInfo {
56 pub name: String,
58
59 pub visibility: String,
61
62 pub is_async: bool,
64
65 pub parameters: Vec<String>,
67
68 pub return_type: Option<String>,
70
71 pub documentation: Option<String>,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct StructInfo {
78 pub name: String,
80
81 pub visibility: String,
83
84 pub fields: Vec<FieldInfo>,
86
87 pub documentation: Option<String>,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct FieldInfo {
94 pub name: String,
96
97 pub field_type: String,
99
100 pub visibility: String,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct EnumInfo {
107 pub name: String,
109
110 pub visibility: String,
112
113 pub variants: Vec<String>,
115
116 pub documentation: Option<String>,
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct ImplInfo {
123 pub target: String,
125
126 pub trait_name: Option<String>,
128
129 pub methods: Vec<FunctionInfo>,
131}
132
133pub struct RustParser;
138
139impl RustParser {
140 pub fn parse_rust_file(file_path: &str, content: &str) -> Result<RustAnalysis> {
181 let syntax_tree = parse_file(content)?;
182
183 let mut analysis = RustAnalysis {
184 file_path: file_path.to_string(),
185 modules: Vec::new(),
186 functions: Vec::new(),
187 structs: Vec::new(),
188 enums: Vec::new(),
189 implementations: Vec::new(),
190 ast_summary: String::new(),
191 };
192
193 for item in &syntax_tree.items {
195 match item {
196 Item::Mod(item_mod) => {
197 analysis.modules.push(Self::parse_module(item_mod));
198 }
199 Item::Fn(item_fn) => {
200 analysis.functions.push(Self::parse_function(item_fn));
201 }
202 Item::Struct(item_struct) => {
203 analysis.structs.push(Self::parse_struct(item_struct));
204 }
205 Item::Enum(item_enum) => {
206 analysis.enums.push(Self::parse_enum(item_enum));
207 }
208 Item::Impl(item_impl) => {
209 analysis.implementations.push(Self::parse_impl(item_impl));
210 }
211 _ => {}
212 }
213 }
214
215 analysis.ast_summary = Self::generate_ast_summary(&analysis);
216
217 Ok(analysis)
218 }
219
220 fn parse_module(item: &ItemMod) -> ModuleInfo {
221 let items_count = item
222 .content
223 .as_ref()
224 .map(|(_, items)| items.len())
225 .unwrap_or(0);
226
227 ModuleInfo {
228 name: item.ident.to_string(),
229 visibility: Self::parse_visibility(&item.vis),
230 items_count,
231 }
232 }
233
234 fn parse_function(item: &ItemFn) -> FunctionInfo {
235 let sig = &item.sig;
236
237 FunctionInfo {
238 name: sig.ident.to_string(),
239 visibility: Self::parse_visibility(&item.vis),
240 is_async: sig.asyncness.is_some(),
241 parameters: Self::parse_parameters(sig),
242 return_type: Self::parse_return_type(sig),
243 documentation: Self::extract_doc_comments(&item.attrs),
244 }
245 }
246
247 fn parse_struct(item: &ItemStruct) -> StructInfo {
248 let fields = match &item.fields {
249 syn::Fields::Named(fields) => fields
250 .named
251 .iter()
252 .map(|f| FieldInfo {
253 name: f.ident.as_ref().unwrap().to_string(),
254 field_type: f.ty.to_token_stream().to_string(),
255 visibility: Self::parse_visibility(&f.vis),
256 })
257 .collect(),
258 syn::Fields::Unnamed(fields) => fields
259 .unnamed
260 .iter()
261 .enumerate()
262 .map(|(i, f)| FieldInfo {
263 name: format!("field_{i}"),
264 field_type: f.ty.to_token_stream().to_string(),
265 visibility: Self::parse_visibility(&f.vis),
266 })
267 .collect(),
268 syn::Fields::Unit => Vec::new(),
269 };
270
271 StructInfo {
272 name: item.ident.to_string(),
273 visibility: Self::parse_visibility(&item.vis),
274 fields,
275 documentation: Self::extract_doc_comments(&item.attrs),
276 }
277 }
278
279 fn parse_enum(item: &ItemEnum) -> EnumInfo {
280 let variants = item.variants.iter().map(|v| v.ident.to_string()).collect();
281
282 EnumInfo {
283 name: item.ident.to_string(),
284 visibility: Self::parse_visibility(&item.vis),
285 variants,
286 documentation: Self::extract_doc_comments(&item.attrs),
287 }
288 }
289
290 fn parse_impl(item: &ItemImpl) -> ImplInfo {
291 let target = item.self_ty.to_token_stream().to_string();
292 let trait_name = item
293 .trait_
294 .as_ref()
295 .map(|(_, path, _)| path.to_token_stream().to_string());
296
297 let methods = item
298 .items
299 .iter()
300 .filter_map(|item| {
301 if let syn::ImplItem::Fn(method) = item {
302 Some(FunctionInfo {
303 name: method.sig.ident.to_string(),
304 visibility: Self::parse_visibility(&method.vis),
305 is_async: method.sig.asyncness.is_some(),
306 parameters: Self::parse_parameters(&method.sig),
307 return_type: Self::parse_return_type(&method.sig),
308 documentation: Self::extract_doc_comments(&method.attrs),
309 })
310 } else {
311 None
312 }
313 })
314 .collect();
315
316 ImplInfo {
317 target,
318 trait_name,
319 methods,
320 }
321 }
322
323 fn parse_visibility(vis: &syn::Visibility) -> String {
324 match vis {
325 syn::Visibility::Public(_) => "pub".to_string(),
326 syn::Visibility::Restricted(restricted) => {
327 format!("pub({})", restricted.path.to_token_stream())
328 }
329 syn::Visibility::Inherited => "private".to_string(),
330 }
331 }
332
333 fn parse_parameters(sig: &Signature) -> Vec<String> {
334 sig.inputs
335 .iter()
336 .map(|input| match input {
337 syn::FnArg::Receiver(receiver) => {
338 if receiver.mutability.is_some() {
339 "&mut self".to_string()
340 } else {
341 "&self".to_string()
342 }
343 }
344 syn::FnArg::Typed(typed) => {
345 format!(
346 "{}: {}",
347 typed.pat.to_token_stream(),
348 typed.ty.to_token_stream()
349 )
350 }
351 })
352 .collect()
353 }
354
355 fn parse_return_type(sig: &Signature) -> Option<String> {
356 match &sig.output {
357 syn::ReturnType::Default => None,
358 syn::ReturnType::Type(_, ty) => Some(ty.to_token_stream().to_string()),
359 }
360 }
361
362 fn extract_doc_comments(attrs: &[syn::Attribute]) -> Option<String> {
363 let mut doc_comments = Vec::new();
364
365 for attr in attrs {
366 if attr.path().is_ident("doc") {
367 if let Ok(syn::Lit::Str(lit_str)) = attr.parse_args() {
368 doc_comments.push(lit_str.value());
369 }
370 }
371 }
372
373 if doc_comments.is_empty() {
374 None
375 } else {
376 Some(doc_comments.join("\n"))
377 }
378 }
379
380 fn generate_ast_summary(analysis: &RustAnalysis) -> String {
381 let mut summary = String::new();
382
383 summary.push_str(&format!("# AST Summary for {}\n\n", analysis.file_path));
384
385 if !analysis.modules.is_empty() {
386 summary.push_str("## Modules\n");
387 for module in &analysis.modules {
388 summary.push_str(&format!(
389 "- `{}` ({}) - {} items\n",
390 module.name, module.visibility, module.items_count
391 ));
392 }
393 summary.push('\n');
394 }
395
396 if !analysis.structs.is_empty() {
397 summary.push_str("## Structs\n");
398 for struct_info in &analysis.structs {
399 summary.push_str(&format!(
400 "- `{}` ({}) - {} fields\n",
401 struct_info.name,
402 struct_info.visibility,
403 struct_info.fields.len()
404 ));
405 }
406 summary.push('\n');
407 }
408
409 if !analysis.enums.is_empty() {
410 summary.push_str("## Enums\n");
411 for enum_info in &analysis.enums {
412 summary.push_str(&format!(
413 "- `{}` ({}) - {} variants\n",
414 enum_info.name,
415 enum_info.visibility,
416 enum_info.variants.len()
417 ));
418 }
419 summary.push('\n');
420 }
421
422 if !analysis.functions.is_empty() {
423 summary.push_str("## Functions\n");
424 for func in &analysis.functions {
425 let async_marker = if func.is_async { "async " } else { "" };
426 summary.push_str(&format!(
427 "- `{}{}{}` ({})\n",
428 async_marker,
429 func.name,
430 if func.parameters.is_empty() {
431 "()"
432 } else {
433 "(...)"
434 },
435 func.visibility
436 ));
437 }
438 summary.push('\n');
439 }
440
441 if !analysis.implementations.is_empty() {
442 summary.push_str("## Implementations\n");
443 for impl_info in &analysis.implementations {
444 let trait_part = impl_info
445 .trait_name
446 .as_ref()
447 .map(|t| format!("{t} for "))
448 .unwrap_or_default();
449 summary.push_str(&format!("- `impl {}{}`\n", trait_part, impl_info.target));
450 }
451 }
452
453 summary
454 }
455}