From 4b7aff9062819843a98976d730fadf2189cce765 Mon Sep 17 00:00:00 2001 From: Stephen Adamson Date: Fri, 14 Nov 2025 17:26:06 +0000 Subject: [PATCH] import and call nodes fix --- mcp_codebase.py | 89 +++++++++++++++++++++++++++++-------------------- 1 file changed, 52 insertions(+), 37 deletions(-) diff --git a/mcp_codebase.py b/mcp_codebase.py index 476c813..a2587f4 100644 --- a/mcp_codebase.py +++ b/mcp_codebase.py @@ -1628,11 +1628,15 @@ def parse_rust_file_cached(filepath_str: str, file_hash: str) -> tuple: except Exception as e: logger.warning(f"Rust AST helper failed for {filepath}: {e}; falling back to regex") - # Fallback: Regex-based Rust parsing - logger.info(f" Falling back to regex parsing for {filepath}") - regex_chunks = _parse_rust_with_regex(filepath, code, lines) - chunks.extend(regex_chunks) - logger.info(f" Regex found {len(regex_chunks)} chunks") + + # --- Import extraction -------------------------------------------- + import_re = re.compile(r'^\s*use\s+([^;]+);', re.MULTILINE) + + # Fallback: Regex-based Rust parsing + logger.info(f" Falling back to regex parsing for {filepath}") + regex_chunks = _parse_rust_with_regex(filepath, code, lines, import_re) + chunks.extend(regex_chunks) + logger.info(f" Regex found {len(regex_chunks)} chunks") except Exception as e: logger.warning(f"parse_rust_file failed {filepath}: {e}") @@ -1641,13 +1645,13 @@ def parse_rust_file_cached(filepath_str: str, file_hash: str) -> tuple: logger.info(f"📦 Final result: {len(final_result)} chunks with metadata") return final_result -def _parse_rust_with_regex(filepath: Path, code: str, lines: list) -> list: +def _parse_rust_with_regex(filepath: Path, code: str, lines: list, import_re) -> list: """Fallback regex-based Rust parser.""" chunks = [] # Pattern for Rust functions (including async, unsafe, methods) func_pattern = re.compile( - r'^(?:pub\s+)?(?:async\s+)?(?:unsafe\s+)?fn\s+(\w+)\s*[<(]', + r'^(?:pub\\s+)?(?:async\\s+)?(?:unsafe\\s+)?fn\\s+(\\w+)\\s*[\u003c(]', re.MULTILINE ) @@ -1683,15 +1687,20 @@ def _parse_rust_with_regex(filepath: Path, code: str, lines: list) -> list: # Parse functions for match in func_pattern.finditer(code): - func_name = match.group(1) start_pos = match.start() - start_line = code[:start_pos].count('\n') + start_line = code[:start_pos].count('\\n') end_line = _find_rust_brace_block_end(lines, start_line) func_code = "".join(lines[start_line:end_line + 1]) - # Enhanced function analysis - is_async = 'async' in match.group(0) - is_unsafe = 'unsafe' in match.group(0) + # Extract function name + func_name = match.group(1) + + # Extract visibility (pub or private) + visibility = "pub" if "pub" in func_code else "private" + + # Detect async/unsafe + is_async = "async" in func_code + is_unsafe = "unsafe" in func_code is_pub = 'pub' in match.group(0) chunk_text = f"File: {filepath}\nType: function\nName: {func_name}\n" @@ -2263,8 +2272,17 @@ def build_indexes(): graph.add_edge(file_node_id, node_id, "contains") # ---------- IMPORTS ---------- - for imp in m.get("imports", []): - # Find an existing module node with this name. + # The imports metadata might be a list or a comma-separated string + # We need to handle both cases + 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()] + else: + imports = imports_raw + + for imp in imports: + # Try to find an existing node for this import candidate_id = None for node_id, data in created_nodes.items(): if data.get("type") in {"module", "import"} and data.get("name") == imp: @@ -2272,14 +2290,10 @@ def build_indexes(): break if not candidate_id: - # If nothing exists, fall back to a generic import node. + # No existing node found - create a placeholder candidate_id = f"import::{imp}" - # ---- Import node -------------------------------------------- - # For imports we want a node that mirrors the target module if - # it exists; otherwise we still create a stub with language & - # file attributes for consistency. if candidate_id not in graph.graph.nodes: - # First try to find a module node with that name + # Double-check for a module node with this name found = False for n_id, n_data in created_nodes.items(): if n_data.get("name") == imp and n_data.get("type") == "module": @@ -2287,8 +2301,7 @@ def build_indexes(): found = True break if not found: - # Generic import stub - candidate_id = f"import::{imp}" + # Create a generic import node graph.add_node( candidate_id, type="Import", @@ -2296,13 +2309,20 @@ def build_indexes(): lang="unknown", file="", ) - graph.add_edge(file_node_id, candidate_id, "imports") - + graph.add_edge(file_node_id, candidate_id, "imports") + # Create the connection graph.add_edge(file_node_id, candidate_id, "imports") # ---------- FUNCTION CALLS ---------- - for call in m.get("calls", []): - # Try to find a function/method node with this name. + calls_raw = m.get("calls", []) + if isinstance(calls_raw, str): + # Split the string back into a list + calls = [c.strip() for c in calls_raw.split(",") if c.strip()] + else: + calls = calls_raw + + for call in calls: + # Try to find the function/method being called target_id = None for node_id, data in created_nodes.items(): if data.get("type") in {"function", "method", "constructor"} and data.get("name") == call: @@ -2310,14 +2330,10 @@ def build_indexes(): break if not target_id: - # Fallback to a generic call node. + # No existing function found - create a placeholder target_id = f"call::{call}" - # ---- Call node --------------------------------------------- - # If we already know the target node (by name), reuse it. Otherwise - # create a generic node that still carries language & file context - # so it can be displayed or filtered later. if target_id not in graph.graph.nodes: - # Attempt a best‑effort lookup for the actual function node + # Double-check for a matching function found = False for n_id, n_data in created_nodes.items(): if ( @@ -2328,8 +2344,7 @@ def build_indexes(): found = True break if not found: - # Fallback to a generic FunctionCall node - target_id = f"call::{call}" + # Create a generic function call node graph.add_node( target_id, type="FunctionCall", @@ -2337,8 +2352,8 @@ def build_indexes(): lang="unknown", file="", ) - graph.add_edge(node_id, target_id, "calls") - + graph.add_edge(node_id, target_id, "calls") + # Create the connection graph.add_edge(node_id, target_id, "calls") @@ -2517,7 +2532,7 @@ def _connect_impl_relationships(graph, node_id, node_data): def build_bevy_relationships(graph, created_nodes): """Build Bevy-specific relationships""" - connections = 0 + # Extract return type = 0 # Find all plugin structs and impls plugin_structs = {}