Repository: clap-rs/clap
Pull request #4131 (2022-08-27 00:50:08)

fix(help): Do not propagate global args to help
This prevents global args from showing in help completions,
since help completions should only suggest subcommands.
Adds tests to ensure the args still show in the generated
help messages of subcommands.

This is a followup to #4115 


Patch:
diff --git a/src/builder/command.rs b/src/builder/command.rs
index d4f6212f..9e0a64e6 100644
--- a/src/builder/command.rs
+++ b/src/builder/command.rs
@@ -4070,7 +4070,17 @@ impl Command {
     pub(crate) fn _propagate_global_args(&mut self) {
         debug!("Command::_propagate_global_args:{}", self.name);
 
+        let autogenerated_help_subcommand = !self.is_disable_help_subcommand_set();
+
         for sc in &mut self.subcommands {
+            if sc.get_name() == "help" && autogenerated_help_subcommand {
+                // Avoid propagating args to the autogenerated help subtrees used in completion.
+                // This prevents args from showing up during help completions like
+                // `myapp help subcmd <TAB>`, which should only suggest subcommands and not args,
+                // while still allowing args to show up properly on the generated help message.
+                continue;
+            }
+
             for a in self.args.args().filter(|a| a.is_global_set()) {
                 if sc.find(&a.id).is_some() {
                     debug!(


Test patch:
diff --git a/clap_complete/tests/snapshots/basic.bash b/clap_complete/tests/snapshots/basic.bash
index f55933fd..08b27e0b 100644
--- a/clap_complete/tests/snapshots/basic.bash
+++ b/clap_complete/tests/snapshots/basic.bash
@@ -39,7 +39,7 @@ _my-app() {
             return 0
             ;;
         my__app__help)
-            opts="-c test help"
+            opts="test help"
             if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
                 COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
                 return 0
@@ -53,7 +53,7 @@ _my-app() {
             return 0
             ;;
         my__app__help__help)
-            opts="-c"
+            opts=""
             if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
                 COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
                 return 0
@@ -67,7 +67,7 @@ _my-app() {
             return 0
             ;;
         my__app__help__test)
-            opts="-c"
+            opts=""
             if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
                 COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
                 return 0
diff --git a/clap_complete/tests/snapshots/basic.elvish b/clap_complete/tests/snapshots/basic.elvish
index 71494e46..2465d3ca 100644
--- a/clap_complete/tests/snapshots/basic.elvish
+++ b/clap_complete/tests/snapshots/basic.elvish
@@ -32,15 +32,12 @@ set edit:completion:arg-completer[my-app] = {|@words|
             cand --help 'Print help information'
         }
         &'my-app;help'= {
-            cand -c 'c'
             cand test 'Subcommand'
             cand help 'Print this message or the help of the given subcommand(s)'
         }
         &'my-app;help;test'= {
-            cand -c 'c'
         }
         &'my-app;help;help'= {
-            cand -c 'c'
         }
     ]
     $completions[$command]
diff --git a/clap_complete/tests/snapshots/basic.fish b/clap_complete/tests/snapshots/basic.fish
index 54415fd6..5a719a2a 100644
--- a/clap_complete/tests/snapshots/basic.fish
+++ b/clap_complete/tests/snapshots/basic.fish
@@ -6,8 +6,5 @@ complete -c my-app -n "__fish_use_subcommand" -f -a "help" -d 'Print this messag
 complete -c my-app -n "__fish_seen_subcommand_from test" -s d
 complete -c my-app -n "__fish_seen_subcommand_from test" -s c
 complete -c my-app -n "__fish_seen_subcommand_from test" -s h -l help -d 'Print help information'
-complete -c my-app -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from test; and not __fish_seen_subcommand_from help" -s c
 complete -c my-app -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from test; and not __fish_seen_subcommand_from help" -f -a "test" -d 'Subcommand'
 complete -c my-app -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from test; and not __fish_seen_subcommand_from help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
-complete -c my-app -n "__fish_seen_subcommand_from help; and __fish_seen_subcommand_from test" -s c
-complete -c my-app -n "__fish_seen_subcommand_from help; and __fish_seen_subcommand_from help" -s c
diff --git a/clap_complete/tests/snapshots/basic.ps1 b/clap_complete/tests/snapshots/basic.ps1
index 242b7e2d..89aae1dc 100644
--- a/clap_complete/tests/snapshots/basic.ps1
+++ b/clap_complete/tests/snapshots/basic.ps1
@@ -37,17 +37,14 @@ Register-ArgumentCompleter -Native -CommandName 'my-app' -ScriptBlock {
             break
         }
         'my-app;help' {
-            [CompletionResult]::new('-c', 'c', [CompletionResultType]::ParameterName, 'c')
             [CompletionResult]::new('test', 'test', [CompletionResultType]::ParameterValue, 'Subcommand')
             [CompletionResult]::new('help', 'help', [CompletionResultType]::ParameterValue, 'Print this message or the help of the given subcommand(s)')
             break
         }
         'my-app;help;test' {
-            [CompletionResult]::new('-c', 'c', [CompletionResultType]::ParameterName, 'c')
             break
         }
         'my-app;help;help' {
-            [CompletionResult]::new('-c', 'c', [CompletionResultType]::ParameterName, 'c')
             break
         }
     })
diff --git a/clap_complete/tests/snapshots/basic.zsh b/clap_complete/tests/snapshots/basic.zsh
index ad6c2aec..f7407180 100644
--- a/clap_complete/tests/snapshots/basic.zsh
+++ b/clap_complete/tests/snapshots/basic.zsh
@@ -38,7 +38,6 @@ _arguments "${_arguments_options[@]}" /
 ;;
 (help)
 _arguments "${_arguments_options[@]}" /
-'*-c[]' /
 ":: :_my-app__help_commands" /
 "*::: :->help" /
 && ret=0
@@ -51,12 +50,10 @@ _arguments "${_arguments_options[@]}" /
         case $line[1] in
             (test)
 _arguments "${_arguments_options[@]}" /
-'*-c[]' /
 && ret=0
 ;;
 (help)
 _arguments "${_arguments_options[@]}" /
-'*-c[]' /
 && ret=0
 ;;
         esac
diff --git a/clap_complete_fig/tests/snapshots/basic.fig.js b/clap_complete_fig/tests/snapshots/basic.fig.js
index e488df4b..a8cd3fcc 100644
--- a/clap_complete_fig/tests/snapshots/basic.fig.js
+++ b/clap_complete_fig/tests/snapshots/basic.fig.js
@@ -26,25 +26,10 @@ const completion: Fig.Spec = {
         {
           name: "test",
           description: "Subcommand",
-          options: [
-            {
-              name: "-c",
-            },
-          ],
         },
         {
           name: "help",
           description: "Print this message or the help of the given subcommand(s)",
-          options: [
-            {
-              name: "-c",
-            },
-          ],
-        },
-      ],
-      options: [
-        {
-          name: "-c",
         },
       ],
     },
diff --git a/tests/builder/help.rs b/tests/builder/help.rs
index 3224321c..ac68374d 100644
--- a/tests/builder/help.rs
+++ b/tests/builder/help.rs
@@ -2097,6 +2097,111 @@ Arguments:
     );
 }
 
