weavatrix-edit 0.1.4

Deterministic, Unicode-safe edit-plan validation and application for source refactoring
Documentation
use weavatrix_edit::{
    ApplyLimits, ByteEdit, DiagnosticLimits, ErrorCode, Position, PositionEncoding, Provenance,
    TextEdit, diagnose_byte_edits, diagnose_byte_edits_with_limits, diagnose_edits,
    diagnose_edits_with_encoding_and_limits,
};

use blazingly_json::to_string;

#[test]
fn mismatch_evidence_is_structured_and_strictly_bounded() {
    let source = "actual";
    let edits = [ByteEdit::replace(
        0..source.len(),
        "expected-secret-value",
        "replacement",
        Provenance::EXACT_LSP,
    )];
    let report = diagnose_byte_edits_with_limits(
        source,
        &edits,
        ApplyLimits::default(),
        DiagnosticLimits {
            max_items: 8,
            max_preview_bytes: 5,
        },
    );

    assert!(!report.is_valid());
    assert_eq!(report.total_diagnostics(), 1);
    let error = &report.diagnostics()[0];
    assert_eq!(error.code(), ErrorCode::BeforeMismatch);
    assert!(!error.message().contains("expected-secret-value"));
    let mismatch = error.mismatch().unwrap();
    assert_eq!(mismatch.source_range().start, 0);
    assert_eq!(mismatch.source_range().end, source.len());
    assert_eq!(mismatch.expected().byte_len(), 21);
    assert_eq!(mismatch.expected().text(), "expec");
    assert!(mismatch.expected().is_truncated());
    assert_eq!(mismatch.actual().text(), "actua");
    assert!(mismatch.actual().is_truncated());

    let wire = to_string(&report).unwrap();
    assert!(!wire.contains("expected-secret-value"));
    assert!(wire.contains(r#""text":"expec""#));
    assert!(wire.contains(r#""byteLen":21"#));
}

#[test]
fn report_counts_multiple_failures_and_bounds_retained_items() {
    let edits = [
        ByteEdit::replace(0..2, "wrong-one", "X", Provenance::RESOLVED),
        ByteEdit::replace(1..3, "wrong-two", "Y", Provenance::EXACT_LSP),
    ];
    let report = diagnose_byte_edits_with_limits(
        "abcdef",
        &edits,
        ApplyLimits::default(),
        DiagnosticLimits {
            max_items: 2,
            max_preview_bytes: 4,
        },
    );

    assert_eq!(report.total_diagnostics(), 3);
    assert_eq!(report.diagnostics().len(), 2);
    assert!(report.is_truncated());
    assert!(
        report
            .diagnostics()
            .iter()
            .all(|diagnostic| diagnostic.code() == ErrorCode::BeforeMismatch)
    );
}

#[test]
fn text_diagnostics_continue_after_an_invalid_position() {
    let edits = [
        TextEdit::insert(Position::new(9, 0), "x", Provenance::EXACT_LSP),
        TextEdit::replace(
            weavatrix_edit::TextRange::new(Position::new(1, 0), Position::new(1, 1)),
            "wrong",
            "y",
            Provenance::RESOLVED,
        ),
    ];
    let report = diagnose_edits("abc", &edits);

    assert_eq!(report.total_diagnostics(), 2);
    assert_eq!(
        report.diagnostics()[0].code(),
        ErrorCode::PositionOutOfRange
    );
    assert_eq!(report.diagnostics()[1].code(), ErrorCode::BeforeMismatch);
}

#[test]
fn diagnostics_cover_structural_resource_and_output_failures() {
    let source_limited = ApplyLimits {
        max_source_bytes: 0,
        ..ApplyLimits::default()
    };
    let text_limit = diagnose_edits_with_encoding_and_limits(
        "a",
        &[],
        PositionEncoding::Utf16,
        source_limited,
        DiagnosticLimits::default(),
    );
    assert_eq!(text_limit.diagnostics()[0].code(), ErrorCode::PlanTooLarge);

    let byte_limit =
        diagnose_byte_edits_with_limits("a", &[], source_limited, DiagnosticLimits::default());
    assert_eq!(byte_limit.diagnostics()[0].code(), ErrorCode::PlanTooLarge);

    let invalid_byte = diagnose_byte_edits(
        "a",
        &[ByteEdit::replace(0..1, "a", "a", Provenance::EXACT_LSP)],
    );
    assert_eq!(invalid_byte.diagnostics()[0].code(), ErrorCode::InvalidEdit);

    let output_limited = diagnose_byte_edits_with_limits(
        "a",
        &[ByteEdit::insert(0, "xx", Provenance::EXACT_LSP)],
        ApplyLimits {
            max_output_bytes: 1,
            ..ApplyLimits::default()
        },
        DiagnosticLimits::default(),
    );
    assert_eq!(
        output_limited.diagnostics()[0].code(),
        ErrorCode::OutputTooLarge
    );

    let invalid_text = diagnose_edits_with_encoding_and_limits(
        "a",
        &[
            TextEdit::insert(Position::new(0, 0), "x", Provenance::EXACT_LSP),
            TextEdit::replace(
                weavatrix_edit::TextRange::new(Position::new(1, 0), Position::new(1, 99)),
                "a",
                "b",
                Provenance::EXACT_LSP,
            ),
        ],
        PositionEncoding::Utf16,
        ApplyLimits::default(),
        DiagnosticLimits::default(),
    );
    assert_eq!(invalid_text.total_diagnostics(), 2);
    assert_eq!(invalid_text.diagnostics()[0].code(), ErrorCode::InvalidEdit);
    assert_eq!(
        invalid_text.diagnostics()[1].code(),
        ErrorCode::PositionOutOfRange
    );
}