meta-ast 0.6.1

Polyglot static-analysis engine: extract symbols and cross-language dependency graphs from 9 supported source languages, with optional MetaCall deployment manifest generation.
Documentation
//! Edge types for the dependency graph.
//!
//! Defines the semantic relationships between nodes in the code graph.
//! Edges are directed and carry metadata about the relationship type
//! and confidence level.
//!
//! ## Design: uni-directional edges
//!
//! All edges are directed. There is no bidirectional or monitor/link
//! pattern - an edge from A to B means A depends on B, not that B will
//! be notified of A's failure. Shared-fate semantics (failure propagation)
//! are a separate, optional annotation that the deploy layer may add
//! during cut-edge RPC conversion. This follows the principle from
//! *A Unified Semantics for Future Erlang* §2.2/§6.2: bidirectional links
//! are replaced by uni-directional links plus monitors, and supervision
//! trees can be built from uni-directional links alone.

/// Semantic kind of a directed edge in the code graph.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize)]
#[non_exhaustive]
pub enum EdgeKind {
    /// Ownership edge: File owns/contains a symbol.
    Ownership,

    /// Import edge: File imports/depends on another file.
    Import,

    /// Reference edge: Symbol references/uses another symbol.
    Reference,

    /// Flow edge: DataNode def-use or dataflow relationship.
    Flow,
}

/// Confidence ladder for scope-resolved edges.
///
/// Own file and direct same-language imports score 1.0. Transitive
/// same-language imports decay to 0.8. Cross-language imports score 0.6.
pub const CONFIDENCE_OWN_OR_DIRECT: f32 = 1.0;
/// Transitive same-language import.
pub const CONFIDENCE_TRANSITIVE: f32 = 0.8;
/// Cross-language import.
pub const CONFIDENCE_CROSS_LANGUAGE: f32 = 0.6;

/// Confidence ladder for MetaCall client-call edges.
///
/// Unique load-confirmed calls score 1.0. Multiple load-confirmed calls
/// score 0.8. Unique global matches score 0.6. Multiple global matches
/// score 0.5. Computed names cap at 0.4.
pub const CONFIDENCE_CLIENT_UNIQUE_LOAD: f32 = 1.0;
/// Multiple load-confirmed candidates.
pub const CONFIDENCE_CLIENT_MULTI_LOAD: f32 = 0.8;
/// Unique global fallback match.
pub const CONFIDENCE_CLIENT_UNIQUE_GLOBAL: f32 = 0.6;
/// Multiple global fallback matches.
pub const CONFIDENCE_CLIENT_MULTI_GLOBAL: f32 = 0.5;
/// Computed function, tag, or script name.
pub const CONFIDENCE_COMPUTED: f32 = 0.4;

/// Dataflow def-use edge confidence.
pub const CONFIDENCE_DEF_USE: f32 = 0.9;

impl EdgeKind {
    /// Returns true if this edge kind participates in SCC computation.
    pub fn participates_in_scc(self) -> bool {
        matches!(self, EdgeKind::Import | EdgeKind::Reference)
    }

    /// Returns true if this edge represents a cross-file dependency.
    pub fn is_cross_file(self) -> bool {
        matches!(self, EdgeKind::Import)
    }

    /// Returns the human-readable name of this edge kind.
    pub const fn as_str(self) -> &'static str {
        match self {
            EdgeKind::Ownership => "ownership",
            EdgeKind::Import => "import",
            EdgeKind::Reference => "reference",
            EdgeKind::Flow => "flow",
        }
    }
}

impl std::fmt::Display for EdgeKind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.as_str())
    }
}

/// Data stored for each edge in the graph.
#[derive(Debug, Clone, Copy, serde::Serialize)]
pub struct EdgeData {
    /// Semantic kind of the relationship.
    pub kind: EdgeKind,

    /// Confidence level for the edge resolution.
    /// Used for cross-language and best-effort resolution.
    pub confidence: f32,

    /// Flow kind for dataflow edges (None for non-Flow edges).
    pub flow_kind: Option<crate::model::FlowKind>,
}

impl EdgeData {
    /// Creates a new edge with full confidence (1.0) and no flow kind.
    pub fn new(kind: EdgeKind) -> Self {
        Self {
            kind,
            confidence: 1.0,
            flow_kind: None,
        }
    }

    /// Creates a new edge with specified confidence and no flow kind.
    pub fn with_confidence(kind: EdgeKind, confidence: f32) -> Self {
        Self {
            kind,
            confidence: confidence.clamp(0.0, 1.0),
            flow_kind: None,
        }
    }

    /// Creates a Flow edge with a flow kind and confidence.
    pub fn flow(flow_kind: crate::model::FlowKind, confidence: f32) -> Self {
        Self {
            kind: EdgeKind::Flow,
            confidence: confidence.clamp(0.0, 1.0),
            flow_kind: Some(flow_kind),
        }
    }

    pub fn participates_in_scc(&self) -> bool {
        self.kind.participates_in_scc()
    }
}

impl Default for EdgeData {
    fn default() -> Self {
        Self::new(EdgeKind::Reference)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn edge_kind_scc_participation() {
        assert!(!EdgeKind::Ownership.participates_in_scc());
        assert!(EdgeKind::Import.participates_in_scc());
        assert!(EdgeKind::Reference.participates_in_scc());
    }

    #[test]
    fn edge_kind_as_str() {
        assert_eq!(EdgeKind::Ownership.as_str(), "ownership");
        assert_eq!(EdgeKind::Import.as_str(), "import");
        assert_eq!(EdgeKind::Reference.as_str(), "reference");
        assert_eq!(EdgeKind::Flow.as_str(), "flow");
    }

    #[test]
    fn flow_edge_excluded_from_scc() {
        assert!(!EdgeKind::Flow.participates_in_scc());
    }

    #[test]
    fn flow_edge_not_cross_file() {
        assert!(!EdgeKind::Flow.is_cross_file());
    }

    #[test]
    fn edge_kind_display() {
        assert_eq!(format!("{}", EdgeKind::Import), "import");
    }

    #[test]
    fn edge_data_new_defaults_to_full_confidence() {
        let edge = EdgeData::new(EdgeKind::Import);
        assert_eq!(edge.kind, EdgeKind::Import);
        assert_eq!(edge.confidence, 1.0);
        assert!(edge.participates_in_scc());
    }

    #[test]
    fn edge_data_with_confidence_clamps() {
        let low = EdgeData::with_confidence(EdgeKind::Reference, -0.5);
        assert_eq!(low.confidence, 0.0);

        let high = EdgeData::with_confidence(EdgeKind::Reference, 1.5);
        assert_eq!(high.confidence, 1.0);

        let mid = EdgeData::with_confidence(EdgeKind::Reference, 0.75);
        assert_eq!(mid.confidence, 0.75);
    }

    #[test]
    fn edge_data_default() {
        let edge: EdgeData = Default::default();
        assert_eq!(edge.confidence, 1.0);
        assert!(edge.participates_in_scc());
    }
}