+#[test]
+fn global_args_should_show_on_toplevel_help_message() {
+    static HELP: &str = "myapp\x20
+
+Usage:
+    myapp [OPTIONS] [SUBCOMMAND]
+
+Subcommands:
+    subcmd\x20\x20\x20\x20
+    help      Print this message or the help of the given subcommand(s)
+
+Options:
+    -g, --some-global <someglobal>\x20\x20\x20\x20
+    -h, --help                        Print help information
+";
+
+    let cmd = Command::new("myapp")
+        .arg(
+            Arg::new("someglobal")
+                .short('g')
+                .long("some-global")
+                .global(true),
+        )
+        .subcommand(Command::new("subcmd").subcommand(Command::new("multi").version("1.0")));
+
+    utils::assert_output(cmd, "myapp help", HELP, false);
+}
+
+#[test]
+fn global_args_should_not_show_on_help_message_for_help_help() {
+    static HELP_HELP: &str = "myapp-help\x20
+Print this message or the help of the given subcommand(s)
+
+Usage:
+    myapp help [SUBCOMMAND]...
+
+Arguments:
+    [SUBCOMMAND]...    The subcommand whose help message to display
+";
+
+    let cmd = Command::new("myapp")
+        .arg(
+            Arg::new("someglobal")
+                .short('g')
+                .long("some-global")
+                .global(true),
+        )
+        .subcommand(Command::new("subcmd").subcommand(Command::new("multi").version("1.0")));
+
+    utils::assert_output(cmd, "myapp help help", HELP_HELP, false);
+}
+
+#[test]
+fn global_args_should_show_on_help_message_for_subcommand() {
+    static HELP_SUBCMD: &str = "myapp-subcmd\x20
+
+Usage:
+    myapp subcmd [OPTIONS] [SUBCOMMAND]
+
+Subcommands:
+    multi\x20\x20\x20\x20
+    help     Print this message or the help of the given subcommand(s)
+
+Options:
+    -g, --some-global <someglobal>\x20\x20\x20\x20
+    -h, --help                        Print help information
+";
+
+    let cmd = Command::new("myapp")
+        .arg(
+            Arg::new("someglobal")
+                .short('g')
+                .long("some-global")
+                .global(true),
+        )
+        .subcommand(Command::new("subcmd").subcommand(Command::new("multi").version("1.0")));
+
+    utils::assert_output(cmd, "myapp help subcmd", HELP_SUBCMD, false);
+}
+
+#[test]
+fn global_args_should_show_on_help_message_for_nested_subcommand() {
+    static HELP_SUB_SUBCMD: &str = "myapp-subcmd-multi 1.0
+
+Usage:
+    myapp subcmd multi [OPTIONS]
+
+Options:
+    -g, --some-global <someglobal>\x20\x20\x20\x20
+    -h, --help                        Print help information
+    -V, --version                     Print version information
+";
+
+    let cmd = Command::new("myapp")
+        .arg(
+            Arg::new("someglobal")
+                .short('g')
+                .long("some-global")
+                .global(true),
+        )
+        .subcommand(Command::new("subcmd").subcommand(Command::new("multi").version("1.0")));
+
+    utils::assert_output(cmd, "myapp help subcmd multi", HELP_SUB_SUBCMD, false);
+}
+
 #[test]
 fn option_usage_order() {
     static OPTION_USAGE_ORDER: &str = "order