Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle multiple dependency tables and dependency delimiters #112

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

mkatychev
Copy link

@mkatychev mkatychev commented Feb 27, 2024

Handle multiple dependency tables:

let valid_tables = HashSet::from(["dependencies", "build-dependencies", "dev-dependencies"]);

Handle superset of dependency names:

cargo-machete/src/main.rs

Lines 255 to 263 in 4fb101d

// handle a superset of all dependency name dashed/underscored variants: re'\w[-_]'
fn dep_name_superset(dep_names: &[String]) -> HashSet<String> {
let mut unused: HashSet<String> = dep_names.iter().cloned().collect();
for dep in unused.clone() {
unused.insert(dep.replace('-', "_"));
unused.insert(dep.replace('_', "-"));
}
unused
}

@mkatychev
Copy link
Author

I'm not sure how much the scanner recuses so cases like this may not be handled:

[target.'cfg(not(some_flag))'.dev-dependencies]
prost.workspace = true

unused
}

fn remove_dependencies(manifest: &str, dependency_list: &[String]) -> anyhow::Result<String> {
let mut manifest = toml_edit::Document::from_str(manifest)?;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dependency_list seems to consistently normalize for underscores but the toml_edit::Document preserves multi word delimiters

@mkatychev
Copy link
Author

crate/build.rs probably also needs to be scanned, it doesn't seem to be the case with this PR

Copy link
Owner

@bnjbvr bnjbvr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! So if I understand correctly, it will find unused dependencies as specified from the main dependencies, and remove them when they're unused in any of dependencies, build-dependencies, and dev-dependencies.

Later, we could also handle a dependency that was analyzed in other dependencies arrays, like build-dependencies, and remove it from the list if it's unused there too. For build-dependencies it would require analyzing the build.rs file; for dev-dependencies it might also imply looking only at tests/benchmarks, which I'm not sure we can figure by just scanning text (i.e. there can be tests in any Rust source non-test file). Well, these are things to do later :)

Thanks for the PR! Can you add a test showing the new behavior, please?

@@ -275,7 +317,7 @@ fn main() {
Ok(false) => 0,
Ok(true) => 1,
Err(err) => {
eprintln!("Error: {err}");
eprintln!("Error: {err:?}");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should keep the previous: errors do implement Display. If we need the full context of an anyhow error, we can use {err:#}.

Comment on lines +255 to +256
// handle a superset of all dependency name dashed/underscored variants: re'\w[-_]'
fn dep_name_superset(dep_names: &[String]) -> HashSet<String> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need this: the dependency name should be exact, or using cargo-machete --with-metadata will find the proper name. If you're fixing something here, can you add a test that would fail on main, and show what this is fixing, please?

Comment on lines +274 to +283
.filter(|(k, v)| v.is_table_like() && valid_tables.contains(k.display_repr().as_ref()))
.map(|(k, v)| {
let table = v.as_table_like_mut().context(missing_table_msg)?;
Ok((k, table))
})
.filter(|v| match v {
Ok((_, table)) if dependency_superset.iter().any(|k| table.contains_key(k)) => true,
Err(_) => true,
Ok(_) => false,
})
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.filter(|(k, v)| v.is_table_like() && valid_tables.contains(k.display_repr().as_ref()))
.map(|(k, v)| {
let table = v.as_table_like_mut().context(missing_table_msg)?;
Ok((k, table))
})
.filter(|v| match v {
Ok((_, table)) if dependency_superset.iter().any(|k| table.contains_key(k)) => true,
Err(_) => true,
Ok(_) => false,
})
Maybe `valid_tables` has some kind of `.get()` that would make it so we can avoid the filter, then the map, then the error handling step?
```suggestion
.filter_map(|(k, v)| if v.is_table_like() {
if let Some(table) = valid_tables.get(k.display_repr().as_ref())) {
if dependency_superset.iter().any(|k| table.contains_key(k)) {
Some((k, table))
} else {
None
}
} else {
None
}
})

Or, even better, let's get rid of all the iterators transformers and use plain imperative code instead, as it might be more readable in this case.

.collect::<Result<Vec<(KeyMut, &mut dyn TableLike)>, anyhow::Error>>()?;

for dep in dependency_list {
// for now
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfinished comment here? Maybe we could comment something like "We should find the unused dependency in at least one of the tables; return an error if that's not the case."

for (name, table) in &mut dep_tables {
if table
.remove(dep)
.or_else(|| table.remove(dep.replace('_', "-").as_str()))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, I'm not sure why we would need this; we'd never have to do it, so I would need a proof before being convinced we need to replace dashes ourselves :)

.map(|(k, _)| format!("{k}"))
.collect::<Vec<String>>()
.join(", ");
bail!(anyhow!("{dep} not found").context(format!("tables: {tables}")));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bail! with a context doesn't seem very idiomatic to me: maybe we could instead plain use bail! here, and join the two error messages with a \n or :?

@bnjbvr
Copy link
Owner

bnjbvr commented Jan 6, 2025

hi @mkatychev, happy new year! Do you plan to work on this anytime soon, or should I close this PR for the time being?

@mkatychev
Copy link
Author

@bnjbvr I can take a look later this week.

@mkatychev
Copy link
Author

Just an update, haven't gotten around to this yet but still on my radar

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants