mirror of
https://github.com/Noratrieb/cargo-minimize.git
synced 2026-01-14 16:35:01 +01:00
aaa
This commit is contained in:
parent
002bad34ae
commit
a9e488f3e3
11 changed files with 433 additions and 40 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
|
@ -155,6 +155,8 @@ dependencies = [
|
||||||
"prettyplease",
|
"prettyplease",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
"rustfix",
|
||||||
|
"serde_json",
|
||||||
"syn",
|
"syn",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
@ -965,9 +967,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.46"
|
version = "1.0.48"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b"
|
checksum = "e9d89e5dba24725ae5678020bf8f1357a9aa7ff10736b551adbcd3f8d17d766f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
@ -1134,9 +1136,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.85"
|
version = "1.0.90"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44"
|
checksum = "8778cc0b528968fe72abec38b5db5a20a70d148116cd9325d2bc5f5180ca3faf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,9 @@ anyhow = "1.0.65"
|
||||||
cargo = "0.65.0"
|
cargo = "0.65.0"
|
||||||
clap = { version = "4.0.29", features = ["derive"] }
|
clap = { version = "4.0.29", features = ["derive"] }
|
||||||
prettyplease = "0.1.19"
|
prettyplease = "0.1.19"
|
||||||
proc-macro2 = "1.0.46"
|
proc-macro2 = { version = "1.0.48", features = ["span-locations"] }
|
||||||
quote = "1.0.21"
|
quote = "1.0.21"
|
||||||
|
rustfix = "0.6.1"
|
||||||
|
serde_json = "1.0.90"
|
||||||
syn = { version = "1.0.101", features = ["full", "visit", "visit-mut"] }
|
syn = { version = "1.0.101", features = ["full", "visit", "visit-mut"] }
|
||||||
walkdir = "2.3.2"
|
walkdir = "2.3.2"
|
||||||
|
|
|
||||||
54
src/build.rs
54
src/build.rs
|
|
@ -1,5 +1,6 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use std::{fmt::Display, path::PathBuf};
|
use rustfix::diagnostics::Diagnostic;
|
||||||
|
use std::{collections::HashSet, fmt::Display, path::PathBuf};
|
||||||
|
|
||||||
use crate::Options;
|
use crate::Options;
|
||||||
|
|
||||||
|
|
@ -7,6 +8,7 @@ use crate::Options;
|
||||||
pub struct Build {
|
pub struct Build {
|
||||||
mode: BuildMode,
|
mode: BuildMode,
|
||||||
input_path: PathBuf,
|
input_path: PathBuf,
|
||||||
|
no_verify: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -28,6 +30,7 @@ impl Build {
|
||||||
Self {
|
Self {
|
||||||
mode,
|
mode,
|
||||||
input_path: options.path.clone(),
|
input_path: options.path.clone(),
|
||||||
|
no_verify: options.no_verify,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,19 +64,58 @@ impl Build {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(BuildResult { reproduces_issue })
|
Ok(BuildResult {
|
||||||
|
reproduces_issue,
|
||||||
|
no_verify: self.no_verify,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_suggestions(&self) -> Result<(Vec<Diagnostic>, Vec<rustfix::Suggestion>)> {
|
||||||
|
match self.mode {
|
||||||
|
BuildMode::Cargo => {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
BuildMode::Script(_) => todo!(),
|
||||||
|
BuildMode::Rustc => {
|
||||||
|
let mut cmd = std::process::Command::new("rustc");
|
||||||
|
cmd.args(["--edition", "2018", "--error-format=json"]);
|
||||||
|
cmd.arg(&self.input_path);
|
||||||
|
|
||||||
|
let output = cmd.output()?.stderr;
|
||||||
|
let output = String::from_utf8(output)?;
|
||||||
|
|
||||||
|
let diags = serde_json::Deserializer::from_str(&output).into_iter::<Diagnostic>().collect::<Result<_, _>>()?;
|
||||||
|
|
||||||
|
let suggestions = rustfix::get_suggestions_from_json(
|
||||||
|
&output,
|
||||||
|
&HashSet::new(),
|
||||||
|
rustfix::Filter::Everything,
|
||||||
|
)
|
||||||
|
.context("reading output as rustfix suggestions")?;
|
||||||
|
|
||||||
|
Ok((diags, suggestions))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct BuildResult {
|
pub struct BuildResult {
|
||||||
pub reproduces_issue: bool,
|
reproduces_issue: bool,
|
||||||
|
no_verify: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BuildResult {
|
||||||
|
pub fn reproduces_issue(&self) -> bool {
|
||||||
|
self.reproduces_issue || self.no_verify
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for BuildResult {
|
impl Display for BuildResult {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self.reproduces_issue {
|
match (self.reproduces_issue, self.no_verify) {
|
||||||
true => f.write_str("yes"),
|
(true, _) => f.write_str("yes"),
|
||||||
false => f.write_str("no"),
|
(false, true) => f.write_str("no (ignore)"),
|
||||||
|
(false, false) => f.write_str("no"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
14
src/lib.rs
14
src/lib.rs
|
|
@ -8,11 +8,11 @@ mod expand;
|
||||||
mod privatize;
|
mod privatize;
|
||||||
mod processor;
|
mod processor;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{Context, Result};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use processor::Minimizer;
|
use processor::Minimizer;
|
||||||
|
|
||||||
use crate::{everybody_loops::EverybodyLoops, processor::Processor};
|
use crate::{everybody_loops::EverybodyLoops, processor::Processor, privatize::Privatize};
|
||||||
|
|
||||||
#[derive(clap::Parser)]
|
#[derive(clap::Parser)]
|
||||||
pub struct Options {
|
pub struct Options {
|
||||||
|
|
@ -20,6 +20,8 @@ pub struct Options {
|
||||||
verify_error_path: Option<PathBuf>,
|
verify_error_path: Option<PathBuf>,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
cargo: bool,
|
cargo: bool,
|
||||||
|
#[arg(long)]
|
||||||
|
no_verify: bool,
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -28,15 +30,19 @@ pub fn minimize() -> Result<()> {
|
||||||
|
|
||||||
let build = build::Build::new(&options);
|
let build = build::Build::new(&options);
|
||||||
|
|
||||||
let mut minimizer = Minimizer::new_glob_dir(&options.path, build);
|
let mut minimizer = Minimizer::new_glob_dir(&options.path, build, &options);
|
||||||
|
|
||||||
println!("{minimizer:?}");
|
println!("{minimizer:?}");
|
||||||
|
|
||||||
|
minimizer.delete_dead_code().context("deleting dead code")?;
|
||||||
|
|
||||||
minimizer.run_passes([
|
minimizer.run_passes([
|
||||||
//Box::new(Privarize::default()) as Box<dyn Processor>,
|
Box::new(Privatize::default()) as Box<dyn Processor>,
|
||||||
Box::new(EverybodyLoops::default()) as Box<dyn Processor>,
|
Box::new(EverybodyLoops::default()) as Box<dyn Processor>,
|
||||||
])?;
|
])?;
|
||||||
|
|
||||||
|
minimizer.delete_dead_code().context("deleting dead code")?;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
let file = expand::expand(&dir).context("during expansion")?;
|
let file = expand::expand(&dir).context("during expansion")?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,9 @@ impl VisitMut for Visitor {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Privarize {}
|
pub struct Privatize {}
|
||||||
|
|
||||||
impl Processor for Privarize {
|
impl Processor for Privatize {
|
||||||
fn process_file(&mut self, krate: &mut syn::File, _: &mut ProcessChecker) -> bool {
|
fn process_file(&mut self, krate: &mut syn::File, _: &mut ProcessChecker) -> bool {
|
||||||
let mut visitor = Visitor::new();
|
let mut visitor = Visitor::new();
|
||||||
visitor.visit_file_mut(krate);
|
visitor.visit_file_mut(krate);
|
||||||
|
|
|
||||||
80
src/processor/files.rs
Normal file
80
src/processor/files.rs
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use std::{
|
||||||
|
fs,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SourceFile {
|
||||||
|
pub path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Changes {
|
||||||
|
any_change: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FileChange<'a, 'b> {
|
||||||
|
pub path: &'a Path,
|
||||||
|
content: String,
|
||||||
|
changes: &'b mut Changes,
|
||||||
|
has_written_change: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileChange<'_, '_> {
|
||||||
|
pub fn before_content(&self) -> &str {
|
||||||
|
&self.content
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(&mut self, new: &str) -> Result<()> {
|
||||||
|
self.has_written_change = true;
|
||||||
|
fs::write(&self.path, new).with_context(|| format!("writing file {}", self.path.display()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rollback(mut self) -> Result<()> {
|
||||||
|
assert!(self.has_written_change);
|
||||||
|
self.has_written_change = false;
|
||||||
|
fs::write(self.path, &self.content)
|
||||||
|
.with_context(|| format!("writing file {}", self.path.display()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn commit(mut self) {
|
||||||
|
assert!(self.has_written_change);
|
||||||
|
self.has_written_change = false;
|
||||||
|
self.changes.any_change = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for FileChange<'_, '_> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if self.has_written_change {
|
||||||
|
fs::write(&self.path, self.before_content()).ok();
|
||||||
|
if !std::thread::panicking() {
|
||||||
|
panic!("File contains unsaved changes!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SourceFile {
|
||||||
|
pub fn try_change<'file, 'change>(
|
||||||
|
&'file self,
|
||||||
|
changes: &'change mut Changes,
|
||||||
|
) -> Result<FileChange<'file, 'change>> {
|
||||||
|
let path = &self.path;
|
||||||
|
Ok(FileChange {
|
||||||
|
path,
|
||||||
|
changes,
|
||||||
|
has_written_change: false,
|
||||||
|
content: fs::read_to_string(path)
|
||||||
|
.with_context(|| format!("opening file {}", path.display()))?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Changes {
|
||||||
|
pub fn had_changes(&self) -> bool {
|
||||||
|
self.any_change
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
use std::{
|
mod files;
|
||||||
ffi::OsStr,
|
mod reaper;
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
use std::{ffi::OsStr, path::Path};
|
||||||
|
|
||||||
use anyhow::{ensure, Context, Result};
|
use anyhow::{ensure, Context, Result};
|
||||||
|
|
||||||
use crate::build::Build;
|
use crate::{build::Build, processor::files::Changes, Options};
|
||||||
|
|
||||||
|
use self::files::SourceFile;
|
||||||
|
|
||||||
pub trait Processor {
|
pub trait Processor {
|
||||||
fn process_file(&mut self, krate: &mut syn::File, checker: &mut ProcessChecker) -> bool;
|
fn process_file(&mut self, krate: &mut syn::File, checker: &mut ProcessChecker) -> bool;
|
||||||
|
|
@ -15,12 +17,13 @@ pub trait Processor {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Minimizer {
|
pub struct Minimizer {
|
||||||
files: Vec<PathBuf>,
|
files: Vec<SourceFile>,
|
||||||
build: Build,
|
build: Build,
|
||||||
|
no_verify: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Minimizer {
|
impl Minimizer {
|
||||||
pub fn new_glob_dir(path: &Path, build: Build) -> Self {
|
pub fn new_glob_dir(path: &Path, build: Build, options: &Options) -> Self {
|
||||||
let walk = walkdir::WalkDir::new(path);
|
let walk = walkdir::WalkDir::new(path);
|
||||||
|
|
||||||
let files = walk
|
let files = walk
|
||||||
|
|
@ -33,10 +36,16 @@ impl Minimizer {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.filter(|entry| entry.path().extension() == Some(OsStr::new("rs")))
|
.filter(|entry| entry.path().extension() == Some(OsStr::new("rs")))
|
||||||
.map(|entry| entry.into_path())
|
.map(|entry| SourceFile {
|
||||||
|
path: entry.into_path(),
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Self { files, build }
|
Self {
|
||||||
|
files,
|
||||||
|
build,
|
||||||
|
no_verify: options.no_verify,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_passes<'a>(
|
pub fn run_passes<'a>(
|
||||||
|
|
@ -45,23 +54,24 @@ impl Minimizer {
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let inital_build = self.build.build()?;
|
let inital_build = self.build.build()?;
|
||||||
println!("Initial build: {}", inital_build);
|
println!("Initial build: {}", inital_build);
|
||||||
ensure!(
|
if !self.no_verify {
|
||||||
inital_build.reproduces_issue,
|
ensure!(
|
||||||
"Initial build must reproduce issue"
|
inital_build.reproduces_issue(),
|
||||||
);
|
"Initial build must reproduce issue"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
for mut pass in passes {
|
for mut pass in passes {
|
||||||
'pass: loop {
|
'pass: loop {
|
||||||
println!("Starting a round of {}", pass.name());
|
println!("Starting a round of {}", pass.name());
|
||||||
let mut any_change = false;
|
let mut changes = Changes::default();
|
||||||
|
|
||||||
for file in &self.files {
|
for file in &self.files {
|
||||||
let file_display = file.display();
|
let file_display = file.path.display();
|
||||||
|
|
||||||
let before_string = std::fs::read_to_string(file)
|
let mut change = file.try_change(&mut changes)?;
|
||||||
.with_context(|| format!("opening file {file_display}"))?;
|
|
||||||
|
|
||||||
let mut krate = syn::parse_file(&before_string)
|
let mut krate = syn::parse_file(change.before_content())
|
||||||
.with_context(|| format!("parsing file {file_display}"))?;
|
.with_context(|| format!("parsing file {file_display}"))?;
|
||||||
|
|
||||||
let has_made_change = pass.process_file(&mut krate, &mut ProcessChecker {});
|
let has_made_change = pass.process_file(&mut krate, &mut ProcessChecker {});
|
||||||
|
|
@ -69,23 +79,23 @@ impl Minimizer {
|
||||||
if has_made_change {
|
if has_made_change {
|
||||||
let result = prettyplease::unparse(&krate);
|
let result = prettyplease::unparse(&krate);
|
||||||
|
|
||||||
std::fs::write(file, &result)?;
|
change.write(&result)?;
|
||||||
|
|
||||||
let after = self.build.build()?;
|
let after = self.build.build()?;
|
||||||
|
|
||||||
println!("{file_display}: After {}: {after}", pass.name());
|
println!("{file_display}: After {}: {after}", pass.name());
|
||||||
|
|
||||||
if after.reproduces_issue {
|
if after.reproduces_issue() {
|
||||||
any_change = true;
|
change.commit();
|
||||||
} else {
|
} else {
|
||||||
std::fs::write(file, before_string)?;
|
change.rollback()?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println!("{file_display}: After {}: no change", pass.name());
|
println!("{file_display}: After {}: no change", pass.name());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !any_change {
|
if !changes.had_changes() {
|
||||||
println!("Finished {}", pass.name());
|
println!("Finished {}", pass.name());
|
||||||
break 'pass;
|
break 'pass;
|
||||||
}
|
}
|
||||||
229
src/processor/reaper.rs
Normal file
229
src/processor/reaper.rs
Normal file
|
|
@ -0,0 +1,229 @@
|
||||||
|
//! Deletes dead code.
|
||||||
|
|
||||||
|
use super::{files::Changes, Minimizer, Processor};
|
||||||
|
use anyhow::{ensure, Context, Result};
|
||||||
|
use proc_macro2::Span;
|
||||||
|
use rustfix::{diagnostics::Diagnostic, Suggestion};
|
||||||
|
use std::{collections::HashMap, ops::Range, path::Path};
|
||||||
|
use syn::{visit_mut::VisitMut, ImplItem, Item};
|
||||||
|
|
||||||
|
fn file_for_suggestion(suggestion: &Suggestion) -> &str {
|
||||||
|
&suggestion.solutions[0].replacements[0].snippet.file_name
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Minimizer {
|
||||||
|
pub fn delete_dead_code(&mut self) -> Result<()> {
|
||||||
|
let inital_build = self.build.build()?;
|
||||||
|
println!("Before reaper: {}", inital_build);
|
||||||
|
if !self.no_verify {
|
||||||
|
ensure!(
|
||||||
|
inital_build.reproduces_issue(),
|
||||||
|
"Initial build must reproduce issue"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (diags, suggestions) = self
|
||||||
|
.build
|
||||||
|
.get_suggestions()
|
||||||
|
.context("getting suggestions from rustc")?;
|
||||||
|
|
||||||
|
let mut suggestions_for_file = HashMap::<_, Vec<_>>::new();
|
||||||
|
for suggestion in &suggestions {
|
||||||
|
suggestions_for_file
|
||||||
|
.entry(file_for_suggestion(suggestion))
|
||||||
|
.or_default()
|
||||||
|
.push(suggestion);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always unconditionally apply unused imports.
|
||||||
|
self.apply_unused_imports(&suggestions_for_file)?;
|
||||||
|
|
||||||
|
self.run_passes([Box::new(DeleteUnusedFunctions(diags)) as Box<dyn Processor>])
|
||||||
|
.context("deleting unused functions")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_unused_imports<'a>(
|
||||||
|
&mut self,
|
||||||
|
suggestions: &HashMap<&str, Vec<&Suggestion>>,
|
||||||
|
) -> Result<()> {
|
||||||
|
for (file, suggestions) in suggestions {
|
||||||
|
let file = self
|
||||||
|
.files
|
||||||
|
.iter()
|
||||||
|
.find(|source| source.path == Path::new(file))
|
||||||
|
.expect("unknown file");
|
||||||
|
|
||||||
|
let mut changes = &mut Changes::default();
|
||||||
|
|
||||||
|
let mut change = file.try_change(&mut changes)?;
|
||||||
|
|
||||||
|
let desired_suggestions = suggestions
|
||||||
|
.iter()
|
||||||
|
.filter(|sugg| sugg.message.contains("unused import"))
|
||||||
|
.cloned()
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let result = rustfix::apply_suggestions(change.before_content(), &desired_suggestions)?;
|
||||||
|
|
||||||
|
change.write(&result)?;
|
||||||
|
|
||||||
|
let after = self.build.build()?;
|
||||||
|
|
||||||
|
println!("{}: After reaper: {after}", file.path.display());
|
||||||
|
|
||||||
|
if after.reproduces_issue() {
|
||||||
|
change.commit();
|
||||||
|
} else {
|
||||||
|
change.rollback()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DeleteUnusedFunctions(Vec<Diagnostic>);
|
||||||
|
|
||||||
|
impl Processor for DeleteUnusedFunctions {
|
||||||
|
fn process_file(&mut self, krate: &mut syn::File, _: &mut super::ProcessChecker) -> bool {
|
||||||
|
let mut visitor = FindUnusedFunction::new(self.0.iter());
|
||||||
|
visitor.visit_file_mut(krate);
|
||||||
|
|
||||||
|
visitor.has_change
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"delete-unused-functions"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Unused {
|
||||||
|
name: String,
|
||||||
|
line: usize,
|
||||||
|
column: Range<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Unused {
|
||||||
|
fn span_matches(&self, ident_span: Span) -> bool {
|
||||||
|
let (start, end) = (ident_span.start(), ident_span.end());
|
||||||
|
|
||||||
|
assert_eq!(start.line, end.line);
|
||||||
|
|
||||||
|
let line_matches = self.line == start.line;
|
||||||
|
let column_matches = self.column.start <= start.column && self.column.end >= end.column;
|
||||||
|
|
||||||
|
line_matches && column_matches
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FindUnusedFunction {
|
||||||
|
unused_functions: Vec<Unused>,
|
||||||
|
has_change: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FindUnusedFunction {
|
||||||
|
fn new<'a>(diags: impl Iterator<Item = &'a Diagnostic>) -> Self {
|
||||||
|
let unused_functions = diags
|
||||||
|
.filter_map(|diag| {
|
||||||
|
// FIXME: use `code` correctly
|
||||||
|
if diag
|
||||||
|
.code
|
||||||
|
.as_ref()
|
||||||
|
.map_or(false, |code| code.code != "dead_code")
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !diag.message.contains("function") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = diag.message.split("`").nth(1)?.to_owned();
|
||||||
|
let span = &diag.spans[0];
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
span.line_start, span.line_end,
|
||||||
|
"encountered multiline span in dead_code"
|
||||||
|
);
|
||||||
|
|
||||||
|
Some(Unused {
|
||||||
|
name,
|
||||||
|
line: span.line_start,
|
||||||
|
column: (span.column_start - 1)..(span.column_end - 1),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
unused_functions,
|
||||||
|
has_change: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_retain_item(&mut self, span: Span) -> bool {
|
||||||
|
let span_matches = self
|
||||||
|
.unused_functions
|
||||||
|
.iter()
|
||||||
|
.map(|a| a.span_matches(span))
|
||||||
|
.filter(|&matches| matches)
|
||||||
|
.count();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
span_matches < 2,
|
||||||
|
"multiple dead_code spans matched identifier: {span_matches}"
|
||||||
|
);
|
||||||
|
|
||||||
|
if span_matches == 1 {
|
||||||
|
self.has_change = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
span_matches == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VisitMut for FindUnusedFunction {
|
||||||
|
fn visit_item_impl_mut(&mut self, item_impl: &mut syn::ItemImpl) {
|
||||||
|
item_impl.items.retain(|item| match item {
|
||||||
|
ImplItem::Method(method) => {
|
||||||
|
let span = method.sig.ident.span();
|
||||||
|
|
||||||
|
self.should_retain_item(span)
|
||||||
|
}
|
||||||
|
_ => true,
|
||||||
|
});
|
||||||
|
|
||||||
|
syn::visit_mut::visit_item_impl_mut(self, item_impl);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_file_mut(&mut self, krate: &mut syn::File) {
|
||||||
|
krate.items.retain(|item| match item {
|
||||||
|
Item::Fn(func) => {
|
||||||
|
let span = func.sig.ident.span();
|
||||||
|
|
||||||
|
self.should_retain_item(span)
|
||||||
|
}
|
||||||
|
_ => true,
|
||||||
|
});
|
||||||
|
|
||||||
|
syn::visit_mut::visit_file_mut(self, krate);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_item_mod_mut(&mut self, module: &mut syn::ItemMod) {
|
||||||
|
if let Some((_, content)) = &mut module.content {
|
||||||
|
content.retain(|item| match item {
|
||||||
|
Item::Fn(func) => {
|
||||||
|
let span = func.sig.ident.span();
|
||||||
|
|
||||||
|
self.should_retain_item(span)
|
||||||
|
}
|
||||||
|
_ => true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
syn::visit_mut::visit_item_mod_mut(self, module);
|
||||||
|
}
|
||||||
|
}
|
||||||
7
test-cases/unused-code/Cargo.lock
generated
Normal file
7
test-cases/unused-code/Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hello-world"
|
||||||
|
version = "0.1.0"
|
||||||
10
test-cases/unused-code/Cargo.toml
Normal file
10
test-cases/unused-code/Cargo.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
[workspace]
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "hello-world"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
5
test-cases/unused-code/src/main.rs
Normal file
5
test-cases/unused-code/src/main.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
fn unused() {}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
unused();
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue