Skip to content

Commit

Permalink
fix CompletionFunc implementation (#2234)
Browse files Browse the repository at this point in the history
The new type CompletionFunc could lead to a regression.
This commit make the new `CompletionFunc` type a type alias instead,
in case projects using Cobra have created their own similar type.

This commit also adds a test to ensure that the completion function
remains backwards-compatible.

Signed-off-by: ccoVeille <3875889+ccoVeille@users.noreply.github.com>
  • Loading branch information
ccoVeille authored Feb 16, 2025
1 parent 5f9c408 commit a97f9fd
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 2 deletions.
2 changes: 1 addition & 1 deletion completions.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ type CompletionOptions struct {
type Completion = string

// CompletionFunc is a function that provides completion results.
type CompletionFunc func(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective)
type CompletionFunc = func(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective)

// CompletionWithDesc returns a [Completion] with a description by using the TAB delimited format.
func CompletionWithDesc(choice string, description string) Completion {
Expand Down
88 changes: 87 additions & 1 deletion completions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2885,13 +2885,99 @@ func TestCompleteWithRootAndLegacyArgs(t *testing.T) {
"arg1",
"arg2",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
"Completion ended with directive: ShellCompDirectiveNoFileComp", "",
}, "\n")

if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}

func TestCompletionFuncCompatibility(t *testing.T) {
t.Run("validate signature", func(t *testing.T) {
t.Run("format with []string", func(t *testing.T) {
var userComp func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)

// check against new signature
var _ CompletionFunc = userComp

// check Command accepts
cmd := Command{
ValidArgsFunction: userComp,
}

_ = cmd.RegisterFlagCompletionFunc("foo", userComp)
})

t.Run("format with []Completion", func(t *testing.T) {
var userComp func(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective)

// check against new signature
var _ CompletionFunc = userComp

// check Command accepts
cmd := Command{
ValidArgsFunction: userComp,
}

_ = cmd.RegisterFlagCompletionFunc("foo", userComp)
})

t.Run("format with CompletionFunc", func(t *testing.T) {
var userComp CompletionFunc

// check helper against old signature
var _ func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) = userComp
var _ func(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective) = userComp

// check Command accepts
cmd := Command{
ValidArgsFunction: userComp,
}

_ = cmd.RegisterFlagCompletionFunc("foo", userComp)
})
})

t.Run("user defined completion helper", func(t *testing.T) {
t.Run("type helper", func(t *testing.T) {
// This is a type that may have been defined by the user of the library
// This replicates the issue https://github.com/docker/cli/issues/5827
// https://github.com/docker/cli/blob/b6e7eba4470ecdca460e4b63270fba8179674ad6/cli/command/completion/functions.go#L18
type UserCompletionTypeHelper func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)

var userComp UserCompletionTypeHelper

// Here we are validating the existing type validates the CompletionFunc type
var _ CompletionFunc = userComp

cmd := Command{
ValidArgsFunction: userComp,
}

_ = cmd.RegisterFlagCompletionFunc("foo", userComp)
})

t.Run("type alias helper", func(t *testing.T) {
// This is a type that may have been defined by the user of the library
// This replicates the possible fix that was tried here https://github.com/docker/cli/pull/5828
// https://github.com/docker/cli/blob/ae3d4db9f658259dace9dee515718be7c1b1f517/cli/command/completion/functions.go#L18
type UserCompletionTypeAliasHelper = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)

var userComp UserCompletionTypeAliasHelper

// Here we are validating the existing type validates the CompletionFunc type
var _ CompletionFunc = userComp

cmd := Command{
ValidArgsFunction: userComp,
}

_ = cmd.RegisterFlagCompletionFunc("foo", userComp)
})
})
}

func TestFixedCompletions(t *testing.T) {
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
choices := []string{"apple", "banana", "orange"}
Expand Down

0 comments on commit a97f9fd

Please sign in to comment.