From 043679107f4d852ec8e8d8ba137f9faf9eab83cd Mon Sep 17 00:00:00 2001 From: carlos-alm <127798846+carlos-alm@users.noreply.github.com> Date: Thu, 19 Mar 2026 03:57:00 -0600 Subject: [PATCH 1/2] fix(native): align edge builder kind filters with JS parity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Rust edge builder only matched `kind == "class"` when looking up source nodes and targets for extends/implements edges. This caused all `impl Trait for Struct` relationships (and any non-class hierarchy) to be silently dropped — producing 0 implements edges for Rust sources while WASM correctly found 9. Align the three kind filter sets with the JS-side constants: - Source: class, struct, record, enum (was: class only) - Extends targets: class, struct, trait, record (was: class only) - Implements targets: interface, class, trait (was: interface, class) Fixes #530 (partial — implements parity gap) Impact: 1 functions changed, 0 affected --- crates/codegraph-core/src/edge_builder.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/codegraph-core/src/edge_builder.rs b/crates/codegraph-core/src/edge_builder.rs index 522ce768..f702d899 100644 --- a/crates/codegraph-core/src/edge_builder.rs +++ b/crates/codegraph-core/src/edge_builder.rs @@ -339,13 +339,17 @@ pub fn build_call_edges( for cls in &file_input.classes { let source_row = nodes_by_name_and_file .get(&(cls.name.as_str(), rel_path.as_str())) - .and_then(|v| v.iter().find(|n| n.kind == "class")); + .and_then(|v| v.iter().find(|n| { + n.kind == "class" || n.kind == "struct" || n.kind == "record" || n.kind == "enum" + })); if let Some(source) = source_row { if let Some(ref extends_name) = cls.extends { let targets = nodes_by_name .get(extends_name.as_str()) - .map(|v| v.iter().filter(|n| n.kind == "class").collect::>()) + .map(|v| v.iter().filter(|n| { + n.kind == "class" || n.kind == "struct" || n.kind == "trait" || n.kind == "record" + }).collect::>()) .unwrap_or_default(); for t in targets { edges.push(ComputedEdge { @@ -362,7 +366,7 @@ pub fn build_call_edges( .get(implements_name.as_str()) .map(|v| { v.iter() - .filter(|n| n.kind == "interface" || n.kind == "class") + .filter(|n| n.kind == "interface" || n.kind == "class" || n.kind == "trait") .collect::>() }) .unwrap_or_default(); From c0e1b68867d8c22d52fbfb5b287d570b2aaaa930 Mon Sep 17 00:00:00 2001 From: carlos-alm <127798846+carlos-alm@users.noreply.github.com> Date: Thu, 19 Mar 2026 04:37:54 -0600 Subject: [PATCH 2/2] refactor: extract kind-filter constants for readability (#541) Extract inline kind-filter closures into named constants (HIERARCHY_SOURCE_KINDS, EXTENDS_TARGET_KINDS, IMPLEMENTS_TARGET_KINDS) at module level, mirroring the JS-side convention in build-edges.js. This prevents future native/WASM drift by giving each set a single source of truth. --- crates/codegraph-core/src/edge_builder.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/crates/codegraph-core/src/edge_builder.rs b/crates/codegraph-core/src/edge_builder.rs index f702d899..b2e66251 100644 --- a/crates/codegraph-core/src/edge_builder.rs +++ b/crates/codegraph-core/src/edge_builder.rs @@ -4,6 +4,14 @@ use napi_derive::napi; use crate::import_resolution; +/// Kind sets for hierarchy edge resolution -- mirrors the JS constants in +/// `build-edges.js` (`HIERARCHY_SOURCE_KINDS`, `EXTENDS_TARGET_KINDS`, +/// `IMPLEMENTS_TARGET_KINDS`). Keeping them in one place prevents the +/// native/WASM drift that caused the original parity bug. +const HIERARCHY_SOURCE_KINDS: &[&str] = &["class", "struct", "record", "enum"]; +const EXTENDS_TARGET_KINDS: &[&str] = &["class", "struct", "trait", "record"]; +const IMPLEMENTS_TARGET_KINDS: &[&str] = &["interface", "trait", "class"]; + #[napi(object)] pub struct NodeInfo { pub id: u32, @@ -339,16 +347,14 @@ pub fn build_call_edges( for cls in &file_input.classes { let source_row = nodes_by_name_and_file .get(&(cls.name.as_str(), rel_path.as_str())) - .and_then(|v| v.iter().find(|n| { - n.kind == "class" || n.kind == "struct" || n.kind == "record" || n.kind == "enum" - })); + .and_then(|v| v.iter().find(|n| HIERARCHY_SOURCE_KINDS.contains(&n.kind.as_str()))); if let Some(source) = source_row { if let Some(ref extends_name) = cls.extends { let targets = nodes_by_name .get(extends_name.as_str()) .map(|v| v.iter().filter(|n| { - n.kind == "class" || n.kind == "struct" || n.kind == "trait" || n.kind == "record" + EXTENDS_TARGET_KINDS.contains(&n.kind.as_str()) }).collect::>()) .unwrap_or_default(); for t in targets { @@ -366,7 +372,7 @@ pub fn build_call_edges( .get(implements_name.as_str()) .map(|v| { v.iter() - .filter(|n| n.kind == "interface" || n.kind == "class" || n.kind == "trait") + .filter(|n| IMPLEMENTS_TARGET_KINDS.contains(&n.kind.as_str())) .collect::>() }) .unwrap_or_default();