daipendency-extractor-rust 0.5.0

Daipendency extractor for Rust library crates
Documentation
mod module_directory;
mod module_extraction;
mod namespace_construction;
mod parsing;
mod symbol_collection;
mod symbol_resolution;
mod test_helpers;

use daipendency_extractor::ExtractionError;
use daipendency_extractor::Namespace;
use module_extraction::extract_modules;
use std::path::Path;
use tree_sitter::Parser;

use namespace_construction::construct_namespaces;
use symbol_collection::collect_module_directories;
use symbol_resolution::resolve_symbols;

pub fn build_public_api(
    entry_point: &Path,
    crate_name: &str,
    parser: &mut Parser,
) -> Result<Vec<Namespace>, ExtractionError> {
    let module_directories = collect_module_directories(entry_point, parser)?;
    let modules = extract_modules(&module_directories)?;
    let resolution = resolve_symbols(&modules)?;
    let namespaces = construct_namespaces(resolution, crate_name);
    Ok(namespaces)
}

#[cfg(test)]
mod tests {
    use assertables::assert_matches;

    use super::*;
    use crate::test_helpers::setup_parser;
    use crate::test_helpers::{create_file, create_temp_dir};

    const STUB_CRATE_NAME: &str = "test_crate";

    #[test]
    fn nonexistent_file() {
        let mut parser = setup_parser();
        let path = std::path::PathBuf::from("nonexistent.rs");

        let result = build_public_api(&path, STUB_CRATE_NAME, &mut parser);

        assert_matches!(result, Err(ExtractionError::Io(_)));
    }

    #[test]
    fn integration() {
        let temp_dir = create_temp_dir();
        let lib_rs = temp_dir.path().join("src").join("lib.rs");
        let module_rs = temp_dir.path().join("src").join("module.rs");
        create_file(
            &lib_rs,
            r#"
pub mod module;
pub use module::Format;

pub fn process(format: Format) -> String {
    "processed".to_string()
}
"#,
        );
        create_file(
            &module_rs,
            r#"
pub enum Format {
    Text,
    Binary,
}
"#,
        );
        let mut parser = setup_parser();

        let namespaces = build_public_api(&lib_rs, STUB_CRATE_NAME, &mut parser).unwrap();

        assert_eq!(namespaces.len(), 2);
        let root = namespaces
            .iter()
            .find(|n| n.name == STUB_CRATE_NAME)
            .unwrap();
        assert_eq!(root.symbols.len(), 2);
        assert!(root.symbols.iter().any(|s| s.name == "process"));
        assert!(root.symbols.iter().any(|s| s.name == "Format"));

        let module = namespaces
            .iter()
            .find(|n| n.name == format!("{}::module", STUB_CRATE_NAME))
            .unwrap();
        assert_eq!(module.symbols.len(), 1);
        assert!(module.symbols.iter().any(|s| s.name == "Format"));
    }

    #[test]
    fn wildcard_reexport() {
        let temp_dir = create_temp_dir();
        let lib_rs = temp_dir.path().join("src").join("lib.rs");
        let submodule_rs = temp_dir.path().join("src").join("submodule.rs");
        create_file(
            &lib_rs,
            r#"
mod submodule;
pub use submodule::*;
"#,
        );
        create_file(
            &submodule_rs,
            r#"
pub struct One;
pub struct Two;
"#,
        );
        let mut parser = setup_parser();

        let namespaces = build_public_api(&lib_rs, STUB_CRATE_NAME, &mut parser).unwrap();

        assert_eq!(namespaces.len(), 1);
        let root = &namespaces[0];
        assert_eq!(root.symbols.len(), 2);
        assert!(root.get_symbol("One").is_some());
        assert!(root.get_symbol("Two").is_some());
    }

    #[test]
    fn new_style_module_directory() {
        let temp_dir = create_temp_dir();
        let src_dir = temp_dir.path().join("src");
        let lib_rs = src_dir.join("lib.rs");
        let module_rs = src_dir.join("module.rs");
        let module_dir = src_dir.join("module");
        let submodule_rs = module_dir.join("submodule.rs");
        create_file(&lib_rs, r#"pub mod module;"#);
        create_file(&module_rs, r#"pub mod submodule;"#);
        create_file(&submodule_rs, r#"pub struct Foo;"#);
        let mut parser = setup_parser();

        let namespaces = build_public_api(&lib_rs, STUB_CRATE_NAME, &mut parser).unwrap();

        assert_eq!(namespaces.len(), 1);
        let namespace = &namespaces[0];
        assert_eq!(
            namespace.name,
            format!("{STUB_CRATE_NAME}::module::submodule")
        );
        assert_eq!(namespace.symbols.len(), 1);
        assert!(namespace.get_symbol("Foo").is_some());
    }

    #[test]
    fn external_dependency_reexport() {
        let temp_dir = create_temp_dir();
        let lib_rs = temp_dir.path().join("src").join("lib.rs");
        create_file(
            &lib_rs,
            r#"
pub use serde_json;
"#,
        );
        let mut parser = setup_parser();

        let namespaces = build_public_api(&lib_rs, STUB_CRATE_NAME, &mut parser).unwrap();

        assert_eq!(namespaces.len(), 1);
        let root = &namespaces[0];
        assert_eq!(root.name, STUB_CRATE_NAME);
        assert_eq!(root.symbols.len(), 1);
        let symbol = root.get_symbol("serde_json").unwrap();
        assert_eq!(symbol.source_code, "pub use serde_json;");
    }
}