use super::diagnostic::{Applicability, Fix};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FixOutcome {
pub output: String,
pub applied: usize,
pub skipped_conflicts: usize,
}
pub fn apply_fixes(source: &str, fixes: &[Fix], include_unsafe: bool) -> FixOutcome {
let mut eligible: Vec<&Fix> = fixes
.iter()
.filter(|f| include_unsafe || f.applicability == Applicability::Safe)
.collect();
eligible.sort_by_key(|f| (f.start, f.end));
let mut accepted: Vec<&Fix> = Vec::with_capacity(eligible.len());
let mut skipped_conflicts = 0usize;
let mut last_end = 0usize;
for fix in eligible {
if fix.start > fix.end || fix.end > source.len() {
skipped_conflicts += 1;
continue;
}
if !accepted.is_empty() && fix.start < last_end {
skipped_conflicts += 1;
continue;
}
last_end = fix.end;
accepted.push(fix);
}
let applied = accepted.len();
let mut output = source.to_string();
for fix in accepted.iter().rev() {
output.replace_range(fix.start..fix.end, &fix.content);
}
FixOutcome {
output,
applied,
skipped_conflicts,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn safe(start: usize, end: usize, content: &str) -> Fix {
Fix::safe(start, end, content, "test")
}
#[test]
fn applies_single_fix() {
let out = apply_fixes("if (x = 1) y", &[safe(6, 7, "==")], false);
assert_eq!(out.output, "if (x == 1) y");
assert_eq!(out.applied, 1);
assert_eq!(out.skipped_conflicts, 0);
}
#[test]
fn applies_multiple_fixes_right_to_left() {
let out = apply_fixes("a = b = c", &[safe(2, 3, "=="), safe(6, 7, "==")], false);
assert_eq!(out.output, "a == b == c");
assert_eq!(out.applied, 2);
}
#[test]
fn skips_unsafe_unless_opted_in() {
let fixes = [Fix::unsafe_(0, 6, "", "delete")];
let kept = apply_fixes("x <- 1\n", &fixes, false);
assert_eq!(kept.output, "x <- 1\n");
assert_eq!(kept.applied, 0);
let applied = apply_fixes("x <- 1\n", &fixes, true);
assert_eq!(applied.output, "\n");
assert_eq!(applied.applied, 1);
}
#[test]
fn drops_overlapping_fixes() {
let out = apply_fixes("abcdef", &[safe(0, 3, "X"), safe(2, 5, "Y")], false);
assert_eq!(out.output, "Xdef");
assert_eq!(out.applied, 1);
assert_eq!(out.skipped_conflicts, 1);
}
#[test]
fn adjacent_fixes_do_not_conflict() {
let out = apply_fixes("abcd", &[safe(0, 2, "X"), safe(2, 4, "Y")], false);
assert_eq!(out.output, "XY");
assert_eq!(out.applied, 2);
assert_eq!(out.skipped_conflicts, 0);
}
}