weavatrix-rust 2.1.1

Protocol-independent Rust repository intelligence: typed evidence graphs for impact, architecture, APIs, Git, search, and memory
Documentation
#![cfg(feature = "lang-rust")]

use blazingly_json::Value;
use std::fs;
use std::path::PathBuf;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
use weavatrix_rust::{Analyzer, EdgeKind, NodeKind};

static FIXTURE_SEQUENCE: AtomicU64 = AtomicU64::new(0);

#[test]
fn analyzes_a_rust_repository_without_external_processes() {
    let fixture = Fixture::new();
    fixture.write(".gitignore", "ignored.rs\n");
    fixture.write(
        "src/lib.rs",
        r"
pub struct Worker;
pub fn helper() {}
pub fn run() { helper(); }
",
    );
    fixture.write("ignored.rs", "fn should_not_exist() {}\n");

    let first = Analyzer::default().analyze(&fixture.root).unwrap();
    let second = Analyzer::default().analyze(&fixture.root).unwrap();

    assert_eq!(first.revision, second.revision);
    assert_eq!(first.nodes, second.nodes);
    assert!(
        first
            .nodes
            .iter()
            .any(|node| { node.kind == NodeKind::Struct && node.label == "Worker" })
    );
    assert!(
        !first
            .nodes
            .iter()
            .any(|node| node.label == "should_not_exist")
    );

    let helper = first
        .nodes
        .iter()
        .find(|node| node.kind == NodeKind::Function && node.label == "helper")
        .unwrap();
    let run = first
        .nodes
        .iter()
        .find(|node| node.kind == NodeKind::Function && node.label == "run")
        .unwrap();
    assert!(first.edges.iter().any(|edge| {
        edge.kind == EdgeKind::Calls && edge.source == run.id && edge.target == helper.id
    }));
}

#[test]
fn resolves_rust_reexports_and_repository_relative_imports() {
    let fixture = Fixture::new();
    fixture.write(
        "Cargo.toml",
        r#"
[package]
name = "fixture-engine"
version = "0.1.0"
"#,
    );
    fixture.write(
        "src/lib.rs",
        r"
mod facts;

pub use facts::{Fact, private_fact};
",
    );
    fixture.write(
        "src/facts.rs",
        r"
pub struct Fact;
pub(crate) fn private_fact() {}
",
    );
    fixture.write(
        "tests/client.rs",
        "use fixture_engine::Fact;\nfn open() { let _ = Fact; }\n",
    );
    fixture.write("README.md", "Read the [guide](docs/guide.md).\n");
    fixture.write("docs/guide.md", "# Guide\n");

    let snapshot = Analyzer::default().analyze(&fixture.root).unwrap();
    let edge = |kind, source: &str, target: &str| {
        snapshot.edges.iter().any(|item| {
            item.kind == kind && item.source.as_str() == source && item.target.as_str() == target
        })
    };

    assert!(
        edge(EdgeKind::ReExports, "file:src/lib.rs", "file:src/facts.rs"),
        "pub use facts::... resolves to the sibling Rust module"
    );
    assert!(
        edge(EdgeKind::Imports, "file:tests/client.rs", "file:src/lib.rs"),
        "the Cargo package name resolves to the current crate root"
    );
    assert!(
        edge(EdgeKind::Imports, "file:README.md", "file:docs/guide.md"),
        "repository-relative Markdown links are local dependencies"
    );
}

