Don't complete --help flag when flag parsing disabled (#2061)

Fixes #2060

When a command sets `DisableFlagParsing = true` it requests the
responsibility of doing all the flag parsing. Therefore even the
`--help/-f/--version/-v` flags should not be automatically completed
by Cobra in such a case.

Without this change the `--help/-h/--version/-v` flags can end up being
completed twice for plugins: one time from cobra and one time from the
plugin (which has set `DisableFlagParsing = true`).

Signed-off-by: Marc Khouzam <marc.khouzam@gmail.com>
This commit is contained in:
Marc Khouzam 2023-10-28 16:10:06 -04:00 committed by GitHub
parent 8b1eba4761
commit b711e8760b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 55 additions and 6 deletions

View file

@ -302,9 +302,13 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
// These flags are normally added when `execute()` is called on `finalCmd`, // These flags are normally added when `execute()` is called on `finalCmd`,
// however, when doing completion, we don't call `finalCmd.execute()`. // however, when doing completion, we don't call `finalCmd.execute()`.
// Let's add the --help and --version flag ourselves. // Let's add the --help and --version flag ourselves but only if the finalCmd
// has not disabled flag parsing; if flag parsing is disabled, it is up to the
// finalCmd itself to handle the completion of *all* flags.
if !finalCmd.DisableFlagParsing {
finalCmd.InitDefaultHelpFlag() finalCmd.InitDefaultHelpFlag()
finalCmd.InitDefaultVersionFlag() finalCmd.InitDefaultVersionFlag()
}
// Check if we are doing flag value completion before parsing the flags. // Check if we are doing flag value completion before parsing the flags.
// This is important because if we are completing a flag value, we need to also // This is important because if we are completing a flag value, we need to also
@ -408,6 +412,11 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) { finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
doCompleteFlags(flag) doCompleteFlags(flag)
}) })
// Try to complete non-inherited flags even if DisableFlagParsing==true.
// This allows programs to tell Cobra about flags for completion even
// if the actual parsing of flags is not done by Cobra.
// For instance, Helm uses this to provide flag name completion for
// some of its plugins.
finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) { finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
doCompleteFlags(flag) doCompleteFlags(flag)
}) })

View file

@ -2622,8 +2622,6 @@ func TestCompleteWithDisableFlagParsing(t *testing.T) {
expected := strings.Join([]string{ expected := strings.Join([]string{
"--persistent", "--persistent",
"-p", "-p",
"--help",
"-h",
"--nonPersistent", "--nonPersistent",
"-n", "-n",
"--flag", "--flag",
@ -3053,8 +3051,26 @@ func TestCompletionCobraFlags(t *testing.T) {
return []string{"extra3"}, ShellCompDirectiveNoFileComp return []string{"extra3"}, ShellCompDirectiveNoFileComp
}, },
} }
childCmd4 := &Command{
Use: "child4",
Version: "1.1.1",
Run: emptyRun,
ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
return []string{"extra4"}, ShellCompDirectiveNoFileComp
},
DisableFlagParsing: true,
}
childCmd5 := &Command{
Use: "child5",
Version: "1.1.1",
Run: emptyRun,
ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
return []string{"extra5"}, ShellCompDirectiveNoFileComp
},
DisableFlagParsing: true,
}
rootCmd.AddCommand(childCmd, childCmd2, childCmd3) rootCmd.AddCommand(childCmd, childCmd2, childCmd3, childCmd4, childCmd5)
_ = childCmd.Flags().Bool("bool", false, "A bool flag") _ = childCmd.Flags().Bool("bool", false, "A bool flag")
_ = childCmd.MarkFlagRequired("bool") _ = childCmd.MarkFlagRequired("bool")
@ -3066,6 +3082,10 @@ func TestCompletionCobraFlags(t *testing.T) {
// Have a command that only adds its own -v flag // Have a command that only adds its own -v flag
_ = childCmd3.Flags().BoolP("verbose", "v", false, "Not a version flag") _ = childCmd3.Flags().BoolP("verbose", "v", false, "Not a version flag")
// Have a command that DisablesFlagParsing but that also adds its own help and version flags
_ = childCmd5.Flags().BoolP("help", "h", false, "My own help")
_ = childCmd5.Flags().BoolP("version", "v", false, "My own version")
return rootCmd return rootCmd
} }
@ -3196,6 +3216,26 @@ func TestCompletionCobraFlags(t *testing.T) {
":4", ":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
}, },
{
desc: "no completion for --help/-h and --version/-v flags when DisableFlagParsing=true",
args: []string{"child4", "-"},
expectedOutput: strings.Join([]string{
"extra4",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
{
desc: "completions for program-defined --help/-h and --version/-v flags even when DisableFlagParsing=true",
args: []string{"child5", "-"},
expectedOutput: strings.Join([]string{
"--help",
"-h",
"--version",
"-v",
"extra5",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
} }
for _, tc := range testcases { for _, tc := range testcases {