reduce imports
This commit is contained in:
+94
-50
@@ -2505,7 +2505,7 @@ def parse_rust_file(filepath: Path) -> List[Dict]:
|
|||||||
|
|
||||||
@lru_cache(maxsize=1000)
|
@lru_cache(maxsize=1000)
|
||||||
def parse_rust_file_cached(filepath_str: str, file_hash: str) -> tuple:
|
def parse_rust_file_cached(filepath_str: str, file_hash: str) -> tuple:
|
||||||
"""Fixed Rust parsing that properly handles imports as metadata, not separate entities."""
|
"""Fixed Rust parsing that aggregates imports and prioritizes code entities."""
|
||||||
filepath = Path(filepath_str)
|
filepath = Path(filepath_str)
|
||||||
chunks = []
|
chunks = []
|
||||||
|
|
||||||
@@ -2530,19 +2530,23 @@ def parse_rust_file_cached(filepath_str: str, file_hash: str) -> tuple:
|
|||||||
decls = json.loads(proc.stdout)
|
decls = json.loads(proc.stdout)
|
||||||
logger.info(f" AST helper found {len(decls)} declarations")
|
logger.info(f" AST helper found {len(decls)} declarations")
|
||||||
|
|
||||||
# Collect imports first to attach to actual code entities
|
# Collect imports to attach to code entities, but don't create separate import chunks
|
||||||
file_imports = []
|
file_imports = []
|
||||||
code_decls = []
|
code_decls = []
|
||||||
|
|
||||||
for d in decls:
|
for d in decls:
|
||||||
if d.get("type_") == "import":
|
if d.get("type_") == "import":
|
||||||
file_imports.extend(d.get("parameters", []))
|
# Collect import paths but don't create chunks for them
|
||||||
|
import_paths = d.get("parameters", [])
|
||||||
|
file_imports.extend(import_paths)
|
||||||
|
logger.debug(f" Found import: {import_paths}")
|
||||||
else:
|
else:
|
||||||
|
# Only process actual code entities
|
||||||
code_decls.append(d)
|
code_decls.append(d)
|
||||||
|
|
||||||
logger.info(f" Found {len(file_imports)} imports and {len(code_decls)} code declarations")
|
logger.info(f" Found {len(file_imports)} imports to attach to {len(code_decls)} code entities")
|
||||||
|
|
||||||
# Process actual code entities (functions, structs, etc.)
|
# Process ACTUAL CODE entities (functions, structs, etc.)
|
||||||
for i, d in enumerate(code_decls):
|
for i, d in enumerate(code_decls):
|
||||||
start_line = max(1, d.get("start_line", 1))
|
start_line = max(1, d.get("start_line", 1))
|
||||||
end_line = max(start_line, d.get("end_line", start_line + 1))
|
end_line = max(start_line, d.get("end_line", start_line + 1))
|
||||||
@@ -2555,26 +2559,34 @@ def parse_rust_file_cached(filepath_str: str, file_hash: str) -> tuple:
|
|||||||
|
|
||||||
chunk_code = "".join(lines[start_line-1:end_line])
|
chunk_code = "".join(lines[start_line-1:end_line])
|
||||||
|
|
||||||
|
# Skip if we don't have meaningful content
|
||||||
|
if not chunk_code.strip() or len(chunk_code.strip()) < 10:
|
||||||
|
continue
|
||||||
|
|
||||||
metadata = {
|
metadata = {
|
||||||
"file": str(filepath),
|
"file": str(filepath),
|
||||||
"name": d.get("name", "") or filepath.stem,
|
"name": d.get("name", "") or filepath.stem,
|
||||||
"type": d.get("type_", "") or "unknown",
|
"type": d.get("type_", "") or "unknown",
|
||||||
"line": start_line,
|
"line": start_line,
|
||||||
"language": "rust",
|
"language": "rust",
|
||||||
"imports": file_imports, # Attach imports to ALL code entities in this file
|
"imports": file_imports, # Attach ALL file imports to each code entity
|
||||||
}
|
}
|
||||||
|
|
||||||
# Enhanced metadata extraction for code entities
|
# Enhanced metadata extraction for code entities
|
||||||
enhanced_fields = [
|
enhanced_fields = [
|
||||||
"visibility", "is_async", "is_unsafe", "generics", "traits",
|
"visibility", "is_async", "is_unsafe", "generics", "traits",
|
||||||
"fields", "methods", "return_type", "parameters", "docstring",
|
"fields", "methods", "return_type", "parameters", "docstring",
|
||||||
"calls" # calls from the specific entity
|
"calls"
|
||||||
]
|
]
|
||||||
|
|
||||||
for field in enhanced_fields:
|
for field in enhanced_fields:
|
||||||
if field in d and d[field] is not None:
|
if field in d and d[field] is not None:
|
||||||
metadata[field] = d[field]
|
metadata[field] = d[field]
|
||||||
|
|
||||||
|
# Skip if it's just a module declaration without real content
|
||||||
|
if metadata["type"] == "module" and not metadata.get("docstring") and len(chunk_code.strip()) < 20:
|
||||||
|
continue
|
||||||
|
|
||||||
logger.info(f" Code {i+1}: {metadata.get('type', 'unknown')} {metadata.get('name', 'unnamed')}")
|
logger.info(f" Code {i+1}: {metadata.get('type', 'unknown')} {metadata.get('name', 'unnamed')}")
|
||||||
|
|
||||||
# Build rich chunk text for CODE entities
|
# Build rich chunk text for CODE entities
|
||||||
@@ -2584,73 +2596,79 @@ def parse_rust_file_cached(filepath_str: str, file_hash: str) -> tuple:
|
|||||||
"metadata": metadata
|
"metadata": metadata
|
||||||
})
|
})
|
||||||
|
|
||||||
# If we have no code declarations but have imports, create a file-level chunk
|
# If we have no code declarations but have meaningful content, create file-level chunk
|
||||||
if not chunks and file_imports:
|
if not chunks and len(code.strip()) > 100:
|
||||||
logger.info(" Creating file-level chunk for imports-only file")
|
logger.info(" Creating file-level chunk for content-rich file")
|
||||||
chunks.append(create_file_level_chunk(filepath, file_imports))
|
chunks.append(create_file_level_chunk(filepath, file_imports, code))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Rust AST helper failed for {filepath}: {e}")
|
logger.warning(f"Rust AST helper failed for {filepath}: {e}")
|
||||||
return create_fallback_chunk(filepath)
|
return create_fallback_chunk(filepath)
|
||||||
|
|
||||||
if chunks:
|
if chunks:
|
||||||
logger.info(f"✅ Successfully parsed {len(chunks)} chunks from {filepath}")
|
logger.info(f"✅ Successfully parsed {len(chunks)} code chunks from {filepath}")
|
||||||
return tuple((c["text"], tuple(c["metadata"].items())) for c in chunks)
|
return tuple((c["text"], tuple(c["metadata"].items())) for c in chunks)
|
||||||
|
|
||||||
logger.warning(f"❌ No chunks created from Rust AST for {filepath}. Emitting fallback.")
|
logger.warning(f"❌ No meaningful code chunks from Rust AST for {filepath}. Emitting fallback.")
|
||||||
return create_fallback_chunk(filepath)
|
return create_fallback_chunk(filepath)
|
||||||
|
|
||||||
def build_rust_code_chunk_text(metadata: dict, code: str) -> str:
|
def build_rust_code_chunk_text(metadata: dict, code: str) -> str:
|
||||||
"""Build comprehensive Rust code chunk text."""
|
"""Build comprehensive Rust code chunk text - focused on ACTUAL CODE."""
|
||||||
parts = [
|
parts = [
|
||||||
f"File: {metadata.get('file', '')}",
|
f"File: {metadata.get('file', '')}",
|
||||||
f"Type: {metadata.get('type', 'unknown')}",
|
f"Type: {metadata.get('type', 'unknown')}",
|
||||||
f"Name: {metadata.get('name', '')}",
|
f"Name: {metadata.get('name', '')}",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Add enhanced metadata for CODE entities
|
# Only include the most valuable metadata
|
||||||
if metadata.get("visibility"):
|
if metadata.get("visibility"):
|
||||||
parts.append(f"Visibility: {metadata['visibility']}")
|
parts.append(f"Visibility: {metadata['visibility']}")
|
||||||
if metadata.get("is_async"):
|
|
||||||
parts.append("Async: yes")
|
|
||||||
if metadata.get("is_unsafe"):
|
|
||||||
parts.append("Unsafe: yes")
|
|
||||||
if metadata.get("generics"):
|
if metadata.get("generics"):
|
||||||
parts.append(f"Generics: {', '.join(metadata['generics'])}")
|
parts.append(f"Generics: {', '.join(metadata['generics'])}")
|
||||||
if metadata.get("traits"):
|
if metadata.get("traits"):
|
||||||
parts.append(f"Implements: {', '.join(metadata['traits'])}")
|
parts.append(f"Implements: {', '.join(metadata['traits'])}")
|
||||||
if metadata.get("fields"):
|
if metadata.get("fields"):
|
||||||
parts.append(f"Fields: {', '.join(metadata['fields'])}")
|
parts.append(f"Fields: {', '.join(metadata['fields'])}")
|
||||||
if metadata.get("imports"):
|
|
||||||
parts.append(f"Imports: {', '.join(metadata['imports'])}")
|
|
||||||
if metadata.get("calls"):
|
|
||||||
parts.append(f"Calls: {', '.join(metadata['calls'])}")
|
|
||||||
if metadata.get("docstring"):
|
if metadata.get("docstring"):
|
||||||
parts.append(f"Doc: {metadata['docstring']}")
|
# Only include substantial docstrings
|
||||||
|
doc = metadata['docstring']
|
||||||
|
if len(doc) > 20 and len(doc) < 300:
|
||||||
|
parts.append(f"Doc: {doc}")
|
||||||
|
|
||||||
|
# Include imports and calls only if they exist and are meaningful
|
||||||
|
meaningful_imports = [imp for imp in metadata.get("imports", [])
|
||||||
|
if len(imp) > 3 and not imp.startswith(('c:', 'b:', 'p:', 'r:', 't:', 'v:'))]
|
||||||
|
if meaningful_imports:
|
||||||
|
parts.append(f"Imports: {', '.join(meaningful_imports[:5])}") # Limit to top 5
|
||||||
|
|
||||||
|
meaningful_calls = [call for call in metadata.get("calls", [])
|
||||||
|
if len(call) > 2 and not call in ['if', 'for', 'let', 'mut', 'pub']]
|
||||||
|
if meaningful_calls:
|
||||||
|
parts.append(f"Calls: {', '.join(meaningful_calls[:5])}") # Limit to top 5
|
||||||
|
|
||||||
parts.append(f"Code:\n{code}")
|
parts.append(f"Code:\n{code}")
|
||||||
return "\n".join(parts)
|
return "\n".join(parts)
|
||||||
|
|
||||||
def create_file_level_chunk(filepath: Path, imports: list) -> dict:
|
def create_file_level_chunk(filepath: Path, imports: list, code: str) -> dict:
|
||||||
"""Create a chunk for files that only have imports."""
|
"""Create a file-level chunk only for files with substantial content."""
|
||||||
try:
|
|
||||||
full_code = filepath.read_text(encoding="utf-8")
|
|
||||||
except Exception:
|
|
||||||
full_code = ""
|
|
||||||
|
|
||||||
metadata = {
|
metadata = {
|
||||||
"file": str(filepath),
|
"file": str(filepath),
|
||||||
"name": filepath.stem,
|
"name": filepath.stem,
|
||||||
"type": "module", # Treat import-only files as modules
|
"type": "file",
|
||||||
"line": 1,
|
"line": 1,
|
||||||
"language": "rust",
|
"language": "rust",
|
||||||
"imports": imports,
|
"imports": imports,
|
||||||
}
|
}
|
||||||
|
|
||||||
chunk_text = f"File: {filepath}\nType: module\nName: {filepath.stem}\nImports: {', '.join(imports)}\nCode:\n{full_code}"
|
# Only include meaningful imports
|
||||||
|
meaningful_imports = [imp for imp in imports if len(imp) > 3]
|
||||||
|
|
||||||
|
chunk_text = f"File: {filepath}\nType: file\nName: {filepath.stem}"
|
||||||
|
if meaningful_imports:
|
||||||
|
chunk_text += f"\nImports: {', '.join(meaningful_imports[:3])}"
|
||||||
|
chunk_text += f"\nCode:\n{code}"
|
||||||
|
|
||||||
return {"text": chunk_text, "metadata": metadata}
|
return {"text": chunk_text, "metadata": metadata}
|
||||||
|
|
||||||
def create_fallback_chunk(filepath: Path) -> tuple:
|
def create_fallback_chunk(filepath: Path) -> tuple:
|
||||||
"""Create a minimal fallback chunk with proper metadata."""
|
"""Create a minimal fallback chunk with proper metadata."""
|
||||||
try:
|
try:
|
||||||
@@ -3573,38 +3591,64 @@ def build_indexes():
|
|||||||
# -------------------------------------------------
|
# -------------------------------------------------
|
||||||
|
|
||||||
def _add_import_edges_enhanced(graph, file_node_id, importing_node_id, imports, lang):
|
def _add_import_edges_enhanced(graph, file_node_id, importing_node_id, imports, lang):
|
||||||
"""Enhanced import edge creation that resolves Rust paths to actual files."""
|
"""Enhanced import edge creation that avoids symbolic node spam."""
|
||||||
new_nodes = {}
|
new_nodes = {}
|
||||||
|
|
||||||
for imp_path in imports:
|
for imp_path in imports:
|
||||||
if not imp_path or not isinstance(imp_path, str):
|
if not imp_path or not isinstance(imp_path, str):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Skip standard library and external crates for now
|
# Skip single-letter and obviously symbolic imports
|
||||||
if any(imp_path.startswith(prefix) for prefix in ['std::', 'core::', 'alloc::', 'bevy::', 'serde::']):
|
if len(imp_path.strip()) <= 1 or imp_path in ['c', 'b', 'p', 'r', 't', 'v', 'd', 'm', 'q', 's', 'n', 'f', 'a']:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Try to resolve relative paths within the codebase
|
# Skip common standard library and external crates (they create too much noise)
|
||||||
resolved_path = resolve_rust_import(imp_path, graph.root_path)
|
external_prefixes = ['std::', 'core::', 'alloc::', 'bevy::', 'serde::', 'rayon::', 'rand::']
|
||||||
if resolved_path:
|
if any(imp_path.startswith(prefix) for prefix in external_prefixes):
|
||||||
# Create edge to the actual file
|
# Only create ONE symbolic node for the entire crate, not individual items
|
||||||
target_file_node_id = f"file::{resolved_path}"
|
crate_name = imp_path.split('::')[0]
|
||||||
if not graph.graph.has_edge(importing_node_id, target_file_node_id):
|
if crate_name:
|
||||||
graph.add_edge(importing_node_id, target_file_node_id, "imports")
|
symbolic_id = f"rust::external::{crate_name}"
|
||||||
else:
|
|
||||||
# Create symbolic node for external/unresolved imports
|
|
||||||
symbolic_id = f"rust::external::{imp_path}"
|
|
||||||
if symbolic_id not in graph.graph.nodes:
|
if symbolic_id not in graph.graph.nodes:
|
||||||
graph.add_node(
|
graph.add_node(
|
||||||
symbolic_id,
|
symbolic_id,
|
||||||
type="external",
|
type="external_crate",
|
||||||
name=imp_path,
|
name=crate_name,
|
||||||
file="",
|
file="",
|
||||||
lang="rust",
|
lang="rust",
|
||||||
is_external=True
|
is_external=True
|
||||||
)
|
)
|
||||||
new_nodes[symbolic_id] = {
|
new_nodes[symbolic_id] = {
|
||||||
"type": "external",
|
"type": "external_crate",
|
||||||
|
"name": crate_name,
|
||||||
|
"file": "",
|
||||||
|
"lang": "rust"
|
||||||
|
}
|
||||||
|
if not graph.graph.has_edge(importing_node_id, symbolic_id):
|
||||||
|
graph.add_edge(importing_node_id, symbolic_id, "imports")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# For internal imports, try to resolve to actual files
|
||||||
|
resolved_path = resolve_rust_import(imp_path, graph.root_path)
|
||||||
|
if resolved_path:
|
||||||
|
target_file_node_id = f"file::{resolved_path}"
|
||||||
|
if not graph.graph.has_edge(importing_node_id, target_file_node_id):
|
||||||
|
graph.add_edge(importing_node_id, target_file_node_id, "imports")
|
||||||
|
else:
|
||||||
|
# Only create symbolic nodes for substantial internal paths
|
||||||
|
if '::' in imp_path and len(imp_path) > 5:
|
||||||
|
symbolic_id = f"rust::symbol::{imp_path}"
|
||||||
|
if symbolic_id not in graph.graph.nodes:
|
||||||
|
graph.add_node(
|
||||||
|
symbolic_id,
|
||||||
|
type="symbol",
|
||||||
|
name=imp_path,
|
||||||
|
file="",
|
||||||
|
lang="rust",
|
||||||
|
is_symbolic=True
|
||||||
|
)
|
||||||
|
new_nodes[symbolic_id] = {
|
||||||
|
"type": "symbol",
|
||||||
"name": imp_path,
|
"name": imp_path,
|
||||||
"file": "",
|
"file": "",
|
||||||
"lang": "rust"
|
"lang": "rust"
|
||||||
|
|||||||
Reference in New Issue
Block a user