perl-module-token 0.12.2

Rewrite standalone Perl module tokens without partial-name matches
Documentation
use perl_module_name::legacy_package_separator;
use perl_module_token::{contains_module_token, replace_module_token};
use proptest::prelude::*;

fn module_name_strategy() -> impl Strategy<Value = String> {
    proptest::collection::vec("[A-Za-z_][A-Za-z0-9_]{0,8}", 1..5)
        .prop_map(|segments| segments.join("::"))
}

fn boundary_padding_strategy() -> impl Strategy<Value = String> {
    let boundary_char = prop_oneof![
        Just(' '),
        Just('\t'),
        Just(';'),
        Just(','),
        Just('('),
        Just(')'),
        Just('['),
        Just(']'),
        Just('{'),
        Just('}'),
        Just('/'),
        Just('-'),
        Just('"'),
    ];

    proptest::collection::vec(boundary_char, 0..8).prop_map(|chars| chars.into_iter().collect())
}

fn nonempty_boundary_padding_strategy() -> impl Strategy<Value = String> {
    boundary_padding_strategy()
        .prop_filter("boundary padding must not be empty", |padding| !padding.is_empty())
}

fn module_char_strategy() -> impl Strategy<Value = char> {
    prop_oneof![
        proptest::char::range('a', 'z'),
        proptest::char::range('A', 'Z'),
        proptest::char::range('0', '9'),
        Just('_'),
        Just(':'),
    ]
}

proptest! {
    #[test]
    fn prop_replacement_occurs_for_standalone_module_tokens(
        prefix in boundary_padding_strategy(),
        suffix in boundary_padding_strategy(),
        from in module_name_strategy(),
        to in module_name_strategy(),
    ) {
        prop_assume!(from != to);

        let line = format!("{prefix}{from}{suffix}");
        let (rewritten, changed) = replace_module_token(&line, &from, &to);

        prop_assert!(changed);
        prop_assert_eq!(rewritten, format!("{prefix}{to}{suffix}"));
    }

    #[test]
    fn prop_replacement_occurs_for_legacy_module_tokens(
        prefix in boundary_padding_strategy(),
        suffix in boundary_padding_strategy(),
        from in module_name_strategy(),
        to in module_name_strategy(),
    ) {
        prop_assume!(from != to);

        let legacy_from = legacy_package_separator(&from).into_owned();
        let legacy_to = legacy_package_separator(&to).into_owned();
        let line = format!("{prefix}{legacy_from}{suffix}");
        let (rewritten, changed) = replace_module_token(&line, &legacy_from, &legacy_to);

        prop_assert!(changed);
        prop_assert_eq!(rewritten, format!("{prefix}{legacy_to}{suffix}"));
    }

    #[test]
    fn prop_replacement_does_not_trigger_on_partial_module_names(
        left in module_char_strategy(),
        right in module_char_strategy(),
        from in module_name_strategy(),
        to in module_name_strategy(),
    ) {
        prop_assume!(from != to);

        let line = format!("{left}{from}{right}");
        let (rewritten, changed) = replace_module_token(&line, &from, &to);

        prop_assert!(!changed);
        prop_assert_eq!(rewritten.as_str(), line.as_str());
        prop_assert!(!contains_module_token(&rewritten, &from));
    }

    #[test]
    fn prop_replacement_updates_every_standalone_occurrence(
        prefix in boundary_padding_strategy(),
        infix in nonempty_boundary_padding_strategy(),
        suffix in boundary_padding_strategy(),
        from in module_name_strategy(),
        to in module_name_strategy(),
    ) {
        prop_assume!(from != to);

        let line = format!("{prefix}{from}{infix}{from}{suffix}");
        let (rewritten, changed) = replace_module_token(&line, &from, &to);

        prop_assert!(changed);
        let expected = format!("{prefix}{to}{infix}{to}{suffix}");
        prop_assert_eq!(rewritten.as_str(), expected.as_str());
        prop_assert!(!contains_module_token(&rewritten, &from));
        prop_assert!(contains_module_token(&rewritten, &to));
    }

    #[test]
    fn prop_self_replacement_preserves_the_line(
        line in "(?s).{0,256}",
        module in module_name_strategy(),
    ) {
        let (rewritten, changed) = replace_module_token(&line, &module, &module);
        prop_assert_eq!(contains_module_token(&line, &module), changed);
        prop_assert_eq!(rewritten, line);
    }
}