Skip to main content

icb_report/
report.rs

1use icb_common::NodeKind;
2use icb_graph::analysis;
3use icb_graph::graph::{CodePropertyGraph, GraphData};
4use serde_json::json;
5use std::collections::HashSet;
6
7/// Generates a full HTML report for the given project graph.
8///
9/// # Arguments
10///
11/// * `cpg` - The Code Property Graph to report on.
12/// * `project_name` - A human‑readable name for the project.
13///
14/// # Returns
15///
16/// A `Result` containing the HTML string, or an error if serialization fails.
17pub fn generate_report(
18    cpg: &CodePropertyGraph,
19    project_name: &str,
20) -> Result<String, anyhow::Error> {
21    let total_functions = cpg
22        .graph
23        .node_weights()
24        .filter(|n| n.kind == NodeKind::Function)
25        .count();
26    let total_classes = cpg
27        .graph
28        .node_weights()
29        .filter(|n| n.kind == NodeKind::Class)
30        .count();
31    let total_calls = cpg
32        .graph
33        .edge_weights()
34        .filter(|e| matches!(e, icb_graph::graph::Edge::Call))
35        .count();
36
37    let cycles = analysis::detect_call_cycles(cpg);
38    let dead = analysis::detect_dead_code(cpg, &["main".to_string()]);
39    let complex = analysis::detect_complex_functions(cpg, 20);
40
41    let mut graph_data = GraphData::from(cpg);
42    for node in &mut graph_data.nodes {
43        let name = node.name.as_deref().unwrap_or("");
44        let is_cycle = cycles
45            .iter()
46            .any(|c| c.functions.contains(&name.to_string()));
47        let is_dead = dead.iter().any(|n| n.name.as_deref() == Some(name));
48        let complexity = complex
49            .iter()
50            .find(|r| r.function_name == name)
51            .map(|r| r.ast_node_count);
52        node.usr = Some(
53            json!({
54                "is_cycle": is_cycle,
55                "is_dead": is_dead,
56                "complexity": complexity,
57            })
58            .to_string(),
59        );
60    }
61
62    // Deduplicate edges (Graphology rejects duplicates)
63    let mut seen = HashSet::new();
64    graph_data.edges.retain(|(src, tgt, _)| {
65        let key = (*src, *tgt);
66        if seen.contains(&key) {
67            false
68        } else {
69            seen.insert(key);
70            true
71        }
72    });
73
74    let json_graph = serde_json::to_string(&graph_data)?;
75    let json_stats = json!({
76        "project": project_name,
77        "total_functions": total_functions,
78        "total_classes": total_classes,
79        "total_calls": total_calls,
80        "cycle_count": cycles.len(),
81        "dead_count": dead.len(),
82        "complex_count": complex.len(),
83    })
84    .to_string();
85
86    let html = include_str!("template_report.html")
87        .replace("__GRAPH_DATA__", &json_graph)
88        .replace("__STATS_DATA__", &json_stats)
89        .replace("__PROJECT_NAME__", project_name);
90
91    Ok(html)
92}