diff --git a/Sources/ArgumentParser/Completions/BashCompletionsGenerator.swift b/Sources/ArgumentParser/Completions/BashCompletionsGenerator.swift index 753ebcf51..1fea1b73e 100644 --- a/Sources/ArgumentParser/Completions/BashCompletionsGenerator.swift +++ b/Sources/ArgumentParser/Completions/BashCompletionsGenerator.swift @@ -132,7 +132,7 @@ struct BashCompletionsGenerator { /// /// These consist of completions that are defined as `.list` or `.custom`. fileprivate static func generateArgumentCompletions(_ commands: [ParsableCommand.Type]) -> [String] { - ArgumentSet(commands.last!, visibility: .default, parent: .root) + ArgumentSet(commands.last!, visibility: .default, parent: nil) .compactMap { arg -> String? in guard arg.isPositional else { return nil } @@ -159,7 +159,7 @@ struct BashCompletionsGenerator { /// Returns the case-matching statements for supplying completions after an option or flag. fileprivate static func generateOptionHandlers(_ commands: [ParsableCommand.Type]) -> String { - ArgumentSet(commands.last!, visibility: .default, parent: .root) + ArgumentSet(commands.last!, visibility: .default, parent: nil) .compactMap { arg -> String? in let words = arg.bashCompletionWords() if words.isEmpty { return nil } diff --git a/Sources/ArgumentParser/Parsable Properties/Flag.swift b/Sources/ArgumentParser/Parsable Properties/Flag.swift index 334df8874..74a97f795 100644 --- a/Sources/ArgumentParser/Parsable Properties/Flag.swift +++ b/Sources/ArgumentParser/Parsable Properties/Flag.swift @@ -396,7 +396,7 @@ extension Flag where Value: EnumerableFlag { // flag, the default value to show to the user is the `--value-name` // flag that a user would provide on the command line, not a Swift value. let defaultValueFlag = initial.flatMap { value -> String? in - let defaultKey = InputKey(name: String(describing: value), parent: .key(key)) + let defaultKey = InputKey(name: String(describing: value), parent: key) let defaultNames = Value.name(for: value).makeNames(defaultKey) return defaultNames.first?.synopsisString } @@ -405,7 +405,7 @@ extension Flag where Value: EnumerableFlag { let hasCustomCaseHelp = caseHelps.contains(where: { $0 != nil }) let args = Value.allCases.enumerated().map { (i, value) -> ArgumentDefinition in - let caseKey = InputKey(name: String(describing: value), parent: .key(key)) + let caseKey = InputKey(name: String(describing: value), parent: key) let name = Value.name(for: value) let helpForCase = caseHelps[i] ?? help @@ -519,7 +519,7 @@ extension Flag { let hasCustomCaseHelp = caseHelps.contains(where: { $0 != nil }) let args = Element.allCases.enumerated().map { (i, value) -> ArgumentDefinition in - let caseKey = InputKey(name: String(describing: value), parent: .key(parentKey)) + let caseKey = InputKey(name: String(describing: value), parent: parentKey) let name = Element.name(for: value) let helpForCase = hasCustomCaseHelp ? (caseHelps[i] ?? help) : help @@ -552,7 +552,7 @@ extension Flag { let hasCustomCaseHelp = caseHelps.contains(where: { $0 != nil }) let args = Element.allCases.enumerated().map { (i, value) -> ArgumentDefinition in - let caseKey = InputKey(name: String(describing: value), parent: .key(parentKey)) + let caseKey = InputKey(name: String(describing: value), parent: parentKey) let name = Element.name(for: value) let helpForCase = hasCustomCaseHelp ? (caseHelps[i] ?? help) : help let help = ArgumentDefinition.Help( diff --git a/Sources/ArgumentParser/Parsable Properties/NameSpecification.swift b/Sources/ArgumentParser/Parsable Properties/NameSpecification.swift index bfdd48c4a..a71d9df6a 100644 --- a/Sources/ArgumentParser/Parsable Properties/NameSpecification.swift +++ b/Sources/ArgumentParser/Parsable Properties/NameSpecification.swift @@ -167,7 +167,7 @@ extension FlagInversion { case .short, .customShort: return includingShort ? element.name(for: key) : nil case .long: - let modifiedKey = key.with(newName: key.name.addingIntercappedPrefix(prefix)) + let modifiedKey = InputKey(name: key.name.addingIntercappedPrefix(prefix), parent: key) return element.name(for: modifiedKey) case .customLong(let name, let withSingleDash): let modifiedName = name.addingPrefixWithAutodetectedStyle(prefix) diff --git a/Sources/ArgumentParser/Parsable Properties/OptionGroup.swift b/Sources/ArgumentParser/Parsable Properties/OptionGroup.swift index b74865b62..13192f8ed 100644 --- a/Sources/ArgumentParser/Parsable Properties/OptionGroup.swift +++ b/Sources/ArgumentParser/Parsable Properties/OptionGroup.swift @@ -78,7 +78,7 @@ public struct OptionGroup: Decodable, ParsedWrapper { visibility: ArgumentVisibility = .default ) { self.init(_parsedValue: .init { parentKey in - var args = ArgumentSet(Value.self, visibility: .private, parent: .key(parentKey)) + var args = ArgumentSet(Value.self, visibility: .private, parent: parentKey) args.content.withEach { $0.help.parentTitle = title } diff --git a/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift b/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift index b2e572a6e..d85283c37 100644 --- a/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift +++ b/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift @@ -248,7 +248,7 @@ extension ArgumentSetProvider { } extension ArgumentSet { - init(_ type: ParsableArguments.Type, visibility: ArgumentVisibility, parent: InputKey.Parent) { + init(_ type: ParsableArguments.Type, visibility: ArgumentVisibility, parent: InputKey?) { #if DEBUG do { try type._validate(parent: parent) diff --git a/Sources/ArgumentParser/Parsable Types/ParsableArgumentsValidation.swift b/Sources/ArgumentParser/Parsable Types/ParsableArgumentsValidation.swift index f633d91f7..c03e9d3af 100644 --- a/Sources/ArgumentParser/Parsable Types/ParsableArgumentsValidation.swift +++ b/Sources/ArgumentParser/Parsable Types/ParsableArgumentsValidation.swift @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// fileprivate protocol ParsableArgumentsValidator { - static func validate(_ type: ParsableArguments.Type, parent: InputKey.Parent) -> ParsableArgumentsValidatorError? + static func validate(_ type: ParsableArguments.Type, parent: InputKey?) -> ParsableArgumentsValidatorError? } enum ValidatorErrorKind { @@ -37,7 +37,7 @@ struct ParsableArgumentsValidationError: Error, CustomStringConvertible { } extension ParsableArguments { - static func _validate(parent: InputKey.Parent) throws { + static func _validate(parent: InputKey?) throws { let validators: [ParsableArgumentsValidator.Type] = [ PositionalArgumentsValidator.self, ParsableArgumentsCodingKeyValidator.self, @@ -80,7 +80,7 @@ struct PositionalArgumentsValidator: ParsableArgumentsValidator { var kind: ValidatorErrorKind { .failure } } - static func validate(_ type: ParsableArguments.Type, parent: InputKey.Parent) -> ParsableArgumentsValidatorError? { + static func validate(_ type: ParsableArguments.Type, parent: InputKey?) -> ParsableArgumentsValidatorError? { let sets: [ArgumentSet] = Mirror(reflecting: type.init()) .children .compactMap { child in @@ -190,7 +190,7 @@ struct ParsableArgumentsCodingKeyValidator: ParsableArgumentsValidator { } } - static func validate(_ type: ParsableArguments.Type, parent: InputKey.Parent) -> ParsableArgumentsValidatorError? { + static func validate(_ type: ParsableArguments.Type, parent: InputKey?) -> ParsableArgumentsValidatorError? { let argumentKeys: [InputKey] = Mirror(reflecting: type.init()) .children .compactMap { child in @@ -235,7 +235,7 @@ struct ParsableArgumentsUniqueNamesValidator: ParsableArgumentsValidator { var kind: ValidatorErrorKind { .failure } } - static func validate(_ type: ParsableArguments.Type, parent: InputKey.Parent) -> ParsableArgumentsValidatorError? { + static func validate(_ type: ParsableArguments.Type, parent: InputKey?) -> ParsableArgumentsValidatorError? { let argSets: [ArgumentSet] = Mirror(reflecting: type.init()) .children .compactMap { child in @@ -283,7 +283,7 @@ struct NonsenseFlagsValidator: ParsableArgumentsValidator { var kind: ValidatorErrorKind { .warning } } - static func validate(_ type: ParsableArguments.Type, parent: InputKey.Parent) -> ParsableArgumentsValidatorError? { + static func validate(_ type: ParsableArguments.Type, parent: InputKey?) -> ParsableArgumentsValidatorError? { let argSets: [ArgumentSet] = Mirror(reflecting: type.init()) .children .compactMap { child in diff --git a/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift b/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift index 288ea7f6e..af835a007 100644 --- a/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift +++ b/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift @@ -166,7 +166,7 @@ extension ParsableCommand { /// `true` if this command contains any array arguments that are declared /// with `.unconditionalRemaining`. internal static var includesUnconditionalArguments: Bool { - ArgumentSet(self, visibility: .private, parent: .root).contains(where: { + ArgumentSet(self, visibility: .private, parent: nil).contains(where: { $0.isRepeatingPositional && $0.parsingStrategy == .allRemainingInput }) } diff --git a/Sources/ArgumentParser/Parsing/ArgumentDefinition.swift b/Sources/ArgumentParser/Parsing/ArgumentDefinition.swift index bdbda9a26..d1036b39b 100644 --- a/Sources/ArgumentParser/Parsing/ArgumentDefinition.swift +++ b/Sources/ArgumentParser/Parsing/ArgumentDefinition.swift @@ -217,7 +217,7 @@ extension ArgumentDefinition { /// /// This initializer is used for any property defined on a `ParsableArguments` /// type that isn't decorated with one of ArgumentParser's property wrappers. - init(unparsedKey: String, default defaultValue: Any?, parent: InputKey.Parent) { + init(unparsedKey: String, default defaultValue: Any?, parent: InputKey?) { self.init( container: Bare.self, key: InputKey(name: unparsedKey, parent: parent), diff --git a/Sources/ArgumentParser/Parsing/ArgumentSet.swift b/Sources/ArgumentParser/Parsing/ArgumentSet.swift index 6adfebf99..dc689dc5e 100644 --- a/Sources/ArgumentParser/Parsing/ArgumentSet.swift +++ b/Sources/ArgumentParser/Parsing/ArgumentSet.swift @@ -438,7 +438,7 @@ extension ArgumentSet { func firstPositional( named name: String ) -> ArgumentDefinition? { - let key = InputKey(name: name, parent: .root) + let key = InputKey(name: name, parent: nil) return first(where: { $0.help.keys.contains(key) }) } diff --git a/Sources/ArgumentParser/Parsing/CommandParser.swift b/Sources/ArgumentParser/Parsing/CommandParser.swift index ee327c41f..5c17cf228 100644 --- a/Sources/ArgumentParser/Parsing/CommandParser.swift +++ b/Sources/ArgumentParser/Parsing/CommandParser.swift @@ -140,7 +140,7 @@ extension CommandParser { /// possible. fileprivate mutating func parseCurrent(_ split: inout SplitArguments) throws -> ParsableCommand { // Build the argument set (i.e. information on how to parse): - let commandArguments = ArgumentSet(currentNode.element, visibility: .private, parent: .root) + let commandArguments = ArgumentSet(currentNode.element, visibility: .private, parent: nil) // Parse the arguments, ignoring anything unexpected let values = try commandArguments.lenientParse( @@ -325,7 +325,7 @@ extension CommandParser { let completionValues = Array(args) // Generate the argument set and parse the argument to find in the set - let argset = ArgumentSet(current.element, visibility: .private, parent: .root) + let argset = ArgumentSet(current.element, visibility: .private, parent: nil) let parsedArgument = try! parseIndividualArg(argToMatch, at: 0).first! // Look up the specified argument and retrieve its custom completion function diff --git a/Sources/ArgumentParser/Parsing/InputKey.swift b/Sources/ArgumentParser/Parsing/InputKey.swift index 1d47d17cf..9837e41e0 100644 --- a/Sources/ArgumentParser/Parsing/InputKey.swift +++ b/Sources/ArgumentParser/Parsing/InputKey.swift @@ -9,122 +9,48 @@ // //===----------------------------------------------------------------------===// -/// Represents the path to a parsed field, annotated with ``Flag``, ``Option`` or -/// ``Argument``. It has a parent, which will either be ``InputKey/Parent/root`` -/// if the field is on the root ``ParsableComand`` or ``AsyncParsableCommand``, -/// or it will have a ``InputKey/Parent/key(InputKey)`` if it is defined in -/// a ``ParsableArguments`` instance. +/// Represents the path to a parsed field, annotated with ``Flag``, ``Option`` +/// or ``Argument``. Fields that are directly declared on a ``ParsableComand`` +/// have a path of length 1, while fields that are declared indirectly (and +/// included via an option group) have longer paths. struct InputKey: Hashable { - /// Describes the parent of an ``InputKey``. - indirect enum Parent: Hashable { - /// There is no parent key. - case root - /// There is a parent key. - case key(InputKey) - - /// Initialises a parent depending on whether the key is provided. - init(_ key: InputKey?) { - if let key = key { - self = .key(key) - } else { - self = .root - } - } - } - /// The name of the input key. - let name: String - - /// The parent of this key. - let parent: Parent + var name: String + + /// The path through the field's parents, if any. + var path: [String] + /// The full path of the field. + var fullPath: [String] { path + [name] } - /// Constructs a new ``InputKey``, cleaing the `name`, with the specified ``InputKey/Parent``. + /// Constructs a new input key, cleaning the name, with the specified parent. /// /// - Parameter name: The name of the key. - /// - Parameter parent: The ``InputKey/Parent`` of the key. - init(name: String, parent: Parent) { - self.name = Self.clean(codingKey: name) - self.parent = parent - } - - @inlinable - init?(path: [CodingKey]) { - var parentPath = path - guard let key = parentPath.popLast() else { - return nil - } - self.name = Self.clean(codingKey: key) - self.parent = Parent(InputKey(path: parentPath)) - } - - /// Constructs a new ``InputKey``, "cleaning the `value` and `path` if necessary. - /// - /// - Parameter value: The base value of the key. - /// - Parameter path: The list of ``CodingKey`` values that lead to this one. May be empty. - @inlinable - init(name: String, path: [CodingKey]) { - self.init(name: name, parent: Parent(InputKey(path: path))) + /// - Parameter parent: The input key of the parent. + init(name: String, parent: InputKey?) { + // Property wrappers have underscore-prefixed names, so we remove the + // leading `_`, if present. + self.name = name.first == "_" + ? String(name.dropFirst(1)) + : name + self.path = parent?.fullPath ?? [] } - /// Constructs a new ``InputKey``, "cleaning the `value` and `path` if necessary. + /// Constructs a new input key from the given coding key and parent path. /// - /// - Parameter codingKey: The base ``CodingKey`` - /// - Parameter path: The list of ``CodingKey`` values that lead to this one. May be empty. + /// - Parameter codingKey: The base ``CodingKey``. Leading underscores in + /// `codingKey` is preserved. + /// - Parameter path: The list of ``CodingKey`` values that lead to this one. + /// `path` may be empty. @inlinable init(codingKey: CodingKey, path: [CodingKey]) { - self.init(name: codingKey.stringValue, parent: Parent(InputKey(path: path))) - } - - /// The full path, including the ``parent`` and the ``name``. - var fullPath: [String] { - switch parent { - case .root: - return [name] - case .key(let key): - var parentPath = key.fullPath - parentPath.append(name) - return parentPath - } - } - - /// Returns a new ``InputKey`` with the same ``path`` and a new ``name``. - /// The new value will be cleaned. - /// - /// - Parameter newName: The new ``String`` value. - /// - Returns: A new ``InputKey`` with the cleaned value and the same ``path``. - func with(newName: String) -> InputKey { - return .init(name: Self.clean(codingKey: newName), parent: self.parent) - } -} - -extension InputKey { - /// Property wrappers have underscore-prefixed names, so this returns a "clean" - /// version of the `codingKey`, which has the leading `'_'` removed, if present. - /// - /// - Parameter codingKey: The key to clean. - /// - Returns: The cleaned key. - static func clean(codingKey: String) -> String { - String(codingKey.first == "_" ? codingKey.dropFirst(1) : codingKey.dropFirst(0)) - } - - /// Property wrappers have underscore-prefixed names, so this returns a "clean" - /// version of the `codingKey`, which has the leading `'_'` removed, if present. - /// - /// - Parameter codingKey: The key to clean. - /// - Returns: The cleaned key. - static func clean(codingKey: CodingKey) -> String { - clean(codingKey: codingKey.stringValue) + self.name = codingKey.stringValue + self.path = path.map { $0.stringValue } } } extension InputKey: CustomStringConvertible { var description: String { - switch parent { - case .key(let parent): - return "\(parent).\(name)" - case .root: - return name - } + fullPath.joined(separator: ".") } } diff --git a/Sources/ArgumentParser/Usage/DumpHelpGenerator.swift b/Sources/ArgumentParser/Usage/DumpHelpGenerator.swift index 658fc9efe..efd0442ab 100644 --- a/Sources/ArgumentParser/Usage/DumpHelpGenerator.swift +++ b/Sources/ArgumentParser/Usage/DumpHelpGenerator.swift @@ -38,7 +38,7 @@ fileprivate extension BidirectionalCollection where Element == ParsableCommand.T /// Returns the ArgumentSet for the last command in this stack, including /// help and version flags, when appropriate. func allArguments() -> ArgumentSet { - guard var arguments = self.last.map({ ArgumentSet($0, visibility: .private, parent: .root) }) + guard var arguments = self.last.map({ ArgumentSet($0, visibility: .private, parent: nil) }) else { return ArgumentSet() } self.versionArgumentDefinition().map { arguments.append($0) } self.helpArgumentDefinition().map { arguments.append($0) } diff --git a/Sources/ArgumentParser/Usage/HelpGenerator.swift b/Sources/ArgumentParser/Usage/HelpGenerator.swift index 7bc6020c6..440f73f3e 100644 --- a/Sources/ArgumentParser/Usage/HelpGenerator.swift +++ b/Sources/ArgumentParser/Usage/HelpGenerator.swift @@ -97,7 +97,7 @@ internal struct HelpGenerator { fatalError() } - let currentArgSet = ArgumentSet(currentCommand, visibility: visibility, parent: .root) + let currentArgSet = ArgumentSet(currentCommand, visibility: visibility, parent: nil) self.commandStack = commandStack // Build the tool name and subcommand name from the command configuration @@ -292,7 +292,7 @@ fileprivate extension NameSpecification { /// step, the name are returned in descending order. func generateHelpNames(visibility: ArgumentVisibility) -> [Name] { self - .makeNames(InputKey(name: "help", parent: .root)) + .makeNames(InputKey(name: "help", parent: nil)) .compactMap { name in guard visibility.base != .default else { return name } switch name { @@ -333,7 +333,7 @@ internal extension BidirectionalCollection where Element == ParsableCommand.Type options: [.isOptional], help: "Show the version.", defaultValue: nil, - key: InputKey(name: "", parent: .root), + key: InputKey(name: "", parent: nil), isComposite: false), completion: .default, update: .nullary({ _, _, _ in }) @@ -350,7 +350,7 @@ internal extension BidirectionalCollection where Element == ParsableCommand.Type options: [.isOptional], help: "Show help information.", defaultValue: nil, - key: InputKey(name: "", parent: .root), + key: InputKey(name: "", parent: nil), isComposite: false), completion: .default, update: .nullary({ _, _, _ in }) @@ -365,7 +365,7 @@ internal extension BidirectionalCollection where Element == ParsableCommand.Type options: [.isOptional], help: ArgumentHelp("Dump help information as JSON."), defaultValue: nil, - key: InputKey(name: "", parent: .root), + key: InputKey(name: "", parent: nil), isComposite: false), completion: .default, update: .nullary({ _, _, _ in }) @@ -375,7 +375,7 @@ internal extension BidirectionalCollection where Element == ParsableCommand.Type /// Returns the ArgumentSet for the last command in this stack, including /// help and version flags, when appropriate. func argumentsForHelp(visibility: ArgumentVisibility) -> ArgumentSet { - guard var arguments = self.last.map({ ArgumentSet($0, visibility: visibility, parent: .root) }) + guard var arguments = self.last.map({ ArgumentSet($0, visibility: visibility, parent: nil) }) else { return ArgumentSet() } self.versionArgumentDefinition().map { arguments.append($0) } self.helpArgumentDefinition().map { arguments.append($0) } diff --git a/Sources/ArgumentParser/Usage/MessageInfo.swift b/Sources/ArgumentParser/Usage/MessageInfo.swift index 68d4a01cd..896c565b7 100644 --- a/Sources/ArgumentParser/Usage/MessageInfo.swift +++ b/Sources/ArgumentParser/Usage/MessageInfo.swift @@ -122,7 +122,7 @@ enum MessageInfo { guard case ParserError.noArguments = parserError else { return usage } return "\n" + HelpGenerator(commandStack: [type.asCommand], visibility: .default).rendered() }() - let argumentSet = ArgumentSet(commandStack.last!, visibility: .default, parent: .root) + let argumentSet = ArgumentSet(commandStack.last!, visibility: .default, parent: nil) let message = argumentSet.errorDescription(error: parserError) ?? "" let helpAbstract = argumentSet.helpDescription(error: parserError) ?? "" self = .validation(message: message, usage: usage, help: helpAbstract) diff --git a/Sources/ArgumentParser/Usage/UsageGenerator.swift b/Sources/ArgumentParser/Usage/UsageGenerator.swift index b80bdee15..baf46c044 100644 --- a/Sources/ArgumentParser/Usage/UsageGenerator.swift +++ b/Sources/ArgumentParser/Usage/UsageGenerator.swift @@ -22,7 +22,7 @@ extension UsageGenerator { self.init(toolName: toolName, definition: definition) } - init(toolName: String, parsable: ParsableArguments, visibility: ArgumentVisibility, parent: InputKey.Parent) { + init(toolName: String, parsable: ParsableArguments, visibility: ArgumentVisibility, parent: InputKey?) { self.init( toolName: toolName, definition: ArgumentSet(type(of: parsable), visibility: visibility, parent: parent)) diff --git a/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift index 4c396c581..4a8e7b7df 100644 --- a/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift @@ -828,3 +828,33 @@ extension DefaultsEndToEndTests { } } } + +extension DefaultsEndToEndTests { + private struct UnderscoredOptional: ParsableCommand { + @Option(name: .customLong("arg")) + var _arg: String? + } + + private struct UnderscoredArray: ParsableCommand { + @Option(name: .customLong("columns"), parsing: .upToNextOption) + var _columns: [String] = [] + } + + func testUnderscoredOptional() throws { + AssertParse(UnderscoredOptional.self, []) { parsed in + XCTAssertNil(parsed._arg) + } + AssertParse(UnderscoredOptional.self, ["--arg", "foo"]) { parsed in + XCTAssertEqual(parsed._arg, "foo") + } + } + + func testUnderscoredArray() throws { + AssertParse(UnderscoredArray.self, []) { parsed in + XCTAssertEqual(parsed._columns, []) + } + AssertParse(UnderscoredArray.self, ["--columns", "foo", "bar", "baz"]) { parsed in + XCTAssertEqual(parsed._columns, ["foo", "bar", "baz"]) + } + } +} diff --git a/Tests/ArgumentParserEndToEndTests/OptionGroupEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/OptionGroupEndToEndTests.swift index c47ac2776..0968157c8 100644 --- a/Tests/ArgumentParserEndToEndTests/OptionGroupEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/OptionGroupEndToEndTests.swift @@ -109,52 +109,66 @@ extension OptionGroupEndToEndTests { } } -fileprivate struct DuplicatedFlagOption: ParsableArguments { +fileprivate struct DuplicatedFlagGroupCustom: ParsableArguments { @Flag(name: .customLong("duplicated-option")) var duplicated: Bool = false - - enum CodingKeys: CodingKey { - case duplicated - } } -fileprivate struct DuplicatedFlagCommand: ParsableCommand { +fileprivate struct DuplicatedFlagGroupCustomCommand: ParsableCommand { + @Flag var duplicated: Bool = false + @OptionGroup var option: DuplicatedFlagGroupCustom +} + +fileprivate struct DuplicatedFlagGroupLong: ParsableArguments { + @Flag var duplicated: Bool = false +} - @Flag +fileprivate struct DuplicatedFlagGroupLongCommand: ParsableCommand { + @Flag(name: .customLong("duplicated-option")) var duplicated: Bool = false - - @OptionGroup var option: DuplicatedFlagOption - - enum CodingKeys: CodingKey { - case duplicated - case option - } + @OptionGroup var option: DuplicatedFlagGroupLong } extension OptionGroupEndToEndTests { func testUniqueNamesForDuplicatedFlag_NoFlags() throws { - AssertParseCommand(DuplicatedFlagCommand.self, DuplicatedFlagCommand.self, []) { command in + AssertParse(DuplicatedFlagGroupCustomCommand.self, []) { command in + XCTAssertFalse(command.duplicated) + XCTAssertFalse(command.option.duplicated) + } + AssertParse(DuplicatedFlagGroupLongCommand.self, []) { command in XCTAssertFalse(command.duplicated) XCTAssertFalse(command.option.duplicated) } } func testUniqueNamesForDuplicatedFlag_RootOnly() throws { - AssertParseCommand(DuplicatedFlagCommand.self, DuplicatedFlagCommand.self, ["--duplicated"]) { command in + AssertParse(DuplicatedFlagGroupCustomCommand.self, ["--duplicated"]) { command in XCTAssertTrue(command.duplicated) XCTAssertFalse(command.option.duplicated) } + AssertParse(DuplicatedFlagGroupLongCommand.self, ["--duplicated"]) { command in + XCTAssertFalse(command.duplicated) + XCTAssertTrue(command.option.duplicated) + } } func testUniqueNamesForDuplicatedFlag_OptionOnly() throws { - AssertParseCommand(DuplicatedFlagCommand.self, DuplicatedFlagCommand.self, ["--duplicated-option"]) { command in + AssertParse(DuplicatedFlagGroupCustomCommand.self, ["--duplicated-option"]) { command in XCTAssertFalse(command.duplicated) XCTAssertTrue(command.option.duplicated) } + AssertParse(DuplicatedFlagGroupLongCommand.self, ["--duplicated-option"]) { command in + XCTAssertTrue(command.duplicated) + XCTAssertFalse(command.option.duplicated) + } } func testUniqueNamesForDuplicatedFlag_RootAndOption() throws { - AssertParseCommand(DuplicatedFlagCommand.self, DuplicatedFlagCommand.self, ["--duplicated", "--duplicated-option"]) { command in + AssertParse(DuplicatedFlagGroupCustomCommand.self, ["--duplicated", "--duplicated-option"]) { command in + XCTAssertTrue(command.duplicated) + XCTAssertTrue(command.option.duplicated) + } + AssertParse(DuplicatedFlagGroupLongCommand.self, ["--duplicated", "--duplicated-option"]) { command in XCTAssertTrue(command.duplicated) XCTAssertTrue(command.option.duplicated) } diff --git a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift index 1b3d7435c..476eb67a8 100644 --- a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift +++ b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift @@ -638,7 +638,7 @@ extension HelpGenerationTests { } func testAllValues() { - let opts = ArgumentSet(AllValues.self, visibility: .private, parent: .root) + let opts = ArgumentSet(AllValues.self, visibility: .private, parent: nil) XCTAssertEqual(AllValues.Manual.allValueStrings, opts[0].help.allValues) XCTAssertEqual(AllValues.Manual.allValueStrings, opts[1].help.allValues) diff --git a/Tests/ArgumentParserUnitTests/NameSpecificationTests.swift b/Tests/ArgumentParserUnitTests/NameSpecificationTests.swift index b0b6c905d..328b77afb 100644 --- a/Tests/ArgumentParserUnitTests/NameSpecificationTests.swift +++ b/Tests/ArgumentParserUnitTests/NameSpecificationTests.swift @@ -17,7 +17,7 @@ final class NameSpecificationTests: XCTestCase { extension NameSpecificationTests { func testFlagNames_withNoPrefix() { - let key = InputKey(name: "index", parent: .root) + let key = InputKey(name: "index", parent: nil) XCTAssertEqual(FlagInversion.prefixedNo.enableDisableNamePair(for: key, name: .customLong("foo")).1, [.long("no-foo")]) XCTAssertEqual(FlagInversion.prefixedNo.enableDisableNamePair(for: key, name: .customLong("foo-bar-baz")).1, [.long("no-foo-bar-baz")]) @@ -26,7 +26,7 @@ extension NameSpecificationTests { } func testFlagNames_withEnableDisablePrefix() { - let key = InputKey(name: "index", parent: .root) + let key = InputKey(name: "index", parent: nil) XCTAssertEqual(FlagInversion.prefixedEnableDisable.enableDisableNamePair(for: key, name: .long).0, [.long("enable-index")]) XCTAssertEqual(FlagInversion.prefixedEnableDisable.enableDisableNamePair(for: key, name: .long).1, [.long("disable-index")]) @@ -42,7 +42,7 @@ extension NameSpecificationTests { } } -fileprivate func Assert(nameSpecification: NameSpecification, key: String, parent: InputKey.Parent = .root, makeNames expected: [Name], file: StaticString = #file, line: UInt = #line) { +fileprivate func Assert(nameSpecification: NameSpecification, key: String, parent: InputKey? = nil, makeNames expected: [Name], file: StaticString = #file, line: UInt = #line) { let names = nameSpecification.makeNames(InputKey(name: key, parent: parent)) Assert(names: names, expected: expected, file: file, line: line) } diff --git a/Tests/ArgumentParserUnitTests/ParsableArgumentsValidationTests.swift b/Tests/ArgumentParserUnitTests/ParsableArgumentsValidationTests.swift index cb2ba5f6e..16a6ecfb4 100644 --- a/Tests/ArgumentParserUnitTests/ParsableArgumentsValidationTests.swift +++ b/Tests/ArgumentParserUnitTests/ParsableArgumentsValidationTests.swift @@ -81,7 +81,7 @@ final class ParsableArgumentsValidationTests: XCTestCase { } func testCodingKeyValidation() throws { - let parent = InputKey.Parent.key(InputKey(name: "parentKey", parent: .root)) + let parent = InputKey(name: "parentKey", parent: nil) XCTAssertNil(ParsableArgumentsCodingKeyValidator.validate(A.self, parent: parent)) XCTAssertNil(ParsableArgumentsCodingKeyValidator.validate(B.self, parent: parent)) @@ -130,7 +130,7 @@ final class ParsableArgumentsValidationTests: XCTestCase { } func testCustomDecoderValidation() throws { - let parent = InputKey.Parent(InputKey(name: "foo", parent: .root)) + let parent = InputKey(name: "foo", parent: nil) if let error = ParsableArgumentsCodingKeyValidator.validate(TypeWithInvalidDecoder.self, parent: parent) as? ParsableArgumentsCodingKeyValidator.InvalidDecoderError { @@ -211,7 +211,7 @@ final class ParsableArgumentsValidationTests: XCTestCase { } func testPositionalArgumentsValidation() throws { - let parent = InputKey.Parent(InputKey(name: "foo", parent: .root)) + let parent = InputKey(name: "foo", parent: nil) XCTAssertNil(PositionalArgumentsValidator.validate(A.self, parent: parent)) XCTAssertNil(PositionalArgumentsValidator.validate(F.self, parent: parent)) XCTAssertNil(PositionalArgumentsValidator.validate(H.self, parent: parent)) @@ -246,7 +246,7 @@ final class ParsableArgumentsValidationTests: XCTestCase { } func testUniqueNamesValidation_NoViolation() throws { - let parent = InputKey.Parent(InputKey(name: "foo", parent: .root)) + let parent = InputKey(name: "foo", parent: nil) XCTAssertNil(ParsableArgumentsUniqueNamesValidator.validate(DifferentNames.self, parent: parent)) } @@ -260,7 +260,7 @@ final class ParsableArgumentsValidationTests: XCTestCase { } func testUniqueNamesValidation_TwoOfSameName() throws { - if let error = ParsableArgumentsUniqueNamesValidator.validate(TwoOfTheSameName.self, parent: .root) + if let error = ParsableArgumentsUniqueNamesValidator.validate(TwoOfTheSameName.self, parent: nil) as? ParsableArgumentsUniqueNamesValidator.Error { XCTAssertEqual(error.description, "Multiple (2) `Option` or `Flag` arguments are named \"--foo\".") @@ -288,7 +288,7 @@ final class ParsableArgumentsValidationTests: XCTestCase { } func testUniqueNamesValidation_TwoDuplications() throws { - let parent = InputKey.Parent(InputKey(name: "option", parent: .root)) + let parent = InputKey(name: "option", parent: nil) if let error = ParsableArgumentsUniqueNamesValidator.validate(MultipleUniquenessViolations.self, parent: parent) as? ParsableArgumentsUniqueNamesValidator.Error { @@ -324,7 +324,7 @@ final class ParsableArgumentsValidationTests: XCTestCase { } func testUniqueNamesValidation_ArgumentHasMultipleNames() throws { - if let error = ParsableArgumentsUniqueNamesValidator.validate(MultipleNamesPerArgument.self, parent: .root) + if let error = ParsableArgumentsUniqueNamesValidator.validate(MultipleNamesPerArgument.self, parent: nil) as? ParsableArgumentsUniqueNamesValidator.Error { XCTAssertEqual(error.description, "Multiple (2) `Option` or `Flag` arguments are named \"-v\".") @@ -355,7 +355,7 @@ final class ParsableArgumentsValidationTests: XCTestCase { } func testUniqueNamesValidation_MoreThanTwoDuplications() throws { - if let error = ParsableArgumentsUniqueNamesValidator.validate(FourDuplicateNames.self, parent: .root) + if let error = ParsableArgumentsUniqueNamesValidator.validate(FourDuplicateNames.self, parent: nil) as? ParsableArgumentsUniqueNamesValidator.Error { XCTAssertEqual(error.description, "Multiple (4) `Option` or `Flag` arguments are named \"--foo\".") @@ -397,7 +397,7 @@ final class ParsableArgumentsValidationTests: XCTestCase { } func testUniqueNamesValidation_DuplicatedFlagFirstLetters_ShortNames() throws { - if let error = ParsableArgumentsUniqueNamesValidator.validate(DuplicatedFirstLettersShortNames.self, parent: .root) + if let error = ParsableArgumentsUniqueNamesValidator.validate(DuplicatedFirstLettersShortNames.self, parent: nil) as? ParsableArgumentsUniqueNamesValidator.Error { XCTAssertEqual(error.description, "Multiple (3) `Option` or `Flag` arguments are named \"-f\".") @@ -407,7 +407,7 @@ final class ParsableArgumentsValidationTests: XCTestCase { } func testUniqueNamesValidation_DuplicatedFlagFirstLetters_LongNames() throws { - XCTAssertNil(ParsableArgumentsUniqueNamesValidator.validate(DuplicatedFirstLettersLongNames.self, parent: .root)) + XCTAssertNil(ParsableArgumentsUniqueNamesValidator.validate(DuplicatedFirstLettersLongNames.self, parent: nil)) } fileprivate struct HasOneNonsenseFlag: ParsableCommand { @@ -439,7 +439,7 @@ final class ParsableArgumentsValidationTests: XCTestCase { } func testNonsenseFlagsValidation_OneFlag() throws { - if let error = NonsenseFlagsValidator.validate(HasOneNonsenseFlag.self, parent: .root) + if let error = NonsenseFlagsValidator.validate(HasOneNonsenseFlag.self, parent: nil) as? NonsenseFlagsValidator.Error { XCTAssertEqual( @@ -476,7 +476,7 @@ final class ParsableArgumentsValidationTests: XCTestCase { } func testNonsenseFlagsValidation_MultipleFlags() throws { - if let error = NonsenseFlagsValidator.validate(MultipleNonsenseFlags.self, parent: .root) + if let error = NonsenseFlagsValidator.validate(MultipleNonsenseFlags.self, parent: nil) as? NonsenseFlagsValidator.Error { XCTAssertEqual( diff --git a/Tests/ArgumentParserUnitTests/UsageGenerationTests.swift b/Tests/ArgumentParserUnitTests/UsageGenerationTests.swift index 7f57f6102..2c605aab8 100644 --- a/Tests/ArgumentParserUnitTests/UsageGenerationTests.swift +++ b/Tests/ArgumentParserUnitTests/UsageGenerationTests.swift @@ -22,7 +22,7 @@ func _testSynopsis( file: StaticString = #file, line: UInt = #line ) { - let help = UsageGenerator(toolName: "example", parsable: T(), visibility: visibility, parent: .root) + let help = UsageGenerator(toolName: "example", parsable: T(), visibility: visibility, parent: nil) XCTAssertEqual(help.synopsis, expected, file: file, line: line) }