diff --git a/crates/codegraph-api/src/mermaid.rs b/crates/codegraph-api/src/mermaid.rs index 6ee9ca717e..2041e47adf 100644 --- a/crates/codegraph-api/src/mermaid.rs +++ b/crates/codegraph-api/src/mermaid.rs @@ -246,8 +246,26 @@ fn node_style( if c.line > 0 { label.push_str(&format!(" · L{}", c.line)); } + // Chỉ hiện `· if ` trên node ĐẦU TIÊN của nhánh (đứng ngay sau marker + // IF_TRUE/IF_FALSE/LOOP/SWITCH_CASE). Các node sau trong cùng nhánh bỏ qua — + // marker đã mang text điều kiện rồi, lặp lại chỉ tạo nhiễu (một `if` dài + // 83 ký tự bị nhắc lại 15+ lần trong train.py::run). + // + // Nếu chain KHÔNG có marker nhánh nào (test case đơn giản), hiển thị condition + // trên mọi call có condition. + let has_branch_marker_before = (0..i).any(|j| { + matches!( + chain[j], + MARKER_IF_TRUE | MARKER_IF_FALSE | MARKER_LOOP | MARKER_SWITCH_CASE + ) + }); + let cond_is_immediate = i > 0 + && matches!( + chain[i - 1], + MARKER_IF_TRUE | MARKER_IF_FALSE | MARKER_LOOP | MARKER_SWITCH_CASE + ); if let Some(cond) = &c.condition { - if !cond.is_empty() { + if !cond.is_empty() && (cond_is_immediate || !has_branch_marker_before) { label.push_str(&format!(" · if {cond}")); } } @@ -691,10 +709,10 @@ async fn bfs( } } -/// Làm sạch label Mermaid: bỏ dấu ngoặc kép / xuống dòng, giới hạn 80 ký tự. +/// Làm sạch label Mermaid: bỏ dấu ngoặc kép / xuống dòng, giới hạn 100 ký tự. fn sanitize(s: &str) -> String { let cleaned = s.replace('"', "'").replace(['\n', '\r'], " "); - let mut out: String = cleaned.chars().take(80).collect(); + let mut out: String = cleaned.chars().take(100).collect(); if out.chars().count() < cleaned.chars().count() { out.push('…'); } diff --git a/crates/codegraph-extract/src/languages/common.rs b/crates/codegraph-extract/src/languages/common.rs index 89006cb04b..ebc1227036 100644 --- a/crates/codegraph-extract/src/languages/common.rs +++ b/crates/codegraph-extract/src/languages/common.rs @@ -693,7 +693,10 @@ fn walk_chain( let loop_cond_text = cond_node .and_then(|c| text(&c, ctx.src)) .filter(|t| !t.is_empty()); - let loop_cond = loop_cond_text.clone().or_else(|| condition.clone()); + // KHÔNG fallback về condition của `if` bao ngoài: `for i in range(n)` lồng trong + // `if c:` sẽ gán nhãn điều kiện CỦA IF cho mọi call trong loop → flood + sai. + // Loop không có condition riêng thì body calls mang condition = None. + let loop_cond = loop_cond_text.clone(); // Nhãn trigger của loop ngay tại marker LOOP. if let Some(l) = &loop_cond_text { emit_branch_label(ctx, l); @@ -807,6 +810,10 @@ fn walk_chain( for ch in named_children(node) { if ctx.spec.except_kinds.contains(&ch.kind()) { chain_push(ctx, MARKER_IF_TRUE); + // Nhãn handler của except: `except ValueError:` → `ValueError`. + if let Some(l) = except_label(&ch, ctx.src) { + emit_branch_label(ctx, &format!("except {l}")); + } walk_clause(ctx, &ch, depth + 1, in_loop, condition.clone()); chain_push(ctx, MARKER_BRANCH_END); } @@ -1113,6 +1120,17 @@ fn is_case_body_kind(kind: &str) -> bool { || kind == "compound_statement" } +/// Nhãn handler của `except`/`catch` — `except ValueError:` → `ValueError`. +/// Tìm node đầu tiên không phải body trong except_clause. +fn except_label<'a>(node: &Node<'a>, src: &[u8]) -> Option { + named_children(node) + .into_iter() + .find(|c| !is_case_body_kind(c.kind())) + .and_then(|c| text(&c, src)) + .map(|t| t.trim().to_string()) + .filter(|t| !t.is_empty()) +} + /// Gắn **nhãn nhánh** vào đúng vị trí marker trong chain (không thêm node): /// thêm một `CallRecord` với `position = `, `condition = label`, /// `call_name = ""`. Marker không bao giờ có call thật ở cùng position nên record