Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions clap_builder/src/error/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,41 @@ impl<F: ErrorFormatter> Error<F> {
err
}

pub(crate) fn unnecessary_double_dash_flag(
cmd: &Command,
arg: String,
usage: Option<StyledStr>,
) -> Self {
use std::fmt::Write as _;
let styles = cmd.get_styles();
let invalid = &styles.get_invalid();
let valid = &styles.get_valid();
let mut err = Self::new(ErrorKind::UnknownArgument).with_cmd(cmd);

#[cfg(feature = "error-context")]
{
let mut styled_suggestion = StyledStr::new();
let _ = write!(
styled_suggestion,
"flag '{valid}{arg}{valid:#}' exists; to use it, remove the '{invalid}--{invalid:#}' before it",
);

err = err.extend_context_unchecked([
(ContextKind::InvalidArg, ContextValue::String(arg)),
(
ContextKind::Suggested,
ContextValue::StyledStrs(vec![styled_suggestion]),
),
]);
if let Some(usage) = usage {
err = err
.insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
}
}

err
}

fn formatted(&self) -> Cow<'_, StyledStr> {
if let Some(message) = self.inner.message.as_ref() {
message.formatted(&self.inner.styles)
Expand Down
23 changes: 23 additions & 0 deletions clap_builder/src/parser/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,29 @@ impl<'cmd> Parser<'cmd> {
Usage::new(self.cmd).create_usage_with_title(&[]),
);
}

// If the arg looks like a flag and matches a known option/flag,
// suggest removing `--`
if arg_os.is_long() || arg_os.is_short() {
let arg_str = arg_os.display().to_string();
let matches_known_arg = if let Some((Ok(long), _)) = arg_os.to_long() {
self.cmd.get_keymap().get(long).is_some()
} else if let Some(mut shorts) = arg_os.to_short() {
shorts
.next_flag()
.and_then(|c| c.ok())
.is_some_and(|c| self.cmd.get_keymap().get(&c).is_some())
} else {
false
};
if matches_known_arg {
return ClapError::unnecessary_double_dash_flag(
self.cmd,
arg_str,
Usage::new(self.cmd).create_usage_with_title(&[]),
);
}
}
}

let suggested_trailing_arg = !trailing_values
Expand Down
26 changes: 26 additions & 0 deletions tests/builder/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,32 @@ For more information, try '--help'.
assert_error(err, expected_kind, message, true);
}

#[test]
#[cfg(feature = "error-context")]
fn flag_used_after_double_dash() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C-TEST says that this should be reproducing the undesired behavior and then the follow up commit should update the test for the new behavior.

let cmd = Command::new("app").arg(
Arg::new("dev")
.long("dev")
.action(ArgAction::SetTrue),
);

let res = cmd.try_get_matches_from(["app", "--", "--dev"]);
assert!(res.is_err());
let err = res.unwrap_err();
let expected_kind = ErrorKind::UnknownArgument;
let message = str![[r#"
error: unexpected argument '--dev' found

tip: flag '--dev' exists; to use it, remove the '--' before it

Usage: app [OPTIONS]

For more information, try '--help'.

"#]];
assert_error(err, expected_kind, message, true);
}

#[test]
#[cfg(feature = "error-context")]
#[cfg(feature = "suggestions")]
Expand Down
Loading