Repository: clap-rs/clap (2025-07-09T23:01:54Z)

Issue:
Missing visible long flag aliases in help
I just found something strange, in this function `sc_spec_vals` for the aliases of the Command, I see we have 3 types:
- **aliases** -> and we show them in the help with `.get_visible_aliases()`
- **short_flag_aliases** -> with `.get_visible_short_flag_aliases()`
- **long_flag_aliases** -> with `.get_visible_long_flag_aliases()`

The visible aliases and the short flag ones are collected, but the long flag aliases are forgotten.

_Originally posted by @GilShoshan94 in https://github.com/clap-rs/clap/pull/6057#discussion_r2182025630_
            
Add some test cases for Command aliases
Hi,
The tests for aliases (of arguments and subcommands) are distributed between several files:

For Arg:
-tests/builder/arg_aliases_short.rs
-tests/builder/arg_aliases.rs
It covers well everything I think regarding aliases.

For Command (on the subcommand):
-tests/builder/flag_subcommands.rs
-tests/builder/subcommands.rs
It does not 100% fully cover in my opinion as **it is missing the following cases:**

[visible_aliases](https://docs.rs/clap/4.5.41/clap/struct.Command.html#method.visible_aliases) (but visible_alias is tested, but only to match the help, not tested with a try_get_matches_from)
[visible_short_flag_alias](https://docs.rs/clap/4.5.41/clap/struct.Command.html#method.visible_short_flag_alias) (but visible_short_flag_aliases is properly tested)
[visible_long_flag_alias](https://docs.rs/clap/4.5.41/clap/struct.Command.html#method.visible_long_flag_alias) (but visible_long_flag_aliases is properly tested)

---

**Either we should add the few missing case, or we should maybe consolidate all the aliases cases into one place** (expect for the help that are fully covered in `tests/builder/help.rs`).

---

Here a code for inspiration that covers all the aliases and check for matching.
```rust
use clap::{Arg, Command};

fn setup_aliases() -> Command {
    Command::new("ctest")
        .version("0.1")
        .arg(
            Arg::new("dest")
                .short('d')
                .long("destination")
                .value_name("FILE")
                .help("File to save into")
                .long_help("The Filepath to save into the result")
                .short_alias('q')
                .short_aliases(['w', 'e'])
                .alias("arg-alias")
                .aliases(["do-stuff", "do-tests"])
                .visible_short_alias('t')
                .visible_short_aliases(['i', 'o'])
                .visible_alias("file")
                .visible_aliases(["into", "to"])
                .action(ArgAction::Set),
        )
        .subcommand(
            Command::new("rev")
                .short_flag('r')
                .long_flag("inplace")
                .about("In place")
                .long_about("Change mode to work in place on source")
                .alias("subc-alias")
                .aliases(["subc-do-stuff", "subc-do-tests"])
                .short_flag_alias('j')
                .short_flag_aliases(['k', 'l'])
                .long_flag_alias("subc-long-flag-alias")
                .long_flag_aliases(["subc-long-do-stuff", "subc-long-do-tests"])
                .visible_alias("source")
                .visible_aliases(["from", "onsource"])
                .visible_short_flag_alias('s')
                .visible_short_flag_aliases(['f', 'g'])
                .visible_long_flag_alias("origin")
                .visible_long_flag_aliases(["path", "tryfrom"])
                .arg(
                    Arg::new("input")
                        .value_name("INPUT")
                        .help("The source file"),
                ),
        )
}

#[test]
fn all_aliases_work() {
    let app = setup_aliases();

    for arg_al in [
        "-d",
        "--destination",
        "-q",
        "-w",
        "-e",
        "--arg-alias",
        "--do-stuff",
        "--do-tests",
        "-t",
        "-i",
        "-o",
        "--file",
        "--into",
        "--to",
    ] {
        let m = app
            .clone()
            .try_get_matches_from(vec!["ctest", arg_al, "filepath"]);
        assert!(m.is_ok(), "{}", m.unwrap_err());
        let m = m.unwrap();
        assert!(m.subcommand_name().is_none());
    }

    for subc_al in [
        "rev",
        "-r",
        "--inplace",
        "subc-alias",
        "subc-do-stuff",
        "subc-do-tests",
        "-j",
        "-k",
        "-l",
        "--subc-long-flag-alias",
        "--subc-long-do-tests",
        "source",
        "from",
        "onsource",
        "-s",
        "-f",
        "-g",
        "--origin",
        "--path",
        "--tryfrom",
    ] {
        let m = app
            .clone()
            .try_get_matches_from(vec!["ctest", subc_al, "filepath"]);
        assert!(m.is_ok(), "{}", m.unwrap_err());
        let m = m.unwrap();
        assert!(m.subcommand_name().is_some());
    }
}
```


Discussion:
This was overlooked in #1974 and it is taken care of in PR #6068 as a minor fix in a commit

Closed by https://github.com/clap-rs/clap/pull/6068



Patch:
diff --git a/clap_builder/src/output/help_template.rs b/clap_builder/src/output/help_template.rs
index 5ac11fe71cf..d54d5da902c 100644
--- a/clap_builder/src/output/help_template.rs
+++ b/clap_builder/src/output/help_template.rs
@@ -1003,8 +1003,12 @@ impl HelpTemplate<'_, '_> {
 
         let mut short_als = a
             .get_visible_short_flag_aliases()
-            .map(|a| format!("{ctx_val}-{a}{ctx_val:#}"))
+            .map(|s| format!("{ctx_val}-{s}{ctx_val:#}"))
             .collect::<Vec<_>>();
+        let long_als = a
+            .get_visible_long_flag_aliases()
+            .map(|s| format!("{ctx_val}--{s}{ctx_val:#}"));
+        short_als.extend(long_als);
         let als = a
             .get_visible_aliases()
             .map(|s| format!("{ctx_val}{s}{ctx_val:#}"));
@@ -1019,6 +1023,10 @@ impl HelpTemplate<'_, '_> {
                 "HelpTemplate::spec_vals: Found short flag aliases...{:?}",
                 a.get_all_short_flag_aliases().collect::<Vec<_>>()
             );
+            debug!(
+                "HelpTemplate::spec_vals: Found long flag aliases...{:?}",
+                a.get_all_long_flag_aliases().collect::<Vec<_>>()
+            );
             spec_vals.push(format!("{ctx}[aliases: {ctx:#}{all_als}{ctx}]{ctx:#}"));
         }
 


Test patch:
diff --git a/tests/builder/flag_subcommands.rs b/tests/builder/flag_subcommands.rs
index 620fbc3aa24..895343c74b3 100644
--- a/tests/builder/flag_subcommands.rs
+++ b/tests/builder/flag_subcommands.rs
@@ -23,6 +23,46 @@ fn flag_subcommand_normal() {
         .expect("defaulted by clap"));
 }
 
+#[test]
+fn flag_subcommand_normal_with_alias_vis_and_hidden() {
+    let cmd = Command::new("myprog").subcommand(
+        Command::new("some")
+            .short_flag('S')
+            .long_flag("S")
+            .arg(
+                Arg::new("test")
+                    .short('t')
+                    .long("test")
+                    .help("testing testing")
+                    .action(ArgAction::SetTrue),
+            )
+            .alias("result")
+            .aliases(["subc-do-stuff", "subc-do-tests"])
+            .visible_alias("many")
+            .visible_aliases(["several", "few"]),
+    );
+
+    for i in [
+        "some",
+        "result",
+        "subc-do-stuff",
+        "subc-do-tests",
+        "many",
+        "several",
+        "few",
+    ] {
+        let app = cmd.clone();
+        let matches = app
+            .try_get_matches_from(vec!["myprog", i, "--test"])
+            .unwrap();
+        assert_eq!(matches.subcommand_name().unwrap(), "some");
+        let sub_matches = matches.subcommand_matches("some").unwrap();
+        assert!(*sub_matches
+            .get_one::<bool>("test")
+            .expect("defaulted by clap"));
+    }
+}
+
 #[test]
 fn flag_subcommand_normal_with_alias() {
     let matches = Command::new("test")
@@ -148,6 +188,7 @@ fn flag_subcommand_short_with_aliases_vis_and_hidden() {
                     .long("test")
                     .help("testing testing"),
             )
+            .visible_short_flag_alias('X')
             .visible_short_flag_aliases(['M', 'B'])
             .short_flag_alias('C'),
     );
@@ -162,6 +203,46 @@ fn flag_subcommand_short_with_aliases_vis_and_hidden() {
     let app3 = cmd.clone();
     let matches3 = app3.try_get_matches_from(vec!["test", "-B"]).unwrap();
     assert_eq!(matches3.subcommand_name().unwrap(), "some");
+
+    let app4 = cmd.clone();
+    let matches4 = app4.try_get_matches_from(vec!["test", "-X"]).unwrap();
+    assert_eq!(matches4.subcommand_name().unwrap(), "some");
+}
+
+#[test]
+fn flag_subcommand_long_with_aliases_vis_and_hidden() {
+    let cmd = Command::new("test").subcommand(
+        Command::new("some")
+            .long_flag("sync")
+            .arg(
+                Arg::new("test")
+                    .short('t')
+                    .long("test")
+                    .help("testing testing"),
+            )
+            .visible_long_flag_alias("several")
+            .visible_long_flag_aliases(["result", "someall"])
+            .long_flag_alias("flag"),
+    );
+    let app1 = cmd.clone();
+    let matches1 = app1.try_get_matches_from(vec!["test", "--result"]).unwrap();
+    assert_eq!(matches1.subcommand_name().unwrap(), "some");
+
+    let app2 = cmd.clone();
+    let matches2 = app2.try_get_matches_from(vec!["test", "--flag"]).unwrap();
+    assert_eq!(matches2.subcommand_name().unwrap(), "some");
+
+    let app3 = cmd.clone();
+    let matches3 = app3
+        .try_get_matches_from(vec!["test", "--someall"])
+        .unwrap();
+    assert_eq!(matches3.subcommand_name().unwrap(), "some");
+
+    let app4 = cmd.clone();
+    let matches4 = app4
+        .try_get_matches_from(vec!["test", "--several"])
+        .unwrap();
+    assert_eq!(matches4.subcommand_name().unwrap(), "some");
 }
 
 #[test]
diff --git a/tests/builder/help.rs b/tests/builder/help.rs
index 1a2014dad40..cee79cca69e 100644
--- a/tests/builder/help.rs
+++ b/tests/builder/help.rs
@@ -905,6 +905,105 @@ Options:
     utils::assert_output(app, "ctest --help", expected, false);
 }
 
+#[cfg(feature = "wrap_help")]
+fn setup_aliases() -> Command {
+    Command::new("ctest")
+        .version("0.1")
+        .arg(
+            Arg::new("dest")
+                .short('d')
+                .long("destination")
+                .value_name("FILE")
+                .help("File to save into")
+                .long_help("The Filepath to save into the result")
+                .short_alias('q')
+                .short_aliases(['w', 'e'])
+                .alias("arg-alias")
+                .aliases(["do-stuff", "do-tests"])
+                .visible_short_alias('t')
+                .visible_short_aliases(['i', 'o'])
+                .visible_alias("file")
+                .visible_aliases(["into", "to"])
+                .action(ArgAction::Set),
+        )
+        .subcommand(
+            Command::new("rev")
+                .short_flag('r')
+                .long_flag("inplace")
+                .about("In place")
+                .long_about("Change mode to work in place on source")
+                .alias("subc-alias")
+                .aliases(["subc-do-stuff", "subc-do-tests"])
+                .short_flag_alias('j')
+                .short_flag_aliases(['k', 'l'])
+                .long_flag_alias("subc-long-flag-alias")
+                .long_flag_aliases(["subc-long-do-stuff", "subc-long-do-tests"])
+                .visible_alias("source")
+                .visible_aliases(["from", "onsource"])
+                .visible_short_flag_alias('s')
+                .visible_short_flag_aliases(['f', 'g'])
+                .visible_long_flag_alias("origin")
+                .visible_long_flag_aliases(["path", "tryfrom"])
+                .arg(
+                    Arg::new("input")
+                        .value_name("INPUT")
+                        .help("The source file"),
+                ),
+        )
+}
+
+#[test]
+#[cfg(feature = "wrap_help")]
+fn visible_aliases_with_short_help() {
+    let app = setup_aliases().term_width(80);
+
+    let expected = str![[r#"
+Usage: ctest [OPTIONS] [COMMAND]
+
+Commands:
+  rev, -r, --inplace  In place [aliases: -s, -f, -g, --origin, --path,
+                      --tryfrom, source, from, onsource]
+  help                Print this message or the help of the given subcommand(s)
+
+Options:
+  -d, --destination <FILE>  File to save into [aliases: -t, -i, -o, --file,
+                            --into, --to]
+  -h, --help                Print help (see more with '--help')
+  -V, --version             Print version
+
+"#]];
+    utils::assert_output(app, "ctest -h", expected, false);
+}
+
+#[test]
+#[cfg(feature = "wrap_help")]
+fn visible_aliases_with_long_help() {
+    let app = setup_aliases().term_width(80);
+
+    let expected = str![[r#"
+Usage: ctest [OPTIONS] [COMMAND]
+
+Commands:
+  rev, -r, --inplace  In place [aliases: -s, -f, -g, --origin, --path,
+                      --tryfrom, source, from, onsource]
+  help                Print this message or the help of the given subcommand(s)
+
+Options:
+  -d, --destination <FILE>
+          The Filepath to save into the result
+          
+          [aliases: -t, -i, -o, --file, --into, --to]
+
+  -h, --help
+          Print help (see a summary with '-h')
+
+  -V, --version
+          Print version
+
+"#]];
+    utils::assert_output(app, "ctest --help", expected, false);
+}
+
 #[test]
 fn hidden_possible_vals() {
     let app = Command::new("ctest").arg(
diff --git a/tests/ui/arg_required_else_help_stderr.toml b/tests/ui/arg_required_else_help_stderr.toml
index bb023bae7ff..ac6524ee9c6 100644
--- a/tests/ui/arg_required_else_help_stderr.toml
+++ b/tests/ui/arg_required_else_help_stderr.toml
@@ -9,9 +9,9 @@ Commands:
   more                    
   test                    Subcommand with one visible alias [aliases: do-stuff]
   test_2                  several visible aliases [aliases: do-other-stuff, tests]
-  test_3, --test          several visible long flag aliases
+  test_3, --test          several visible long flag aliases [aliases: --testing, --testall, --test_all]
   test_4, -t              several visible short flag aliases [aliases: -q, -w]
-  test_5, -e, --test-hdr  all kinds of visible aliases [aliases: -r, -y, tests_4k]
+  test_5, -e, --test-hdr  all kinds of visible aliases [aliases: -r, -y, --thetests, --t4k, tests_4k]
   help                    Print this message or the help of the given subcommand(s)
 
 Arguments:
diff --git a/tests/ui/h_flag_stdout.toml b/tests/ui/h_flag_stdout.toml
index 3436e117267..36192e195ad 100644
--- a/tests/ui/h_flag_stdout.toml
+++ b/tests/ui/h_flag_stdout.toml
@@ -8,9 +8,9 @@ Commands:
   more                    
   test                    Subcommand with one visible alias [aliases: do-stuff]
   test_2                  several visible aliases [aliases: do-other-stuff, tests]
-  test_3, --test          several visible long flag aliases
+  test_3, --test          several visible long flag aliases [aliases: --testing, --testall, --test_all]
   test_4, -t              several visible short flag aliases [aliases: -q, -w]
-  test_5, -e, --test-hdr  all kinds of visible aliases [aliases: -r, -y, tests_4k]
+  test_5, -e, --test-hdr  all kinds of visible aliases [aliases: -r, -y, --thetests, --t4k, tests_4k]
   help                    Print this message or the help of the given subcommand(s)
 
 Arguments:
diff --git a/tests/ui/help_cmd_stdout.toml b/tests/ui/help_cmd_stdout.toml
index ffdc39dd47b..a612357e013 100644
--- a/tests/ui/help_cmd_stdout.toml
+++ b/tests/ui/help_cmd_stdout.toml
@@ -8,9 +8,9 @@ Commands:
   more                    
   test                    Subcommand with one visible alias [aliases: do-stuff]
   test_2                  several visible aliases [aliases: do-other-stuff, tests]
-  test_3, --test          several visible long flag aliases
+  test_3, --test          several visible long flag aliases [aliases: --testing, --testall, --test_all]
   test_4, -t              several visible short flag aliases [aliases: -q, -w]
-  test_5, -e, --test-hdr  all kinds of visible aliases [aliases: -r, -y, tests_4k]
+  test_5, -e, --test-hdr  all kinds of visible aliases [aliases: -r, -y, --thetests, --t4k, tests_4k]
   help                    Print this message or the help of the given subcommand(s)
 
 Arguments:
diff --git a/tests/ui/help_flag_stdout.toml b/tests/ui/help_flag_stdout.toml
index 79193808013..7c1daf04e85 100644
--- a/tests/ui/help_flag_stdout.toml
+++ b/tests/ui/help_flag_stdout.toml
@@ -8,9 +8,9 @@ Commands:
   more                    
   test                    Subcommand with one visible alias [aliases: do-stuff]
   test_2                  several visible aliases [aliases: do-other-stuff, tests]
-  test_3, --test          several visible long flag aliases
+  test_3, --test          several visible long flag aliases [aliases: --testing, --testall, --test_all]
   test_4, -t              several visible short flag aliases [aliases: -q, -w]
-  test_5, -e, --test-hdr  all kinds of visible aliases [aliases: -r, -y, tests_4k]
+  test_5, -e, --test-hdr  all kinds of visible aliases [aliases: -r, -y, --thetests, --t4k, tests_4k]
   help                    Print this message or the help of the given subcommand(s)
 
 Arguments: