diff --git a/mcp_codebase.py b/mcp_codebase.py index 0d57198..a9a5e96 100644 --- a/mcp_codebase.py +++ b/mcp_codebase.py @@ -828,7 +828,7 @@ def parse_go_file_cached(filepath_str: str, file_hash: str) -> tuple: chunk_text += f"Code:\n{chunk_code}" # Build comprehensive metadata - metadata = { + parse_rust_file_cached = { "file": str(filepath), "name": name, "type": typ, @@ -1648,6 +1648,26 @@ def parse_rust_file_cached(filepath_str: str, file_hash: str) -> tuple: except Exception as e: logger.warning(f"parse_rust_file failed {filepath}: {e}") + # Very simple call extractor: finds foo(), bar::(), module::foo() + call_re = re.compile(r'([A-Za-z_][A-Za-z0-9_]*)\s*!\s*\(|([A-Za-z_][A-Za-z0-9_:]*)\s*\(', re.MULTILINE) + calls = [] + + for match in call_re.findall(code): + name1, name2 = match + name = name1 or name2 + if "::" in name: + name = name.split("::")[-1] + # filter out keywords and type constructors + if name not in ["if", "for", "match", "while", "loop"]: + calls.append(name) + + # attach to every chunk belonging to the file + for c in chunks: + md = dict(c["metadata"]) + md["calls"] = calls + c["metadata"] = md + + final_result = tuple((c["text"], tuple(c["metadata"].items())) for c in chunks) logger.info(f"📦 Final result: {len(final_result)} chunks with metadata") return final_result @@ -2124,6 +2144,10 @@ def build_indexes(): if not name or node_type == "unknown" or not fpath: continue + # convert rust defs + if "implements_traits" in m and m["implements_traits"]: + m["implements"] = m["implements_traits"] + # ---- File node --------------------------------------------- # Keep the same ID (`file::`) but store the full set of # attributes so that downstream code can rely on `name`, `lang`, @@ -2194,6 +2218,15 @@ def build_indexes(): param_parts = param.split(':') if len(param_parts) >= 2: param_type = param_parts[-1].strip() + param_type = ( + param_type + .replace('&', '') + .replace('mut ', '') + .replace('<', '') + .replace('>', '') + .replace(',', '') + .strip() + ) param_type = param_type.replace('&', '').replace('mut ', '').strip() if (param_type and param_type not in ['self', '&self', '&mut self', 'mut self'] and @@ -2222,6 +2255,15 @@ def build_indexes(): param_parts = param.split(':') if len(param_parts) >= 2: param_type = param_parts[-1].strip() + param_type = ( + param_type + .replace('&', '') + .replace('mut ', '') + .replace('<', '') + .replace('>', '') + .replace(',', '') + .strip() + ) param_type = param_type.replace('&', '').replace('mut ', '').strip() if (param_type and param_type not in ['self', '&self', '&mut self', 'mut self'] and @@ -2286,9 +2328,10 @@ def build_indexes(): graph.add_edge(file_node_id, node_id, "contains") # ---------- IMPORTS ---------- - # The imports metadata might be a list or a comma-separated string - # We need to handle both cases - imports_raw = m.get("imports", []) + if node_type == "import" and "parameters" in m: + imports_raw = m["parameters"] # override metadata pathway + else: + imports_raw = m.get("imports", []) if isinstance(imports_raw, str): # Split the string back into a list imports = [imp.strip() for imp in imports_raw.split(",") if imp.strip()] diff --git a/tools/src/main.rs b/tools/src/main.rs index 6e43b5f..2ce2263 100644 --- a/tools/src/main.rs +++ b/tools/src/main.rs @@ -4,8 +4,8 @@ use std::env; use std::fs; use std::process; use syn::{ - spanned::Spanned, FnArg, Item, ItemEnum, ItemFn, ItemImpl, ItemMod, ItemStruct, ItemTrait, Pat, - Type, Visibility, + spanned::Spanned, FnArg, Item, ItemEnum, ItemFn, ItemImpl, ItemMod, ItemStruct, ItemTrait, + ItemUse, Pat, Type, Visibility, }; #[derive(Serialize)] @@ -88,6 +88,27 @@ fn main() { } } + for item in &syntax.items { + if let Item::Use(item_use) = item { + let path = path_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); } @@ -106,13 +127,13 @@ fn parse_function(item_fn: &ItemFn) -> Option { let mut parameters = Vec::new(); for input in &item_fn.sig.inputs { if let FnArg::Typed(pat_type) = input { - // pat_type.pat is Box - if let Pat::Ident(pat_ident) = &*pat_type.pat { - parameters.push(pat_ident.ident.to_string()); + let name = if let Pat::Ident(pat_ident) = &*pat_type.pat { + pat_ident.ident.to_string() } else { - // fallback: try to stringify the pattern - parameters.push(format!("{}", quote::ToTokens::to_token_stream(&pat_type.pat))); - } + 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()); } @@ -263,12 +284,12 @@ fn parse_impl(item_impl: &ItemImpl) -> Option { .collect(); // If this impl implements a trait, extract its path - let traits: Vec = item_impl + let implements = item_impl .trait_ .as_ref() .map(|(_, path, _)| path_to_string(path)) .into_iter() - .collect(); + .collect::>(); Some(RustDecl { name: format!("impl_{}", target), @@ -284,7 +305,7 @@ fn parse_impl(item_impl: &ItemImpl) -> Option { .iter() .map(generic_param_to_string) .collect(), - traits, + traits: implements.clone(), fields: Vec::new(), methods, return_type: None,