#[test]
fn connects_rust_impl_and_trait_methods_to_owners() {
    let fixture = Fixture::new();
    fixture.write(
        "src/lib.rs",
        r"
mod engine_impl;

pub struct Engine;
pub trait Runner {
    fn run(&self);
}
",
    );
    fixture.write(
        "src/engine_impl.rs",
        r"
use crate::{Engine, Runner};

impl Engine {
    pub fn open() -> Self { Self }
}

impl Runner for Engine {
    fn run(&self) {}
}
",
    );

    let snapshot = Analyzer::default().analyze(&fixture.root).unwrap();
    let edge = |kind, source: &str, target: &str| {
        snapshot.edges.iter().any(|item| {
            item.kind == kind && item.source.as_str() == source && item.target.as_str() == target
        })
    };

    let engine = snapshot
        .nodes
        .iter()
        .find(|node| node.kind == NodeKind::Struct && node.label == "Engine")
        .unwrap();
    let runner = snapshot
        .nodes
        .iter()
        .find(|node| node.kind == NodeKind::Trait && node.label == "Runner")
        .unwrap();
    let open = snapshot
        .nodes
        .iter()
        .find(|node| node.kind == NodeKind::Method && node.label == "open")
        .unwrap();
    let trait_run = snapshot
        .nodes
        .iter()
        .find(|node| {
            node.kind == NodeKind::Method
                && node.label == "run"
                && node
                    .span
                    .as_ref()
                    .is_some_and(|span| span.file == "src/lib.rs")
        })
        .unwrap();
    let impl_run = snapshot
        .nodes
        .iter()
        .find(|node| {
            node.kind == NodeKind::Method
                && node.label == "run"
                && node
                    .span
                    .as_ref()
                    .is_some_and(|span| span.file == "src/engine_impl.rs")
        })
        .unwrap();

    assert!(edge(EdgeKind::Method, engine.id.as_str(), open.id.as_str()));
    assert!(edge(
        EdgeKind::Method,
        engine.id.as_str(),
        impl_run.id.as_str()
    ));
    assert!(edge(
        EdgeKind::Method,
        runner.id.as_str(),
        trait_run.id.as_str()
    ));
    assert!(
        snapshot.edges.iter().all(|item| {
            item.kind != EdgeKind::Method
                || snapshot
                    .nodes
                    .iter()
                    .any(|node| node.id == item.target && node.kind == NodeKind::Method)
        }),
        "ordinary lexical nesting never becomes a method edge"
    );
}

#[test]
fn emits_a_javascript_weavatrix_compatible_graph() {
    let fixture = Fixture::new();
    fixture.write(
        "src/lib.rs",
        r"
pub fn helper() {}
pub fn run() { helper(); }
",
    );

    let json = Analyzer::default()
        .analyze_legacy_json(&fixture.root, false)
        .unwrap();
    let graph: Value = blazingly_json::from_str(&json).unwrap();

    assert_eq!(graph["schemaVersion"], "weavatrix.rust.legacy.v1");
    assert_eq!(graph["edgeTypesV"], 2);
    assert_eq!(graph["edgeProvenanceV"], 1);
    assert!(graph["nodes"].as_array().is_some_and(|nodes| {
        nodes.iter().any(|node| {
            node["kind"] == "function"
                && node["label"] == "run"
                && node["language"] == "rust"
                && node["source_range"]["start"]["line"].is_u64()
        }) && nodes
            .iter()
            .any(|node| node["kind"] == "file" && node["bytes"].is_u64())
    }));
    assert!(graph["links"].as_array().is_some_and(|links| {
        links.iter().any(|link| {
            link["relation"] == "calls"
                && link["provenance"] == "resolved"
                && link["confidence"] == "high"
                && link["line"].is_u64()
        })
    }));
}

struct Fixture {
    root: PathBuf,
}

impl Fixture {
    fn new() -> Self {
        let nonce = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_nanos();
        let root = std::env::temp_dir().join(format!(
            "weavatrix-rust-test-{}-{nonce}-{}",
            std::process::id(),
            FIXTURE_SEQUENCE.fetch_add(1, Ordering::Relaxed)
        ));
        fs::create_dir_all(&root).unwrap();
        Self { root }
    }

    fn write(&self, relative: &str, contents: &str) {
        let path = self.root.join(relative);
        if let Some(parent) = path.parent() {
            fs::create_dir_all(parent).unwrap();
        }
        fs::write(path, contents).unwrap();
    }
}

impl Drop for Fixture {
    fn drop(&mut self) {
        let _ = fs::remove_dir_all(&self.root);
    }
}