Skip to content

Commit 32330f6

Browse files
committed
feat(add): suggest similarly named features
1 parent 185c695 commit 32330f6

File tree

15 files changed

+246
-0
lines changed

15 files changed

+246
-0
lines changed

src/cargo/ops/cargo_add/mod.rs

+28
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ use crate::core::Summary;
2929
use crate::core::Workspace;
3030
use crate::sources::source::QueryKind;
3131
use crate::util::cache_lock::CacheLockMode;
32+
use crate::util::edit_distance;
3233
use crate::util::style;
3334
use crate::util::toml::lookup_path_base;
3435
use crate::util::toml_mut::dependency::Dependency;
@@ -168,6 +169,33 @@ pub fn add(workspace: &Workspace<'_>, options: &AddOptions<'_>) -> CargoResult<(
168169
write!(message, "no features available for crate {}", dep.name)?;
169170
} else {
170171
if !deactivated.is_empty() {
172+
let mut suggested_features = Vec::new();
173+
for unknown_feature in &unknown_features {
174+
if let Some(suggested_feature) =
175+
edit_distance::closest(unknown_feature, deactivated.iter(), |dep| *dep)
176+
{
177+
suggested_features.push(suggested_feature);
178+
}
179+
}
180+
if (1..=MAX_FEATURE_PRINTS).contains(&suggested_features.len()) {
181+
writeln!(
182+
message,
183+
"help: similarly named feature{suffix1} exist{suffix2}:{newline} {suggestions}",
184+
suffix1 = if suggested_features.len() == 1 { "" } else { "s" },
185+
suffix2 = if suggested_features.len() == 1 { "s" } else { "" },
186+
newline = if suggested_features.len() == 1 { "" } else { "\n" },
187+
suggestions = suggested_features
188+
.into_iter()
189+
.map(|s| s.to_string())
190+
.coalesce(|x, y| if x.len() + y.len() < 78 {
191+
Ok(format!("{x}, {y}"))
192+
} else {
193+
Err((x, y))
194+
})
195+
.into_iter()
196+
.format("\n ")
197+
)?;
198+
}
171199
if deactivated.len() <= MAX_FEATURE_PRINTS {
172200
writeln!(
173201
message,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[workspace]
2+
3+
[package]
4+
name = "cargo-list-test-fixture"
5+
version = "0.0.0"
6+
edition = "2024"
7+

tests/testsuite/cargo_add/feature_suggestion_multiple/in/src/lib.rs

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
use cargo_test_support::current_dir;
2+
use cargo_test_support::file;
3+
use cargo_test_support::prelude::*;
4+
use cargo_test_support::Project;
5+
6+
#[cargo_test]
7+
fn case() {
8+
let project = Project::from_template(current_dir!().join("in"));
9+
let project_root = project.root();
10+
let cwd = &project_root;
11+
12+
cargo_test_support::registry::Package::new("my-package", "0.1.0+my-package")
13+
.feature("bar", &[])
14+
.feature("foo", &[])
15+
.publish();
16+
17+
snapbox::cmd::Command::cargo_ui()
18+
.arg("add")
19+
.arg_line("my-package --features baz --features feo")
20+
.current_dir(cwd)
21+
.assert()
22+
.failure()
23+
.stderr_eq(file!["stderr.term.svg"]);
24+
}
Loading

tests/testsuite/cargo_add/feature_suggestion_none/in/Cargo.lock

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[workspace]
2+
3+
[package]
4+
name = "cargo-list-test-fixture"
5+
version = "0.0.0"
6+
edition = "2024"
7+

tests/testsuite/cargo_add/feature_suggestion_none/in/src/lib.rs

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
use cargo_test_support::current_dir;
2+
use cargo_test_support::file;
3+
use cargo_test_support::prelude::*;
4+
use cargo_test_support::Project;
5+
6+
#[cargo_test]
7+
fn case() {
8+
let project = Project::from_template(current_dir!().join("in"));
9+
let project_root = project.root();
10+
let cwd = &project_root;
11+
12+
cargo_test_support::registry::Package::new("my-package", "0.1.0+my-package")
13+
.feature("bar", &[])
14+
.feature("foo", &[])
15+
.publish();
16+
17+
snapbox::cmd::Command::cargo_ui()
18+
.arg("add")
19+
.arg_line("my-package --features none_existent")
20+
.current_dir(cwd)
21+
.assert()
22+
.failure()
23+
.stderr_eq(file!["stderr.term.svg"]);
24+
}
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[workspace]
2+
3+
[package]
4+
name = "cargo-list-test-fixture"
5+
version = "0.0.0"
6+
edition = "2024"
7+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fn main() {
2+
println!("Hello, world!");
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
use cargo_test_support::current_dir;
2+
use cargo_test_support::file;
3+
use cargo_test_support::prelude::*;
4+
use cargo_test_support::Project;
5+
6+
#[cargo_test]
7+
fn case() {
8+
let project = Project::from_template(current_dir!().join("in"));
9+
let project_root = project.root();
10+
let cwd = &project_root;
11+
12+
cargo_test_support::registry::Package::new("my-package", "0.1.0+my-package")
13+
.feature("bar", &[])
14+
.publish();
15+
16+
snapbox::cmd::Command::cargo_ui()
17+
.arg("add")
18+
.arg_line("my-package --features baz")
19+
.current_dir(cwd)
20+
.assert()
21+
.failure()
22+
.stderr_eq(file!["stderr.term.svg"]);
23+
}
Loading

tests/testsuite/cargo_add/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ mod dev_existing_path_base;
2121
mod dev_prefer_existing_version;
2222
mod dry_run;
2323
mod empty_dep_name;
24+
mod feature_suggestion_multiple;
25+
mod feature_suggestion_none;
26+
mod feature_suggestion_single;
2427
mod features;
2528
mod features_activated_over_limit;
2629
mod features_deactivated_over_limit;

0 commit comments

Comments
 (0)