weavatrix-rust 2.16.1

Protocol-independent Rust repository intelligence: typed evidence graphs for impact, architecture, APIs, Git, search, and memory
Documentation
use super::abi::{AbiDocument, Completeness};
use super::clients::ConsumerOccurrence;
use super::identity::member_key;
use super::kinds::{abi_kind, artifact_kind, binds, consumer_kind, member_kind};
use super::span::file_span;
use crate::language::{BoundEdgeFact, DomainFact, FileFacts, SymbolFact, SymbolLocator};
use crate::model::Diagnostic;
use weavatrix_graph::{EdgeKind, NodeKind};

pub(super) fn documents_to_facts(
    path: &str,
    documents: &[AbiDocument],
    extra: Vec<Diagnostic>,
) -> FileFacts {
    let mut facts = FileFacts {
        diagnostics: extra,
        ..FileFacts::default()
    };
    for document in documents {
        emit_document(&mut facts, path, document);
    }
    facts
}

fn emit_document(facts: &mut FileFacts, path: &str, document: &AbiDocument) {
    let label = document
        .contract_name
        .clone()
        .unwrap_or_else(|| file_label(path));
    let container = symbol(&label, artifact_kind(), document_span(path, document));
    let owner = locator(&container);
    facts.symbols.push(container);
    facts.domains.push(domain(
        format!("web3.profile:{}", document.profile.as_str()),
        NodeKind::Unknown,
        owner.clone(),
    ));
    facts.domains.push(domain(
        format!("web3.provenance:{}", document.provenance),
        NodeKind::Unknown,
        owner.clone(),
    ));
    facts.domains.push(domain(
        format!("web3.completeness:{}", document.completeness.as_str()),
        NodeKind::Unknown,
        owner.clone(),
    ));
    facts.domains.push(domain(
        "web3.deployment:not_provided".into(),
        NodeKind::Unknown,
        owner.clone(),
    ));
    if document.profile == super::abi::Profile::Hardhat3Incomplete {
        facts.diagnostics.push(Diagnostic {
            code: "web3.artifact.incomplete".into(),
            message: "Hardhat 3 artifact without a paired output is incomplete; compiler facts were not fabricated".into(),
            span: Some(file_span(path)),
        });
    }
    for member in &document.members {
        emit_member(facts, path, &owner, member);
    }
    let _ = Completeness::Full;
}

fn emit_member(
    facts: &mut FileFacts,
    path: &str,
    owner: &SymbolLocator,
    member: &super::abi::AbiMember,
) {
    let kind = member_kind(member.kind.as_str());
    let item = symbol(&member.call_signature, kind.clone(), member.span.clone());
    let loc = locator(&item);
    facts.bound_edges.push(BoundEdgeFact {
        from: owner.clone(),
        to: loc.clone(),
        kind: EdgeKind::Contains,
        span: member.span.clone(),
        detail: format!("abi-member:{}", member.kind.as_str()),
    });
    facts.symbols.push(item);
    facts.domains.push(domain(
        format!(
            "web3.member_key:{}",
            member_key(path, member.kind.as_str(), &member.call_signature)
        ),
        abi_kind(),
        loc.clone(),
    ));
    facts.domains.push(domain(
        format!("web3.call:{}", member.call_signature),
        kind.clone(),
        loc.clone(),
    ));
    if !member.return_shape.is_empty() {
        facts.domains.push(domain(
            format!("web3.return:{}", member.return_shape),
            NodeKind::TypeAlias,
            loc.clone(),
        ));
    }
    if !member.event_layout.is_empty() {
        facts.domains.push(domain(
            format!("web3.event_layout:{}", member.event_layout),
            kind,
            loc.clone(),
        ));
    }
    facts.domains.push(domain(
        format!("web3.client_surface:{}", member.client_surface),
        NodeKind::Unknown,
        loc.clone(),
    ));
    if let Some(selector) = &member.selector {
        facts.domains.push(domain(
            format!("web3.selector:{selector}"),
            NodeKind::Unknown,
            loc.clone(),
        ));
    }
    if member.unsupported {
        facts.domains.push(domain(
            "web3.support:unsupported".into(),
            NodeKind::Unknown,
            loc,
        ));
    }
}

pub(super) fn emit_consumers(
    facts: &mut FileFacts,
    path: &str,
    occurrences: &[ConsumerOccurrence],
) {
    for occurrence in occurrences {
        let label = occurrence
            .member_name
            .clone()
            .unwrap_or_else(|| occurrence.api.clone());
        let item = symbol(
            &format!("{}#{}", occurrence.api, occurrence.ordinal),
            consumer_kind(),
            occurrence.span.clone(),
        );
        let loc = locator(&item);
        facts.symbols.push(item);
        facts.domains.push(domain(
            format!("web3.consumer_api:{}", occurrence.api),
            NodeKind::Unknown,
            loc.clone(),
        ));
        facts.domains.push(domain(
            format!(
                "web3.binding:{}",
                if occurrence.resolved {
                    "matched_artifact"
                } else {
                    "unresolved"
                }
            ),
            NodeKind::Unknown,
            loc.clone(),
        ));
        if let Some(name) = &occurrence.member_name {
            facts.domains.push(domain(
                format!("web3.needs:{}:{name}", occurrence.member_kind),
                consumer_kind(),
                loc.clone(),
            ));
        }
        if let Some(local) = &occurrence.abi_local {
            facts.domains.push(domain(
                format!("web3.abi_local:{local}"),
                NodeKind::Unknown,
                loc.clone(),
            ));
        }
        if let (Some(import_path), Some(name)) = (&occurrence.import_path, &occurrence.member_name)
            && occurrence.resolved
        {
            facts.domains.push(domain(
                format!("web3.bind:{import_path}#{}:{name}", occurrence.member_kind),
                consumer_kind(),
                loc.clone(),
            ));
        }
        if !occurrence.resolved {
            facts.domains.push(domain(
                "web3.unresolved:dynamic_or_spread".into(),
                NodeKind::Unknown,
                loc.clone(),
            ));
        }
        facts.domains.push(domain(
            format!("web3.occurrence:{}:{}", path, occurrence.ordinal),
            NodeKind::Unknown,
            loc,
        ));
        let _ = binds();
        let _ = label;
    }
}

fn symbol(name: &str, kind: NodeKind, span: weavatrix_graph::SourceSpan) -> SymbolFact {
    SymbolFact {
        name: name.to_owned(),
        kind,
        span,
        test_only: false,
        exported: true,
        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 domain(name: String, kind: NodeKind, owner: SymbolLocator) -> DomainFact {
    DomainFact {
        name,
        kind,
        relation: EdgeKind::Configures,
        span: owner.span.clone(),
        owner: Some(owner),
    }
}

fn file_label(path: &str) -> String {
    std::path::Path::new(path)
        .file_name()
        .and_then(|name| name.to_str())
        .unwrap_or(path)
        .to_owned()
}

fn document_span(path: &str, document: &AbiDocument) -> weavatrix_graph::SourceSpan {
    document
        .members
        .first()
        .map_or_else(|| file_span(path), |member| member.span.clone())
}