diff --git a/command.go b/command.go index 2cc18891d..48bbc7642 100644 --- a/command.go +++ b/command.go @@ -929,8 +929,6 @@ func (c *Command) ExecuteC() (cmd *Command, err error) { // initialize help at the last point to allow for user overriding c.InitDefaultHelpCmd() - // initialize completion at the last point to allow for user overriding - c.initDefaultCompletionCmd() args := c.args @@ -939,7 +937,9 @@ func (c *Command) ExecuteC() (cmd *Command, err error) { args = os.Args[1:] } - // initialize the hidden command to be used for shell completion + // initialize the default completion command + c.initDefaultCompletionCmd(args) + // initialize the __complete command to be used for shell completion c.initCompleteCmd(args) var flags []string diff --git a/completions.go b/completions.go index 6a839f99d..e269a1ccc 100644 --- a/completions.go +++ b/completions.go @@ -585,7 +585,7 @@ func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*p // 1- the feature has been explicitly disabled by the program, // 2- c has no subcommands (to avoid creating one), // 3- c already has a 'completion' command provided by the program. -func (c *Command) initDefaultCompletionCmd() { +func (c *Command) initDefaultCompletionCmd(args []string) { if c.CompletionOptions.DisableDefaultCmd { return } @@ -598,7 +598,8 @@ func (c *Command) initDefaultCompletionCmd() { } haveNoDescFlag := !c.CompletionOptions.DisableNoDescFlag && !c.CompletionOptions.DisableDescriptions - hidden := c.CompletionOptions.HiddenDefaultCmd || !c.HasSubCommands() + hasSubCommands := c.HasSubCommands() + hidden := c.CompletionOptions.HiddenDefaultCmd || !hasSubCommands completionCmd := &Command{ Use: compCmdName, @@ -612,6 +613,18 @@ See each sub-command's help for details on how to use the generated script. } c.AddCommand(completionCmd) + if !hasSubCommands { + // If the completion command will be the only sub-command + // we only create it if it is actually being called. + // This avoids breaking programs that would suddenly find themselves with + // a subcommand, which would prevent them from accepting arguments. + subCmd, _, err := c.Find(args) + if err != nil || subCmd.Name() != compCmdName { + // The completion command is not being called, remove it. + c.RemoveCommand(completionCmd) + } + } + out := c.OutOrStdout() noDesc := c.CompletionOptions.DisableDescriptions shortDesc := "Generate the autocompletion script for %s" diff --git a/completions_test.go b/completions_test.go index 5137ab893..060a9fd62 100644 --- a/completions_test.go +++ b/completions_test.go @@ -2287,22 +2287,26 @@ func TestDefaultCompletionCmd(t *testing.T) { Run: emptyRun, } - // Test that default completion command is created, hidden if there are no other sub-commands + // Test that when there are no sub-commands, the completion command is not created if it is not called directly. assertNoErr(t, rootCmd.Execute()) - found := false for _, cmd := range rootCmd.commands { if cmd.Name() == compCmdName { - found = true - if !cmd.Hidden { - t.Errorf("The 'completion' command should be hidden when there are no other sub-commands of root") - } + t.Errorf("Should not have a 'completion' command when there are no other sub-commands of root") break } } - if !found { - t.Errorf("Should have a 'completion' command when there are no other sub-commands of root") + + // Test that when there are no sub-commands, the completion command is created when it is called directly. + _, err := executeCommand(rootCmd, compCmdName) + if err != nil { + t.Errorf("Unexpected error: %v", err) } + // Reset the arguments + rootCmd.args = nil + // Remove completion command for the next test + removeCompCmd(rootCmd) + // Add a sub-command subCmd := &Command{ Use: "sub", Run: emptyRun, @@ -2310,7 +2314,7 @@ func TestDefaultCompletionCmd(t *testing.T) { rootCmd.AddCommand(subCmd) // Test that a completion command is created if there are other sub-commands - found = false + found := false assertNoErr(t, rootCmd.Execute()) for _, cmd := range rootCmd.commands { if cmd.Name() == compCmdName {