fix: Suggest similar looking feature names when feature is missing (#15454)

### What does this PR try to resolve?

I recently depended on a package with `preserve-order` rather than
`preserve_order` and the error message didn't help me with the problem
so I figure I'd fix that. I also found other improvements along the way

- Suggest an alternative feature when a feature includes a missing
feature
- Suggest an alternative feature when a dependency includes a missing
feature
- Lower case error messages
- Re-frame prescriptive information as help
- Change plural "features" error messages to singular "feature" as they
can only ever have one (knowing an the `MissingFeature` string only has
one feature in it was important for doing a `closest` match on the
feature).

### How should we test and review this PR?

### Additional information
diff --git a/src/cargo/core/resolver/dep_cache.rs b/src/cargo/core/resolver/dep_cache.rs
index f0b56db..2a93ced 100644
--- a/src/cargo/core/resolver/dep_cache.rs
+++ b/src/cargo/core/resolver/dep_cache.rs
@@ -524,15 +524,13 @@
                                 "feature",
                             );
                             ActivateError::Fatal(anyhow::format_err!(
-                                "Package `{}` does not have the feature `{}`{}",
+                                "package `{}` does not have the feature `{}`{}",
                                 summary.package_id(),
                                 feat,
                                 closest
                             ))
                         }
-                        Some(p) => {
-                            ActivateError::Conflict(p, ConflictReason::MissingFeatures(feat))
-                        }
+                        Some(p) => ActivateError::Conflict(p, ConflictReason::MissingFeature(feat)),
                     };
                 }
                 if deps.iter().any(|dep| dep.is_optional()) {
@@ -555,9 +553,11 @@
                             }
                             ActivateError::Fatal(anyhow::format_err!(
                                 "\
-Package `{}` does not have feature `{}`. It has an optional dependency \
-with that name, but that dependency uses the \"dep:\" \
-syntax in the features table, so it does not have an implicit feature with that name.{}",
+package `{}` does not have feature `{}`
+
+help: an optional dependency \
+with that name exists, but the `features` table includes it with the \"dep:\" \
+syntax so it does not have an implicit feature with that name{}",
                                 summary.package_id(),
                                 feat,
                                 suggestion
@@ -571,8 +571,9 @@
                 } else {
                     match parent {
                         None => ActivateError::Fatal(anyhow::format_err!(
-                            "Package `{}` does not have feature `{}`. It has a required dependency \
-                             with that name, but only optional dependencies can be used as features.",
+                            "package `{}` does not have feature `{}`
+
+help: a depednency with that name exists but it is required dependency and only optional dependencies can be used as features.",
                             summary.package_id(),
                             feat,
                         )),
@@ -592,9 +593,7 @@
                     )),
                     // This code path currently isn't used, since `foo/bar`
                     // and `dep:` syntax is not allowed in a dependency.
-                    Some(p) => {
-                        ActivateError::Conflict(p, ConflictReason::MissingFeatures(dep_name))
-                    }
+                    Some(p) => ActivateError::Conflict(p, ConflictReason::MissingFeature(dep_name)),
                 }
             }
             RequirementError::Cycle(feat) => ActivateError::Fatal(anyhow::format_err!(
diff --git a/src/cargo/core/resolver/errors.rs b/src/cargo/core/resolver/errors.rs
index 6c8679e..169fcd1 100644
--- a/src/cargo/core/resolver/errors.rs
+++ b/src/cargo/core/resolver/errors.rs
@@ -5,7 +5,7 @@
 use crate::core::{Dependency, PackageId, Registry, Summary};
 use crate::sources::source::QueryKind;
 use crate::sources::IndexSummary;
-use crate::util::edit_distance::edit_distance;
+use crate::util::edit_distance::{closest, edit_distance};
 use crate::util::errors::CargoResult;
 use crate::util::{GlobalContext, OptVersionReq, VersionExt};
 use anyhow::Error;
@@ -137,7 +137,7 @@
                     has_semver = true;
                 }
                 ConflictReason::Links(link) => {
-                    msg.push_str("\n\nthe package `");
+                    msg.push_str("\n\npackage `");
                     msg.push_str(&*dep.package_name());
                     msg.push_str("` links to the native library `");
                     msg.push_str(link);
@@ -150,46 +150,54 @@
                     msg.push_str(link);
                     msg.push_str("\"` value. For more information, see https://doc.rust-lang.org/cargo/reference/resolver.html#links.");
                 }
-                ConflictReason::MissingFeatures(features) => {
-                    msg.push_str("\n\nthe package `");
+                ConflictReason::MissingFeature(feature) => {
+                    msg.push_str("\n\npackage `");
                     msg.push_str(&*p.name());
                     msg.push_str("` depends on `");
                     msg.push_str(&*dep.package_name());
-                    msg.push_str("`, with features: `");
-                    msg.push_str(features);
+                    msg.push_str("` with feature `");
+                    msg.push_str(feature);
                     msg.push_str("` but `");
                     msg.push_str(&*dep.package_name());
-                    msg.push_str("` does not have these features.\n");
+                    msg.push_str("` does not have that feature.\n");
+                    let latest = candidates.last().expect("in the non-empty branch");
+                    if let Some(closest) = closest(feature, latest.features().keys(), |k| k) {
+                        msg.push_str(" package `");
+                        msg.push_str(&*dep.package_name());
+                        msg.push_str("` does have feature `");
+                        msg.push_str(closest);
+                        msg.push_str("`\n");
+                    }
                     // p == parent so the full path is redundant.
                 }
-                ConflictReason::RequiredDependencyAsFeature(features) => {
-                    msg.push_str("\n\nthe package `");
+                ConflictReason::RequiredDependencyAsFeature(feature) => {
+                    msg.push_str("\n\npackage `");
                     msg.push_str(&*p.name());
                     msg.push_str("` depends on `");
                     msg.push_str(&*dep.package_name());
-                    msg.push_str("`, with features: `");
-                    msg.push_str(features);
+                    msg.push_str("` with feature `");
+                    msg.push_str(feature);
                     msg.push_str("` but `");
                     msg.push_str(&*dep.package_name());
-                    msg.push_str("` does not have these features.\n");
+                    msg.push_str("` does not have that feature.\n");
                     msg.push_str(
-                        " It has a required dependency with that name, \
+                        " A required dependency with that name exists, \
                          but only optional dependencies can be used as features.\n",
                     );
                     // p == parent so the full path is redundant.
                 }
-                ConflictReason::NonImplicitDependencyAsFeature(features) => {
-                    msg.push_str("\n\nthe package `");
+                ConflictReason::NonImplicitDependencyAsFeature(feature) => {
+                    msg.push_str("\n\npackage `");
                     msg.push_str(&*p.name());
                     msg.push_str("` depends on `");
                     msg.push_str(&*dep.package_name());
-                    msg.push_str("`, with features: `");
-                    msg.push_str(features);
+                    msg.push_str("` with feature `");
+                    msg.push_str(feature);
                     msg.push_str("` but `");
                     msg.push_str(&*dep.package_name());
-                    msg.push_str("` does not have these features.\n");
+                    msg.push_str("` does not have that feature.\n");
                     msg.push_str(
-                        " It has an optional dependency with that name, \
+                        " An optional dependency with that name exists, \
                          but that dependency uses the \"dep:\" \
                          syntax in the features table, so it does not have an \
                          implicit feature with that name.\n",
diff --git a/src/cargo/core/resolver/types.rs b/src/cargo/core/resolver/types.rs
index 2a5c51a..8bc1c9f 100644
--- a/src/cargo/core/resolver/types.rs
+++ b/src/cargo/core/resolver/types.rs
@@ -339,10 +339,10 @@
     /// we're only allowed one per dependency graph.
     Links(InternedString),
 
-    /// A dependency listed features that weren't actually available on the
+    /// A dependency listed a feature that wasn't actually available on the
     /// candidate. For example we tried to activate feature `foo` but the
     /// candidate we're activating didn't actually have the feature `foo`.
-    MissingFeatures(InternedString),
+    MissingFeature(InternedString),
 
     /// A dependency listed a feature that ended up being a required dependency.
     /// For example we tried to activate feature `foo` but the
@@ -360,8 +360,8 @@
         matches!(self, ConflictReason::Links(_))
     }
 
-    pub fn is_missing_features(&self) -> bool {
-        matches!(self, ConflictReason::MissingFeatures(_))
+    pub fn is_missing_feature(&self) -> bool {
+        matches!(self, ConflictReason::MissingFeature(_))
     }
 
     pub fn is_required_dependency_as_features(&self) -> bool {
diff --git a/src/cargo/core/summary.rs b/src/cargo/core/summary.rs
index 168a20f..d12a6f4 100644
--- a/src/cargo/core/summary.rs
+++ b/src/cargo/core/summary.rs
@@ -1,4 +1,5 @@
 use crate::core::{Dependency, PackageId, SourceId};
+use crate::util::closest_msg;
 use crate::util::interning::InternedString;
 use crate::util::CargoResult;
 use anyhow::bail;
@@ -241,9 +242,10 @@
                 Feature(f) => {
                     if !features.contains_key(f) {
                         if !is_any_dep {
+                            let closest = closest_msg(f, features.keys(), |k| k, "feature");
                             bail!(
                                 "feature `{feature}` includes `{fv}` which is neither a dependency \
-                                 nor another feature"
+                                 nor another feature{closest}"
                               );
                         }
                         if is_optional_dep {
diff --git a/tests/testsuite/build_script.rs b/tests/testsuite/build_script.rs
index 17d8228..f3ed272 100644
--- a/tests/testsuite/build_script.rs
+++ b/tests/testsuite/build_script.rs
@@ -1032,7 +1032,7 @@
     ... required by package `foo v0.5.0 ([ROOT]/foo)`
 versions that meet the requirements `*` are: 0.5.0
 
-the package `a-sys` links to the native library `a`, but it conflicts with a previous package which links to `a` as well:
+package `a-sys` links to the native library `a`, but it conflicts with a previous package which links to `a` as well:
 package `foo v0.5.0 ([ROOT]/foo)`
 Only one package in the dependency graph may specify the same links value. This helps ensure that only one copy of a native library is linked in the final binary. Try to adjust your dependencies so that only one package uses the `links = "a"` value. For more information, see https://doc.rust-lang.org/cargo/reference/resolver.html#links.
 
@@ -1159,7 +1159,7 @@
     ... which satisfies path dependency `a` of package `foo v0.5.0 ([ROOT]/foo)`
 versions that meet the requirements `*` are: 0.5.0
 
-the package `a-sys` links to the native library `a`, but it conflicts with a previous package which links to `a` as well:
+package `a-sys` links to the native library `a`, but it conflicts with a previous package which links to `a` as well:
 package `foo v0.5.0 ([ROOT]/foo)`
 Only one package in the dependency graph may specify the same links value. This helps ensure that only one copy of a native library is linked in the final binary. Try to adjust your dependencies so that only one package uses the `links = "a"` value. For more information, see https://doc.rust-lang.org/cargo/reference/resolver.html#links.
 
@@ -4767,7 +4767,7 @@
     ... required by package `foo v0.5.0 ([ROOT]/foo)`
 versions that meet the requirements `*` are: 0.5.0
 
-the package `a` links to the native library `a`, but it conflicts with a previous package which links to `a` as well:
+package `a` links to the native library `a`, but it conflicts with a previous package which links to `a` as well:
 package `foo v0.5.0 ([ROOT]/foo)`
 Only one package in the dependency graph may specify the same links value. This helps ensure that only one copy of a native library is linked in the final binary. Try to adjust your dependencies so that only one package uses the `links = "a"` value. For more information, see https://doc.rust-lang.org/cargo/reference/resolver.html#links.
 
diff --git a/tests/testsuite/features.rs b/tests/testsuite/features.rs
index 94a69cc..da39e16 100644
--- a/tests/testsuite/features.rs
+++ b/tests/testsuite/features.rs
@@ -6,7 +6,7 @@
 use cargo_test_support::{basic_manifest, project};
 
 #[cargo_test]
-fn invalid1() {
+fn feature_activates_missing_feature() {
     let p = project()
         .file(
             "Cargo.toml",
@@ -32,6 +32,42 @@
 Caused by:
   feature `bar` includes `baz` which is neither a dependency nor another feature
 
+  [HELP] a feature with a similar name exists: `bar`
+
+"#]])
+        .run();
+}
+
+#[cargo_test]
+fn feature_activates_typoed_feature() {
+    let p = project()
+        .file(
+            "Cargo.toml",
+            r#"
+                [package]
+                name = "foo"
+                version = "0.0.1"
+                edition = "2015"
+                authors = []
+
+                [features]
+                bar = ["baz"]
+                jaz = []
+            "#,
+        )
+        .file("src/main.rs", "")
+        .build();
+
+    p.cargo("check")
+        .with_status(101)
+        .with_stderr_data(str![[r#"
+[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml`
+
+Caused by:
+  feature `bar` includes `baz` which is neither a dependency nor another feature
+
+  [HELP] a feature with a similar name exists: `bar`
+
 "#]])
         .run();
 }
@@ -120,7 +156,7 @@
 }
 
 #[cargo_test]
-fn invalid3() {
+fn feature_activates_required_dependency() {
     let p = project()
         .file(
             "Cargo.toml",
@@ -155,7 +191,7 @@
 }
 
 #[cargo_test]
-fn invalid4() {
+fn dependency_activates_missing_feature() {
     let p = project()
         .file(
             "Cargo.toml",
@@ -183,7 +219,7 @@
     ... required by package `foo v0.0.1 ([ROOT]/foo)`
 versions that meet the requirements `*` are: 0.0.1
 
-the package `foo` depends on `bar`, with features: `bar` but `bar` does not have these features.
+package `foo` depends on `bar` with feature `bar` but `bar` does not have that feature.
 
 
 failed to select a version for `bar` which could resolve this conflict
@@ -196,14 +232,65 @@
     p.cargo("check --features test")
         .with_status(101)
         .with_stderr_data(str![[r#"
-[ERROR] Package `foo v0.0.1 ([ROOT]/foo)` does not have the feature `test`
+[ERROR] package `foo v0.0.1 ([ROOT]/foo)` does not have the feature `test`
 
 "#]])
         .run();
 }
 
 #[cargo_test]
-fn invalid5() {
+fn dependency_activates_typoed_feature() {
+    let p = project()
+        .file(
+            "Cargo.toml",
+            r#"
+                [package]
+                name = "foo"
+                version = "0.0.1"
+                edition = "2015"
+                authors = []
+
+                [dependencies.bar]
+                path = "bar"
+                features = ["bar"]
+            "#,
+        )
+        .file("src/main.rs", "")
+        .file(
+            "bar/Cargo.toml",
+            r#"
+                [package]
+                name = "bar"
+                version = "0.0.1"
+                edition = "2015"
+                authors = []
+
+                [features]
+                baz = []
+"#,
+        )
+        .file("bar/src/lib.rs", "")
+        .build();
+
+    p.cargo("check")
+        .with_status(101)
+        .with_stderr_data(str![[r#"
+[ERROR] failed to select a version for `bar`.
+    ... required by package `foo v0.0.1 ([ROOT]/foo)`
+versions that meet the requirements `*` are: 0.0.1
+
+package `foo` depends on `bar` with feature `bar` but `bar` does not have that feature.
+ package `bar` does have feature `baz`
+
+
+failed to select a version for `bar` which could resolve this conflict
+
+"#]])
+        .run();
+}
+
+#[cargo_test]
+fn optional_dev_dependency() {
     let p = project()
         .file(
             "Cargo.toml",
@@ -235,7 +322,7 @@
 }
 
 #[cargo_test]
-fn invalid6() {
+fn feature_activates_missing_dep_feature() {
     let p = project()
         .file(
             "Cargo.toml",
@@ -269,7 +356,7 @@
 }
 
 #[cargo_test]
-fn invalid7() {
+fn feature_activates_feature_inside_feature() {
     let p = project()
         .file(
             "Cargo.toml",
@@ -304,7 +391,7 @@
 }
 
 #[cargo_test]
-fn invalid8() {
+fn dependency_activates_dep_feature() {
     let p = project()
         .file(
             "Cargo.toml",
@@ -339,7 +426,7 @@
 }
 
 #[cargo_test]
-fn invalid9() {
+fn cli_activates_required_dependency() {
     let p = project()
         .file(
             "Cargo.toml",
@@ -362,7 +449,9 @@
     p.cargo("check --features bar")
         .with_stderr_data(str![[r#"
 [LOCKING] 1 package to latest compatible version
-[ERROR] Package `foo v0.0.1 ([ROOT]/foo)` does not have feature `bar`. It has a required dependency with that name, but only optional dependencies can be used as features.
+[ERROR] package `foo v0.0.1 ([ROOT]/foo)` does not have feature `bar`
+
+[HELP] a depednency with that name exists but it is required dependency and only optional dependencies can be used as features.
 
 "#]])
         .with_status(101)
@@ -370,7 +459,7 @@
 }
 
 #[cargo_test]
-fn invalid10() {
+fn dependency_activates_required_dependency() {
     let p = project()
         .file(
             "Cargo.toml",
@@ -411,8 +500,8 @@
     ... required by package `foo v0.0.1 ([ROOT]/foo)`
 versions that meet the requirements `*` are: 0.0.1
 
-the package `foo` depends on `bar`, with features: `baz` but `bar` does not have these features.
- It has a required dependency with that name, but only optional dependencies can be used as features.
+package `foo` depends on `bar` with feature `baz` but `bar` does not have that feature.
+ A required dependency with that name exists, but only optional dependencies can be used as features.
 
 
 failed to select a version for `bar` which could resolve this conflict
diff --git a/tests/testsuite/features_namespaced.rs b/tests/testsuite/features_namespaced.rs
index f7315fe..f104825 100644
--- a/tests/testsuite/features_namespaced.rs
+++ b/tests/testsuite/features_namespaced.rs
@@ -75,6 +75,8 @@
 Caused by:
   feature `bar` includes `baz` which is neither a dependency nor another feature
 
+  [HELP] a feature with a similar name exists: `bar`
+
 "#]])
         .run();
 }
@@ -417,7 +419,9 @@
 
     p.cargo("run --features lazy_static")
         .with_stderr_data(str![[r#"
-[ERROR] Package `foo v0.1.0 ([ROOT]/foo)` does not have feature `lazy_static`. It has an optional dependency with that name, but that dependency uses the "dep:" syntax in the features table, so it does not have an implicit feature with that name.
+[ERROR] package `foo v0.1.0 ([ROOT]/foo)` does not have feature `lazy_static`
+
+[HELP] an optional dependency with that name exists, but the `features` table includes it with the "dep:" syntax so it does not have an implicit feature with that name
 Dependency `lazy_static` would be enabled by these features:
 	- `regex`
 
diff --git a/tests/testsuite/package_features.rs b/tests/testsuite/package_features.rs
index 2aeb390..23a7402 100644
--- a/tests/testsuite/package_features.rs
+++ b/tests/testsuite/package_features.rs
@@ -338,7 +338,7 @@
     p.cargo("run -p bar --features f1,f2")
         .with_status(101)
         .with_stderr_data(str![[r#"
-[ERROR] Package `foo v0.1.0 ([ROOT]/foo)` does not have the feature `f2`
+[ERROR] package `foo v0.1.0 ([ROOT]/foo)` does not have the feature `f2`
 
 [HELP] a feature with a similar name exists: `f1`
 
@@ -406,7 +406,7 @@
     p.cargo("check --features testt")
         .with_status(101)
         .with_stderr_data(str![[r#"
-[ERROR] Package `a v0.1.0 ([ROOT]/foo)` does not have the feature `testt`
+[ERROR] package `a v0.1.0 ([ROOT]/foo)` does not have the feature `testt`
 
 [HELP] a feature with a similar name exists: `test`
 
@@ -458,7 +458,9 @@
         .with_stderr_data(str![[r#"
 [UPDATING] `dummy-registry` index
 [LOCKING] 1 package to latest compatible version
-[ERROR] Package `a v0.1.0 ([ROOT]/foo)` does not have feature `bar`. It has an optional dependency with that name, but that dependency uses the "dep:" syntax in the features table, so it does not have an implicit feature with that name.
+[ERROR] package `a v0.1.0 ([ROOT]/foo)` does not have feature `bar`
+
+[HELP] an optional dependency with that name exists, but the `features` table includes it with the "dep:" syntax so it does not have an implicit feature with that name
 Dependency `bar` would be enabled by these features:
 	- `foo`
 
@@ -496,7 +498,9 @@
         .with_stderr_data(str![[r#"
 [UPDATING] `dummy-registry` index
 [LOCKING] 1 package to latest compatible version
-[ERROR] Package `a v0.1.0 ([ROOT]/foo)` does not have feature `bar`. It has an optional dependency with that name, but that dependency uses the "dep:" syntax in the features table, so it does not have an implicit feature with that name.
+[ERROR] package `a v0.1.0 ([ROOT]/foo)` does not have feature `bar`
+
+[HELP] an optional dependency with that name exists, but the `features` table includes it with the "dep:" syntax so it does not have an implicit feature with that name
 Dependency `bar` would be enabled by these features:
 	- `f1`
 	- `f2`
@@ -537,7 +541,9 @@
         .with_stderr_data(str![[r#"
 [UPDATING] `dummy-registry` index
 [LOCKING] 1 package to latest compatible version
-[ERROR] Package `a v0.1.0 ([ROOT]/foo)` does not have feature `bar`. It has an optional dependency with that name, but that dependency uses the "dep:" syntax in the features table, so it does not have an implicit feature with that name.
+[ERROR] package `a v0.1.0 ([ROOT]/foo)` does not have feature `bar`
+
+[HELP] an optional dependency with that name exists, but the `features` table includes it with the "dep:" syntax so it does not have an implicit feature with that name
 Dependency `bar` would be enabled by these features:
 	- `f1`
 	- `f2`
@@ -583,7 +589,9 @@
         .with_stderr_data(str![[r#"
 [UPDATING] `dummy-registry` index
 [LOCKING] 1 package to latest compatible version
-[ERROR] Package `a v0.1.0 ([ROOT]/foo)` does not have feature `bar`. It has an optional dependency with that name, but that dependency uses the "dep:" syntax in the features table, so it does not have an implicit feature with that name.
+[ERROR] package `a v0.1.0 ([ROOT]/foo)` does not have feature `bar`
+
+[HELP] an optional dependency with that name exists, but the `features` table includes it with the "dep:" syntax so it does not have an implicit feature with that name
 Dependency `bar` would be enabled by these features:
 	- `f1`
 	- `f2`
@@ -820,7 +828,7 @@
         .cwd("member2")
         .with_status(101)
         .with_stderr_data(str![[r#"
-[ERROR] Package `member1 v0.1.0 ([ROOT]/foo/member1)` does not have the feature `m2-feature`
+[ERROR] package `member1 v0.1.0 ([ROOT]/foo/member1)` does not have the feature `m2-feature`
 
 [HELP] a feature with a similar name exists: `m1-feature`
 
diff --git a/tests/testsuite/test.rs b/tests/testsuite/test.rs
index 13eeac1..711fde2 100644
--- a/tests/testsuite/test.rs
+++ b/tests/testsuite/test.rs
@@ -4007,7 +4007,7 @@
     ... required by package `foo v0.1.0 ([ROOT]/foo)`
 versions that meet the requirements `*` are: 0.1.0
 
-the package `foo` depends on `foo`, with features: `missing` but `foo` does not have these features.
+package `foo` depends on `foo` with feature `missing` but `foo` does not have that feature.
 
 
 failed to select a version for `foo` which could resolve this conflict