pub fn apply_fixes(
source: &str,
result: &LintResult,
options: &FixOptions,
) -> io::Result<FixResult> {
let mut modified = source.to_string();
let mut fixes_applied = 0;
let mut diagnostics_with_fixes: Vec<&Diagnostic> = result
.diagnostics
.iter()
.filter(|d| {
if let Some(fix) = &d.fix {
if options.apply_assumptions {
fix.is_safe_with_assumptions()
} else {
fix.is_safe()
}
} else {
false
}
})
.collect();
diagnostics_with_fixes.sort_by(|a, b| {
let priority_a = FixPriority::from_code(&a.code);
let priority_b = FixPriority::from_code(&b.code);
priority_b
.cmp(&priority_a)
.then(b.span.start_line.cmp(&a.span.start_line))
.then(b.span.start_col.cmp(&a.span.start_col))
});
let mut applied_spans: Vec<Span> = Vec::new();
for diagnostic in diagnostics_with_fixes {
if let Some(fix) = &diagnostic.fix {
let has_conflict = applied_spans
.iter()
.any(|s| spans_overlap(s, &diagnostic.span));
if has_conflict {
continue;
}
modified = apply_single_fix(&modified, &diagnostic.span, &fix.replacement)?;
fixes_applied += 1;
applied_spans.push(diagnostic.span);
}
}
Ok(FixResult {
fixes_applied,
modified_source: if options.dry_run {
None
} else {
Some(modified)
},
backup_path: None,
})
}
pub fn apply_fixes_to_file(
file_path: &Path,
result: &LintResult,
options: &FixOptions,
) -> io::Result<FixResult> {
let source = fs::read_to_string(file_path)?;
let mut fix_result = apply_fixes(&source, result, options)?;
if options.create_backup && !options.dry_run {
let backup_path = format!("{}{}", file_path.display(), options.backup_suffix);
fs::copy(file_path, &backup_path)?;
fix_result.backup_path = Some(backup_path);
}
if !options.dry_run {
if let Some(ref modified) = fix_result.modified_source {
let output = if let Some(ref out_path) = options.output_path {
out_path
} else {
file_path
};
fs::write(output, modified)?;
}
}
Ok(fix_result)
}
fn apply_single_fix(source: &str, span: &Span, replacement: &str) -> io::Result<String> {
let lines: Vec<&str> = source.lines().collect();
if span.start_line == 0 || span.start_line > lines.len() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("Invalid line number: {}", span.start_line),
));
}
let line_idx = span.start_line - 1; let line = lines[line_idx];
if span.start_col == 0 || span.start_col > line.len() + 1 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("Invalid start column: {}", span.start_col),
));
}
if span.end_col == 0 || span.end_col > line.len() + 1 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("Invalid end column: {}", span.end_col),
));
}
let before = &line[..span.start_col - 1];
let after = &line[span.end_col - 1..];
let fixed_line = format!("{}{}{}", before, replacement, after);
let mut result_lines = lines.clone();
result_lines[line_idx] = &fixed_line;
let result = result_lines.join("\n");
if source.ends_with('\n') && !result.ends_with('\n') {
Ok(format!("{}\n", result))
} else {
Ok(result)
}
}
#[cfg(test)]
#[path = "autofix_tests_apply_single.rs"]
mod tests_extracted;