Repository: spf13/cobra
Pull request #1349 (2021-02-13 17:05:51)

Correcting misspelled words

CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/spf13/cobra?pullRequest=1349) <br/>Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla-assistant.io/spf13/cobra?pullRequest=1349) before we can accept your contribution.<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/spf13/cobra?pullRequest=1349) it.</sub>
moritamori: @jpmcb
Thank you for your feedback. 
I rebased this branch and inclueded the changes suggested in #1350.

Patch:
diff --git a/bash_completions.go b/bash_completions.go
index 7106147..b47f898 100644
--- a/bash_completions.go
+++ b/bash_completions.go
@@ -384,7 +384,7 @@ func writePostscript(buf io.StringWriter, name string) {
 	name = strings.Replace(name, ":", "__", -1)
 	WriteStringAndCheck(buf, fmt.Sprintf("__start_%s()\n", name))
 	WriteStringAndCheck(buf, fmt.Sprintf(`{
-    local cur prev words cword
+    local cur prev words cword split
     declare -A flaghash 2>/dev/null || :
     declare -A aliashash 2>/dev/null || :
     if declare -F _init_completion >/dev/null 2>&1; then
@@ -400,11 +400,13 @@ func writePostscript(buf io.StringWriter, name string) {
     local flags_with_completion=()
     local flags_completion=()
     local commands=("%[1]s")
+    local command_aliases=()
     local must_have_one_flag=()
     local must_have_one_noun=()
     local has_completion_function
     local last_command
     local nouns=()
+    local noun_aliases=()
 
     __%[1]s_handle_word
 }
diff --git a/command.go b/command.go
index ce94d40..5c85c89 100644
--- a/command.go
+++ b/command.go
@@ -887,7 +887,8 @@ func (c *Command) preRun() {
 }
 
 // ExecuteContext is the same as Execute(), but sets the ctx on the command.
-// Retrieve ctx by calling cmd.Context() inside your *Run lifecycle functions.
+// Retrieve ctx by calling cmd.Context() inside your *Run lifecycle or ValidArgs
+// functions.
 func (c *Command) ExecuteContext(ctx context.Context) error {
 	c.ctx = ctx
 	return c.Execute()
@@ -901,6 +902,14 @@ func (c *Command) Execute() error {
 	return err
 }
 
+// ExecuteContextC is the same as ExecuteC(), but sets the ctx on the command.
+// Retrieve ctx by calling cmd.Context() inside your *Run lifecycle or ValidArgs
+// functions.
+func (c *Command) ExecuteContextC(ctx context.Context) (*Command, error) {
+	c.ctx = ctx
+	return c.ExecuteC()
+}
+
 // ExecuteC executes the command.
 func (c *Command) ExecuteC() (cmd *Command, err error) {
 	if c.ctx == nil {
diff --git a/completions.go b/completions.go
index fea2c6f..04dc71e 100644
--- a/completions.go
+++ b/completions.go
@@ -175,10 +175,6 @@ func (c *Command) initCompleteCmd(args []string) {
 				fmt.Fprintln(finalCmd.OutOrStdout(), comp)
 			}
 
-			if directive >= shellCompDirectiveMaxValue {
-				directive = ShellCompDirectiveDefault
-			}
-
 			// As the last printout, print the completion directive for the completion script to parse.
 			// The directive integer must be that last character following a single colon (:).
 			// The completion script expects :<directive>
@@ -221,6 +217,7 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
 		// Unable to find the real command. E.g., <program> someInvalidCmd <TAB>
 		return c, []string{}, ShellCompDirectiveDefault, fmt.Errorf("Unable to find a command for arguments: %v", trimmedArgs)
 	}
+	finalCmd.ctx = c.ctx
 
 	// 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
@@ -328,7 +325,7 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
 		if len(finalArgs) == 0 && !foundLocalNonPersistentFlag {
 			// We only complete sub-commands if:
 			// - there are no arguments on the command-line and
-			// - there are no local, non-peristent flag on the command-line or TraverseChildren is true
+			// - there are no local, non-persistent flags on the command-line or TraverseChildren is true
 			for _, subCmd := range finalCmd.Commands() {
 				if subCmd.IsAvailableCommand() || subCmd == finalCmd.helpCommand {
 					if strings.HasPrefix(subCmd.Name(), toComplete) {
@@ -468,7 +465,16 @@ func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*p
 	if len(lastArg) > 0 && lastArg[0] == '-' {
 		if index := strings.Index(lastArg, "="); index >= 0 {
 			// Flag with an =
-			flagName = strings.TrimLeft(lastArg[:index], "-")
+			if strings.HasPrefix(lastArg[:index], "--") {
+				// Flag has full name
+				flagName = lastArg[2:index]
+			} else {
+				// Flag is shorthand
+				// We have to get the last shorthand flag name
+				// e.g. `-asd` => d to provide the correct completion
+				// https://github.com/spf13/cobra/issues/1257
+				flagName = lastArg[index-1 : index]
+			}
 			lastArg = lastArg[index+1:]
 			flagWithEqual = true
 		} else {
@@ -485,8 +491,16 @@ func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*p
 				// If the flag contains an = it means it has already been fully processed,
 				// so we don't need to deal with it here.
 				if index := strings.Index(prevArg, "="); index < 0 {
-					flagName = strings.TrimLeft(prevArg, "-")
-
+					if strings.HasPrefix(prevArg, "--") {
+						// Flag has full name
+						flagName = prevArg[2:]
+					} else {
+						// Flag is shorthand
+						// We have to get the last shorthand flag name
+						// e.g. `-asd` => d to provide the correct completion
+						// https://github.com/spf13/cobra/issues/1257
+						flagName = prevArg[len(prevArg)-1:]
+					}
 					// Remove the uncompleted flag or else there could be an error created
 					// for an invalid value for that flag
 					trimmedArgs = args[:len(args)-1]
diff --git a/fish_completions.go b/fish_completions.go
index 3e11234..b0db323 100644
--- a/fish_completions.go
+++ b/fish_completions.go
@@ -21,44 +21,47 @@ func genFishComp(buf io.StringWriter, name string, includeDesc bool) {
 	WriteStringAndCheck(buf, fmt.Sprintf("# fish completion for %-36s -*- shell-script -*-\n", name))
 	WriteStringAndCheck(buf, fmt.Sprintf(`
 function __%[1]s_debug
-    set file "$BASH_COMP_DEBUG_FILE"
+    set -l file "$BASH_COMP_DEBUG_FILE"
     if test -n "$file"
         echo "$argv" >> $file
     end
 end
 
 function __%[1]s_perform_completion
-    __%[1]s_debug "Starting __%[1]s_perform_completion with: $argv"
+    __%[1]s_debug "Starting __%[1]s_perform_completion"
 
-    set args (string split -- " " "$argv")
-    set lastArg "$args[-1]"
+    # Extract all args except the last one
+    set -l args (commandline -opc)
+    # Extract the last arg and escape it in case it is a space
+    set -l lastArg (string escape -- (commandline -ct))
 
     __%[1]s_debug "args: $args"
     __%[1]s_debug "last arg: $lastArg"
 
-    set emptyArg ""
-    if test -z "$lastArg"
-        __%[1]s_debug "Setting emptyArg"
-        set emptyArg \"\"
-    end
-    __%[1]s_debug "emptyArg: $emptyArg"
-
-    if not type -q "$args[1]"
-        # This can happen when "complete --do-complete %[2]s" is called when running this script.
-        __%[1]s_debug "Cannot find $args[1]. No completions."
-        return
-    end
+    set -l requestComp "$args[1] %[3]s $args[2..-1] $lastArg"
 
-    set requestComp "$args[1] %[3]s $args[2..-1] $emptyArg"
     __%[1]s_debug "Calling $requestComp"
+    set -l results (eval $requestComp 2> /dev/null)
+
+    # Some programs may output extra empty lines after the directive.
+    # Let's ignore them or else it will break completion.
+    # Ref: https://github.com/spf13/cobra/issues/1279
+    for line in $results[-1..1]
+        if test (string trim -- $line) = ""
+            # Found an empty line, remove it
+            set results $results[1..-2]
+        else
+            # Found non-empty line, we have our proper output
+            break
+        end
+    end
 
-    set results (eval $requestComp 2> /dev/null)
-    set comps $results[1..-2]
-    set directiveLine $results[-1]
+    set -l comps $results[1..-2]
+    set -l directiveLine $results[-1]
 
     # For Fish, when completing a flag with an = (e.g., <program> -n=<TAB>)
     # completions must be prefixed with the flag
-    set flagPrefix (string match -r -- '-.*=' "$lastArg")
+    set -l flagPrefix (string match -r -- '-.*=' "$lastArg")
 
     __%[1]s_debug "Comps: $comps"
     __%[1]s_debug "DirectiveLine: $directiveLine"
@@ -71,115 +74,124 @@ function __%[1]s_perform_completion
     printf "%%s\n" "$directiveLine"
 end
 
-# This function does three things:
-# 1- Obtain the completions and store them in the global __%[1]s_comp_results
-# 2- Set the __%[1]s_comp_do_file_comp flag if file completion should be performed
-#    and unset it otherwise
-# 3- Return true if the completion results are not empty
+# This function does two things:
+# - Obtain the completions and store them in the global __%[1]s_comp_results
+# - Return false if file completion should be performed
 function __%[1]s_prepare_completions
+    __%[1]s_debug ""
+    __%[1]s_debug "========= starting completion logic =========="
+
     # Start fresh
-    set --erase __%[1]s_comp_do_file_comp
     set --erase __%[1]s_comp_results
 
-    # Check if the command-line is already provided.  This is useful for testing.
-    if not set --query __%[1]s_comp_commandLine
-        # Use the -c flag to allow for completion in the middle of the line
-        set __%[1]s_comp_commandLine (commandline -c)
-    end
-    __%[1]s_debug "commandLine is: $__%[1]s_comp_commandLine"
-
-    set results (__%[1]s_perform_completion "$__%[1]s_comp_commandLine")
-    set --erase __%[1]s_comp_commandLine
+    set -l results (__%[1]s_perform_completion)
     __%[1]s_debug "Completion results: $results"
 
     if test -z "$results"
         __%[1]s_debug "No completion, probably due to a failure"
         # Might as well do file completion, in case it helps
-        set --global __%[1]s_comp_do_file_comp 1
         return 1
     end
 
-    set directive (string sub --start 2 $results[-1])
+    set -l directive (string sub --start 2 $results[-1])
     set --global __%[1]s_comp_results $results[1..-2]
 
     __%[1]s_debug "Completions are: $__%[1]s_comp_results"
     __%[1]s_debug "Directive is: $directive"
 
-    set shellCompDirectiveError %[4]d
-    set shellCompDirectiveNoSpace %[5]d
-    set shellCompDirectiveNoFileComp %[6]d
-    set shellCompDirectiveFilterFileExt %[7]d
-    set shellCompDirectiveFilterDirs %[8]d
+    set -l shellCompDirectiveError %[4]d
+    set -l shellCompDirectiveNoSpace %[5]d
+    set -l shellCompDirectiveNoFileComp %[6]d
+    set -l shellCompDirectiveFilterFileExt %[7]d
+    set -l shellCompDirectiveFilterDirs %[8]d
 
     if test -z "$directive"
         set directive 0
     end
 
-    set compErr (math (math --scale 0 $directive / $shellCompDirectiveError) %% 2)
+    set -l compErr (math (math --scale 0 $directive / $shellCompDirectiveError) %% 2)
     if test $compErr -eq 1
         __%[1]s_debug "Received error directive: aborting."
         # Might as well do file completion, in case it helps
-        set --global __%[1]s_comp_do_file_comp 1
         return 1
     end
 
-    set filefilter (math (math --scale 0 $directive / $shellCompDirectiveFilterFileExt) %% 2)
-    set dirfilter (math (math --scale 0 $directive / $shellCompDirectiveFilterDirs) %% 2)
+    set -l filefilter (math (math --scale 0 $directive / $shellCompDirectiveFilterFileExt) %% 2)
+    set -l dirfilter (math (math --scale 0 $directive / $shellCompDirectiveFilterDirs) %% 2)
     if test $filefilter -eq 1; or test $dirfilter -eq 1
         __%[1]s_debug "File extension filtering or directory filtering not supported"
         # Do full file completion instead
-        set --global __%[1]s_comp_do_file_comp 1
         return 1
     end
 
-    set nospace (math (math --scale 0 $directive / $shellCompDirectiveNoSpace) %% 2)
-    set nofiles (math (math --scale 0 $directive / $shellCompDirectiveNoFileComp) %% 2)
+    set -l nospace (math (math --scale 0 $directive / $shellCompDirectiveNoSpace) %% 2)
+    set -l nofiles (math (math --scale 0 $directive / $shellCompDirectiveNoFileComp) %% 2)
 
     __%[1]s_debug "nospace: $nospace, nofiles: $nofiles"
 
-    # Important not to quote the variable for count to work
-    set numComps (count $__%[1]s_comp_results)
-    __%[1]s_debug "numComps: $numComps"
-
-    if test $numComps -eq 1; and test $nospace -ne 0
-        # To support the "nospace" directive we trick the shell
-        # by outputting an extra, longer completion.
-        __%[1]s_debug "Adding second completion to perform nospace directive"
-        set --append __%[1]s_comp_results $__%[1]s_comp_results[1].
-    end
-
-    if test $numComps -eq 0; and test $nofiles -eq 0
-        __%[1]s_debug "Requesting file completion"
-        set --global __%[1]s_comp_do_file_comp 1
+    # If we want to prevent a space, or if file completion is NOT disabled,
+    # we need to count the number of valid completions.
+    # To do so, we will filter on prefix as the completions we have received
+    # may not already be filtered so as to allow fish to match on different
+    # criteria than the prefix.
+    if test $nospace -ne 0; or test $nofiles -eq 0
+        set -l prefix (commandline -t | string escape --style=regex)
+        __%[1]s_debug "prefix: $prefix"
+
+        set -l completions (string match -r -- "^$prefix.*" $__%[1]s_comp_results)
+        set --global __%[1]s_comp_results $completions
+        __%[1]s_debug "Filtered completions are: $__%[1]s_comp_results"
+
+        # Important not to quote the variable for count to work
+        set -l numComps (count $__%[1]s_comp_results)
+        __%[1]s_debug "numComps: $numComps"
+
+        if test $numComps -eq 1; and test $nospace -ne 0
+            # We must first split on \t to get rid of the descriptions to be
+            # able to check what the actual completion will be.
+            # We don't need descriptions anyway since there is only a single
+            # real completion which the shell will expand immediately.
+            set -l split (string split --max 1 \t $__%[1]s_comp_results[1])
+        
+            # Fish won't add a space if the completion ends with any
+            # of the following characters: @=/:.,
+            set -l lastChar (string sub -s -1 -- $split)
+            if not string match -r -q "[@=/:.,]" -- "$lastChar"                
+                # In other cases, to support the "nospace" directive we trick the shell
+                # by outputting an extra, longer completion.
+                __%[1]s_debug "Adding second completion to perform nospace directive"
+                set --global __%[1]s_comp_results $split[1] $split[1].
+                __%[1]s_debug "Completions are now: $__%[1]s_comp_results"
+            end
+        end
+
+        if test $numComps -eq 0; and test $nofiles -eq 0
+            # To be consistent with bash and zsh, we only trigger file
+            # completion when there are no other completions
+            __%[1]s_debug "Requesting file completion"
+            return 1
+        end
     end
 
-    # If we don't want file completion, we must return true even if there
-    # are no completions found.  This is because fish will perform the last
-    # completion command, even if its condition is false, if no other
-    # completion command was triggered
-    return (not set --query __%[1]s_comp_do_file_comp)
+    return 0
 end
 
 # Since Fish completions are only loaded once the user triggers them, we trigger them ourselves
 # so we can properly delete any completions provided by another script.
-# The space after the the program name is essential to trigger completion for the program
-# and not completion of the program name itself.
-complete --do-complete "%[2]s " > /dev/null 2>&1
-# Using '> /dev/null 2>&1' since '&>' is not supported in older versions of fish.
+# Only do this if the program can be found, or else fish may print some errors; besides,
+# the existing completions will only be loaded if the program can be found.
+if type -q "%[2]s"
+    # The space after the program name is essential to trigger completion for the program
+    # and not completion of the program name itself.
+    # Also, we use '> /dev/null 2>&1' since '&>' is not supported in older versions of fish.
+    complete --do-complete "%[2]s " > /dev/null 2>&1
+end
 
 # Remove any pre-existing completions for the program since we will be handling all of them.
 complete -c %[2]s -e
 
-# The order in which the below two lines are defined is very important so that __%[1]s_prepare_completions
-# is called first.  It is __%[1]s_prepare_completions that sets up the __%[1]s_comp_do_file_comp variable.
-#
-# This completion will be run second as complete commands are added FILO.
-# It triggers file completion choices when __%[1]s_comp_do_file_comp is set.
-complete -c %[2]s -n 'set --query __%[1]s_comp_do_file_comp'
-
-# This completion will be run first as complete commands are added FILO.
-# The call to __%[1]s_prepare_completions will setup both __%[1]s_comp_results and __%[1]s_comp_do_file_comp.
-# It provides the program's completion choices.
+# The call to __%[1]s_prepare_completions will setup __%[1]s_comp_results
+# which provides the program's completion choices.
 complete -c %[2]s -n '__%[1]s_prepare_completions' -f -a '$__%[1]s_comp_results'
 
 `, nameForVar, name, compCmd,
diff --git a/powershell_completions.go b/powershell_completions.go
index 383ad74..59234c0 100644
--- a/powershell_completions.go
+++ b/powershell_completions.go
@@ -140,19 +140,6 @@ Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock {
         $Space = ""
     }
 
-    if (($Directive -band $ShellCompDirectiveNoFileComp) -ne 0 ) {
-        __%[1]s_debug "ShellCompDirectiveNoFileComp is called"
-
-        if ($Values.Length -eq 0) {
-            # Just print an empty string here so the
-            # shell does not start to complete paths.
-            # We cannot use CompletionResult here because
-            # it does not accept an empty string as argument.
-            ""
-            return
-        }
-    }
-
     if ((($Directive -band $ShellCompDirectiveFilterFileExt) -ne 0 ) -or
        (($Directive -band $ShellCompDirectiveFilterDirs) -ne 0 ))  {
         __%[1]s_debug "ShellCompDirectiveFilterFileExt ShellCompDirectiveFilterDirs are not supported"
@@ -165,20 +152,33 @@ Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock {
         # filter the result
         $_.Name -like "$WordToComplete*"
 
-        # Join the flag back if we have a equal sign flag
+        # Join the flag back if we have an equal sign flag
         if ( $IsEqualFlag ) {
             __%[1]s_debug "Join the equal sign flag back to the completion value"
             $_.Name = $Flag + "=" + $_.Name
         }
     }
 
+    if (($Directive -band $ShellCompDirectiveNoFileComp) -ne 0 ) {
+        __%[1]s_debug "ShellCompDirectiveNoFileComp is called"
+
+        if ($Values.Length -eq 0) {
+            # Just print an empty string here so the
+            # shell does not start to complete paths.
+            # We cannot use CompletionResult here because
+            # it does not accept an empty string as argument.
+            ""
+            return
+        }
+    }
+
     # Get the current mode
     $Mode = (Get-PSReadLineKeyHandler | Where-Object {$_.Key -eq "Tab" }).Function
     __%[1]s_debug "Mode: $Mode"
 
     $Values | ForEach-Object {
 
-        # store temporay because switch will overwrite $_
+        # store temporary because switch will overwrite $_
         $comp = $_
 
         # PowerShell supports three different completion modes
diff --git a/zsh_completions.go b/zsh_completions.go
index 2e84028..1afec30 100644
--- a/zsh_completions.go
+++ b/zsh_completions.go
@@ -95,7 +95,7 @@ _%[1]s()
     local shellCompDirectiveFilterFileExt=%[6]d
     local shellCompDirectiveFilterDirs=%[7]d
 
-    local lastParam lastChar flagPrefix requestComp out directive compCount comp lastComp
+    local lastParam lastChar flagPrefix requestComp out directive comp lastComp noSpace
     local -a completions
 
     __%[1]s_debug "\n========= starting completion logic =========="
@@ -163,7 +163,6 @@ _%[1]s()
         return
     fi
 
-    compCount=0
     while IFS='\n' read -r comp; do
         if [ -n "$comp" ]; then
             # If requested, completions are returned with a description.
@@ -175,13 +174,17 @@ _%[1]s()
             local tab=$(printf '\t')
             comp=${comp//$tab/:}
 
-            ((compCount++))
             __%[1]s_debug "Adding completion: ${comp}"
             completions+=${comp}
             lastComp=$comp
         fi
     done < <(printf "%%s\n" "${out[@]}")
 
+    if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then
+        __%[1]s_debug "Activating nospace."
+        noSpace="-S ''"
+    fi
+
     if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then
         # File extension filtering
         local filteringCmd
@@ -208,25 +211,40 @@ _%[1]s()
             __%[1]s_debug "Listing directories in ."
         fi
 
+        local result
         _arguments '*:dirname:_files -/'" ${flagPrefix}"
+        result=$?
         if [ -n "$subdir" ]; then
             popd >/dev/null 2>&1
         fi
-    elif [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ] && [ ${compCount} -eq 1 ]; then
-        __%[1]s_debug "Activating nospace."
-        # We can use compadd here as there is no description when
-        # there is only one completion.
-        compadd -S '' "${lastComp}"
-    elif [ ${compCount} -eq 0 ]; then
-        if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then
-            __%[1]s_debug "deactivating file completion"
+        return $result
+    else
+        __%[1]s_debug "Calling _describe"
+        if eval _describe "completions" completions $flagPrefix $noSpace; then
+            __%[1]s_debug "_describe found some completions"
+
+            # Return the success of having called _describe
+            return 0
         else
-            # Perform file completion
-            __%[1]s_debug "activating file completion"
-            _arguments '*:filename:_files'" ${flagPrefix}"
+            __%[1]s_debug "_describe did not find completions."
+            __%[1]s_debug "Checking if we should do file completion."
+            if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then
+                __%[1]s_debug "deactivating file completion"
+
+                # We must return an error code here to let zsh know that there were no
+                # completions found by _describe; this is what will trigger other
+                # matching algorithms to attempt to find completions.
+                # For example zsh can match letters in the middle of words.
+                return 1
+            else
+                # Perform file completion
+                __%[1]s_debug "Activating file completion"
+
+                # We must return the result of this command, so it must be the
+                # last command, or else we must store its result to return it.
+                _arguments '*:filename:_files'" ${flagPrefix}"
+            fi
         fi
-    else
-        _describe "completions" completions $(echo $flagPrefix)
     fi
 }
 


Test patch:
diff --git a/.github/workflows/Test.yml b/.github/workflows/Test.yml
index 260948c..c34c2fb 100644
--- a/.github/workflows/Test.yml
+++ b/.github/workflows/Test.yml
@@ -77,7 +77,7 @@ jobs:
     - run: |
         export GOBIN=$HOME/go/bin
         curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | bash -s -- -b $GOBIN latest
-        go install github.com/kyoh86/richgo
-        go install github.com/mitchellh/gox
+        go install github.com/kyoh86/richgo@latest
+        go install github.com/mitchellh/gox@latest
 
     - run: PATH=$HOME/go/bin:$PATH make
diff --git a/cobra/cmd/init_test.go b/cobra/cmd/init_test.go
index 6d21ef7..e364f40 100644
--- a/cobra/cmd/init_test.go
+++ b/cobra/cmd/init_test.go
@@ -49,7 +49,7 @@ func TestGoldenInitCmd(t *testing.T) {
 			expectErr: true,
 		},
 		{
-			name:      "returns error when passing an relative path for project",
+			name:      "returns error when passing a relative path for project",
 			args:      []string{"github.com/spf13/testproject"},
 			pkgName:   "github.com/spf13/testproject",
 			expectErr: true,
diff --git a/command_test.go b/command_test.go
index 9640fc5..583cb02 100644
--- a/command_test.go
+++ b/command_test.go
@@ -42,6 +42,17 @@ func executeCommandC(root *Command, args ...string) (c *Command, output string,
 	return c, buf.String(), err
 }
 
+func executeCommandWithContextC(ctx context.Context, root *Command, args ...string) (c *Command, output string, err error) {
+	buf := new(bytes.Buffer)
+	root.SetOut(buf)
+	root.SetErr(buf)
+	root.SetArgs(args)
+
+	c, err = root.ExecuteContextC(ctx)
+
+	return c, buf.String(), err
+}
+
 func resetCommandLineFlagSet() {
 	pflag.CommandLine = pflag.NewFlagSet(os.Args[0], pflag.ExitOnError)
 }
@@ -178,6 +189,35 @@ func TestExecuteContext(t *testing.T) {
 	}
 }
 
+func TestExecuteContextC(t *testing.T) {
+	ctx := context.TODO()
+
+	ctxRun := func(cmd *Command, args []string) {
+		if cmd.Context() != ctx {
+			t.Errorf("Command %q must have context when called with ExecuteContext", cmd.Use)
+		}
+	}
+
+	rootCmd := &Command{Use: "root", Run: ctxRun, PreRun: ctxRun}
+	childCmd := &Command{Use: "child", Run: ctxRun, PreRun: ctxRun}
+	granchildCmd := &Command{Use: "grandchild", Run: ctxRun, PreRun: ctxRun}
+
+	childCmd.AddCommand(granchildCmd)
+	rootCmd.AddCommand(childCmd)
+
+	if _, _, err := executeCommandWithContextC(ctx, rootCmd, ""); err != nil {
+		t.Errorf("Root command must not fail: %+v", err)
+	}
+
+	if _, _, err := executeCommandWithContextC(ctx, rootCmd, "child"); err != nil {
+		t.Errorf("Subcommand must not fail: %+v", err)
+	}
+
+	if _, _, err := executeCommandWithContextC(ctx, rootCmd, "child", "grandchild"); err != nil {
+		t.Errorf("Command child must not fail: %+v", err)
+	}
+}
+
 func TestExecute_NoContext(t *testing.T) {
 	run := func(cmd *Command, args []string) {
 		if cmd.Context() != context.Background() {
diff --git a/completions_test.go b/completions_test.go
index 603c409..9bd6a41 100644
--- a/completions_test.go
+++ b/completions_test.go
@@ -2,6 +2,7 @@ package cobra
 
 import (
 	"bytes"
+	"context"
 	"strings"
 	"testing"
 )
@@ -1203,6 +1204,48 @@ func TestFlagDirFilterCompletionInGo(t *testing.T) {
 	}
 }
 
+func TestValidArgsFuncCmdContext(t *testing.T) {
+	validArgsFunc := func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
+		ctx := cmd.Context()
+
+		if ctx == nil {
+			t.Error("Received nil context in completion func")
+		} else if ctx.Value("testKey") != "123" {
+			t.Error("Received invalid context")
+		}
+
+		return nil, ShellCompDirectiveDefault
+	}
+
+	rootCmd := &Command{
+		Use: "root",
+		Run: emptyRun,
+	}
+	childCmd := &Command{
+		Use:               "childCmd",
+		ValidArgsFunction: validArgsFunc,
+		Run:               emptyRun,
+	}
+	rootCmd.AddCommand(childCmd)
+
+	//nolint:golint,staticcheck // We can safely use a basic type as key in tests.
+	ctx := context.WithValue(context.Background(), "testKey", "123")
+
+	// Test completing an empty string on the childCmd
+	_, output, err := executeCommandWithContextC(ctx, rootCmd, ShellCompNoDescRequestCmd, "childCmd", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected := strings.Join([]string{
+		":0",
+		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+}
+
 func TestValidArgsFuncSingleCmd(t *testing.T) {
 	rootCmd := &Command{
 		Use:               "root",
@@ -2158,3 +2201,96 @@ func TestCompleteCompletion(t *testing.T) {
 		}
 	}
 }
+
+func TestMultipleShorthandFlagCompletion(t *testing.T) {
+	rootCmd := &Command{
+		Use:       "root",
+		ValidArgs: []string{"foo", "bar"},
+		Run:       emptyRun,
+	}
+	f := rootCmd.Flags()
+	f.BoolP("short", "s", false, "short flag 1")
+	f.BoolP("short2", "d", false, "short flag 2")
+	f.StringP("short3", "f", "", "short flag 3")
+	_ = rootCmd.RegisterFlagCompletionFunc("short3", func(*Command, []string, string) ([]string, ShellCompDirective) {
+		return []string{"works"}, ShellCompDirectiveNoFileComp
+	})
+
+	// Test that a single shorthand flag works
+	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-s", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected := strings.Join([]string{
+		"foo",
+		"bar",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that multiple boolean shorthand flags work
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-sd", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"foo",
+		"bar",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that multiple boolean + string shorthand flags work
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-sdf", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"works",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that multiple boolean + string with equal sign shorthand flags work
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-sdf=")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"works",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test that multiple boolean + string with equal sign with value shorthand flags work
+	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-sdf=abc", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"foo",
+		"bar",
+		":4",
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+}