rust-split 0.2.2

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

#[test]
fn oversized_mod_is_extracted_to_its_own_file() {
    let mut inner = String::new();
    for i in 0..10 {
        inner.push_str(&format!("    fn t{i}() {{}}\n"));
    }
    let src = format!("fn main() {{}}\n\nmod tests {{\n{inner}}}\n");
    let exploded = explode(&src).unwrap();
    let plan = plan_split(&exploded, 6);
    // ceiling large enough that the test mod fits whole -> single tests.rs
    let out = reassemble_bin(&exploded, &plan, "main", 1000);
    let tests = out.files.iter().find(|f| f.path == "tests.rs").unwrap();
    assert!(tests.contents.contains("fn t0()"));
    assert!(!tests.contents.contains("mod tests"));
    let root = out.files.iter().find(|f| f.path == "main.rs").unwrap();
    assert!(root.contents.contains("mod tests;"));
}

#[test]
fn an_over_ceiling_unconditional_mod_is_nested_into_a_subdir() {
    // 30 test fns referencing a shared helper -> test mod far over a tiny ceiling
    let mut inner =
        String::from("    use super::*;\n\n    fn helper() -> i32 {\n        1\n    }\n");
    for i in 0..30 {
        inner.push_str(&format!(
            "\n    #[test]\n    fn t{i}() {{\n        assert_eq!(helper(), 1);\n    }}\n"
        ));
    }
    let src = format!(
        "pub fn helper() -> i32 {{\n    1\n}}\n\nfn main() {{}}\n\nmod tests {{\n{inner}}}\n"
    );
    let exploded = explode(&src).unwrap();
    let out = split_bin(&exploded, 60, "main");

    // the test mod became a `tests/` subdir, every file under the ceiling
    let mod_rs = out.files.iter().find(|f| f.path == "tests/mod.rs").unwrap();
    assert!(
        mod_rs.contents.contains("pub(crate) use super::*;"),
        "forwards the parent"
    );
    assert!(mod_rs.contents.contains("mod g00;"));
    assert!(out.files.iter().any(|f| f.path == "tests/g00.rs"));
    for f in &out.files {
        assert!(f.loc < 60, "{} over ceiling: {}", f.path, f.loc);
    }
    let root = out.files.iter().find(|f| f.path == "main.rs").unwrap();
    assert!(root.contents.contains("mod tests;"));
}

// ----- razel regression 2: the cfg(test) gate travels with the tests mod --

#[test]
fn under_budget_cfg_test_mod_keeps_its_braced_gate() {
    // The razel failure: the tests mod fit under the item budget, so it was
    // CLUSTERED — wrapped inside a part module with an ungated root decl —
    // instead of preserved. A conditional module must keep its braced
    // scope and its gate in the original module.
    let src = "pub fn alpha() -> i32 {\n    1\n}\n\n\
               #[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    \
               fn smoke() {\n        assert_eq!(alpha(), 1);\n    }\n}\n";
    let exploded = explode(src).unwrap();
    // Budget far above the tests mod's size: it would have been clustered.
    let out = split_bin(&exploded, 10_000, "lib");

    let root = out.files.iter().find(|f| f.path == "lib.rs").unwrap();
    assert!(
        root.contents.contains("#[cfg(test)]\nmod tests {"),
        "the gate must travel to the root declaration:\n{}",
        root.contents
    );
    assert!(
        !root.contents.contains("use tests::*"),
        "an inline conditional mod is not glob re-exported:\n{}",
        root.contents
    );
    assert!(root.contents.contains("fn smoke()"));
    assert!(!out.files.iter().any(|f| f.path == "tests.rs"));
}

#[test]
fn pub_mod_keeps_its_visibility_on_the_extracted_decl() {
    let src = "pub fn api() -> i32 {\n    1\n}\n\n\
               /// Config store.\npub mod config {\n    pub fn get() -> i32 {\n        2\n    }\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("/// Config store.\npub mod config;"),
        "docs and `pub` travel to the declaration:\n{}",
        root.contents
    );
    let config = out.files.iter().find(|f| f.path == "config.rs").unwrap();
    assert!(config.contents.contains("pub fn get()"));
}