Repository: apple/swift-argument-parser (swift)

Issue:
Custom completion per shell (zsh, bash, or fish)
### Problem

Different shells use different syntaxes for their completions.

e.g., zsh's `_describe` accepts a different syntax than bash's `compgen`.

The `completion:` parameter for `@Argument` & for `@Option` is used for all shells.

To the best of my knowledge, Swift Argument Parser (SAP) doesn't inform Swift the shell for which completions are being requested.

It is thus difficult to output the correct values to properly configure completion in multiple shells.

### Versions

**ArgumentParser version:**
1.5.0

**Swift version:**
swift-driver version: 1.62.15 Apple Swift version 5.7.2 (swiftlang-5.7.2.135.5 clang-1400.0.29.51)
Target: x86_64-apple-macosx12.0

### Checklist
- [x] If possible, I've reproduced the issue using the `main` branch of this package
- [x] I've searched for [existing GitHub issues](https://github.com/apple/swift-argument-parser/issues)

### Steps to Reproduce
1. Look for some way to know what shell is requesting completions.
2. Fail to find some way to know what shell is requesting completions.
3. Profit!

### Expected behavior
Ability to easily output completions that are correct for the current shell.

### Actual behavior
Cannot easily output completions that are correct for the current shell.

### Solution

There are many ways to solve this, but the simplest way to do so without changing the existing `CompletionKind`s or the `completion:` parameter of `@Argument` or `@Option` seems to be for SAP to provide a singleton `CompletionShell` named `CompletionShell.requesting` (`requesting` for short) that can be used by code supplying an associated value for a `CompletionKind` to determine the shell for which completions are being requested.

For `.custom(:)`, `requesting ` must be available at runtime when a completion script requests completions. For the other current `CompletionKind`s, `requesting ` need only be available when a completion script is generated by using `--generate-completion-script <shell>`.

For `.custom(:)`, each completion script generated by using `--generate-completion-script <shell>` will export an environment variable `SAP_SHELL` = `<shell>` so SAP can use it to populate `requesting ` before it calls `completion` from `.custom(:)`.

```bash
# bash
export SAP_SHELL=bash
# fish
set -x SAP_SHELL fish
# zsh
export SAP_SHELL=zsh
```

For the other current `CompletionKind`s, the `*CompletionsGenerator.generateCompletionScript(type:)` function will populate `requesting ` with the correct value before calling the shell-specific `*CompletionsGenerator.generateCompletionScript(:)`. Any function called to supply the associated value for these `CompletionKind`s can read `requesting ` to determine the shell for which completions are being requested.

Pull request:
Add singleton to indicate which shell is requesting completion candidates.

A `CompletionShell` singleton named `CompletionShell.requesting` has been created that indicates which shell is requesting completion candidates.

The singleton is populated when a completion script is generated, so functions used to generate arguments for `CompletionKind` creation functions can read the singleton to be able to return completion candidates / shell commands tailored for the requesting shell.

For the `custom(:)` `CompletionKind` creation function, the singleton is populated at runtime (when a completion script requests completions from the Swift app after a user types tab while composing a command line to call the app).

The requesting shell is communicated to the Swift app via an environment variable named `SAP_SHELL`, which is exported by each of the generated completion scripts.

Resolve #672

### Checklist
- [x] I've added at least one test that validates that my change is working, if appropriate
- [x] I've followed the code style of the rest of the project
- [x] I've read the [Contribution Guidelines](https://github.com/apple/swift-argument-parser/blob/main/CONTRIBUTING.md)
- [x] I've updated the documentation if necessary

