This commit is contained in:
2025-11-15 22:33:55 +00:00
parent 4784070e32
commit 4580638cfd
+136 -103
View File
@@ -2860,11 +2860,11 @@ def build_indexes():
created_nodes = {} created_nodes = {}
# Track language-specific features for cross-language relationships # Track language-specific features for cross-language relationships
component_registry = {} # Svelte components component_registry = {}
interface_registry = {} # TypeScript/Java interfaces interface_registry = {}
trait_registry = {} # Rust traits trait_registry = {}
class_registry = {} # Classes across all languages class_registry = {}
package_registry = {} # Packages/modules across languages package_registry = {}
logger.info("=== Phase 1: Creating nodes ===") logger.info("=== Phase 1: Creating nodes ===")
@@ -2952,7 +2952,6 @@ def build_indexes():
class_node_id = f"{lang}::class::{fpath}::{class_name}" class_node_id = f"{lang}::class::{fpath}::{class_name}"
graph.add_edge(class_node_id, node_id, "contains") graph.add_edge(class_node_id, node_id, "contains")
else: else:
# Regular function, async function, or constructor
graph.add_node( graph.add_node(
node_id, node_id,
**base_attrs, **base_attrs,
@@ -3089,7 +3088,7 @@ def build_indexes():
type=node_type, type=node_type,
variants=m.get("variants", []), variants=m.get("variants", []),
members=m.get("members", []), members=m.get("members", []),
fields=m.get("fields", []), # For Rust enum variants with fields fields=m.get("fields", []),
visibility=m.get("visibility") visibility=m.get("visibility")
) )
@@ -3122,7 +3121,7 @@ def build_indexes():
if module_symbol_id not in graph.graph.nodes: if module_symbol_id not in graph.graph.nodes:
graph.add_node( graph.add_node(
module_symbol_id, module_symbol_id,
type=name, # Use module name as type type=name,
name=name, name=name,
file=fpath, file=fpath,
lang=lang lang=lang
@@ -3177,7 +3176,6 @@ def build_indexes():
# ===== SVELTE SCRIPTS ===== # ===== SVELTE SCRIPTS =====
elif node_type in ["script", "script_module"] and lang in ["typescript", "javascript"]: elif node_type in ["script", "script_module"] and lang in ["typescript", "javascript"]:
# These are handled by their contained declarations
pass pass
# ===== VARIABLES & CONSTANTS ===== # ===== VARIABLES & CONSTANTS =====
@@ -3187,7 +3185,7 @@ def build_indexes():
**base_attrs, **base_attrs,
type=node_type, type=node_type,
var_type=m.get("var_type"), var_type=m.get("var_type"),
var_kind=m.get("var_kind"), # const, let, var var_kind=m.get("var_kind"),
is_exported=m.get("is_exported", False), is_exported=m.get("is_exported", False),
is_reactive=m.get("is_reactive", False), is_reactive=m.get("is_reactive", False),
is_store=m.get("is_store", False), is_store=m.get("is_store", False),
@@ -3230,8 +3228,12 @@ def build_indexes():
) )
graph.add_edge(file_node_id, import_node_id, "contains") graph.add_edge(file_node_id, import_node_id, "contains")
# Create symbolic nodes for imports (rich graph structure) # Create symbolic nodes - DON'T pass created_nodes, collect new nodes separately
_create_import_symbolic_nodes(graph, import_node_id, file_node_id, import_path, lang, created_nodes) new_nodes = _create_import_symbolic_nodes(graph, import_node_id, file_node_id, import_path, lang)
# Add new nodes after iteration completes
for new_node_id, new_node_data in new_nodes.items():
if new_node_id not in created_nodes:
created_nodes[new_node_id] = new_node_data
node_id = import_node_id node_id = import_node_id
@@ -3275,7 +3277,11 @@ def build_indexes():
# ------------------------------------------------- # -------------------------------------------------
logger.info("=== Phase 2: Building relationships ===") logger.info("=== Phase 2: Building relationships ===")
for node_id, node_data in created_nodes.items(): # CRITICAL FIX: Create a snapshot of the items to iterate over
# This prevents "dictionary changed size during iteration" errors
nodes_snapshot = list(created_nodes.items())
for node_id, node_data in nodes_snapshot:
m = node_data.get("metadata", {}) m = node_data.get("metadata", {})
if not m: if not m:
continue continue
@@ -3297,7 +3303,11 @@ def build_indexes():
if imports: if imports:
file_node_id = f"file::{fpath}" file_node_id = f"file::{fpath}"
_add_import_edges_enhanced(graph, file_node_id, node_id, imports, lang, created_nodes) # Collect new nodes instead of modifying created_nodes during iteration
new_nodes = _add_import_edges_enhanced(graph, file_node_id, node_id, imports, lang)
for new_node_id, new_node_data in new_nodes.items():
if new_node_id not in created_nodes:
created_nodes[new_node_id] = new_node_data
# ===== FUNCTION CALLS ===== # ===== FUNCTION CALLS =====
calls_raw = m.get("calls", []) calls_raw = m.get("calls", [])
@@ -3311,7 +3321,6 @@ def build_indexes():
# ===== CLASS/STRUCT INHERITANCE ===== # ===== CLASS/STRUCT INHERITANCE =====
if node_type in ["class", "struct"]: if node_type in ["class", "struct"]:
# Extends relationships
extends = m.get("extends") extends = m.get("extends")
if extends: if extends:
if isinstance(extends, list): if isinstance(extends, list):
@@ -3324,18 +3333,15 @@ def build_indexes():
if class_name == parent_name: if class_name == parent_name:
graph.add_edge(node_id, class_info["node_id"], "extends") graph.add_edge(node_id, class_info["node_id"], "extends")
# Implements relationships
implements = m.get("implements", []) implements = m.get("implements", [])
if isinstance(implements, str): if isinstance(implements, str):
implements = [implements] implements = [implements]
for interface_name in implements: for interface_name in implements:
# Check interface registry
for iface_name, iface_info in interface_registry.items(): for iface_name, iface_info in interface_registry.items():
if iface_name == interface_name: if iface_name == interface_name:
graph.add_edge(node_id, iface_info["node_id"], "implements") graph.add_edge(node_id, iface_info["node_id"], "implements")
# Check trait registry (Rust)
for trait_name, trait_info in trait_registry.items(): for trait_name, trait_info in trait_registry.items():
if trait_name == interface_name: if trait_name == interface_name:
graph.add_edge(node_id, trait_info["node_id"], "implements") graph.add_edge(node_id, trait_info["node_id"], "implements")
@@ -3358,12 +3364,10 @@ def build_indexes():
traits = [traits] traits = [traits]
for trait_name in traits: for trait_name in traits:
# Link to trait
for t_name, t_info in trait_registry.items(): for t_name, t_info in trait_registry.items():
if t_name == trait_name: if t_name == trait_name:
graph.add_edge(node_id, t_info["node_id"], "implements") graph.add_edge(node_id, t_info["node_id"], "implements")
# Link to struct/type being implemented
impl_name = m.get("name", "") impl_name = m.get("name", "")
for class_name, class_info in class_registry.items(): for class_name, class_info in class_registry.items():
if class_name == impl_name or class_name in impl_name: if class_name == impl_name or class_name in impl_name:
@@ -3375,7 +3379,6 @@ def build_indexes():
receiver_base = m.get("receiver_base_type") receiver_base = m.get("receiver_base_type")
if receiver_base: if receiver_base:
# Find the struct this method belongs to
for struct_name, struct_info in class_registry.items(): for struct_name, struct_info in class_registry.items():
if struct_name == receiver_base and struct_info["lang"] == "go": if struct_name == receiver_base and struct_info["lang"] == "go":
graph.add_edge(struct_info["node_id"], node_id, "has_method") graph.add_edge(struct_info["node_id"], node_id, "has_method")
@@ -3390,7 +3393,6 @@ def build_indexes():
if type_name == clean_return: if type_name == clean_return:
graph.add_edge(node_id, type_info["node_id"], "returns") graph.add_edge(node_id, type_info["node_id"], "returns")
# Parameter types
parameters = m.get("parameters", []) parameters = m.get("parameters", [])
if isinstance(parameters, list): if isinstance(parameters, list):
for param in parameters: for param in parameters:
@@ -3402,17 +3404,14 @@ def build_indexes():
# ===== SVELTE COMPONENT RELATIONSHIPS ===== # ===== SVELTE COMPONENT RELATIONSHIPS =====
if node_type == "component" or (node_type == "markup" and lang == "svelte"): if node_type == "component" or (node_type == "markup" and lang == "svelte"):
# Link to used components
used_components = m.get("used_components", []) used_components = m.get("used_components", [])
for comp_name in used_components: for comp_name in used_components:
if comp_name in component_registry: if comp_name in component_registry:
comp_info = component_registry[comp_name] comp_info = component_registry[comp_name]
graph.add_edge(node_id, comp_info["node_id"], "uses_component") graph.add_edge(node_id, comp_info["node_id"], "uses_component")
# Link props to type definitions
props = m.get("component_props", []) props = m.get("component_props", [])
for prop_name in props: for prop_name in props:
# Try to find corresponding interface/type
for iface_name, iface_info in interface_registry.items(): for iface_name, iface_info in interface_registry.items():
if f"{m.get('component_name')}Props" == iface_name: if f"{m.get('component_name')}Props" == iface_name:
graph.add_edge(node_id, iface_info["node_id"], "uses_props_type") graph.add_edge(node_id, iface_info["node_id"], "uses_props_type")
@@ -3434,21 +3433,17 @@ def build_indexes():
# ------------------------------------------------- # -------------------------------------------------
logger.info("=== Phase 3: Advanced relationships ===") logger.info("=== Phase 3: Advanced relationships ===")
# Build type relationships
type_connections = build_type_relationships(graph, created_nodes) type_connections = build_type_relationships(graph, created_nodes)
logger.info(f"Added {type_connections} type relationships") logger.info(f"Added {type_connections} type relationships")
# Build framework-specific relationships
bevy_connections = build_bevy_relationships(graph, created_nodes) bevy_connections = build_bevy_relationships(graph, created_nodes)
logger.info(f"Added {bevy_connections} Bevy-specific relationships") logger.info(f"Added {bevy_connections} Bevy-specific relationships")
# Build cross-language relationships
cross_lang_connections = build_cross_language_relationships( cross_lang_connections = build_cross_language_relationships(
graph, created_nodes, component_registry, interface_registry, class_registry graph, created_nodes, component_registry, interface_registry, class_registry
) )
logger.info(f"Added {cross_lang_connections} cross-language relationships") logger.info(f"Added {cross_lang_connections} cross-language relationships")
# Save graph
graph.save() graph.save()
graph.to_json() graph.to_json()
graph.to_toon() graph.to_toon()
@@ -3511,12 +3506,15 @@ def build_indexes():
elapsed = index_build_time - start_time elapsed = index_build_time - start_time
logger.info(f"✅ Index build complete. Total time: {elapsed:.1f}s") logger.info(f"✅ Index build complete. Total time: {elapsed:.1f}s")
# ------------------------------------------------- # -------------------------------------------------
# Helper Functions # Helper Functions
# ------------------------------------------------- # -------------------------------------------------
def _add_import_edges_enhanced(graph, file_node_id, importing_node_id, imports, lang, created_nodes): def _add_import_edges_enhanced(graph, file_node_id, importing_node_id, imports, lang):
"""Enhanced import edge creation that creates rich symbolic nodes for all languages.""" """Enhanced import edge creation. Returns dict of new nodes to add."""
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
@@ -3525,7 +3523,6 @@ def _add_import_edges_enhanced(graph, file_node_id, importing_node_id, imports,
if lang == "python": if lang == "python":
normalized_path = imp_path.replace('.', '::') normalized_path = imp_path.replace('.', '::')
elif lang in ["javascript", "typescript", "svelte"]: elif lang in ["javascript", "typescript", "svelte"]:
# Skip very short relative imports
if imp_path.startswith('.') and len(imp_path) < 3: if imp_path.startswith('.') and len(imp_path) < 3:
continue continue
normalized_path = imp_path.replace('/', '::').replace('@', '') normalized_path = imp_path.replace('/', '::').replace('@', '')
@@ -3549,20 +3546,19 @@ def _add_import_edges_enhanced(graph, file_node_id, importing_node_id, imports,
if package_node_id not in graph.graph.nodes: if package_node_id not in graph.graph.nodes:
graph.add_node( graph.add_node(
package_node_id, package_node_id,
type=package_name, # Rich type name type=package_name,
name=package_name, name=package_name,
file=imp_path, file=imp_path,
lang=lang, lang=lang,
is_external=True is_external=True
) )
created_nodes[package_node_id] = { new_nodes[package_node_id] = {
"type": package_name, "type": package_name,
"name": package_name, "name": package_name,
"file": imp_path, "file": imp_path,
"lang": lang "lang": lang
} }
# Connect file to package
if not graph.graph.has_edge(file_node_id, package_node_id): if not graph.graph.has_edge(file_node_id, package_node_id):
graph.add_edge(file_node_id, package_node_id, "imports") graph.add_edge(file_node_id, package_node_id, "imports")
@@ -3573,12 +3569,9 @@ def _add_import_edges_enhanced(graph, file_node_id, importing_node_id, imports,
for i, part in enumerate(parts[1:], 1): for i, part in enumerate(parts[1:], 1):
current_path = f"{current_path}::{part}" current_path = f"{current_path}::{part}"
# Determine node type based on position and context
if i == len(parts) - 1: if i == len(parts) - 1:
# Last part - could be a class, function, or module
node_type_name = part node_type_name = part
else: else:
# Intermediate part - likely a module/namespace
node_type_name = part node_type_name = part
module_node_id = f"symbol::{lang}::{current_path}" module_node_id = f"symbol::{lang}::{current_path}"
@@ -3586,30 +3579,29 @@ def _add_import_edges_enhanced(graph, file_node_id, importing_node_id, imports,
if module_node_id not in graph.graph.nodes: if module_node_id not in graph.graph.nodes:
graph.add_node( graph.add_node(
module_node_id, module_node_id,
type=node_type_name, # Rich type name type=node_type_name,
name=current_path, name=current_path,
file=imp_path, file=imp_path,
lang=lang, lang=lang,
is_symbol=True is_symbol=True
) )
created_nodes[module_node_id] = { new_nodes[module_node_id] = {
"type": node_type_name, "type": node_type_name,
"name": current_path, "name": current_path,
"file": imp_path, "file": imp_path,
"lang": lang "lang": lang
} }
# Connect to parent
if not graph.graph.has_edge(parent_node, module_node_id): if not graph.graph.has_edge(parent_node, module_node_id):
graph.add_edge(parent_node, module_node_id, "contains") graph.add_edge(parent_node, module_node_id, "contains")
parent_node = module_node_id parent_node = module_node_id
# Connect the importing node to the final symbol
if parent_node != package_node_id: if parent_node != package_node_id:
if not graph.graph.has_edge(importing_node_id, parent_node): if not graph.graph.has_edge(importing_node_id, parent_node):
graph.add_edge(importing_node_id, parent_node, "imports") graph.add_edge(importing_node_id, parent_node, "imports")
return new_nodes
def _clean_type_string(type_str: str) -> str: def _clean_type_string(type_str: str) -> str:
"""Clean up type strings for matching across all languages.""" """Clean up type strings for matching across all languages."""
@@ -3652,39 +3644,27 @@ def _clean_type_string(type_str: str) -> str:
return type_str return type_str
def _extract_param_type(param: str) -> str: def _extract_param_type(param: str) -> str:
"""Extract type from parameter string for all languages.""" """Extract type from parameter string for all languages."""
if not param or not isinstance(param, str): if not param or not isinstance(param, str):
return "" return ""
# Handle different parameter formats
# Python: "name: Type" or "name"
# Rust: "name: Type" or "Type"
# Go: "name: Type" or "name Type"
# Java/TypeScript: "Type name" or "name: Type"
if ':' in param: if ':' in param:
# Python, Rust, TypeScript style
parts = param.split(':') parts = param.split(':')
if len(parts) < 2: if len(parts) < 2:
return "" return ""
param_type = parts[-1].strip() param_type = parts[-1].strip()
elif ' ' in param: elif ' ' in param:
# Go, Java style
parts = param.strip().split() parts = param.strip().split()
# Check if first part looks like a type (starts with uppercase or is a keyword) if parts[0] and parts[0][0].isupper() or parts[0] in ['int', 'string', 'bool', 'float', 'double', 'long']:
if parts[0][0].isupper() or parts[0] in ['int', 'string', 'bool', 'float', 'double', 'long']:
param_type = parts[0] param_type = parts[0]
else: else:
# Assume last part is the type param_type = parts[-1] if parts else ""
param_type = parts[-1]
else: else:
return "" return ""
# Clean up type annotations and default values param_type = param_type.split('=')[0].strip()
param_type = param_type.split('=')[0].strip() # Remove default values param_type = param_type.split('`')[0].strip()
param_type = param_type.split('`')[0].strip() # Remove Go struct tags
return _clean_type_string(param_type) return _clean_type_string(param_type)
@@ -3694,37 +3674,25 @@ def _extract_field_type(field: str) -> str:
if not field or not isinstance(field, str): if not field or not isinstance(field, str):
return "" return ""
# Handle different field formats
# Python: "name: Type"
# Rust: "name: Type" or "embedded:Type"
# Go: "name Type" or "name Type `tag`"
# Java: "Type name"
# TypeScript: "name: Type"
# Handle embedded fields (Rust/Go)
if field.startswith('embedded:'): if field.startswith('embedded:'):
return _clean_type_string(field[9:]) return _clean_type_string(field[9:])
if ':' in field: if ':' in field:
# Python, Rust, TypeScript style
parts = field.split(':') parts = field.split(':')
if len(parts) < 2: if len(parts) < 2:
return "" return ""
field_type = parts[-1].strip() field_type = parts[-1].strip()
elif ' ' in field: elif ' ' in field:
# Go, Java style
parts = field.strip().split() parts = field.strip().split()
if len(parts) < 2: if len(parts) < 2:
return "" return ""
# Check if first part looks like a type if parts[0] and parts[0][0].isupper() or parts[0] in ['int', 'string', 'bool', 'float', 'double', 'long']:
if parts[0][0].isupper() or parts[0] in ['int', 'string', 'bool', 'float', 'double', 'long']:
field_type = parts[0] field_type = parts[0]
else: else:
field_type = parts[1] if len(parts) > 1 else parts[0] field_type = parts[1] if len(parts) > 1 else parts[0]
else: else:
return "" return ""
# Remove struct tags, JSON tags, etc.
field_type = re.sub(r'`.*?`', '', field_type).strip() field_type = re.sub(r'`.*?`', '', field_type).strip()
field_type = re.sub(r'".*?"', '', field_type).strip() field_type = re.sub(r'".*?"', '', field_type).strip()
@@ -3736,7 +3704,6 @@ def build_cross_language_relationships(graph, created_nodes, component_registry,
"""Build relationships across different programming languages.""" """Build relationships across different programming languages."""
connections = 0 connections = 0
# TypeScript/Java interfaces <-> Rust structs with matching names
for ts_name, ts_info in interface_registry.items(): for ts_name, ts_info in interface_registry.items():
if ts_info["lang"] in ["typescript", "javascript"]: if ts_info["lang"] in ["typescript", "javascript"]:
for rust_name, rust_info in class_registry.items(): for rust_name, rust_info in class_registry.items():
@@ -3745,7 +3712,6 @@ def build_cross_language_relationships(graph, created_nodes, component_registry,
graph.add_edge(ts_info["node_id"], rust_info["node_id"], "mirrors") graph.add_edge(ts_info["node_id"], rust_info["node_id"], "mirrors")
connections += 1 connections += 1
# Java classes <-> Go structs with matching names
for java_name, java_info in class_registry.items(): for java_name, java_info in class_registry.items():
if java_info["lang"] == "java": if java_info["lang"] == "java":
for go_name, go_info in class_registry.items(): for go_name, go_info in class_registry.items():
@@ -3754,9 +3720,7 @@ def build_cross_language_relationships(graph, created_nodes, component_registry,
graph.add_edge(java_info["node_id"], go_info["node_id"], "mirrors") graph.add_edge(java_info["node_id"], go_info["node_id"], "mirrors")
connections += 1 connections += 1
# Svelte components to TypeScript type definitions
for comp_name, comp_info in component_registry.items(): for comp_name, comp_info in component_registry.items():
# Look for matching Props interface
props_name = f"{comp_name}Props" props_name = f"{comp_name}Props"
for iface_name, iface_info in interface_registry.items(): for iface_name, iface_info in interface_registry.items():
if iface_name == props_name or iface_name == comp_name: if iface_name == props_name or iface_name == comp_name:
@@ -3764,14 +3728,12 @@ def build_cross_language_relationships(graph, created_nodes, component_registry,
graph.add_edge(comp_info["node_id"], iface_info["node_id"], "uses_type") graph.add_edge(comp_info["node_id"], iface_info["node_id"], "uses_type")
connections += 1 connections += 1
# Look for matching class
for class_name, class_info in class_registry.items(): for class_name, class_info in class_registry.items():
if class_name == comp_name or class_name == props_name: if class_name == comp_name or class_name == props_name:
if not graph.graph.has_edge(comp_info["node_id"], class_info["node_id"]): if not graph.graph.has_edge(comp_info["node_id"], class_info["node_id"]):
graph.add_edge(comp_info["node_id"], class_info["node_id"], "uses_type") graph.add_edge(comp_info["node_id"], class_info["node_id"], "uses_type")
connections += 1 connections += 1
# Python classes <-> TypeScript interfaces (common in web backends)
for py_name, py_info in class_registry.items(): for py_name, py_info in class_registry.items():
if py_info["lang"] == "python": if py_info["lang"] == "python":
for ts_name, ts_info in interface_registry.items(): for ts_name, ts_info in interface_registry.items():
@@ -3780,15 +3742,6 @@ def build_cross_language_relationships(graph, created_nodes, component_registry,
graph.add_edge(py_info["node_id"], ts_info["node_id"], "api_type") graph.add_edge(py_info["node_id"], ts_info["node_id"], "api_type")
connections += 1 connections += 1
# Rust traits <-> Go/Java interfaces (behavioral contracts)
for trait_name, trait_info in interface_registry.items():
if trait_info.get("lang") == "rust":
for iface_name, iface_info in interface_registry.items():
if iface_info["lang"] in ["go", "java"] and trait_name == iface_name:
if not graph.graph.has_edge(trait_info["node_id"], iface_info["node_id"]):
graph.add_edge(trait_info["node_id"], iface_info["node_id"], "similar_contract")
connections += 1
return connections return connections
@@ -3796,7 +3749,6 @@ def build_type_relationships(graph, created_nodes):
"""Build enhanced type relationships within language boundaries.""" """Build enhanced type relationships within language boundaries."""
connections = 0 connections = 0
# Track all type definitions by name for quick lookup
type_map = {} type_map = {}
for node_id, node_data in created_nodes.items(): for node_id, node_data in created_nodes.items():
node_type = node_data.get("type") node_type = node_data.get("type")
@@ -3809,7 +3761,6 @@ def build_type_relationships(graph, created_nodes):
type_map[key] = [] type_map[key] = []
type_map[key].append(node_id) type_map[key].append(node_id)
# Build relationships based on type usage
for node_id, node_data in created_nodes.items(): for node_id, node_data in created_nodes.items():
m = node_data.get("metadata", {}) m = node_data.get("metadata", {})
if not m: if not m:
@@ -3860,7 +3811,6 @@ def build_type_relationships(graph, created_nodes):
generics = m.get("generics", []) generics = m.get("generics", [])
if isinstance(generics, list): if isinstance(generics, list):
for generic in generics: for generic in generics:
# Generics might have constraints (e.g., T: Trait)
generic_parts = generic.split(':') generic_parts = generic.split(':')
if len(generic_parts) > 1: if len(generic_parts) > 1:
constraint = generic_parts[1].strip() constraint = generic_parts[1].strip()
@@ -3874,6 +3824,89 @@ def build_type_relationships(graph, created_nodes):
return connections return connections
def build_bevy_relationships(graph, created_nodes):
"""Build Bevy-specific relationships (ECS patterns)."""
connections = 0
systems = []
components = []
resources = []
bundles = []
for node_id, node_data in created_nodes.items():
m = node_data.get("metadata", {})
if not m:
continue
lang = node_data.get("lang")
if lang != "rust":
continue
node_type = node_data.get("type")
name = node_data.get("name")
if node_type in ["function", "method"]:
params = m.get("parameters", [])
has_query = any("Query" in str(p) for p in params)
has_commands = any("Commands" in str(p) for p in params)
has_res = any("Res" in str(p) or "ResMut" in str(p) for p in params)
if has_query or has_commands or has_res:
systems.append(node_id)
if node_type == "struct":
derives = m.get("derives", [])
if "Component" in str(derives):
components.append(node_id)
if "Resource" in str(derives):
resources.append(node_id)
if "Bundle" in str(derives):
bundles.append(node_id)
for system_id in systems:
system_data = created_nodes.get(system_id, {})
m = system_data.get("metadata", {})
params = m.get("parameters", [])
for param in params:
param_str = str(param)
query_match = re.findall(r'Query<[^>]*?([A-Z]\w+)', param_str)
for comp_name in query_match:
for comp_id in components:
comp_data = created_nodes.get(comp_id, {})
if comp_data.get("name") == comp_name:
if not graph.graph.has_edge(system_id, comp_id):
graph.add_edge(system_id, comp_id, "queries_component")
connections += 1
res_match = re.findall(r'Res(?:Mut)?<([A-Z]\w+)', param_str)
for res_name in res_match:
for res_id in resources:
res_data = created_nodes.get(res_id, {})
if res_data.get("name") == res_name:
if not graph.graph.has_edge(system_id, res_id):
graph.add_edge(system_id, res_id, "uses_resource")
connections += 1
for bundle_id in bundles:
bundle_data = created_nodes.get(bundle_id, {})
m = bundle_data.get("metadata", {})
fields = m.get("fields", [])
for field in fields:
field_type = _extract_field_type(field)
for comp_id in components:
comp_data = created_nodes.get(comp_id, {})
if comp_data.get("name") == field_type:
if not graph.graph.has_edge(bundle_id, comp_id):
graph.add_edge(bundle_id, comp_id, "bundles_component")
connections += 1
return connections
def build_bevy_relationships(graph, created_nodes): def build_bevy_relationships(graph, created_nodes):
"""Build Bevy-specific relationships (ECS patterns).""" """Build Bevy-specific relationships (ECS patterns)."""
connections = 0 connections = 0
@@ -3970,18 +4003,19 @@ def build_bevy_relationships(graph, created_nodes):
return connections return connections
def _create_import_symbolic_nodes(graph, import_node_id, file_node_id, import_path, lang, created_nodes): def _create_import_symbolic_nodes(graph, import_node_id, file_node_id, import_path, lang):
"""Create rich symbolic nodes for imports to build dense graph structure.""" """Create rich symbolic nodes for imports. Returns dict of new nodes to add."""
new_nodes = {}
if not import_path: if not import_path:
return return new_nodes
# Normalize path separators based on language # Normalize path separators based on language
if lang == "python": if lang == "python":
parts = import_path.replace('.', '::').split('::') parts = import_path.replace('.', '::').split('::')
elif lang in ["javascript", "typescript"]: elif lang in ["javascript", "typescript"]:
# Handle relative imports
if import_path.startswith('.'): if import_path.startswith('.'):
return # Skip relative imports for symbolic nodes return new_nodes
parts = import_path.replace('/', '::').replace('@', '').split('::') parts = import_path.replace('/', '::').replace('@', '').split('::')
elif lang == "rust": elif lang == "rust":
parts = import_path.split('::') parts = import_path.split('::')
@@ -3994,29 +4028,28 @@ def _create_import_symbolic_nodes(graph, import_node_id, file_node_id, import_pa
parts = [p.strip() for p in parts if p.strip()] parts = [p.strip() for p in parts if p.strip()]
if not parts: if not parts:
return return new_nodes
# Create package/crate node (first part) # Create package/crate node
package_name = parts[0] package_name = parts[0]
package_node_id = f"package::{lang}::{package_name}" package_node_id = f"package::{lang}::{package_name}"
if package_node_id not in graph.graph.nodes: if package_node_id not in graph.graph.nodes:
graph.add_node( graph.add_node(
package_node_id, package_node_id,
type=package_name, # Use package name as type for rich node types type=package_name,
name=package_name, name=package_name,
file=import_path, file=import_path,
lang=lang, lang=lang,
is_external=True is_external=True
) )
created_nodes[package_node_id] = { new_nodes[package_node_id] = {
"type": package_name, "type": package_name,
"name": package_name, "name": package_name,
"file": import_path, "file": import_path,
"lang": lang "lang": lang
} }
# Connect file to package
if not graph.graph.has_edge(file_node_id, package_node_id): if not graph.graph.has_edge(file_node_id, package_node_id):
graph.add_edge(file_node_id, package_node_id, "imports") graph.add_edge(file_node_id, package_node_id, "imports")
@@ -4031,27 +4064,27 @@ def _create_import_symbolic_nodes(graph, import_node_id, file_node_id, import_pa
if module_node_id not in graph.graph.nodes: if module_node_id not in graph.graph.nodes:
graph.add_node( graph.add_node(
module_node_id, module_node_id,
type=part, # Use part name as type for rich node types type=part,
name=current_path, name=current_path,
file=import_path, file=import_path,
lang=lang, lang=lang,
is_symbol=True is_symbol=True
) )
created_nodes[module_node_id] = { new_nodes[module_node_id] = {
"type": part, "type": part,
"name": current_path, "name": current_path,
"file": import_path, "file": import_path,
"lang": lang "lang": lang
} }
# Connect parent to child
if not graph.graph.has_edge(parent_node, module_node_id): if not graph.graph.has_edge(parent_node, module_node_id):
graph.add_edge(parent_node, module_node_id, "contains") graph.add_edge(parent_node, module_node_id, "contains")
parent_node = module_node_id parent_node = module_node_id
# Connect the import node to the final symbol
if parent_node != package_node_id and not graph.graph.has_edge(import_node_id, parent_node): if parent_node != package_node_id and not graph.graph.has_edge(import_node_id, parent_node):
graph.add_edge(import_node_id, parent_node, "imports") graph.add_edge(import_node_id, parent_node, "imports")
return new_nodes
def _extract_parameter_type(param): def _extract_parameter_type(param):
"""Extract type from parameter string, handling multiple formats""" """Extract type from parameter string, handling multiple formats"""