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