Skip to main content

cha_core/
lib.rs

1mod baseline;
2mod cache;
3pub mod config;
4pub mod graph;
5mod health;
6pub mod html_reporter;
7mod ignore;
8mod model;
9mod path_shape;
10mod plugin;
11pub mod plugins;
12mod registry;
13pub mod reporter;
14mod source;
15pub mod wasm;
16
17pub use baseline::Baseline;
18pub use cache::{FileStatus, ProjectCache, env_hash, hash_content};
19pub use config::{
20    Config, DebtWeights, LanguageConfig, LayersConfig, Strictness, TierConfig,
21    builtin_language_profile,
22};
23pub use health::{Grade, HealthScore, score_files};
24pub use ignore::filter_ignored;
25pub use model::*;
26pub use path_shape::is_test_path;
27pub use plugin::*;
28pub use registry::PluginRegistry;
29pub use reporter::{JsonReporter, LlmContextReporter, Reporter, SarifReporter, TerminalReporter};
30pub use source::*;
31
32/// Helper for serde skip_serializing_if on f64 fields.
33pub fn is_zero_f64(v: &f64) -> bool {
34    *v == 0.0
35}
36
37/// Helper for serde skip_serializing_if on usize fields.
38pub fn is_zero_usize(v: &usize) -> bool {
39    *v == 0
40}
41
42/// Sort findings by priority descending (most important first).
43/// Writes the computed score back to `Finding::risk_score` so reporters can
44/// show *why* a finding ranks where it does.
45/// priority = severity_weight × overshoot × compound_factor
46pub fn prioritize_findings(findings: &mut [Finding]) {
47    let per_file: std::collections::HashMap<std::path::PathBuf, usize> = {
48        let mut m = std::collections::HashMap::new();
49        for f in findings.iter() {
50            *m.entry(f.location.path.clone()).or_default() += 1;
51        }
52        m
53    };
54    for f in findings.iter_mut() {
55        f.risk_score = Some(finding_priority(f, &per_file));
56    }
57    findings.sort_by(|a, b| {
58        let pa = a.risk_score.unwrap_or(0.0);
59        let pb = b.risk_score.unwrap_or(0.0);
60        pb.partial_cmp(&pa).unwrap_or(std::cmp::Ordering::Equal)
61    });
62}
63
64fn finding_priority(
65    f: &Finding,
66    per_file: &std::collections::HashMap<std::path::PathBuf, usize>,
67) -> f64 {
68    let sev = match f.severity {
69        Severity::Error => 3.0,
70        Severity::Warning => 2.0,
71        Severity::Hint => 1.0,
72    };
73    let overshoot = match (f.actual_value, f.threshold) {
74        (Some(a), Some(t)) if t > 0.0 => (a / t).max(1.0),
75        _ => 1.0,
76    };
77    let compound = if *per_file.get(&f.location.path).unwrap_or(&1) > 3 {
78        1.5
79    } else {
80        1.0
81    };
82    sev * overshoot * compound
83}
84
85/// Generate JSON Schema for the analysis output (list of findings).
86pub fn findings_json_schema() -> String {
87    let schema = schemars::schema_for!(Vec<Finding>);
88    serde_json::to_string_pretty(&schema).unwrap_or_default()
89}
90
91#[cfg(test)]
92mod tests;