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 @@ + + + + + + + Updating `dummy-registry` index + + Adding my-package v0.1.0 to dependencies + + error: unrecognized features for crate my-package: baz, feo + + + + help: a feature with a similar name exists: `bar` + + + + help: a feature with a similar name exists: `foo` + + + + + + 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 @@ + + + + + + + Updating `dummy-registry` index + + Adding my-package v0.1.0 to dependencies + + error: unrecognized feature for crate my-package: none_existent + + + + disabled features: + + bar, foo + + + + + + 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 @@ + + + + + + + Updating `dummy-registry` index + + Adding my-package v0.1.0 to dependencies + + error: unrecognized feature for crate my-package: baz + + + + help: a feature with a similar name exists: `bar` + + + + + + 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 @@ - + error: unrecognized features for crate your-face: eees100, eees101 - 199 disabled features available + - 1 enabled features available + help: a feature with a similar name exists: `eyes100` + + help: a feature with a similar name exists: `eyes101` + + diff --git a/tests/testsuite/cargo_add/features_unknown/stderr.term.svg b/tests/testsuite/cargo_add/features_unknown/stderr.term.svg index 045e9dc9aad..7de6a5cef7f 100644 --- a/tests/testsuite/cargo_add/features_unknown/stderr.term.svg +++ b/tests/testsuite/cargo_add/features_unknown/stderr.term.svg @@ -25,9 +25,9 @@ error: unrecognized feature for crate your-face: noze - disabled features: + - ears, eyes, mouth, nose + help: a feature with a similar name exists: `nose` diff --git a/tests/testsuite/cargo_add/features_unknown_no_features/stderr.term.svg b/tests/testsuite/cargo_add/features_unknown_no_features/stderr.term.svg index 8501383964c..f0b5d14e1af 100644 --- a/tests/testsuite/cargo_add/features_unknown_no_features/stderr.term.svg +++ b/tests/testsuite/cargo_add/features_unknown_no_features/stderr.term.svg @@ -1,4 +1,4 @@ - + error: unrecognized feature for crate my-package: noze - no features available for crate my-package + - + no features available for crate my-package + + diff --git a/tests/testsuite/cargo_add/mod.rs b/tests/testsuite/cargo_add/mod.rs index a2881cfbca2..283b10d0759 100644 --- a/tests/testsuite/cargo_add/mod.rs +++ b/tests/testsuite/cargo_add/mod.rs @@ -21,6 +21,9 @@ mod dev_existing_path_base; mod dev_prefer_existing_version; mod dry_run; mod empty_dep_name; +mod feature_suggestion_multiple; +mod feature_suggestion_none; +mod feature_suggestion_single; mod features; mod features_activated_over_limit; mod features_deactivated_over_limit; diff --git a/tests/testsuite/cargo_add/unknown_inherited_feature/stderr.term.svg b/tests/testsuite/cargo_add/unknown_inherited_feature/stderr.term.svg index dfc9c508233..ce811660a1e 100644 --- a/tests/testsuite/cargo_add/unknown_inherited_feature/stderr.term.svg +++ b/tests/testsuite/cargo_add/unknown_inherited_feature/stderr.term.svg @@ -1,4 +1,4 @@ - + error: unrecognized feature for crate foo: not_recognized - disabled features: + - merge, merge-base, unrelated + disabled features: - enabled features: + merge, merge-base, unrelated - default-base, default-merge-base, default-test-base + - long-feature-name-because-of-formatting-reasons, test, test-base + enabled features: - + default-base, default-merge-base, default-test-base + + long-feature-name-because-of-formatting-reasons, test, test-base + +