Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
28da58b
chore: release v3.5.0
carlos-alm Mar 30, 2026
e3cf055
Merge remote-tracking branch 'origin/main' into release/3.5.0
carlos-alm Mar 30, 2026
9e1286a
chore(shared): remove dead code from types and shared utilities
carlos-alm Mar 30, 2026
cc89d7a
chore(db): remove dead code from database layer
carlos-alm Mar 30, 2026
9fafa5a
refactor(native): extract shared walk_node_depth helpers into helpers.rs
carlos-alm Mar 30, 2026
c9fba51
refactor(extractors): extract shared visitor utilities from WASM extr…
carlos-alm Mar 30, 2026
a6f942f
refactor(analysis): extract shared query-building helpers
carlos-alm Mar 30, 2026
1673a6c
refactor(leiden): decompose makePartition into focused sub-functions
carlos-alm Mar 30, 2026
ed0707e
fix(leiden): reduce cognitive complexity in adapter and index
carlos-alm Mar 30, 2026
0c0c24c
refactor: decompose MCP server and search CLI formatter
carlos-alm Mar 30, 2026
3f56c5b
refactor(graph): decompose finalize stage into sub-steps
carlos-alm Mar 30, 2026
4de3ac7
refactor(ast): decompose setupVisitors into focused helper functions
carlos-alm Mar 30, 2026
662387b
refactor(extractors): decompose javascript and go WASM extractors
carlos-alm Mar 30, 2026
67a8241
refactor(features): decompose complexity-query and graph-enrichment
carlos-alm Mar 30, 2026
ff32950
refactor(presentation): decompose check, audit, and branch-compare fo…
carlos-alm Mar 30, 2026
3d34774
refactor(structure): decompose computeDirectoryMetrics into focused h…
carlos-alm Mar 30, 2026
b7a6206
refactor(presentation): decompose complexity CLI formatter
carlos-alm Mar 30, 2026
aa34dc4
refactor(native): decompose javascript.rs walk_node_depth
carlos-alm Mar 30, 2026
2653693
refactor(native): decompose go/python/php extractors
carlos-alm Mar 30, 2026
a49e393
refactor(native): decompose java/csharp/ruby/rust extractors
carlos-alm Mar 30, 2026
56c2584
refactor(native): decompose edge_builder, complexity, and cfg modules
carlos-alm Mar 30, 2026
6f3fb3d
refactor(native): decompose dataflow module
carlos-alm Mar 30, 2026
3f25376
refactor(extractors): decompose javascript.ts and go.ts WASM extractors
carlos-alm Mar 30, 2026
6e0e5df
fix: reduce complexity in parser dispatch and config loading
carlos-alm Mar 30, 2026
bbffcd6
fix(extractors): reduce complexity and remove dead code in WASM extra…
carlos-alm Mar 30, 2026
d186da9
fix(analysis): reduce complexity and remove dead code in analysis mod…
carlos-alm Mar 30, 2026
a55ee53
fix(graph): fix empty catches, reduce complexity in graph builder pip…
carlos-alm Mar 30, 2026
da41157
fix(ast): reduce complexity in AST engine and complexity visitor
carlos-alm Mar 30, 2026
4932570
fix(features): reduce complexity in cfg, dataflow, and check modules
carlos-alm Mar 30, 2026
99b733c
fix(native): reduce complexity in roles_db and HCL extractor
carlos-alm Mar 30, 2026
a027aaf
refactor(shared): address warnings in types and database layer
carlos-alm Mar 30, 2026
8468b49
refactor: address warnings in domain analysis and presentation
carlos-alm Mar 30, 2026
6f13090
refactor: address warnings in infrastructure, features, and CLI
carlos-alm Mar 30, 2026
053cfe9
fix: resolve build errors from noUncheckedIndexedAccess and unexporte…
carlos-alm Mar 30, 2026
a48dbb5
chore(titan): add close phase audit report
carlos-alm Mar 30, 2026
122abf5
chore(titan): update report with PR URL
carlos-alm Mar 30, 2026
93d4258
fix(titan): correct fabricated timestamps in report with actual durat…
carlos-alm Mar 30, 2026
76a84be
fix: resolve merge conflicts with main
carlos-alm Mar 30, 2026
f97f2e5
fix: address Greptile review feedback (#699)
carlos-alm Mar 30, 2026
2dfc339
fix: resolve merge conflicts with main (#699)
carlos-alm Mar 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
243 changes: 142 additions & 101 deletions crates/codegraph-core/src/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -452,168 +452,209 @@ impl<'a> CfgBuilder<'a> {
cur
}

/// Process a single statement.
/// Process a single statement — thin dispatcher delegating to focused handlers.
fn process_statement(&mut self, stmt: &Node, current: u32) -> Option<u32> {
let kind = stmt.kind();

// Unwrap expression_statement (Rust uses expressions for control flow)
if kind == "expression_statement" && stmt.named_child_count() == 1 {
if let Some(inner) = stmt.named_child(0) {
let t = inner.kind();
if matches_opt(t, self.rules.if_node)
|| matches_slice(t, self.rules.if_nodes)
|| matches_slice(t, self.rules.for_nodes)
|| matches_opt(t, self.rules.while_node)
|| matches_slice(t, self.rules.while_nodes)
|| matches_opt(t, self.rules.do_node)
|| matches_opt(t, self.rules.infinite_loop_node)
|| matches_opt(t, self.rules.switch_node)
|| matches_slice(t, self.rules.switch_nodes)
|| matches_opt(t, self.rules.return_node)
|| matches_opt(t, self.rules.throw_node)
|| matches_opt(t, self.rules.break_node)
|| matches_opt(t, self.rules.continue_node)
|| matches_opt(t, self.rules.unless_node)
|| matches_opt(t, self.rules.until_node)
{
return self.process_statement(&inner, current);
}
}
if let Some(result) = self.try_unwrap_expr_stmt(stmt, kind, current) {
return result;
}

// Labeled statement
if matches_opt(kind, self.rules.labeled_node) {
let label_node = stmt.child_by_field_name("label");
let body = stmt.child_by_field_name("body");
if let (Some(label_node), Some(body)) = (label_node, body) {
let label_name = label_node.utf8_text(self.source).unwrap_or("").to_string();
// We can't know the loop blocks yet — push a placeholder
self.label_map.push((label_name.clone(), LabelCtx { header_idx: None, exit_idx: None }));
let result = self.process_statement(&body, current);
self.label_map.retain(|(n, _)| n != &label_name);
return result;
}
return Some(current);
if let Some(result) = self.try_process_labeled(stmt, kind, current) {
return result;
}

// If statement
if matches_opt(kind, self.rules.if_node) || matches_slice(kind, self.rules.if_nodes) {
return self.process_if(stmt, current);
// Compound control flow
if let Some(result) = self.try_process_control_flow(stmt, kind, current) {
return result;
}

// Unless (Ruby)
if matches_opt(kind, self.rules.unless_node) {
return self.process_if(stmt, current);
// Terminal statements (return, throw, break, continue)
if let Some(result) = self.try_process_terminal(stmt, kind, current) {
return result;
}

// For loops
if matches_slice(kind, self.rules.for_nodes) {
return self.process_for_loop(stmt, current);
// Regular statement — extend current block
self.set_start_line_if_empty(current, node_line(stmt));
self.set_end_line(current, node_end_line(stmt));
Some(current)
}

/// Unwrap expression_statement wrappers (Rust uses expressions for control flow).
/// Returns `Some(result)` if unwrapped and processed, `None` if not applicable.
fn try_unwrap_expr_stmt(&mut self, stmt: &Node, kind: &str, current: u32) -> Option<Option<u32>> {
if kind != "expression_statement" || stmt.named_child_count() != 1 {
return None;
}
let inner = stmt.named_child(0)?;
let t = inner.kind();
let is_control = matches_opt(t, self.rules.if_node)
|| matches_slice(t, self.rules.if_nodes)
|| matches_slice(t, self.rules.for_nodes)
|| matches_opt(t, self.rules.while_node)
|| matches_slice(t, self.rules.while_nodes)
|| matches_opt(t, self.rules.do_node)
|| matches_opt(t, self.rules.infinite_loop_node)
|| matches_opt(t, self.rules.switch_node)
|| matches_slice(t, self.rules.switch_nodes)
|| matches_opt(t, self.rules.return_node)
|| matches_opt(t, self.rules.throw_node)
|| matches_opt(t, self.rules.break_node)
|| matches_opt(t, self.rules.continue_node)
|| matches_opt(t, self.rules.unless_node)
|| matches_opt(t, self.rules.until_node);
if is_control {
Some(self.process_statement(&inner, current))
} else {
None
}
}

// While loop
if matches_opt(kind, self.rules.while_node) || matches_slice(kind, self.rules.while_nodes) {
return self.process_while_loop(stmt, current);
/// Process labeled statements. Returns `Some(result)` if this was a labeled
/// statement, `None` otherwise.
fn try_process_labeled(&mut self, stmt: &Node, kind: &str, current: u32) -> Option<Option<u32>> {
if !matches_opt(kind, self.rules.labeled_node) {
return None;
}
let label_node = stmt.child_by_field_name("label");
let body = stmt.child_by_field_name("body");
if let (Some(label_node), Some(body)) = (label_node, body) {
let label_name = label_node.utf8_text(self.source).unwrap_or("").to_string();
self.label_map.push((label_name.clone(), LabelCtx { header_idx: None, exit_idx: None }));
let result = self.process_statement(&body, current);
self.label_map.retain(|(n, _)| n != &label_name);
Some(result)
} else {
Some(Some(current))
}
}

/// Dispatch compound control flow (if, for, while, switch, try, etc.).
/// Returns `Some(result)` if handled, `None` if not a control flow node.
fn try_process_control_flow(&mut self, stmt: &Node, kind: &str, current: u32) -> Option<Option<u32>> {
// If / unless
if matches_opt(kind, self.rules.if_node) || matches_slice(kind, self.rules.if_nodes)
|| matches_opt(kind, self.rules.unless_node)
{
return Some(self.process_if(stmt, current));
}

// For loops
if matches_slice(kind, self.rules.for_nodes) {
return Some(self.process_for_loop(stmt, current));
}

// Until (Ruby)
if matches_opt(kind, self.rules.until_node) {
return self.process_while_loop(stmt, current);
// While / until
if matches_opt(kind, self.rules.while_node) || matches_slice(kind, self.rules.while_nodes)
|| matches_opt(kind, self.rules.until_node)
{
return Some(self.process_while_loop(stmt, current));
}

// Do-while
if matches_opt(kind, self.rules.do_node) {
return self.process_do_while_loop(stmt, current);
return Some(self.process_do_while_loop(stmt, current));
}

// Infinite loop (Rust loop {})
if matches_opt(kind, self.rules.infinite_loop_node) {
return self.process_infinite_loop(stmt, current);
return Some(self.process_infinite_loop(stmt, current));
}

// Switch/match
if matches_opt(kind, self.rules.switch_node) || matches_slice(kind, self.rules.switch_nodes) {
return self.process_switch(stmt, current);
return Some(self.process_switch(stmt, current));
}

// Try/catch/finally
if matches_opt(kind, self.rules.try_node) {
return self.process_try_catch(stmt, current);
return Some(self.process_try_catch(stmt, current));
}
// Additional try nodes (e.g. Ruby body_statement with rescue)
if matches_slice(kind, self.rules.try_nodes) {
// Only treat as try if it actually contains a catch/rescue child
let cursor = &mut stmt.walk();
let has_rescue = stmt.named_children(cursor)
.any(|c| matches_opt(c.kind(), self.rules.catch_node));
if has_rescue {
return self.process_try_catch(stmt, current);
return Some(self.process_try_catch(stmt, current));
}
}

// Return
None
}

/// Handle terminal statements: return, throw, break, continue.
/// Returns `Some(result)` if handled, `None` if not a terminal node.
fn try_process_terminal(&mut self, stmt: &Node, kind: &str, current: u32) -> Option<Option<u32>> {
if matches_opt(kind, self.rules.return_node) {
self.set_end_line(current, node_line(stmt));
self.add_edge(current, self.exit_idx, "return");
return None;
return Some(None);
}

// Throw
if matches_opt(kind, self.rules.throw_node) {
self.set_end_line(current, node_line(stmt));
self.add_edge(current, self.exit_idx, "exception");
return None;
return Some(None);
}

// Break
if matches_opt(kind, self.rules.break_node) {
let label_name = stmt.child_by_field_name("label")
.map(|n| n.utf8_text(self.source).unwrap_or("").to_string());

let target = if let Some(ref name) = label_name {
self.label_map.iter().rev()
.find(|(n, _)| n == name)
.and_then(|(_, ctx)| ctx.exit_idx)
} else {
self.loop_stack.last().map(|ctx| ctx.exit_idx)
};

if let Some(target) = target {
self.set_end_line(current, node_line(stmt));
self.add_edge(current, target, "break");
return None;
}
return Some(current);
return Some(self.process_break(stmt, current));
}

// Continue
if matches_opt(kind, self.rules.continue_node) {
let label_name = stmt.child_by_field_name("label")
.map(|n| n.utf8_text(self.source).unwrap_or("").to_string());
return Some(self.process_continue(stmt, current));
}

let target = if let Some(ref name) = label_name {
self.label_map.iter().rev()
.find(|(n, _)| n == name)
.and_then(|(_, ctx)| ctx.header_idx)
} else {
// Walk back to find the nearest actual loop (skip switch entries)
self.loop_stack.iter().rev()
.find(|ctx| ctx.is_loop)
.map(|ctx| ctx.header_idx)
};
None
}

if let Some(target) = target {
self.set_end_line(current, node_line(stmt));
self.add_edge(current, target, "continue");
return None;
}
return Some(current);
/// Process a break statement: resolve label or loop target.
fn process_break(&mut self, stmt: &Node, current: u32) -> Option<u32> {
let label_name = stmt.child_by_field_name("label")
.map(|n| n.utf8_text(self.source).unwrap_or("").to_string());

let target = if let Some(ref name) = label_name {
self.label_map.iter().rev()
.find(|(n, _)| n == name)
.and_then(|(_, ctx)| ctx.exit_idx)
} else {
self.loop_stack.last().map(|ctx| ctx.exit_idx)
};

if let Some(target) = target {
self.set_end_line(current, node_line(stmt));
self.add_edge(current, target, "break");
None
} else {
Some(current)
}
}

// Regular statement — extend current block
self.set_start_line_if_empty(current, node_line(stmt));
self.set_end_line(current, node_end_line(stmt));
Some(current)
/// Process a continue statement: resolve label or nearest loop header.
fn process_continue(&mut self, stmt: &Node, current: u32) -> Option<u32> {
let label_name = stmt.child_by_field_name("label")
.map(|n| n.utf8_text(self.source).unwrap_or("").to_string());

let target = if let Some(ref name) = label_name {
self.label_map.iter().rev()
.find(|(n, _)| n == name)
.and_then(|(_, ctx)| ctx.header_idx)
} else {
self.loop_stack.iter().rev()
.find(|ctx| ctx.is_loop)
.map(|ctx| ctx.header_idx)
};

if let Some(target) = target {
self.set_end_line(current, node_line(stmt));
self.add_edge(current, target, "continue");
None
} else {
Some(current)
}
}

/// Process if/else-if/else chain (handles patterns A, B, C).
Expand Down
Loading
Loading