use std::collections::HashMap;
use std::path::PathBuf;
use crate::{Rewrite, RewriteKind};
pub fn apply_rewrites(rewrites: Vec<Rewrite>) {
let mut by_file: HashMap<PathBuf, Vec<Rewrite>> = HashMap::new();
for r in rewrites {
if is_third_party(&r.file) {
continue;
}
by_file.entry(r.file.clone()).or_default().push(r);
}
for (file, mut file_rewrites) in by_file {
if !file.exists() {
eprintln!("warn-rewrite: file not found: {:?}", file);
continue;
}
let src = match std::fs::read(&file) {
Ok(b) => b,
Err(e) => {
eprintln!("warn-rewrite: read error {:?}: {}", file, e);
continue;
}
};
file_rewrites.sort_by(|a, b| {
b.full_start.cmp(&a.full_start)
.then(b.full_end.cmp(&a.full_end))
});
let mut deduped: Vec<Rewrite> = Vec::new();
for rw in file_rewrites {
let overlaps = deduped.iter().any(|prev| {
rw.full_start < prev.full_end && rw.full_end > prev.full_start
});
if !overlaps {
deduped.push(rw);
}
}
let mut result = src;
for rw in deduped {
let start = rw.full_start as usize;
let end = rw.full_end as usize;
let is_insertion = start == end;
if !is_insertion && (end > result.len() || start > end) {
eprintln!(
"warn-rewrite: offset out of range {}..{} in {:?} (len {})",
start, end, file, result.len()
);
continue;
}
if is_insertion && start > result.len() {
eprintln!(
"warn-rewrite: insertion offset {} out of range in {:?} (len {})",
start, file, result.len()
);
continue;
}
let replacement = generate_replacement(&rw);
let replacement_bytes = replacement.as_bytes();
let mut new_result = Vec::with_capacity(result.len() + replacement_bytes.len());
new_result.extend_from_slice(&result[..start]);
new_result.extend_from_slice(replacement_bytes);
new_result.extend_from_slice(&result[end..]);
result = new_result;
}
if let Err(e) = std::fs::write(&file, &result) {
eprintln!("warn-rewrite: write error {:?}: {}", file, e);
}
}
}
pub fn is_third_party(path: &std::path::Path) -> bool {
path.components().any(|c| {
matches!(c, std::path::Component::Normal(s) if
s == ".cargo" || s == "registry" || s == "rustup" || s == "toolchains"
)
})
}
fn generate_replacement(rw: &Rewrite) -> String {
match &rw.kind {
RewriteKind::TypeFrom { dst } => {
format!("{}::from({})", dst, rw.inner_snippet)
}
RewriteKind::TryFrom { dst } => {
format!("{}::try_from({}).unwrap_or_default()", dst, rw.inner_snippet)
}
RewriteKind::CharFrom => {
format!("char::from({})", rw.inner_snippet)
}
RewriteKind::WrappingBinop { method, lhs_ty } => {
let rhs = rw.rhs_snippet.as_deref().unwrap_or("_rhs_");
let lhs = &rw.inner_snippet;
let lhs_wrapped = if needs_parens(lhs) {
format!("({})", lhs)
} else if is_bare_integer_literal(lhs) {
format!("{}_{}", lhs, lhs_ty)
} else {
lhs.clone()
};
let rhs_annotated = if is_bare_integer_literal(rhs) {
format!("{}_{}", rhs, lhs_ty)
} else {
rhs.to_string()
};
format!("{}.{}({})", lhs_wrapped, method, rhs_annotated)
}
RewriteKind::WrappingAssignOp { method, lhs_snippet } => {
let rhs = rw.rhs_snippet.as_deref().unwrap_or("_rhs_");
let receiver = if needs_parens(lhs_snippet) {
format!("({})", lhs_snippet)
} else {
lhs_snippet.clone()
};
format!("{} = {}.{}({})", lhs_snippet, receiver, method, rhs)
}
RewriteKind::WrappingNeg { operand_ty } => {
let operand = &rw.inner_snippet;
let operand_wrapped = if needs_parens(operand) {
format!("({})", operand)
} else if is_bare_integer_literal(operand) {
format!("{}_{}", operand, operand_ty)
} else {
operand.clone()
};
format!("{}.wrapping_neg()", operand_wrapped)
}
RewriteKind::AllowConstCast => {
"#[allow(clippy::as_conversions)]\n".to_owned()
}
}
}
fn needs_parens(s: &str) -> bool {
let s = s.trim();
if is_bare_integer_literal(s) {
return false;
}
if s.starts_with('*') || s.starts_with('&') || s.starts_with('!') || s.starts_with('-') {
return true;
}
if s.chars().all(|c| c.is_alphanumeric() || matches!(c, '_' | '[' | ']' | '.' | '(' | ')')) {
return false;
}
s.contains(' ')
}
fn is_bare_integer_literal(s: &str) -> bool {
let s = s.trim();
if s.is_empty() {
return false;
}
let bytes = s.as_bytes();
let mut all_digits_underscores = true;
for &b in bytes {
if !b.is_ascii_digit() && b != b'_' {
all_digits_underscores = false;
break;
}
}
if !all_digits_underscores {
return false; }
bytes[0].is_ascii_digit()
}