From 431584956f7ad4caba7f902a59ab8e4f74fbca74 Mon Sep 17 00:00:00 2001 From: Bob Wilson Date: Thu, 11 Dec 2025 14:10:03 -0800 Subject: [PATCH] Fix handling of visibility with nested OptionGroups. The visibility of OptionGroups is ignored if they are nested inside another OptionGroup. Instead of trying to filter groups when creating an ArgumentSet, use the OptionGroup's visibility to modify the arguments within the group. --- .../ArgumentVisibility.swift | 24 ++++++++++++ .../Parsable Properties/OptionGroup.swift | 3 ++ .../Parsable Types/ParsableArguments.swift | 3 -- .../HelpGenerationTests+GroupName.swift | 37 +++++++++++++++++++ 4 files changed, 64 insertions(+), 3 deletions(-) diff --git a/Sources/ArgumentParser/Parsable Properties/ArgumentVisibility.swift b/Sources/ArgumentParser/Parsable Properties/ArgumentVisibility.swift index 0eb4872d1..452cc6354 100644 --- a/Sources/ArgumentParser/Parsable Properties/ArgumentVisibility.swift +++ b/Sources/ArgumentParser/Parsable Properties/ArgumentVisibility.swift @@ -57,4 +57,28 @@ extension ArgumentVisibility { internal func isAtLeastAsVisible(as other: Self) -> Bool { self.base._comparableLevel >= other.base._comparableLevel } + + /// Reduce the visibility to a specified level if it is more restricted than the current value. + internal mutating func reduce(to: ArgumentVisibility) { + switch to.base { + case .default: + break // No effect + case .hidden: + if case .default = self.base { + self.base = .hidden + } + case .private: + self.base = .private + } + } +} + +extension ArgumentDefinition { + internal func reducingHelpVisibility(to visibility: ArgumentVisibility) + -> Self + { + var result = self + result.help.visibility.reduce(to: visibility) + return result + } } diff --git a/Sources/ArgumentParser/Parsable Properties/OptionGroup.swift b/Sources/ArgumentParser/Parsable Properties/OptionGroup.swift index 86ca04dff..0a0d82cc1 100644 --- a/Sources/ArgumentParser/Parsable Properties/OptionGroup.swift +++ b/Sources/ArgumentParser/Parsable Properties/OptionGroup.swift @@ -88,6 +88,9 @@ public struct OptionGroup: Decodable, ParsedWrapper { $0.help.parentTitle = title } } + args.content = args.content.map { arg in + arg.reducingHelpVisibility(to: visibility) + } return args }) self._visibility = visibility diff --git a/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift b/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift index 354d944be..9b5ebd617 100644 --- a/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift +++ b/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift @@ -316,9 +316,6 @@ extension ArgumentSet { guard let codingKey = child.label else { return nil } if let parsed = child.value as? ArgumentSetProvider { - guard parsed._visibility.isAtLeastAsVisible(as: visibility) - else { return nil } - let key = InputKey(name: codingKey, parent: parent) return parsed.argumentSet(for: key) } else { diff --git a/Tests/ArgumentParserUnitTests/HelpGenerationTests+GroupName.swift b/Tests/ArgumentParserUnitTests/HelpGenerationTests+GroupName.swift index 9128369b8..8ac5d396a 100644 --- a/Tests/ArgumentParserUnitTests/HelpGenerationTests+GroupName.swift +++ b/Tests/ArgumentParserUnitTests/HelpGenerationTests+GroupName.swift @@ -233,6 +233,43 @@ extension HelpGenerationTests { """) } + fileprivate struct NestedGroups: ParsableArguments { + @OptionGroup(visibility: .hidden) + var flagsAndOptions: FlagsAndOptions + + @OptionGroup(visibility: .private) + var argsAndFlags: ArgsAndFlags + } + + fileprivate struct NestedHiddenGroups: ParsableCommand { + @OptionGroup() + var nested: NestedGroups + } + + func testNestedHiddenGroups() { + AssertHelp( + .default, for: NestedHiddenGroups.self, + equals: """ + USAGE: nested-hidden-groups + + OPTIONS: + -h, --help Show help information. + + """) + + AssertHelp( + .hidden, for: NestedHiddenGroups.self, + equals: """ + USAGE: nested-hidden-groups [--experimental] --prefix + + OPTIONS: + --experimental example + --prefix example + -h, --help Show help information. + + """) + } + fileprivate struct ParentWithGroups: ParsableCommand { static var configuration: CommandConfiguration { .init(subcommands: [ChildWithGroups.self])