diff --git a/clap_builder/src/parser/features/suggestions.rs b/clap_builder/src/parser/features/suggestions.rs index b8bb7adfa15..310bf203883 100644 --- a/clap_builder/src/parser/features/suggestions.rs +++ b/clap_builder/src/parser/features/suggestions.rs @@ -8,16 +8,24 @@ use crate::builder::Command; /// Returns a Vec of all possible values that exceed a similarity threshold /// sorted by ascending similarity, most similar comes last #[cfg(feature = "suggestions")] -pub(crate) fn did_you_mean(v: &str, possible_values: I) -> Vec +pub(crate) fn did_you_mean(input: &str, possible_values: I) -> Vec where T: AsRef, I: IntoIterator, { let mut candidates: Vec<(f64, String)> = possible_values .into_iter() - // GH #4660: using `jaro` because `jaro_winkler` implementation in `strsim-rs` is wrong - // causing strings with common prefix >=10 to be considered perfectly similar - .map(|pv| (strsim::jaro(v, pv.as_ref()), pv.as_ref().to_owned())) + .map(|pv| { + let confidence = if words_are_swapped(input, pv.as_ref()) { + 0.9 + } else { + // GH #4660: using `jaro` because `jaro_winkler` implementation in `strsim-rs` is wrong + // causing strings with common prefix >=10 to be considered perfectly similar + strsim::jaro(input, pv.as_ref()) + }; + + (confidence, pv.as_ref().to_owned()) + }) // Confidence of 0.7 so that bar -> baz is suggested .filter(|(confidence, _)| *confidence > 0.7) .collect(); @@ -25,6 +33,18 @@ where candidates.into_iter().map(|(_, pv)| pv).collect() } +#[cfg(feature = "suggestions")] +fn words_are_swapped(input: &str, candidate: &str) -> bool { + let input_words = input.split_once('-'); + let candidate_words = candidate.split_once('-'); + match (input_words, candidate_words) { + (Some((input1, input2)), Some((candidate1, candidate2))) => { + input1 == candidate2 && input2 == candidate1 + } + _ => false, + } +} + #[cfg(not(feature = "suggestions"))] pub(crate) fn did_you_mean(_: &str, _: I) -> Vec where @@ -123,6 +143,22 @@ mod test { ); } + #[test] + fn swapped_words_1() { + assert_eq!( + did_you_mean("write-lock", ["lock-write", "no-lock"].iter()), + vec!["no-lock", "lock-write"] + ); + } + + #[test] + fn swapped_words_2() { + assert_eq!( + did_you_mean("features-all", ["all-features", "features"].iter()), + vec!["features", "all-features"] + ); + } + #[test] fn flag_missing_letter() { let p_vals = ["test", "possible", "values"];