diff --git a/src/cargo/ops/cargo_add/mod.rs b/src/cargo/ops/cargo_add/mod.rs
index d872e2f0096..1853b99434b 100644
--- a/src/cargo/ops/cargo_add/mod.rs
+++ b/src/cargo/ops/cargo_add/mod.rs
@@ -29,6 +29,7 @@ use crate::core::Summary;
use crate::core::Workspace;
use crate::sources::source::QueryKind;
use crate::util::cache_lock::CacheLockMode;
+use crate::util::edit_distance;
use crate::util::style;
use crate::util::toml::lookup_path_base;
use crate::util::toml_mut::dependency::Dependency;
@@ -159,19 +160,32 @@ pub fn add(workspace: &Workspace<'_>, options: &AddOptions<'_>) -> CargoResult<(
activated.retain(|f| !unknown_features.contains(f));
let mut message = format!(
- "unrecognized feature{} for crate {}: {}\n",
+ "unrecognized feature{} for crate {}: {}",
if unknown_features.len() == 1 { "" } else { "s" },
dep.name,
unknown_features.iter().format(", "),
);
if activated.is_empty() && deactivated.is_empty() {
- write!(message, "no features available for crate {}", dep.name)?;
+ write!(message, "\n\nno features available for crate {}", dep.name)?;
} else {
- if !deactivated.is_empty() {
+ let mut suggested = false;
+ for unknown_feature in &unknown_features {
+ let suggestion = edit_distance::closest_msg(
+ unknown_feature,
+ deactivated.iter().chain(activated.iter()),
+ |dep| *dep,
+ "feature",
+ );
+ if !suggestion.is_empty() {
+ write!(message, "{suggestion}")?;
+ suggested = true;
+ }
+ }
+ if !deactivated.is_empty() && !suggested {
if deactivated.len() <= MAX_FEATURE_PRINTS {
- writeln!(
+ write!(
message,
- "disabled features:\n {}",
+ "\n\ndisabled features:\n {}",
deactivated
.iter()
.map(|s| s.to_string())
@@ -184,14 +198,18 @@ pub fn add(workspace: &Workspace<'_>, options: &AddOptions<'_>) -> CargoResult<(
.format("\n ")
)?;
} else {
- writeln!(message, "{} disabled features available", deactivated.len())?;
+ write!(
+ message,
+ "\n\n{} disabled features available",
+ deactivated.len()
+ )?;
}
}
- if !activated.is_empty() {
+ if !activated.is_empty() && !suggested {
if deactivated.len() + activated.len() <= MAX_FEATURE_PRINTS {
writeln!(
message,
- "enabled features:\n {}",
+ "\n\nenabled features:\n {}",
activated
.iter()
.map(|s| s.to_string())
@@ -204,7 +222,11 @@ pub fn add(workspace: &Workspace<'_>, options: &AddOptions<'_>) -> CargoResult<(
.format("\n ")
)?;
} else {
- writeln!(message, "{} enabled features available", activated.len())?;
+ writeln!(
+ message,
+ "\n\n{} enabled features available",
+ activated.len()
+ )?;
}
}
}
diff --git a/tests/testsuite/cargo_add/feature_suggestion_multiple/in/Cargo.toml b/tests/testsuite/cargo_add/feature_suggestion_multiple/in/Cargo.toml
new file mode 100644
index 00000000000..92a4974124c
--- /dev/null
+++ b/tests/testsuite/cargo_add/feature_suggestion_multiple/in/Cargo.toml
@@ -0,0 +1,7 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+edition = "2024"
+
diff --git a/tests/testsuite/cargo_add/feature_suggestion_multiple/in/src/lib.rs b/tests/testsuite/cargo_add/feature_suggestion_multiple/in/src/lib.rs
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/tests/testsuite/cargo_add/feature_suggestion_multiple/mod.rs b/tests/testsuite/cargo_add/feature_suggestion_multiple/mod.rs
new file mode 100644
index 00000000000..898f3aa81bc
--- /dev/null
+++ b/tests/testsuite/cargo_add/feature_suggestion_multiple/mod.rs
@@ -0,0 +1,24 @@
+use cargo_test_support::current_dir;
+use cargo_test_support::file;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(current_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ cargo_test_support::registry::Package::new("my-package", "0.1.0+my-package")
+ .feature("bar", &[])
+ .feature("foo", &[])
+ .publish();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package --features baz --features feo")
+ .current_dir(cwd)
+ .assert()
+ .failure()
+ .stderr_eq(file!["stderr.term.svg"]);
+}
diff --git a/tests/testsuite/cargo_add/feature_suggestion_multiple/stderr.term.svg b/tests/testsuite/cargo_add/feature_suggestion_multiple/stderr.term.svg
new file mode 100644
index 00000000000..94d4738d61b
--- /dev/null
+++ b/tests/testsuite/cargo_add/feature_suggestion_multiple/stderr.term.svg
@@ -0,0 +1,40 @@
+
diff --git a/tests/testsuite/cargo_add/feature_suggestion_none/in/Cargo.toml b/tests/testsuite/cargo_add/feature_suggestion_none/in/Cargo.toml
new file mode 100644
index 00000000000..92a4974124c
--- /dev/null
+++ b/tests/testsuite/cargo_add/feature_suggestion_none/in/Cargo.toml
@@ -0,0 +1,7 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+edition = "2024"
+
diff --git a/tests/testsuite/cargo_add/feature_suggestion_none/in/src/lib.rs b/tests/testsuite/cargo_add/feature_suggestion_none/in/src/lib.rs
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/tests/testsuite/cargo_add/feature_suggestion_none/mod.rs b/tests/testsuite/cargo_add/feature_suggestion_none/mod.rs
new file mode 100644
index 00000000000..b157660f705
--- /dev/null
+++ b/tests/testsuite/cargo_add/feature_suggestion_none/mod.rs
@@ -0,0 +1,24 @@
+use cargo_test_support::current_dir;
+use cargo_test_support::file;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(current_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ cargo_test_support::registry::Package::new("my-package", "0.1.0+my-package")
+ .feature("bar", &[])
+ .feature("foo", &[])
+ .publish();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package --features none_existent")
+ .current_dir(cwd)
+ .assert()
+ .failure()
+ .stderr_eq(file!["stderr.term.svg"]);
+}
diff --git a/tests/testsuite/cargo_add/feature_suggestion_none/stderr.term.svg b/tests/testsuite/cargo_add/feature_suggestion_none/stderr.term.svg
new file mode 100644
index 00000000000..01bde8f40f0
--- /dev/null
+++ b/tests/testsuite/cargo_add/feature_suggestion_none/stderr.term.svg
@@ -0,0 +1,38 @@
+
diff --git a/tests/testsuite/cargo_add/feature_suggestion_single/in/Cargo.toml b/tests/testsuite/cargo_add/feature_suggestion_single/in/Cargo.toml
new file mode 100644
index 00000000000..92a4974124c
--- /dev/null
+++ b/tests/testsuite/cargo_add/feature_suggestion_single/in/Cargo.toml
@@ -0,0 +1,7 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+edition = "2024"
+
diff --git a/tests/testsuite/cargo_add/feature_suggestion_single/in/src/lib.rs b/tests/testsuite/cargo_add/feature_suggestion_single/in/src/lib.rs
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/tests/testsuite/cargo_add/feature_suggestion_single/mod.rs b/tests/testsuite/cargo_add/feature_suggestion_single/mod.rs
new file mode 100644
index 00000000000..682dd893a30
--- /dev/null
+++ b/tests/testsuite/cargo_add/feature_suggestion_single/mod.rs
@@ -0,0 +1,23 @@
+use cargo_test_support::current_dir;
+use cargo_test_support::file;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(current_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ cargo_test_support::registry::Package::new("my-package", "0.1.0+my-package")
+ .feature("bar", &[])
+ .publish();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package --features baz")
+ .current_dir(cwd)
+ .assert()
+ .failure()
+ .stderr_eq(file!["stderr.term.svg"]);
+}
diff --git a/tests/testsuite/cargo_add/feature_suggestion_single/stderr.term.svg b/tests/testsuite/cargo_add/feature_suggestion_single/stderr.term.svg
new file mode 100644
index 00000000000..df979ee5b94
--- /dev/null
+++ b/tests/testsuite/cargo_add/feature_suggestion_single/stderr.term.svg
@@ -0,0 +1,36 @@
+
diff --git a/tests/testsuite/cargo_add/features_error_activated_over_limit/stderr.term.svg b/tests/testsuite/cargo_add/features_error_activated_over_limit/stderr.term.svg
index 21b4d42f539..7283ec70da1 100644
--- a/tests/testsuite/cargo_add/features_error_activated_over_limit/stderr.term.svg
+++ b/tests/testsuite/cargo_add/features_error_activated_over_limit/stderr.term.svg
@@ -25,13 +25,13 @@
error: unrecognized features for crate your-face: eees100, eees101
- disabled features:
+
- eyes040, eyes041, eyes042, eyes043, eyes044, eyes045, eyes046, eyes047, eyes048
+ help: a feature with a similar name exists: `eyes000`
- eyes049
+
- 40 enabled features available
+ help: a feature with a similar name exists: `eyes001`
diff --git a/tests/testsuite/cargo_add/features_error_deactivated_over_limit/stderr.term.svg b/tests/testsuite/cargo_add/features_error_deactivated_over_limit/stderr.term.svg
index 7ecf69e01fc..44d15677d57 100644
--- a/tests/testsuite/cargo_add/features_error_deactivated_over_limit/stderr.term.svg
+++ b/tests/testsuite/cargo_add/features_error_deactivated_over_limit/stderr.term.svg
@@ -1,4 +1,4 @@
-