wsb 0.275.7622

Workspace tools for every project under $PRJ: git across submodules, template stamping, GitHub, and the workbench
Documentation
//! One definition of what a replacement is. These are the cases where the two
//! old definitions disagreed — names honoured the flags, contents ignored them.

use super::*;

fn pairs(from: &str, to: &str) -> Vec<(String, String)> {
    vec![(from.to_string(), to.to_string())]
}

#[test]
fn test11121_a_literal_pattern_is_not_a_regular_expression() {
    // `a.c` matches exactly that, not "a, anything, c". Escaping is what a
    // literal mode has to do, and what a bare regex build would not.
    let rules = Replacements::compile(&pairs("a.c", "X"), false, false).expect("compiles");
    assert_eq!(rules.apply("a.c abc adc"), "X abc adc");

    // Also when case folding forces the regex engine into play.
    let folded = Replacements::compile(&pairs("a.c", "X"), false, true).expect("compiles");
    assert_eq!(folded.apply("A.C abc"), "X abc");
}

#[test]
fn test11122_ignore_case_reaches_file_contents() {
    // The whole reason this module exists: contents went through a plain
    // `str::replace`, so `--ignore-case` did nothing to them while doing
    // something to names — one flag, two behaviours.
    let rules = Replacements::compile(&pairs("widget", "gadget"), false, true).expect("compiles");
    assert_eq!(rules.apply("Widget WIDGET widget"), "gadget gadget gadget");
    assert_eq!(rules.count("Widget WIDGET widget"), 3);
}

#[test]
fn test11123_regex_reaches_file_contents_and_expands_captures() {
    let rules =
        Replacements::compile(&pairs(r"fn (\w+)\(", "fn new_$1("), true, false).expect("compiles");
    assert_eq!(rules.apply("fn build(x)"), "fn new_build(x)");
}

#[test]
fn test11124_a_dollar_in_a_literal_replacement_is_two_characters() {
    // `$1` means a capture only where the reader wrote a regex. In literal
    // mode it is what they typed, and expanding it would silently delete it.
    let rules = Replacements::compile(&pairs("PRICE", "$1.00"), false, true).expect("compiles");
    assert_eq!(rules.apply("PRICE"), "$1.00");
}

#[test]
fn test11125_a_pattern_that_cannot_compile_is_refused_before_anything_is_read() {
    // Otherwise it matches nothing across a whole tree and reports "no changes
    // needed", which reads exactly like a successful run that found nothing.
    let refused = Replacements::compile(&pairs("([unclosed", "x"), true, false)
        .expect_err("a bad expression is refused");
    assert!(refused.contains("not a valid regular expression"), "{refused}");

    let empty = Replacements::compile(&pairs("", "x"), false, false).expect_err("empty");
    assert!(empty.contains("cannot be empty"), "{empty}");
}

#[test]
fn test11126_pairs_are_applied_in_order_each_seeing_the_last_ones_output() {
    let rules = Replacements::compile(
        &[
            ("one".to_string(), "two".to_string()),
            ("two".to_string(), "three".to_string()),
        ],
        false,
        false,
    )
    .expect("compiles");
    // Deliberate chaining: `one` becomes `two`, which the next rule turns into
    // `three`. Both replacements really happen and both are counted.
    assert_eq!(rules.apply("one"), "three");
    assert_eq!(rules.count("one"), 2);
}

#[test]
fn test11127_a_replacement_that_changes_nothing_is_not_a_change() {
    // A rule can match and replace a string with itself; a later rule can undo
    // an earlier one. Either would list a file the write never touches.
    let same = Replacements::compile(&pairs("x", "x"), false, false).expect("compiles");
    assert!(!same.changes("x y z"));

    let undone = Replacements::compile(
        &[
            ("a".to_string(), "b".to_string()),
            ("b".to_string(), "a".to_string()),
        ],
        false,
        false,
    )
    .expect("compiles");
    assert!(!undone.changes("a"), "b became a again, so nothing changed");
}

#[test]
fn test11128_a_substitute_may_contain_newlines() {
    // Which is what makes a line-for-line preview wrong, and what the diff has
    // to cope with.
    let rules = Replacements::compile(&pairs("SPLIT", "one\ntwo"), false, false).expect("compiles");
    assert_eq!(rules.apply("a SPLIT b"), "a one\ntwo b");
}

#[test]
fn test11129_a_regex_may_match_across_lines() {
    let rules = Replacements::compile(&pairs(r"first\nsecond", "merged"), true, false)
        .expect("compiles");
    assert_eq!(rules.apply("first\nsecond\n"), "merged\n");
}

#[test]
fn test11130_counting_is_what_will_happen_not_what_each_rule_would_do_alone() {
    // Counted against the text each rule actually sees. Counting each against
    // the ORIGINAL would report two replacements for `aa` where one happens.
    let rules = Replacements::compile(
        &[
            ("aa".to_string(), "b".to_string()),
            ("aa".to_string(), "c".to_string()),
        ],
        false,
        false,
    )
    .expect("compiles");
    assert_eq!(rules.apply("aaaa"), "bb");
    assert_eq!(rules.count("aaaa"), 2, "two by the first rule, none by the second");
}