rust-split 0.2.2

A parser-based god-file splitter for Rust source.
Documentation
use super::*;

#[test]
fn split_mod_uses_super_and_preserves_pub_api() {
    // alpha (pub, the module's API) calls beta (private helper) -> one cluster
    let src =
        "use std::fmt;\n\npub fn alpha() -> i32 {\n    beta()\n}\n\nfn beta() -> i32 {\n    1\n}\n";
    let exploded = explode(src).unwrap();
    let out = split_mod(&exploded, 10_000, "mod");

    let root = out.files.iter().find(|f| f.path == "mod.rs").unwrap();
    assert!(
        root.contents.contains("pub use "),
        "pub re-export preserves the API"
    );
    assert!(
        !root.contents.contains("pub(crate) use "),
        "a single `pub use *` caps each item at its own visibility — no \
         redundant second `pub(crate) use` line"
    );

    let sub = out.files.iter().find(|f| f.path != "mod.rs").unwrap();
    assert!(
        sub.contents.contains("use super::*;"),
        "siblings reached via super, not crate"
    );
    assert!(!sub.contents.contains("use crate::*;"));
    assert!(sub.contents.contains("pub fn alpha"), "pub item stays pub");
    assert!(
        sub.contents.contains("pub(super) fn beta"),
        "private bumped only as far as the siblings need: pub(super)"
    );
}

// ----- issue 2: per-module pub vs pub(crate) vs no re-export decision -------

#[test]
fn private_only_module_reexports_no_wider_than_its_items() {
    // No `pub` item: `pub use *` would warn "nothing public enough". The
    // items were private to the original module, so a nested module
    // re-exports them with a private glob (its children still see it), and
    // a crate root — whose private items were crate-wide — with
    // `pub(crate) use`.
    let src = "fn helper() -> i32 {\n    leaf()\n}\n\nfn leaf() -> i32 {\n    1\n}\n";
    let exploded = explode(src).unwrap();
    for (out, root_path, expected) in [
        (
            split_mod(&exploded, 10_000, "mod"),
            "mod.rs",
            "use helper::*;",
        ),
        (
            split_bin(&exploded, 10_000, "lib"),
            "lib.rs",
            "pub(crate) use helper::*;",
        ),
    ] {
        let root = out.files.iter().find(|f| f.path == root_path).unwrap();
        assert!(
            root.contents.lines().any(|l| l == expected),
            "all-private module re-exports as `{expected}`:\n{}",
            root.contents
        );
        assert!(
            !root.contents.contains("pub use "),
            "no `pub use` for a module with no public item"
        );
    }
}

#[test]
fn impl_only_module_is_declared_but_not_reexported() {
    // `api` is a normal part; the bare impl references no sibling, so it
    // clusters alone with nothing nameable -> declared, but no glob re-export
    // (re-exporting it would only warn "doesn't reexport anything").
    let src =
        "pub fn api() -> i32 {\n    1\n}\n\nimpl Worker for External {\n    fn run(&self) {}\n}\n";
    let exploded = explode(src).unwrap();
    let out = split_mod(&exploded, 10_000, "mod");
    let root = out.files.iter().find(|f| f.path == "mod.rs").unwrap();

    // exactly one glob re-export line (for `api`) though two modules declared
    let reexports = root
        .contents
        .lines()
        .filter(|l| l.contains(" use ") && l.trim_end().ends_with("::*;"))
        .count();
    assert_eq!(
        reexports, 1,
        "only the nameable module is re-exported:\n{}",
        root.contents
    );
    assert!(root.contents.contains("pub use api::*;"));
    assert_eq!(
        root.contents.matches("mod ").count(),
        2,
        "both modules are still declared:\n{}",
        root.contents
    );
}

// ----- issue 3: crate-public entry fn is preserved as `pub use` -------------

#[test]
fn bin_public_entry_fn_is_reexported_pub_so_it_stays_crate_visible() {
    // A lib crate root where `run` is the public entry (the bin calls
    // `crate_name::run`). It must be re-exported `pub`, not `pub(crate)`, or
    // `crate_name::run` is no longer visible to main.rs after the split.
    let src = "fn helper() -> i32 {\n    1\n}\n\npub fn run() -> i32 {\n    helper()\n}\n";
    let exploded = explode(src).unwrap();
    let out = split_bin(&exploded, 10_000, "lib");

    let root = out.files.iter().find(|f| f.path == "lib.rs").unwrap();
    assert!(
        root.contents.contains("pub use "),
        "public `run` re-exported at pub keeps `crate::run` visible:\n{}",
        root.contents
    );
    assert!(
        !root.contents.contains("pub(crate) use "),
        "the module has a pub item, so one `pub use *` suffices (no double line)"
    );
}