mirror of
https://github.com/Noratrieb/cargo-minimize.git
synced 2026-01-14 16:35:01 +01:00
add in memory AST
This commit is contained in:
parent
307cd52178
commit
cf39338b30
5 changed files with 127 additions and 69 deletions
|
|
@ -1,7 +1,12 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
use genemichaels::FormatConfig;
|
use genemichaels::FormatConfig;
|
||||||
|
|
||||||
pub fn format(file: syn::File) -> anyhow::Result<String> {
|
pub fn format(file: syn::File) -> anyhow::Result<String> {
|
||||||
Ok(genemichaels::format_ast(file, &FormatConfig::default(), HashMap::new())?.rendered)
|
Ok(
|
||||||
|
genemichaels::format_ast(file, &FormatConfig::default(), HashMap::new())
|
||||||
|
.context("formatting source file")?
|
||||||
|
.rendered,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -166,7 +166,7 @@ impl PassController {
|
||||||
self.state = PassControllerState::Success;
|
self.state = PassControllerState::Success;
|
||||||
}
|
}
|
||||||
PassControllerState::Bisecting { current, .. } => {
|
PassControllerState::Bisecting { current, .. } => {
|
||||||
unreachable!("No change while bisecting, current was empty somehow: {current:?}");
|
unreachable!("Pass said it didn't change anything in the bisection phase, nils forgot what this means: {current:?}");
|
||||||
}
|
}
|
||||||
PassControllerState::Success { .. } => {}
|
PassControllerState::Success { .. } => {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,90 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::Result;
|
||||||
use std::{
|
use std::{fs, path::Path};
|
||||||
fmt::Debug,
|
|
||||||
fs,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Hash)]
|
pub(crate) use self::file::SourceFile;
|
||||||
pub(crate) struct SourceFile {
|
|
||||||
pub(crate) path: PathBuf,
|
mod file {
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use std::{
|
||||||
|
cell::RefCell,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{Changes, FileChange};
|
||||||
|
|
||||||
|
/// The representation of a source file, with the cached AST.
|
||||||
|
/// IMPORTANT INVARIANT: All file system operations MUST go through this type.
|
||||||
|
/// This also shouldn't be `Clone`, so the cache is always representative of the file system state.
|
||||||
|
/// It is inteded for the "cache" to be the source of truth.
|
||||||
|
pub(crate) struct SourceFile {
|
||||||
|
path: PathBuf,
|
||||||
|
content_str: RefCell<String>,
|
||||||
|
content: RefCell<syn::File>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SourceFile {
|
||||||
|
pub(crate) fn open(path: PathBuf) -> Result<Self> {
|
||||||
|
let string = std::fs::read_to_string(&path)
|
||||||
|
.with_context(|| format!("reading file {}", path.display()))?;
|
||||||
|
let content = syn::parse_file(&string)
|
||||||
|
.with_context(|| format!("parsing file {}", path.display()))?;
|
||||||
|
Ok(SourceFile {
|
||||||
|
path,
|
||||||
|
content_str: RefCell::new(string),
|
||||||
|
content: RefCell::new(content),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn write(&self, new: syn::File) -> Result<()> {
|
||||||
|
let string = crate::formatting::format(new.clone())?;
|
||||||
|
std::fs::write(&self.path, &string)
|
||||||
|
.with_context(|| format!("writing file {}", self.path.display()))?;
|
||||||
|
*self.content_str.borrow_mut() = string;
|
||||||
|
*self.content.borrow_mut() = new;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn path_no_fs_interact(&self) -> &Path {
|
||||||
|
&self.path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for SourceFile {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.path == other.path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::hash::Hash for SourceFile {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.path.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for SourceFile {}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for SourceFile {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.path.display())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SourceFile {
|
||||||
|
pub(crate) fn try_change<'file, 'change>(
|
||||||
|
&'file self,
|
||||||
|
changes: &'change mut Changes,
|
||||||
|
) -> Result<FileChange<'file, 'change>> {
|
||||||
|
let path = &self.path;
|
||||||
|
Ok(FileChange {
|
||||||
|
path,
|
||||||
|
source_file: self,
|
||||||
|
changes,
|
||||||
|
has_written_change: false,
|
||||||
|
before_content_str: self.content_str.borrow().clone(),
|
||||||
|
before_content: self.content.borrow().clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
@ -17,26 +94,29 @@ pub(crate) struct Changes {
|
||||||
|
|
||||||
pub(crate) struct FileChange<'a, 'b> {
|
pub(crate) struct FileChange<'a, 'b> {
|
||||||
pub(crate) path: &'a Path,
|
pub(crate) path: &'a Path,
|
||||||
content: String,
|
source_file: &'a SourceFile,
|
||||||
|
before_content_str: String,
|
||||||
|
before_content: syn::File,
|
||||||
changes: &'b mut Changes,
|
changes: &'b mut Changes,
|
||||||
has_written_change: bool,
|
has_written_change: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileChange<'_, '_> {
|
impl FileChange<'_, '_> {
|
||||||
pub(crate) fn before_content(&self) -> &str {
|
pub(crate) fn before_content(&self) -> (&str, &syn::File) {
|
||||||
&self.content
|
(&self.before_content_str, &self.before_content)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn write(&mut self, new: &str) -> Result<()> {
|
pub(crate) fn write(&mut self, new: syn::File) -> Result<()> {
|
||||||
self.has_written_change = true;
|
self.has_written_change = true;
|
||||||
fs::write(self.path, new).with_context(|| format!("writing file {}", self.path.display()))
|
self.source_file.write(new)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn rollback(mut self) -> Result<()> {
|
pub(crate) fn rollback(mut self) -> Result<()> {
|
||||||
assert!(self.has_written_change);
|
assert!(self.has_written_change);
|
||||||
self.has_written_change = false;
|
self.has_written_change = false;
|
||||||
fs::write(self.path, &self.content)
|
self.source_file.write(self.before_content.clone())?;
|
||||||
.with_context(|| format!("writing file {}", self.path.display()))
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn commit(mut self) {
|
pub(crate) fn commit(mut self) {
|
||||||
|
|
@ -49,7 +129,7 @@ impl FileChange<'_, '_> {
|
||||||
impl Drop for FileChange<'_, '_> {
|
impl Drop for FileChange<'_, '_> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if self.has_written_change {
|
if self.has_written_change {
|
||||||
fs::write(self.path, self.before_content()).ok();
|
fs::write(self.path, self.before_content().0).ok();
|
||||||
if !std::thread::panicking() {
|
if !std::thread::panicking() {
|
||||||
panic!("File contains unsaved changes!");
|
panic!("File contains unsaved changes!");
|
||||||
}
|
}
|
||||||
|
|
@ -57,30 +137,8 @@ impl Drop for FileChange<'_, '_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SourceFile {
|
|
||||||
pub(crate) 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 {
|
impl Changes {
|
||||||
pub(crate) fn had_changes(&self) -> bool {
|
pub(crate) fn had_changes(&self) -> bool {
|
||||||
self.any_change
|
self.any_change
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for SourceFile {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
<Path as Debug>::fmt(&self.path, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -98,13 +98,13 @@ impl Minimizer {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.map(|entry| SourceFile {
|
.map(|entry| SourceFile::open(entry.into_path()))
|
||||||
path: entry.into_path(),
|
|
||||||
})
|
|
||||||
.inspect(|file| {
|
.inspect(|file| {
|
||||||
info!("Collecting file: {}", file.path.display());
|
if let Ok(file) = file {
|
||||||
|
info!("Collecting file: {file:?}");
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
if files.is_empty() {
|
if files.is_empty() {
|
||||||
bail!("Did not find any files for path {}", path.display());
|
bail!("Did not find any files for path {}", path.display());
|
||||||
|
|
@ -188,20 +188,17 @@ impl Minimizer {
|
||||||
|
|
||||||
let mut checker = PassController::new(self.options.clone());
|
let mut checker = PassController::new(self.options.clone());
|
||||||
loop {
|
loop {
|
||||||
let file_display = file.path.display();
|
|
||||||
let mut change = file.try_change(changes)?;
|
let mut change = file.try_change(changes)?;
|
||||||
let mut krate = syn::parse_file(change.before_content())
|
let (_, krate) = change.before_content();
|
||||||
.with_context(|| format!("parsing file {file_display}"))?;
|
let mut krate = krate.clone();
|
||||||
let has_made_change = pass.process_file(&mut krate, file, &mut checker);
|
let has_made_change = pass.process_file(&mut krate, file, &mut checker);
|
||||||
|
|
||||||
match has_made_change {
|
match has_made_change {
|
||||||
ProcessState::Changed | ProcessState::FileInvalidated => {
|
ProcessState::Changed | ProcessState::FileInvalidated => {
|
||||||
let result = crate::formatting::format(krate)?;
|
change.write(krate)?;
|
||||||
|
|
||||||
change.write(&result)?;
|
|
||||||
|
|
||||||
let after = self.build.build()?;
|
let after = self.build.build()?;
|
||||||
info!("{file_display}: After {}: {after}", pass.name());
|
info!("{file:?}: After {}: {after}", pass.name());
|
||||||
|
|
||||||
if after.reproduces_issue() {
|
if after.reproduces_issue() {
|
||||||
change.commit();
|
change.commit();
|
||||||
|
|
@ -217,13 +214,9 @@ impl Minimizer {
|
||||||
}
|
}
|
||||||
ProcessState::NoChange => {
|
ProcessState::NoChange => {
|
||||||
if self.options.no_color {
|
if self.options.no_color {
|
||||||
info!("{file_display}: After {}: no changes", pass.name());
|
info!("{file:?}: After {}: no changes", pass.name());
|
||||||
} else {
|
} else {
|
||||||
info!(
|
info!("{file:?}: After {}: {}", pass.name(), "no changes".yellow());
|
||||||
"{file_display}: After {}: {}",
|
|
||||||
pass.name(),
|
|
||||||
"no changes".yellow()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
checker.no_change();
|
checker.no_change();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use rustfix::{diagnostics::Diagnostic, Suggestion};
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
ops::Range,
|
ops::Range,
|
||||||
path::Path,
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
use syn::{visit_mut::VisitMut, ImplItem, Item};
|
use syn::{visit_mut::VisitMut, ImplItem, Item};
|
||||||
|
|
||||||
|
|
@ -63,7 +63,8 @@ impl Minimizer {
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
for (sugg_file, suggestions) in suggestions {
|
for (sugg_file, suggestions) in suggestions {
|
||||||
let Some(file) = self.files.iter().find(|source| {
|
let Some(file) = self.files.iter().find(|source| {
|
||||||
source.path.ends_with(sugg_file) || sugg_file.ends_with(&source.path)
|
source.path_no_fs_interact().ends_with(sugg_file)
|
||||||
|
|| sugg_file.ends_with(source.path_no_fs_interact())
|
||||||
}) else {
|
}) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
@ -79,13 +80,14 @@ impl Minimizer {
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let result = rustfix::apply_suggestions(change.before_content(), &desired_suggestions)?;
|
let result =
|
||||||
|
rustfix::apply_suggestions(change.before_content().0, &desired_suggestions)?;
|
||||||
change.write(&result)?;
|
let result = syn::parse_file(&result).context("parsing file after rustfix")?;
|
||||||
|
change.write(result)?;
|
||||||
|
|
||||||
let after = self.build.build()?;
|
let after = self.build.build()?;
|
||||||
|
|
||||||
info!("{}: After reaper: {after}", file.path.display());
|
info!("{file:?}: After reaper: {after}");
|
||||||
|
|
||||||
if after.reproduces_issue() {
|
if after.reproduces_issue() {
|
||||||
change.commit();
|
change.commit();
|
||||||
|
|
@ -101,7 +103,7 @@ impl Minimizer {
|
||||||
struct DeleteUnusedFunctions {
|
struct DeleteUnusedFunctions {
|
||||||
diags: Vec<Diagnostic>,
|
diags: Vec<Diagnostic>,
|
||||||
build: Build,
|
build: Build,
|
||||||
invalid: HashSet<SourceFile>,
|
invalid: HashSet<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DeleteUnusedFunctions {
|
impl DeleteUnusedFunctions {
|
||||||
|
|
@ -129,7 +131,7 @@ impl Pass for DeleteUnusedFunctions {
|
||||||
checker: &mut super::PassController,
|
checker: &mut super::PassController,
|
||||||
) -> ProcessState {
|
) -> ProcessState {
|
||||||
assert!(
|
assert!(
|
||||||
!self.invalid.contains(file),
|
!self.invalid.contains(file.path_no_fs_interact()),
|
||||||
"processing with invalid state"
|
"processing with invalid state"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -137,7 +139,7 @@ impl Pass for DeleteUnusedFunctions {
|
||||||
visitor.visit_file_mut(krate);
|
visitor.visit_file_mut(krate);
|
||||||
|
|
||||||
if visitor.process_state == ProcessState::FileInvalidated {
|
if visitor.process_state == ProcessState::FileInvalidated {
|
||||||
self.invalid.insert(file.clone());
|
self.invalid.insert(file.path_no_fs_interact().to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
visitor.process_state
|
visitor.process_state
|
||||||
|
|
@ -203,7 +205,7 @@ impl<'a> FindUnusedFunction<'a> {
|
||||||
);
|
);
|
||||||
|
|
||||||
// When the project directory is remapped, the path may be absolute or generally have some prefix.
|
// When the project directory is remapped, the path may be absolute or generally have some prefix.
|
||||||
if !file.path.ends_with(&span.file_name) {
|
if !file.path_no_fs_interact().ends_with(&span.file_name) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue