use std::io::{self, Read, Write};
use std::path::Path;
const TARGETS: &[&str] = &[
"clippy::arithmetic_side_effects",
"clippy::as_conversions",
];
fn strip_file(path: &Path) -> io::Result<bool> {
let mut src = String::new();
std::fs::File::open(path)?.read_to_string(&mut src)?;
let new_src = strip_allows_from_source(&src);
if new_src == src {
return Ok(false);
}
std::fs::File::create(path)?.write_all(new_src.as_bytes())?;
Ok(true)
}
pub fn strip_allows_from_source(src: &str) -> String {
src.lines()
.map(|line| process_line(line))
.filter_map(|opt| opt)
.collect::<Vec<_>>()
.join("\n")
+ if src.ends_with('\n') { "\n" } else { "" }
}
fn process_line(line: &str) -> Option<String> {
let trimmed = line.trim();
if !trimmed.starts_with("#![allow(") || !trimmed.ends_with(")]") {
return Some(line.to_owned());
}
let inner = &trimmed["#![allow(".len()..trimmed.len() - 2];
let lints: Vec<&str> = inner.split(',').map(|s| s.trim()).collect();
let remaining: Vec<&str> = lints
.iter()
.copied()
.filter(|&l| !TARGETS.contains(&l))
.collect();
if remaining.is_empty() {
return None;
}
if remaining.len() == lints.len() {
return Some(line.to_owned());
}
let leading = &line[..line.len() - line.trim_start().len()];
Some(format!("{}#![allow({})]", leading, remaining.join(", ")))
}
pub fn strip_project(project_dir: &Path, lint: crate::LintTarget) -> usize {
if !lint.includes_arithmetic() && !lint.includes_as_conversions() {
return 0;
}
let mut modified = 0usize;
for entry in walkdir::WalkDir::new(project_dir)
.follow_links(false)
.into_iter()
.filter_map(|e| e.ok())
.filter(|e| e.file_type().is_file())
{
let path = entry.path();
if path.extension().and_then(|e| e.to_str()) != Some("rs") {
continue;
}
if crate::apply::is_third_party(path) {
continue;
}
match strip_file(path) {
Ok(true) => {
modified += 1;
println!(" stripped allows: {}", path.display());
}
Ok(false) => {}
Err(e) => eprintln!("warn-rewrite: strip_allows: {}: {}", path.display(), e),
}
}
modified
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_strip_single_arithmetic() {
let src = "#![allow(clippy::arithmetic_side_effects)]\nfn main() {}\n";
let out = strip_allows_from_source(src);
assert_eq!(out, "fn main() {}\n");
}
#[test]
fn test_strip_single_as_conversions() {
let src = "#![allow(clippy::as_conversions)]\nfn f() {}\n";
let out = strip_allows_from_source(src);
assert_eq!(out, "fn f() {}\n");
}
#[test]
fn test_strip_combined() {
let src =
"#![allow(clippy::arithmetic_side_effects, clippy::as_conversions)]\nfn f() {}\n";
let out = strip_allows_from_source(src);
assert_eq!(out, "fn f() {}\n");
}
#[test]
fn test_narrow_keeps_other_lints() {
let src = "#![allow(dead_code, clippy::arithmetic_side_effects)]\nfn f() {}\n";
let out = strip_allows_from_source(src);
assert_eq!(out, "#![allow(dead_code)]\nfn f() {}\n");
}
#[test]
fn test_unrelated_allow_untouched() {
let src = "#![allow(dead_code)]\nfn f() {}\n";
let out = strip_allows_from_source(src);
assert_eq!(out, src);
}
#[test]
fn test_no_trailing_newline() {
let src = "#![allow(clippy::as_conversions)]";
let out = strip_allows_from_source(src);
assert_eq!(out, "");
}
#[test]
fn test_preserves_leading_whitespace() {
let src = " #![allow(dead_code, clippy::as_conversions)]\nfn f() {}\n";
let out = strip_allows_from_source(src);
assert_eq!(out, " #![allow(dead_code)]\nfn f() {}\n");
}
}