weavatrix-rust 2.16.1

Protocol-independent Rust repository intelligence: typed evidence graphs for impact, architecture, APIs, Git, search, and memory
Documentation
use super::model::{
    Completeness, Diagram, declared_architecture, diagram_kind, element_kind, group_kind,
};
use crate::language::{
    BoundEdgeFact, DomainFact, FileFacts, ReferenceFact, SymbolFact, SymbolLocator,
};
use std::collections::BTreeMap;
use weavatrix_graph::{EdgeKind, NodeKind, SourceSpan};

pub(super) fn to_file_facts(path: &str, diagrams: &[Diagram]) -> FileFacts {
    let mut facts = FileFacts::default();
    for diagram in diagrams {
        emit_diagram(&mut facts, path, diagram);
    }
    facts
}

fn emit_diagram(facts: &mut FileFacts, path: &str, diagram: &Diagram) {
    facts
        .diagnostics
        .extend(diagram.diagnostics.iter().cloned());
    let diagram_symbol = symbol(&diagram.key, diagram_kind(), diagram.span.clone());
    let owner = locator(&diagram_symbol);
    facts.symbols.push(diagram_symbol);
    push_meta(
        facts,
        &owner,
        &diagram.span,
        &[
            format!("kind:{}", diagram.kind),
            format!("direction:{}", empty_or(&diagram.direction, "unspecified")),
            format!("completeness:{}", diagram.completeness.as_str()),
            format!("source:{path}"),
        ],
    );
    let mut locators = BTreeMap::<String, SymbolLocator>::new();
    locators.insert(diagram.key.clone(), owner.clone());
    for group in &diagram.groups {
        let group_symbol = symbol(&group.id, group_kind(), group.span.clone());
        locators.insert(group.id.clone(), locator(&group_symbol));
        facts
            .references
            .push(contains(&owner, &group.id, group.span.clone()));
        facts.symbols.push(group_symbol);
        push_named(
            facts,
            &owner,
            &group.span,
            &format!("group_title:{}", group.title),
            NodeKind::Unknown,
            EdgeKind::Configures,
        );
    }
    for element in &diagram.elements {
        let element_symbol = symbol(&element.id, element_kind(), element.span.clone());
        locators.insert(element.id.clone(), locator(&element_symbol));
        facts
            .references
            .push(contains(&owner, &element.id, element.span.clone()));
        facts.symbols.push(element_symbol);
        push_named(
            facts,
            locators.get(&element.id).unwrap_or(&owner),
            &element.span,
            &format!("label:{}", element.label),
            NodeKind::Unknown,
            EdgeKind::Configures,
        );
        push_named(
            facts,
            locators.get(&element.id).unwrap_or(&owner),
            &element.span,
            &format!("native_id:{}", element.id),
            NodeKind::Unknown,
            EdgeKind::Configures,
        );
        if let Some(group) = &element.group
            && let Some(group_loc) = locators.get(group)
        {
            facts
                .references
                .push(contains(group_loc, &element.id, element.span.clone()));
        }
        for occurrence in &element.occurrences {
            push_named(
                facts,
                locators.get(&element.id).unwrap_or(&owner),
                occurrence,
                &format!(
                    "occurrence:{}:{}",
                    occurrence.start.line, occurrence.start.column
                ),
                NodeKind::Unknown,
                EdgeKind::Configures,
            );
        }
    }
    for relation in &diagram.relations {
        emit_relation(facts, &locators, relation);
    }
    let known = diagram.elements.len();
    let total = known + usize::from(diagram.completeness == Completeness::Partial);
    push_named(
        facts,
        &owner,
        &diagram.span,
        &format!("coverage:elements:{known}/{total}"),
        NodeKind::Unknown,
        EdgeKind::Configures,
    );
}

fn emit_relation(
    facts: &mut FileFacts,
    locators: &BTreeMap<String, SymbolLocator>,
    relation: &super::model::Relation,
) {
    let Some(from) = locators.get(&relation.from) else {
        return;
    };
    let Some(to) = locators.get(&relation.to) else {
        return;
    };
    facts.bound_edges.push(BoundEdgeFact {
        from: from.clone(),
        to: to.clone(),
        kind: declared_architecture(),
        span: relation.span.clone(),
        detail: format!(
            "{}|{}|{}",
            relation.marker, relation.label, relation.occurrence
        ),
    });
}

fn empty_or<'a>(value: &'a str, fallback: &'a str) -> &'a str {
    if value.is_empty() { fallback } else { value }
}

fn symbol(name: &str, kind: NodeKind, span: SourceSpan) -> SymbolFact {
    SymbolFact {
        name: name.to_owned(),
        kind,
        span,
        test_only: false,
        exported: false,
        source_fingerprint: None,
        source_extent: None,
        owner: None,
    }
}

fn locator(symbol: &SymbolFact) -> SymbolLocator {
    SymbolLocator {
        name: symbol.name.clone(),
        kind: symbol.kind.clone(),
        span: symbol.span.clone(),
    }
}

fn contains(owner: &SymbolLocator, name: &str, span: SourceSpan) -> ReferenceFact {
    ReferenceFact {
        name: name.to_owned(),
        kind: EdgeKind::Contains,
        receiver: None,
        qualified: false,
        span,
        owner: Some(owner.clone()),
    }
}

fn push_meta(facts: &mut FileFacts, owner: &SymbolLocator, span: &SourceSpan, names: &[String]) {
    for name in names {
        push_named(
            facts,
            owner,
            span,
            name,
            NodeKind::Unknown,
            EdgeKind::Configures,
        );
    }
}

fn push_named(
    facts: &mut FileFacts,
    owner: &SymbolLocator,
    span: &SourceSpan,
    name: &str,
    kind: NodeKind,
    relation: EdgeKind,
) {
    facts.domains.push(DomainFact {
        name: name.to_owned(),
        kind,
        relation,
        span: span.clone(),
        owner: Some(owner.clone()),
    });
}