Skip to main content

code_ranker_graph/
attrs.rs

1//! Shared attribute helpers used by every enrichment pass: numeric rounding,
2//! the `f64 → AttrValue` bridge, typed attribute reads, and the external-node
3//! predicate. This is a leaf module — it depends only on the plugin API, never
4//! on the crate root, so the enrichment passes can pull helpers from here
5//! without creating a `submodule → crate-root` back-edge.
6
7use code_ranker_plugin_api::{attrs::AttrValue, node::Node};
8
9/// Truncate to 3 significant digits (matching the historical `sig3` serializer):
10/// values ≥ 1 are truncated to 3 decimals, values < 1 to 3 significant figures.
11/// Non-finite values collapse to 0 (JSON has no NaN/Inf).
12pub fn round_sig3(x: f64) -> f64 {
13    if !x.is_finite() || x == 0.0 {
14        return 0.0;
15    }
16    let abs = x.abs();
17    let sign = if x < 0.0 { -1.0 } else { 1.0 };
18    let truncated = if abs >= 1.0 {
19        (abs * 1000.0).floor() / 1000.0
20    } else {
21        let d = abs.log10().floor() as i32;
22        let factor = 10f64.powi(2 - d);
23        (abs * factor).floor() / factor
24    };
25    truncated * sign
26}
27
28/// Round a metric and pick the natural JSON scalar: an integral value becomes
29/// an `Int` (so `1.0` serializes as `1`), otherwise a `Float`. This is the
30/// single bridge metric producers use before inserting into `attrs`.
31pub fn num_attr(x: f64) -> AttrValue {
32    let r = round_sig3(x);
33    if r.fract() == 0.0 && r.abs() < i64::MAX as f64 {
34        AttrValue::Int(r as i64)
35    } else {
36        AttrValue::Float(r)
37    }
38}
39
40/// Read a numeric node attribute as `f64` (from either `Int` or `Float`).
41pub(crate) fn attr_f64(node: &Node, key: &str) -> Option<f64> {
42    match node.attrs.get(key) {
43        Some(AttrValue::Int(i)) => Some(*i as f64),
44        Some(AttrValue::Float(f)) => Some(*f),
45        _ => None,
46    }
47}
48
49/// Is this node an external dependency (a library node, not a project file)?
50/// Derived from `kind == "external"` or an explicit `external: true` attribute.
51pub(crate) fn is_external(node: &Node) -> bool {
52    node.kind == "external" || matches!(node.attrs.get("external"), Some(AttrValue::Bool(true)))
53}