Skip to content

Commit a97f9fd

Browse files
authored
fix CompletionFunc implementation (#2234)
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 <[email protected]>
1 parent 5f9c408 commit a97f9fd

File tree

2 files changed

+88
-2
lines changed

2 files changed

+88
-2
lines changed

completions.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ type CompletionOptions struct {
129129
type Completion = string
130130

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

134134
// CompletionWithDesc returns a [Completion] with a description by using the TAB delimited format.
135135
func CompletionWithDesc(choice string, description string) Completion {

completions_test.go

+87-1
Original file line numberDiff line numberDiff line change
@@ -2885,13 +2885,99 @@ func TestCompleteWithRootAndLegacyArgs(t *testing.T) {
28852885
"arg1",
28862886
"arg2",
28872887
":4",
2888-
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
2888+
"Completion ended with directive: ShellCompDirectiveNoFileComp", "",
2889+
}, "\n")
28892890

28902891
if output != expected {
28912892
t.Errorf("expected: %q, got: %q", expected, output)
28922893
}
28932894
}
28942895

2896+
func TestCompletionFuncCompatibility(t *testing.T) {
2897+
t.Run("validate signature", func(t *testing.T) {
2898+
t.Run("format with []string", func(t *testing.T) {
2899+
var userComp func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)
2900+
2901+
// check against new signature
2902+
var _ CompletionFunc = userComp
2903+
2904+
// check Command accepts
2905+
cmd := Command{
2906+
ValidArgsFunction: userComp,
2907+
}
2908+
2909+
_ = cmd.RegisterFlagCompletionFunc("foo", userComp)
2910+
})
2911+
2912+
t.Run("format with []Completion", func(t *testing.T) {
2913+
var userComp func(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective)
2914+
2915+
// check against new signature
2916+
var _ CompletionFunc = userComp
2917+
2918+
// check Command accepts
2919+
cmd := Command{
2920+
ValidArgsFunction: userComp,
2921+
}
2922+
2923+
_ = cmd.RegisterFlagCompletionFunc("foo", userComp)
2924+
})
2925+
2926+
t.Run("format with CompletionFunc", func(t *testing.T) {
2927+
var userComp CompletionFunc
2928+
2929+
// check helper against old signature
2930+
var _ func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) = userComp
2931+
var _ func(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective) = userComp
2932+
2933+
// check Command accepts
2934+
cmd := Command{
2935+
ValidArgsFunction: userComp,
2936+
}
2937+
2938+
_ = cmd.RegisterFlagCompletionFunc("foo", userComp)
2939+
})
2940+
})
2941+
2942+
t.Run("user defined completion helper", func(t *testing.T) {
2943+
t.Run("type helper", func(t *testing.T) {
2944+
// This is a type that may have been defined by the user of the library
2945+
// This replicates the issue https://github.com/docker/cli/issues/5827
2946+
// https://github.com/docker/cli/blob/b6e7eba4470ecdca460e4b63270fba8179674ad6/cli/command/completion/functions.go#L18
2947+
type UserCompletionTypeHelper func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)
2948+
2949+
var userComp UserCompletionTypeHelper
2950+
2951+
// Here we are validating the existing type validates the CompletionFunc type
2952+
var _ CompletionFunc = userComp
2953+
2954+
cmd := Command{
2955+
ValidArgsFunction: userComp,
2956+
}
2957+
2958+
_ = cmd.RegisterFlagCompletionFunc("foo", userComp)
2959+
})
2960+
2961+
t.Run("type alias helper", func(t *testing.T) {
2962+
// This is a type that may have been defined by the user of the library
2963+
// This replicates the possible fix that was tried here https://github.com/docker/cli/pull/5828
2964+
// https://github.com/docker/cli/blob/ae3d4db9f658259dace9dee515718be7c1b1f517/cli/command/completion/functions.go#L18
2965+
type UserCompletionTypeAliasHelper = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)
2966+
2967+
var userComp UserCompletionTypeAliasHelper
2968+
2969+
// Here we are validating the existing type validates the CompletionFunc type
2970+
var _ CompletionFunc = userComp
2971+
2972+
cmd := Command{
2973+
ValidArgsFunction: userComp,
2974+
}
2975+
2976+
_ = cmd.RegisterFlagCompletionFunc("foo", userComp)
2977+
})
2978+
})
2979+
}
2980+
28952981
func TestFixedCompletions(t *testing.T) {
28962982
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
28972983
choices := []string{"apple", "banana", "orange"}

0 commit comments

Comments
 (0)