weavatrix-edit 0.1.7

Deterministic, Unicode-safe edit-plan validation and application for source refactoring
Documentation
use weavatrix_edit::{ByteEdit, ErrorCode, Provenance, apply_byte_edits, prepare_byte_edits};

#[test]
fn shuffled_disjoint_plans_match_reference_splicer() {
    let source = "abcdefghijklmnopqrstuvwxyz".repeat(64);
    let mut seed = 0x5eed_cafe_f00d_beef_u64;

    for case in 0..2_000 {
        let mut edits = make_disjoint_edits(&source, &mut seed, case);
        let expected = reference_apply(&source, &edits);
        shuffle(&mut edits, &mut seed);
        let actual = apply_byte_edits(&source, &edits).unwrap().text;
        assert_eq!(actual, expected, "case {case}");
        let prepared = prepare_byte_edits(&source, &edits).unwrap().apply().text;
        assert_eq!(prepared, expected, "prepared case {case}");
    }
}

#[test]
fn arbitrary_offsets_never_panic_and_never_split_utf8() {
    let source = "א😀z\r\nend";
    let mut seed = 0x1234_5678_9abc_def0_u64;

    for _ in 0..20_000 {
        let start = bounded(&mut seed, source.len() + 8);
        let end = bounded(&mut seed, source.len() + 8);
        let edit = ByteEdit::replace(start..end, "", "x", Provenance::EXACT_LSP);
        let result = prepare_byte_edits(source, &[edit]);
        if let Ok(prepared) = result {
            assert!(prepared.apply().text.is_char_boundary(0));
        }
    }
}

#[test]
fn overlapping_inputs_are_rejected_for_every_permutation() {
    let source = "abcdefgh";
    let mut edits = vec![
        ByteEdit::replace(1..5, "bcde", "B", Provenance::EXACT_LSP),
        ByteEdit::replace(3..6, "def", "D", Provenance::EXACT_LSP),
        ByteEdit::insert(4, "!", Provenance::EXACT_LSP),
    ];
    let mut seed = 7_u64;
    for _ in 0..100 {
        shuffle(&mut edits, &mut seed);
        assert_eq!(
            prepare_byte_edits(source, &edits).unwrap_err().code(),
            ErrorCode::OverlappingEdits
        );
    }
}

fn make_disjoint_edits(source: &str, seed: &mut u64, case: usize) -> Vec<ByteEdit> {
    let edit_count = 1 + bounded(seed, 24);
    let stride = source.len() / (edit_count + 1);
    let mut edits = Vec::with_capacity(edit_count);
    for index in 0..edit_count {
        let start = (index + 1) * stride;
        let width = 1 + bounded(seed, 3);
        let end = (start + width).min(source.len());
        let replacement = format!("<{case}:{index}>");
        edits.push(ByteEdit::replace(
            start..end,
            &source[start..end],
            replacement,
            Provenance::EXACT_LSP,
        ));
    }
    edits
}

fn reference_apply(source: &str, edits: &[ByteEdit]) -> String {
    let mut ordered = edits.iter().collect::<Vec<_>>();
    ordered.sort_by_key(|edit| edit.start);
    let mut output = String::new();
    let mut cursor = 0;
    for edit in ordered {
        output.push_str(&source[cursor..edit.start]);
        output.push_str(&edit.after);
        cursor = edit.end;
    }
    output.push_str(&source[cursor..]);
    output
}

fn shuffle<T>(values: &mut [T], seed: &mut u64) {
    for index in (1..values.len()).rev() {
        values.swap(index, bounded(seed, index + 1));
    }
}

fn next(seed: &mut u64) -> u64 {
    *seed = seed.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
    *seed
}

fn bounded(seed: &mut u64, upper: usize) -> usize {
    let upper = u64::try_from(upper).unwrap_or(u64::MAX);
    usize::try_from(next(seed) % upper).expect("modulo result must fit in usize")
}