Patch:
diff --git a/Sources/ArgumentParser/Completions/BashCompletionsGenerator.swift b/Sources/ArgumentParser/Completions/BashCompletionsGenerator.swift
index cd7864b5c..8d5d8aed1 100644
--- a/Sources/ArgumentParser/Completions/BashCompletionsGenerator.swift
+++ b/Sources/ArgumentParser/Completions/BashCompletionsGenerator.swift
@@ -57,6 +57,7 @@ struct BashCompletionsGenerator {
     // that other command functions don't need.
     if isRootCommand {
       result += """
+        export \(CompletionShell.environmentVariableName)=bash
         cur="${COMP_WORDS[COMP_CWORD]}"
         prev="${COMP_WORDS[COMP_CWORD-1]}"
         COMPREPLY=()
diff --git a/Sources/ArgumentParser/Completions/CompletionsGenerator.swift b/Sources/ArgumentParser/Completions/CompletionsGenerator.swift
index bbfe0bb72..f233a8c95 100644
--- a/Sources/ArgumentParser/Completions/CompletionsGenerator.swift
+++ b/Sources/ArgumentParser/Completions/CompletionsGenerator.swift
@@ -41,6 +41,20 @@ public struct CompletionShell: RawRepresentable, Hashable, CaseIterable {
   public static var allCases: [CompletionShell] {
     [.zsh, .bash, .fish]
   }
+
+  /// While generating a shell completion script or while a Swift custom completion
+  /// function is executing to offer completions for a word from a command line
+  /// (e.g., when `customCompletion` from `@Option(completion: .custom(customCompletion))`
+  /// executes), an instance representing the shell for which completions will
+  /// be or are being requested, respectively. Otherwise `nil`.
+  public internal(set) static var requesting: CompletionShell?
+
+  /// The name of the environment variable whose value is the name of the shell
+  /// for which completions are being requested from a custom completion
+  /// handler.
+  ///
+  /// The environment variable is set in generated completion scripts.
+  static let environmentVariableName = "SAP_SHELL"
 }
 
 struct CompletionsGenerator {
@@ -69,6 +83,7 @@ struct CompletionsGenerator {
   
   /// Generates a Bash completion script for this generators shell and command..
   func generateCompletionScript() -> String {
+    CompletionShell.requesting = shell
     switch shell {
     case .zsh:
       return ZshCompletionsGenerator.generateCompletionScript(command)
diff --git a/Sources/ArgumentParser/Completions/FishCompletionsGenerator.swift b/Sources/ArgumentParser/Completions/FishCompletionsGenerator.swift
index 454ca2a9a..206b6ac4b 100644
--- a/Sources/ArgumentParser/Completions/FishCompletionsGenerator.swift
+++ b/Sources/ArgumentParser/Completions/FishCompletionsGenerator.swift
@@ -147,6 +147,7 @@ extension FishCompletionsGenerator {
     let preprocessorFunctionName = preprocessorFunctionName(commandName: commandName)
     return """
     function \(functionName)
+        set -gx \(CompletionShell.environmentVariableName) fish
         set -l currentCommands (\(preprocessorFunctionName) (commandline -opc))
         set -l expectedCommands (string split \"\(separator)\" $argv[1])
         set -l subcommands (string split \"\(separator)\" $argv[2])
diff --git a/Sources/ArgumentParser/Completions/ZshCompletionsGenerator.swift b/Sources/ArgumentParser/Completions/ZshCompletionsGenerator.swift
index 96143bd02..7cf57c0b9 100644
--- a/Sources/ArgumentParser/Completions/ZshCompletionsGenerator.swift
+++ b/Sources/ArgumentParser/Completions/ZshCompletionsGenerator.swift
@@ -83,7 +83,7 @@ struct ZshCompletionsGenerator {
     }
     
     let functionText = """
-      \(functionName)() {
+      \(functionName)() {\(isRootCommand ? "\n    export \(CompletionShell.environmentVariableName)=zsh" : "")
           integer ret=1
           local -a args
           args+=(
diff --git a/Sources/ArgumentParser/Documentation.docc/Articles/CustomizingCompletions.md b/Sources/ArgumentParser/Documentation.docc/Articles/CustomizingCompletions.md
index 0876e0141..336b95900 100644
--- a/Sources/ArgumentParser/Documentation.docc/Articles/CustomizingCompletions.md
+++ b/Sources/ArgumentParser/Documentation.docc/Articles/CustomizingCompletions.md
@@ -60,3 +60,52 @@ struct SwiftRun {
 ```
 
 In this example, when a user requests completions for the `--target` option, the completion script runs the `SwiftRun` command-line tool with a special syntax, calling the `listExecutables` function with an array of the arguments given so far.
+
+### Configuring Completion Candidates per Shell
+
+The shells supported for parameter completion all have different completion candidate formats,
+as well as their own different syntaxes and built-in commands.
+
+The `CompletionShell.requesting` singleton (of type `CompletionShell?`) can be read to determine
+which shell is requesting completion candidates when evaluating functions that either provide
+arguments to a `CompletionKind` creation function, or that are themselves arguments to a
+`CompletionKind` creation function. e.g.:
+
+```swift
+struct Tool {
+    @Option(completion: .shellCommand(generateCommandPerShell()))
+    var x: String?
+
+    @Option(completion: .custom(generateCompletionCandidatesPerShell))
+    var y: String?
+}
+
+/// Runs when a completion script is generated; results hardcoded into script.
+func generateCommandPerShell() -> String {
+    switch CompletionShell.requesting {
+    case CompletionShell.bash:
+        return "bash-specific script"
+    case CompletionShell.fish:
+        return "fish-specific script"
+    case CompletionShell.zsh:
+        return "zsh-specific script"
+    default:
+        // return a universal no-op for unknown shells
+        return ":"
+    }
+}
+
+/// Runs during completion while user is typing command line to use your tool
+func generateCompletionCandidatesPerShell(_ arguments: [String]) -> [String] {
+    switch CompletionShell.requesting {
+    case CompletionShell.bash:
+        return ["A:in:bash:syntax", "B:in:bash:syntax", "C:in:bash:syntax"]
+    case CompletionShell.fish:
+        return ["A:in:fish:syntax", "B:in:bash:syntax", "C:in:bash:syntax"]
+    case CompletionShell.zsh:
+        return ["A:in:zsh:syntax",  "B:in:zsh:syntax",  "C:in:zsh:syntax"]
+    default:
+        return []
+    }
+}
+```
diff --git a/Sources/ArgumentParser/Parsing/CommandParser.swift b/Sources/ArgumentParser/Parsing/CommandParser.swift
index c09220b34..29c66fa5b 100644
--- a/Sources/ArgumentParser/Parsing/CommandParser.swift
+++ b/Sources/ArgumentParser/Parsing/CommandParser.swift
@@ -9,6 +9,14 @@
 //
 //===----------------------------------------------------------------------===//
 
+#if swift(>=5.11)
+internal import class Foundation.ProcessInfo
+#elseif swift(>=5.10)
+import class Foundation.ProcessInfo
+#else
+@_implementationOnly import class Foundation.ProcessInfo
+#endif
+
 struct CommandError: Error {
   var commandStack: [ParsableCommand.Type]
   var parserError: ParserError
@@ -353,6 +361,10 @@ extension CommandParser {
       throw ParserError.invalidState
     }
     
+    if let completionShellName = ProcessInfo.processInfo.environment[CompletionShell.environmentVariableName] {
+      CompletionShell.requesting = CompletionShell(rawValue: completionShellName)
+    }
+
     // Parsing and retrieval successful! We don't want to continue with any
     // other parsing here, so after printing the result of the completion
     // function, exit with a success code.


Test patch:
diff --git a/Tests/ArgumentParserExampleTests/MathExampleTests.swift b/Tests/ArgumentParserExampleTests/MathExampleTests.swift
index ee677a6a0..744331e75 100644
--- a/Tests/ArgumentParserExampleTests/MathExampleTests.swift
+++ b/Tests/ArgumentParserExampleTests/MathExampleTests.swift
@@ -241,6 +241,7 @@ private let bashCompletionScriptText = """
 #!/bin/bash
 
 _math() {
+    export SAP_SHELL=bash
     cur="${COMP_WORDS[COMP_CWORD]}"
     prev="${COMP_WORDS[COMP_CWORD-1]}"
     COMPREPLY=()
@@ -395,6 +396,7 @@ _math_commandname=$words[1]
 typeset -A opt_args
 
 _math() {
+    export SAP_SHELL=zsh
     integer ret=1
     local -a args
     args+=(
@@ -583,6 +585,7 @@ function _swift_math_preprocessor
 end
 
 function _swift_math_using_command
+    set -gx SAP_SHELL fish
     set -l currentCommands (_swift_math_preprocessor (commandline -opc))
     set -l expectedCommands (string split \" \" $argv[1])
     set -l subcommands (string split \" \" $argv[2])
diff --git a/Tests/ArgumentParserUnitTests/CompletionScriptTests.swift b/Tests/ArgumentParserUnitTests/CompletionScriptTests.swift
index 62f793add..8f1fc0472 100644
--- a/Tests/ArgumentParserUnitTests/CompletionScriptTests.swift
+++ b/Tests/ArgumentParserUnitTests/CompletionScriptTests.swift
@@ -177,6 +177,7 @@ _base_test_commandname=$words[1]
 typeset -A opt_args
 
 _base-test() {
+    export SAP_SHELL=zsh
     integer ret=1
     local -a args
     args+=(
@@ -258,6 +259,7 @@ private let bashBaseCompletions = """
 #!/bin/bash
 
 _base_test() {
+    export SAP_SHELL=bash
     cur="${COMP_WORDS[COMP_CWORD]}"
     prev="${COMP_WORDS[COMP_CWORD-1]}"
     COMPREPLY=()
@@ -350,6 +352,7 @@ _escaped_command_commandname=$words[1]
 typeset -A opt_args
 
 _escaped-command() {
+    export SAP_SHELL=zsh
     integer ret=1
     local -a args
     args+=(
@@ -385,6 +388,7 @@ function _swift_base-test_preprocessor
 end
 
 function _swift_base-test_using_command
+    set -gx SAP_SHELL fish
     set -l currentCommands (_swift_base-test_preprocessor (commandline -opc))
     set -l expectedCommands (string split " " $argv[1])
     set -l subcommands (string split " " $argv[2])
@@ -486,6 +490,7 @@ _parent_commandname=$words[1]
 typeset -A opt_args
 
 _parent() {
+    export SAP_SHELL=zsh
     integer ret=1
     local -a args
     args+=(
@@ -509,6 +514,7 @@ let bashHiddenCompletion = """
 #!/bin/bash
 
 _parent() {
+    export SAP_SHELL=bash
     cur="${COMP_WORDS[COMP_CWORD]}"
     prev="${COMP_WORDS[COMP_CWORD-1]}"
     COMPREPLY=()
@@ -538,6 +544,7 @@ function _swift_parent_preprocessor
 end
 
 function _swift_parent_using_command
+    set -gx SAP_SHELL fish
     set -l currentCommands (_swift_parent_preprocessor (commandline -opc))
     set -l expectedCommands (string split " " $argv[1])
     set -l subcommands (string split " " $argv[2])
diff --git a/Tests/ArgumentParserUnitTests/RequestingCompletionScriptTests.swift b/Tests/ArgumentParserUnitTests/RequestingCompletionScriptTests.swift
new file mode 100644
index 000000000..024e4fc09
--- /dev/null
+++ b/Tests/ArgumentParserUnitTests/RequestingCompletionScriptTests.swift
@@ -0,0 +1,611 @@
+//===----------------------------------------------------------*- swift -*-===//
+//
+// This source file is part of the Swift Argument Parser open source project
+//
+// Copyright (c) 2020 Apple Inc. and the Swift project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See https://swift.org/LICENSE.txt for license information
+//
+//===----------------------------------------------------------------------===//
+
+import XCTest
+import ArgumentParserTestHelpers
+@testable import ArgumentParser
+
+private func candidates(prefix: String) -> [String] {
+  switch CompletionShell.requesting {
+  case CompletionShell.bash:
+    return ["\(prefix)1_bash", "\(prefix)2_bash", "\(prefix)3_bash"]
+  case CompletionShell.fish:
+    return ["\(prefix)1_fish", "\(prefix)2_fish", "\(prefix)3_fish"]
+  case CompletionShell.zsh:
+    return ["\(prefix)1_zsh", "\(prefix)2_zsh", "\(prefix)3_zsh"]
+  default:
+    return []
+  }
+}
+
+final class RequestingCompletionScriptTests: XCTestCase {
+}
+
+extension RequestingCompletionScriptTests {
+  struct Path: ExpressibleByArgument {
+    var path: String
+    
+    init?(argument: String) {
+      self.path = argument
+    }
+    
+    static var defaultCompletionKind: CompletionKind {
+      .file()
+    }
+  }
+    
+  enum Kind: String, ExpressibleByArgument, EnumerableFlag {
+    case one, two, three = "custom-three"
+  }
+
+  struct NestedArguments: ParsableArguments {
+    @Argument(completion: .custom { _ in candidates(prefix: "a") })
+    var nestedArgument: String
+  }
+  
+  struct Base: ParsableCommand {
+    static let configuration = CommandConfiguration(
+      commandName: "base-test",
+      subcommands: [SubCommand.self]
+    )
+
+    @Option(help: "The user's name.") var name: String
+    @Option() var kind: Kind
+    @Option(completion: .list(candidates(prefix: "b"))) var otherKind: Kind
+    
+    @Option() var path1: Path
+    @Option() var path2: Path?
+    @Option(completion: .list(candidates(prefix: "c"))) var path3: Path
+    
+    @Flag(help: .hidden) var verbose = false
+    @Flag var allowedKinds: [Kind] = []
+    @Flag var kindCounter: Int
+    
+    @Option() var rep1: [String]
+    @Option(name: [.short, .long]) var rep2: [String]
+    
+    @Argument(completion: .custom { _ in candidates(prefix: "d") }) var argument: String
+    @OptionGroup var nested: NestedArguments
+    
+    struct SubCommand: ParsableCommand {
+      static let configuration = CommandConfiguration(
+        commandName: "sub-command"
+      )
+    }
+  }
+
+  func testBase_Zsh() throws {
+    let script1 = try CompletionsGenerator(command: Base.self, shell: .zsh)
+          .generateCompletionScript()
+    XCTAssertEqual(zshRequestingBaseCompletions, script1)
+    
+    let script2 = try CompletionsGenerator(command: Base.self, shellName: "zsh")
+          .generateCompletionScript()
+    XCTAssertEqual(zshRequestingBaseCompletions, script2)
+    
+    let script3 = Base.completionScript(for: .zsh)
+    XCTAssertEqual(zshRequestingBaseCompletions, script3)
+  }
+
+  func testBase_Bash() throws {
+    let script1 = try CompletionsGenerator(command: Base.self, shell: .bash)
+          .generateCompletionScript()
+    XCTAssertEqual(bashRequestingBaseCompletions, script1)
+    
+    let script2 = try CompletionsGenerator(command: Base.self, shellName: "bash")
+          .generateCompletionScript()
+    XCTAssertEqual(bashRequestingBaseCompletions, script2)
+    
+    let script3 = Base.completionScript(for: .bash)
+    XCTAssertEqual(bashRequestingBaseCompletions, script3)
+  }
+
+  func testBase_Fish() throws {
+    let script1 = try CompletionsGenerator(command: Base.self, shell: .fish)
+          .generateCompletionScript()
+    XCTAssertEqual(fishRequestingBaseCompletions, script1)
+    
+    let script2 = try CompletionsGenerator(command: Base.self, shellName: "fish")
+          .generateCompletionScript()
+    XCTAssertEqual(fishRequestingBaseCompletions, script2)
+    
+    let script3 = Base.completionScript(for: .fish)
+    XCTAssertEqual(fishRequestingBaseCompletions, script3)
+  }
+}
+
+extension RequestingCompletionScriptTests {
+  struct Custom: ParsableCommand {
+    @Option(name: .shortAndLong, completion: .custom { _ in candidates(prefix: "e") })
+    var one: String
+
+    @Argument(completion: .custom { _ in candidates(prefix: "f") })
+    var two: String
+
+    @Option(name: .customShort("z"), completion: .custom { _ in candidates(prefix: "g") })
+    var three: String
+    
+    @OptionGroup var nested: NestedArguments
+    
+    struct NestedArguments: ParsableArguments {
+      @Argument(completion: .custom { _ in candidates(prefix: "h") })
+      var four: String
+    }
+  }
+  
+  func verifyCustomOutput(
+    _ arg: String,
+    forShell shell: String,
+    expectedOutputPrefix prefix: String,
+    file: StaticString = #filePath,
+    line: UInt = #line
+  ) throws {
+    do {
+      setenv("SAP_SHELL", shell, 1)
+      defer {
+        unsetenv("SAP_SHELL")
+      }
+      _ = try Custom.parse(["---completion", "--", arg])
+      XCTFail("Didn't error as expected", file: (file), line: line)
+    } catch let error as CommandError {
+      guard case .completionScriptCustomResponse(let output) = error.parserError else {
+        throw error
+      }
+      XCTAssertEqual(
+        prefix.isEmpty
+        ? ""
+        : "\(prefix)1_\(shell)\n\(prefix)2_\(shell)\n\(prefix)3_\(shell)",
+        output,
+        file: (file),
+        line: line
+      )
+    }
+  }
+  
+  func testCustomCompletions(forShell shell: String) throws {
+    try verifyCustomOutput("-o", forShell: shell, expectedOutputPrefix: "e")
+    try verifyCustomOutput("--one", forShell: shell, expectedOutputPrefix: "e")
+    try verifyCustomOutput("two", forShell: shell, expectedOutputPrefix: "f")
+    try verifyCustomOutput("-z", forShell: shell, expectedOutputPrefix: "g")
+    try verifyCustomOutput("nested.four", forShell: shell, expectedOutputPrefix: "h")
+    
+    XCTAssertThrowsError(try verifyCustomOutput("--bad", forShell: shell, expectedOutputPrefix: ""))
+    XCTAssertThrowsError(try verifyCustomOutput("four", forShell: shell, expectedOutputPrefix: ""))
+  }
+
+  func testBashCustomCompletions() throws {
+    try testCustomCompletions(forShell: "bash")
+  }
+
+  func testFishCustomCompletions() throws {
+    try testCustomCompletions(forShell: "fish")
+  }
+
+  func testZshCustomCompletions() throws {
+    try testCustomCompletions(forShell: "zsh")
+  }
+}
+
+extension RequestingCompletionScriptTests {
+  struct EscapedCommand: ParsableCommand {
+    @Option(help: #"Escaped chars: '[]\."#)
+    var one: String
+    
+    @Argument(completion: .custom { _ in candidates(prefix: "i") })
+    var two: String
+  }
+
+  func testEscaped_Zsh() throws {
+    XCTAssertEqual(zshRequestingEscapedCompletion, EscapedCommand.completionScript(for: .zsh))
+  }
+}
+
+let zshRequestingBaseCompletions = """
+#compdef base-test
+local context state state_descr line
+_base_test_commandname=$words[1]
+typeset -A opt_args
+
+_base-test() {
+    export SAP_SHELL=zsh
+    integer ret=1
+    local -a args
+    args+=(
+        '--name[The user'"'"'s name.]:name:'
+        '--kind:kind:(one two custom-three)'
+        '--other-kind:other-kind:(b1_zsh b2_zsh b3_zsh)'
+        '--path1:path1:_files'
+        '--path2:path2:_files'
+        '--path3:path3:(c1_zsh c2_zsh c3_zsh)'
+        '--one'
+        '--two'
+        '--three'
+        '*--kind-counter'
+        '*--rep1:rep1:'
+        '*'{-r,--rep2}':rep2:'
+        ':argument:{_custom_completion $_base_test_commandname ---completion  -- argument $words}'
+        ':nested-argument:{_custom_completion $_base_test_commandname ---completion  -- nested.nestedArgument $words}'
+        '(-h --help)'{-h,--help}'[Show help information.]'
+        '(-): :->command'
+        '(-)*:: :->arg'
+    )
+    _arguments -w -s -S $args[@] && ret=0
+    case $state in
+        (command)
+            local subcommands
+            subcommands=(
+                'sub-command:'
+                'help:Show subcommand help information.'
+            )
+            _describe "subcommand" subcommands
+            ;;
+        (arg)
+            case ${words[1]} in
+                (sub-command)
+                    _base-test_sub-command
+                    ;;
+                (help)
+                    _base-test_help
+                    ;;
+            esac
+            ;;
+    esac
+
+    return ret
+}
+
+_base-test_sub-command() {
+    integer ret=1
+    local -a args
+    args+=(
+        '(-h --help)'{-h,--help}'[Show help information.]'
+    )
+    _arguments -w -s -S $args[@] && ret=0
+
+    return ret
+}
+
+_base-test_help() {
+    integer ret=1
+    local -a args
+    args+=(
+        ':subcommands:'
+    )
+    _arguments -w -s -S $args[@] && ret=0
+
+    return ret
+}
+
+
+_custom_completion() {
+    local completions=("${(@f)$($*)}")
+    _describe '' completions
+}
+
+_base-test
+"""
+
+let bashRequestingBaseCompletions = """
+#!/bin/bash
+
+_base_test() {
+    export SAP_SHELL=bash
+    cur="${COMP_WORDS[COMP_CWORD]}"
+    prev="${COMP_WORDS[COMP_CWORD-1]}"
+    COMPREPLY=()
+    opts="--name --kind --other-kind --path1 --path2 --path3 --one --two --three --kind-counter --rep1 -r --rep2 -h --help sub-command help"
+    opts="$opts $("${COMP_WORDS[0]}" ---completion  -- argument "${COMP_WORDS[@]}")"
+    opts="$opts $("${COMP_WORDS[0]}" ---completion  -- nested.nestedArgument "${COMP_WORDS[@]}")"
+    if [[ $COMP_CWORD == "1" ]]; then
+        COMPREPLY=( $(compgen -W "$opts" -- "$cur") )
+        return
+    fi
+    case $prev in
+        --name)
+
+            return
+        ;;
+        --kind)
+            COMPREPLY=( $(compgen -W "one two custom-three" -- "$cur") )
+            return
+        ;;
+        --other-kind)
+            COMPREPLY=( $(compgen -W "b1_bash b2_bash b3_bash" -- "$cur") )
+            return
+        ;;
+        --path1)
+            if declare -F _filedir >/dev/null; then
+              _filedir
+            else
+              COMPREPLY=( $(compgen -f -- "$cur") )
+            fi
+            return
+        ;;
+        --path2)
+            if declare -F _filedir >/dev/null; then
+              _filedir
+            else
+              COMPREPLY=( $(compgen -f -- "$cur") )
+            fi
+            return
+        ;;
+        --path3)
+            COMPREPLY=( $(compgen -W "c1_bash c2_bash c3_bash" -- "$cur") )
+            return
+        ;;
+        --rep1)
+
+            return
+        ;;
+        -r|--rep2)
+
+            return
+        ;;
+    esac
+    case ${COMP_WORDS[1]} in
+        (sub-command)
+            _base_test_sub-command 2
+            return
+            ;;
+        (help)
+            _base_test_help 2
+            return
+            ;;
+    esac
+    COMPREPLY=( $(compgen -W "$opts" -- "$cur") )
+}
+_base_test_sub_command() {
+    opts="-h --help"
+    if [[ $COMP_CWORD == "$1" ]]; then
+        COMPREPLY=( $(compgen -W "$opts" -- "$cur") )
+        return
+    fi
+    COMPREPLY=( $(compgen -W "$opts" -- "$cur") )
+}
+_base_test_help() {
+    opts=""
+    if [[ $COMP_CWORD == "$1" ]]; then
+        COMPREPLY=( $(compgen -W "$opts" -- "$cur") )
+        return
+    fi
+    COMPREPLY=( $(compgen -W "$opts" -- "$cur") )
+}
+
+
+complete -F _base_test base-test
+"""
+
+let zshRequestingEscapedCompletion = """
+#compdef escaped-command
+local context state state_descr line
+_escaped_command_commandname=$words[1]
+typeset -A opt_args
+
+_escaped-command() {
+    export SAP_SHELL=zsh
+    integer ret=1
+    local -a args
+    args+=(
+        '--one[Escaped chars: '"'"'\\[\\]\\\\.]:one:'
+        ':two:{_custom_completion $_escaped_command_commandname ---completion  -- two $words}'
+        '(-h --help)'{-h,--help}'[Show help information.]'
+    )
+    _arguments -w -s -S $args[@] && ret=0
+
+    return ret
+}
+
+
+_custom_completion() {
+    local completions=("${(@f)$($*)}")
+    _describe '' completions
+}
+
+_escaped-command
+"""
+
+let fishRequestingBaseCompletions = """
+# A function which filters options which starts with "-" from $argv.
+function _swift_base-test_preprocessor
+    set -l results
+    for i in (seq (count $argv))
+        switch (echo $argv[$i] | string sub -l 1)
+            case '-'
+            case '*'
+                echo $argv[$i]
+        end
+    end
+end
+
+function _swift_base-test_using_command
+    set -gx SAP_SHELL fish
+    set -l currentCommands (_swift_base-test_preprocessor (commandline -opc))
+    set -l expectedCommands (string split " " $argv[1])
+    set -l subcommands (string split " " $argv[2])
+    if [ (count $currentCommands) -ge (count $expectedCommands) ]
+        for i in (seq (count $expectedCommands))
+            if [ $currentCommands[$i] != $expectedCommands[$i] ]
+                return 1
+            end
+        end
+        if [ (count $currentCommands) -eq (count $expectedCommands) ]
+            return 0
+        end
+        if [ (count $subcommands) -gt 1 ]
+            for i in (seq (count $subcommands))
+                if [ $currentCommands[(math (count $expectedCommands) + 1)] = $subcommands[$i] ]
+                    return 1
+                end
+            end
+        end
+        return 0
+    end
+    return 1
+end
+
+complete -c base-test -n '_swift_base-test_using_command "base-test sub-command"' -s h -l help -d 'Show help information.'
+complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command help"' -l name -d 'The user\\'s name.'
+complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command help"' -l kind -r -f -k -a 'one two custom-three'
+complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command help"' -l other-kind -r -f -k -a 'b1_fish b2_fish b3_fish'
+complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command help"' -l path1 -r -f -a '(for i in *.{}; echo $i;end)'
+complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command help"' -l path2 -r -f -a '(for i in *.{}; echo $i;end)'
+complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command help"' -l path3 -r -f -k -a 'c1_fish c2_fish c3_fish'
+complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command help"' -l one
+complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command help"' -l two
+complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command help"' -l three
+complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command help"' -l kind-counter
+complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command help"' -l rep1
+complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command help"' -s r -l rep2
+complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command help"' -r -f -a '(command base-test ---completion  -- argument (commandline -opc)[1..-1])'
+complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command help"' -r -f -a '(command base-test ---completion  -- nested.nestedArgument (commandline -opc)[1..-1])'
+complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command help"' -s h -l help -d 'Show help information.'
+complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command help"' -f -a 'sub-command' -d ''
+complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command help"' -f -a 'help' -d 'Show subcommand help information.'
+"""
+
+// MARK: - Test Hidden Subcommand
+struct RequestingParent: ParsableCommand {
+    static let configuration = CommandConfiguration(subcommands: [RequestingHiddenChild.self])
+}
+
+struct RequestingHiddenChild: ParsableCommand {
+    static let configuration = CommandConfiguration(shouldDisplay: false)
+}
+
+extension RequestingCompletionScriptTests {
+  func testHiddenSubcommand_Zsh() throws {
+    let script1 = try CompletionsGenerator(command: Parent.self, shell: .zsh)
+          .generateCompletionScript()
+    XCTAssertEqual(zshRequestingHiddenCompletion, script1)
+
+    let script2 = try CompletionsGenerator(command: Parent.self, shellName: "zsh")
+          .generateCompletionScript()
+    XCTAssertEqual(zshRequestingHiddenCompletion, script2)
+
+    let script3 = Parent.completionScript(for: .zsh)
+    XCTAssertEqual(zshRequestingHiddenCompletion, script3)
+  }
+
+  func testHiddenSubcommand_Bash() throws {
+    let script1 = try CompletionsGenerator(command: Parent.self, shell: .bash)
+          .generateCompletionScript()
+    XCTAssertEqual(bashRequestingHiddenCompletion, script1)
+
+    let script2 = try CompletionsGenerator(command: Parent.self, shellName: "bash")
+          .generateCompletionScript()
+    XCTAssertEqual(bashRequestingHiddenCompletion, script2)
+
+    let script3 = Parent.completionScript(for: .bash)
+    XCTAssertEqual(bashRequestingHiddenCompletion, script3)
+  }
+
+  func testHiddenSubcommand_Fish() throws {
+    let script1 = try CompletionsGenerator(command: Parent.self, shell: .fish)
+          .generateCompletionScript()
+    XCTAssertEqual(fishRequestingHiddenCompletion, script1)
+
+    let script2 = try CompletionsGenerator(command: Parent.self, shellName: "fish")
+          .generateCompletionScript()
+    XCTAssertEqual(fishRequestingHiddenCompletion, script2)
+
+    let script3 = Parent.completionScript(for: .fish)
+    XCTAssertEqual(fishRequestingHiddenCompletion, script3)
+  }
+}
+
+let zshRequestingHiddenCompletion = """
+#compdef parent
+local context state state_descr line
+_parent_commandname=$words[1]
+typeset -A opt_args
+
+_parent() {
+    export SAP_SHELL=zsh
+    integer ret=1
+    local -a args
+    args+=(
+        '(-h --help)'{-h,--help}'[Show help information.]'
+    )
+    _arguments -w -s -S $args[@] && ret=0
+
+    return ret
+}
+
+
+_custom_completion() {
+    local completions=("${(@f)$($*)}")
+    _describe '' completions
+}
+
+_parent
+"""
+
+let bashRequestingHiddenCompletion = """
+#!/bin/bash
+
+_parent() {
+    export SAP_SHELL=bash
+    cur="${COMP_WORDS[COMP_CWORD]}"
+    prev="${COMP_WORDS[COMP_CWORD-1]}"
+    COMPREPLY=()
+    opts="-h --help"
+    if [[ $COMP_CWORD == "1" ]]; then
+        COMPREPLY=( $(compgen -W "$opts" -- "$cur") )
+        return
+    fi
+    COMPREPLY=( $(compgen -W "$opts" -- "$cur") )
+}
+
+
+complete -F _parent parent
+"""
+
+let fishRequestingHiddenCompletion = """
+# A function which filters options which starts with "-" from $argv.
+function _swift_parent_preprocessor
+    set -l results
+    for i in (seq (count $argv))
+        switch (echo $argv[$i] | string sub -l 1)
+            case '-'
+            case '*'
+                echo $argv[$i]
+        end
+    end
+end
+
+function _swift_parent_using_command
+    set -gx SAP_SHELL fish
+    set -l currentCommands (_swift_parent_preprocessor (commandline -opc))
+    set -l expectedCommands (string split " " $argv[1])
+    set -l subcommands (string split " " $argv[2])
+    if [ (count $currentCommands) -ge (count $expectedCommands) ]
+        for i in (seq (count $expectedCommands))
+            if [ $currentCommands[$i] != $expectedCommands[$i] ]
+                return 1
+            end
+        end
+        if [ (count $currentCommands) -eq (count $expectedCommands) ]
+            return 0
+        end
+        if [ (count $subcommands) -gt 1 ]
+            for i in (seq (count $subcommands))
+                if [ $currentCommands[(math (count $expectedCommands) + 1)] = $subcommands[$i] ]
+                    return 1
+                end
+            end
+        end
+        return 0
+    end
+    return 1
+end
+
+complete -c parent -n '_swift_parent_using_command \"parent\"' -s h -l help -d 'Show help information.'
+"""