diff --git a/app.go b/app.go index af072e7699..7b3558ae9f 100644 --- a/app.go +++ b/app.go @@ -319,7 +319,14 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { // flag name as the value of the flag before it which is undesirable // note that we can only do this because the shell autocomplete function // always appends the completion flag at the end of the command - shellComplete, arguments := checkShellCompleteFlag(a, arguments) + shellComplete, completionDetected, arguments := checkShellCompleteFlag(a, arguments) + + // If the completion flag was present but shell completion is disabled + // (e.g., because "--" was on the command line), do not execute the + // action. The flag has already been stripped from arguments. + if completionDetected && !shellComplete { + return nil + } cCtx := NewContext(a, nil, &Context{Context: ctx}) cCtx.shellComplete = shellComplete diff --git a/help.go b/help.go index d27e8ce385..cad9ead81c 100644 --- a/help.go +++ b/help.go @@ -435,28 +435,34 @@ func checkHelp(cCtx *Context) bool { return found } -func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) { +func checkShellCompleteFlag(a *App, arguments []string) (bool, bool, []string) { if !a.EnableBashCompletion { - return false, arguments + return false, false, arguments } pos := len(arguments) - 1 lastArg := arguments[pos] if lastArg != "--generate-bash-completion" { - return false, arguments + return false, false, arguments } + // The completion flag was detected — strip it from arguments regardless + // of "--" presence, so that it never leaks into the action's arguments. + stripped := make([]string, 0, len(arguments)-1) + stripped = append(stripped, arguments[:pos]...) + stripped = append(stripped, arguments[pos+1:]...) + for _, arg := range arguments { // If arguments include "--", shell completion is disabled // because after "--" only positional arguments are accepted. // https://unix.stackexchange.com/a/11382 if arg == "--" { - return false, arguments + return false, true, stripped } } - return true, arguments[:pos] + return true, true, stripped } func checkCompletions(cCtx *Context) bool { diff --git a/help_test.go b/help_test.go index 8a856258ae..cad0355f06 100644 --- a/help_test.go +++ b/help_test.go @@ -1716,18 +1716,20 @@ GLOBAL OPTIONS: func Test_checkShellCompleteFlag(t *testing.T) { t.Parallel() tests := []struct { - name string - app *App - arguments []string - wantShellCompletion bool - wantArgs []string + name string + app *App + arguments []string + wantShellCompletion bool + wantCompletionDetected bool + wantArgs []string }{ { - name: "disable bash completion", - arguments: []string{"--generate-bash-completion"}, - app: &App{}, - wantShellCompletion: false, - wantArgs: []string{"--generate-bash-completion"}, + name: "disable bash completion", + arguments: []string{"--generate-bash-completion"}, + app: &App{}, + wantShellCompletion: false, + wantCompletionDetected: false, + wantArgs: []string{"--generate-bash-completion"}, }, { name: "--generate-bash-completion isn't used", @@ -1735,8 +1737,9 @@ func Test_checkShellCompleteFlag(t *testing.T) { app: &App{ EnableBashCompletion: true, }, - wantShellCompletion: false, - wantArgs: []string{"foo"}, + wantShellCompletion: false, + wantCompletionDetected: false, + wantArgs: []string{"foo"}, }, { name: "arguments include double dash", @@ -1744,8 +1747,9 @@ func Test_checkShellCompleteFlag(t *testing.T) { app: &App{ EnableBashCompletion: true, }, - wantShellCompletion: false, - wantArgs: []string{"--", "foo", "--generate-bash-completion"}, + wantShellCompletion: false, + wantCompletionDetected: true, + wantArgs: []string{"--", "foo"}, }, { name: "--generate-bash-completion", @@ -1753,8 +1757,9 @@ func Test_checkShellCompleteFlag(t *testing.T) { app: &App{ EnableBashCompletion: true, }, - wantShellCompletion: true, - wantArgs: []string{"foo"}, + wantShellCompletion: true, + wantCompletionDetected: true, + wantArgs: []string{"foo"}, }, } @@ -1762,11 +1767,15 @@ func Test_checkShellCompleteFlag(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() - shellCompletion, args := checkShellCompleteFlag(tt.app, tt.arguments) + shellCompletion, completionDetected, args := checkShellCompleteFlag(tt.app, tt.arguments) if tt.wantShellCompletion != shellCompletion { t.Errorf("Unexpected shell completion, got:\n%v\nexpected: %v", shellCompletion, tt.wantShellCompletion) } + if tt.wantCompletionDetected != completionDetected { + t.Errorf("Unexpected completion detected, got:\n%v\nexpected: %v", + completionDetected, tt.wantCompletionDetected) + } if !reflect.DeepEqual(tt.wantArgs, args) { t.Errorf("Unexpected arguments, got:\n%v\nexpected: %v", args, tt.wantArgs)