diff --git a/Sources/SwiftDocC/Semantics/DirectiveInfrastructure/DirectiveArgumentWrapper.swift b/Sources/SwiftDocC/Semantics/DirectiveInfrastructure/DirectiveArgumentWrapper.swift index 816791c44c..af602d7a96 100644 --- a/Sources/SwiftDocC/Semantics/DirectiveInfrastructure/DirectiveArgumentWrapper.swift +++ b/Sources/SwiftDocC/Semantics/DirectiveInfrastructure/DirectiveArgumentWrapper.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2022 Apple Inc. and the Swift project authors + Copyright (c) 2022-2023 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 @@ -91,6 +91,43 @@ public struct DirectiveArgumentWrapped: _DirectiveArgumentProtocol { fatalError() } + // Expected argument configurations + + @_disfavoredOverload + init( + wrappedValue: Value, + name: _DirectiveArgumentName = .inferredFromPropertyName, + parseArgument: @escaping (_ bundle: DocumentationBundle, _ argumentValue: String) -> (Value?), + allowedValues: [String]? = nil, + hiddenFromDocumentation: Bool = false + ) { + self.init( + value: wrappedValue, + name: name, + transform: parseArgument, + allowedValues: allowedValues, + required: nil, + hiddenFromDocumentation: hiddenFromDocumentation + ) + } + + @_disfavoredOverload + init( + name: _DirectiveArgumentName = .inferredFromPropertyName, + parseArgument: @escaping (_ bundle: DocumentationBundle, _ argumentValue: String) -> (Value?), + allowedValues: [String]? = nil, + hiddenFromDocumentation: Bool = false + ) { + self.init( + value: nil, + name: name, + transform: parseArgument, + allowedValues: allowedValues, + required: nil, + hiddenFromDocumentation: hiddenFromDocumentation + ) + } + private init( value: Value?, name: _DirectiveArgumentName, @@ -99,32 +136,37 @@ public struct DirectiveArgumentWrapped: _DirectiveArgumentProtocol { required: Bool?, hiddenFromDocumentation: Bool ) { + let required = required ?? (value == nil) + self.name = name self.defaultValue = value - if let optionallyWrappedValue = Value.self as? OptionallyWrapped.Type { - self.typeDisplayName = String(describing: optionallyWrappedValue.baseType()) + "?" - } else { - self.typeDisplayName = String(describing: Value.self) - } - - if let required = required { - self.required = required - } else { - self.required = defaultValue == nil - } - + self.typeDisplayName = typeDisplayNameDescription(defaultValue: value, required: required) self.parseArgument = transform self.allowedValues = allowedValues + self.required = required self.hiddenFromDocumentation = hiddenFromDocumentation } + func setProperty( + on containingDirective: T, + named propertyName: String, + to any: Any + ) where T: AutomaticDirectiveConvertible { + let path = T.keyPaths[propertyName] as! ReferenceWritableKeyPath> + let wrappedValuePath = path.appending(path: \Self.parsedValue) + containingDirective[keyPath: wrappedValuePath] = any as! Value? + } + + // Warnings and errors for unexpected argument configurations + @_disfavoredOverload + @available(*, deprecated, message: "Use an optional type or a default value to control whether or not a directive argument is required.") init( wrappedValue: Value, name: _DirectiveArgumentName = .inferredFromPropertyName, parseArgument: @escaping (_ bundle: DocumentationBundle, _ argumentValue: String) -> (Value?), allowedValues: [String]? = nil, - required: Bool? = nil, + required: Bool, hiddenFromDocumentation: Bool = false ) { self.init( @@ -138,11 +180,12 @@ public struct DirectiveArgumentWrapped: _DirectiveArgumentProtocol { } @_disfavoredOverload + @available(*, deprecated, message: "Use an optional type or a default value to control whether or not a directive argument is required.") init( name: _DirectiveArgumentName = .inferredFromPropertyName, parseArgument: @escaping (_ bundle: DocumentationBundle, _ argumentValue: String) -> (Value?), allowedValues: [String]? = nil, - required: Bool? = nil, + required: Bool, hiddenFromDocumentation: Bool = false ) { self.init( @@ -154,19 +197,12 @@ public struct DirectiveArgumentWrapped: _DirectiveArgumentProtocol { hiddenFromDocumentation: hiddenFromDocumentation ) } - - func setProperty( - on containingDirective: T, - named propertyName: String, - to any: Any - ) where T: AutomaticDirectiveConvertible { - let path = T.keyPaths[propertyName] as! ReferenceWritableKeyPath> - let wrappedValuePath = path.appending(path: \Self.parsedValue) - containingDirective[keyPath: wrappedValuePath] = any as! Value? - } } extension DirectiveArgumentWrapped where Value: DirectiveArgumentValueConvertible { + // Expected argument configurations + + @_disfavoredOverload init( name: _DirectiveArgumentName = .inferredFromPropertyName, hiddenFromDocumentation: Bool = false @@ -174,6 +210,7 @@ extension DirectiveArgumentWrapped where Value: DirectiveArgumentValueConvertibl self.init(value: nil, name: name, hiddenFromDocumentation: hiddenFromDocumentation) } + @_disfavoredOverload init( wrappedValue: Value, name: _DirectiveArgumentName = .inferredFromPropertyName, @@ -182,53 +219,169 @@ extension DirectiveArgumentWrapped where Value: DirectiveArgumentValueConvertibl self.init(value: wrappedValue, name: name, hiddenFromDocumentation: hiddenFromDocumentation) } - private init( - value: Value?, - name: _DirectiveArgumentName, - hiddenFromDocumentation: Bool - ) { + private init(value: Value?, name: _DirectiveArgumentName, hiddenFromDocumentation: Bool) { self.name = name self.defaultValue = value - if let value = value { - self.typeDisplayName = String(describing: Value.self) + " = " + String(describing: value) - } else { - self.typeDisplayName = String(describing: Value.self) - } - + let required = value == nil + self.typeDisplayName = typeDisplayNameDescription(defaultValue: value, required: required) self.parseArgument = { _, argument in Value.init(rawDirectiveArgumentValue: argument) } self.allowedValues = Value.allowedValues() - self.required = value == nil + self.required = required self.hiddenFromDocumentation = hiddenFromDocumentation } + + // Warnings and errors for unexpected argument configurations + + @_disfavoredOverload + @available(*, unavailable, message: "Directive argument of non-optional types without default value need to be required. Use an optional type or provide a default value to make this argument non-required.") + init(name: _DirectiveArgumentName = .inferredFromPropertyName, required: Bool) { + fatalError() + } } -protocol OptionallyWrappedDirectiveArgumentValueConvertible: OptionallyWrapped {} -extension Optional: OptionallyWrappedDirectiveArgumentValueConvertible where Wrapped: DirectiveArgumentValueConvertible {} -extension DirectiveArgumentWrapped where Value: OptionallyWrappedDirectiveArgumentValueConvertible { +protocol _OptionalDirectiveArgument { + associatedtype WrappedArgument + var wrapped: WrappedArgument? { get } + init(wrapping: WrappedArgument?) +} +extension Optional: _OptionalDirectiveArgument { + typealias WrappedArgument = Wrapped + var wrapped: WrappedArgument? { + switch self { + case .some(let value): + return value + case .none: return + nil + } + } + init(wrapping: WrappedArgument?) { + if let wrapped = wrapping { + self = .some(wrapped) + } else { + self = .none + } + } +} + +extension DirectiveArgumentWrapped where Value: _OptionalDirectiveArgument, Value.WrappedArgument: DirectiveArgumentValueConvertible { + + // When the wrapped value is DirectiveArgumentValueConvertible, additional arguments may be omitted + + init( + name: _DirectiveArgumentName = .inferredFromPropertyName, + hiddenFromDocumentation: Bool = false + ) { + self = .init(value: nil, name: name, hiddenFromDocumentation: hiddenFromDocumentation) + } + + @_disfavoredOverload init( wrappedValue: Value, name: _DirectiveArgumentName = .inferredFromPropertyName, - required: Bool = false, hiddenFromDocumentation: Bool = false ) { - let argumentValueType = Value.baseType() as! DirectiveArgumentValueConvertible.Type + self = .init(value: wrappedValue, name: name, hiddenFromDocumentation: hiddenFromDocumentation) + } + + private init( + value: Value?, + name: _DirectiveArgumentName, + hiddenFromDocumentation: Bool + ) { + let argumentValueType = Value.WrappedArgument.self + self = .init( + value: value, + name: name, + parseArgument: { _, argument in + Value(wrapping: argumentValueType.init(rawDirectiveArgumentValue: argument)) + }, + allowedValues: argumentValueType.allowedValues(), + hiddenFromDocumentation: hiddenFromDocumentation + ) + } +} + +extension DirectiveArgumentWrapped where Value: _OptionalDirectiveArgument { + + // Expected argument configurations + + @_disfavoredOverload + init( + name: _DirectiveArgumentName = .inferredFromPropertyName, + parseArgument: @escaping (_ bundle: DocumentationBundle, _ argumentValue: String) -> (Value?), + allowedValues: [String]? = nil, + hiddenFromDocumentation: Bool = false + ) { + self = .init(value: nil, name: name, parseArgument: parseArgument, allowedValues: allowedValues, hiddenFromDocumentation: hiddenFromDocumentation) + } + + @_disfavoredOverload + init( + wrappedValue: Value, + name: _DirectiveArgumentName = .inferredFromPropertyName, + parseArgument: @escaping (_ bundle: DocumentationBundle, _ argumentValue: String) -> (Value?), + allowedValues: [String]? = nil, + hiddenFromDocumentation: Bool = false + ) { + self = .init(value: wrappedValue, name: name, parseArgument: parseArgument, allowedValues: allowedValues, hiddenFromDocumentation: hiddenFromDocumentation) + } + + private init( + value: Value?, + name: _DirectiveArgumentName, + parseArgument: @escaping (_ bundle: DocumentationBundle, _ argumentValue: String) -> (Value?), + allowedValues: [String]? = nil, + hiddenFromDocumentation: Bool = false + ) { self.name = name - self.defaultValue = wrappedValue - if required { - self.typeDisplayName = String(describing: argumentValueType) - } else { - self.typeDisplayName = String(describing: argumentValueType) + "?" - } - - self.parseArgument = { _, argument in - argumentValueType.init(rawDirectiveArgumentValue: argument) - } - self.allowedValues = argumentValueType.allowedValues() - self.required = required + self.defaultValue = value + self.typeDisplayName = typeDisplayNameDescription(optionalDefaultValue: value, required: false) + self.parseArgument = parseArgument + self.allowedValues = allowedValues + self.required = false self.hiddenFromDocumentation = hiddenFromDocumentation } + + // Warnings and errors for unexpected argument configurations + + @_disfavoredOverload + @available(*, unavailable, message: "Directive arguments with an Optional type shouldn't be required.") + init( + name: _DirectiveArgumentName = .inferredFromPropertyName, + required: Bool, + hiddenFromDocumentation: Bool = false + ) { + fatalError() + } + + @_disfavoredOverload + @available(*, unavailable, message: "Directive arguments with an Optional type shouldn't be required.") + init( + wrappedValue: Value, + name: _DirectiveArgumentName = .inferredFromPropertyName, + required: Bool, + hiddenFromDocumentation: Bool = false + ) { + fatalError() + } +} + +private func typeDisplayNameDescription(defaultValue: Value?, required: Bool) -> String { + var name = "\(Value.self)" + + if let defaultValue = defaultValue { + name += " = \(defaultValue)" + } else if !required { + name += "?" + } + + return name +} + +private func typeDisplayNameDescription(optionalDefaultValue: Value?, required: Bool) -> String { + return typeDisplayNameDescription(defaultValue: optionalDefaultValue?.wrapped, required: required) } diff --git a/Tests/SwiftDocCTests/Infrastructure/DirectiveArgumentWrappedTests.swift b/Tests/SwiftDocCTests/Infrastructure/DirectiveArgumentWrappedTests.swift new file mode 100644 index 0000000000..7e70584d25 --- /dev/null +++ b/Tests/SwiftDocCTests/Infrastructure/DirectiveArgumentWrappedTests.swift @@ -0,0 +1,338 @@ +/* + This source file is part of the Swift.org open source project + + Copyright (c) 2022-2023 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 + See https://swift.org/CONTRIBUTORS.txt for Swift project authors +*/ + +import Foundation +import XCTest + +import Markdown + +@testable import SwiftDocC + +final class DirectiveArgumentWrappedTests: XCTestCase { + + // MARK: - Declarations + + // A custom type that is directive-argument-convertible + enum Something: String, CaseIterable, DirectiveArgumentValueConvertible { + case something + } + + // These are all declared directly on the test case so that the property wrappers can be easily accessed in the test. + + // MARK: Values + + // Without default values + + @DirectiveArgumentWrapped + var boolean: Bool + + @DirectiveArgumentWrapped + var number: Int + + @DirectiveArgumentWrapped + var customValue: Something + + // With default values + + @DirectiveArgumentWrapped + var booleanWithDefault: Bool = false + + @DirectiveArgumentWrapped + var numberWithDefault: Int = 0 + + @DirectiveArgumentWrapped + var customValueWithDefault: Something = .something + + // MARK: Optional values + + // Without default values + + @DirectiveArgumentWrapped + var optionalBoolean: Bool? + + @DirectiveArgumentWrapped + var optionalNumber: Int? + + @DirectiveArgumentWrapped + var optionalCustomValue: Something? + + // With default values + + @DirectiveArgumentWrapped + var optionalBooleanWithDefault: Bool? = true + + @DirectiveArgumentWrapped + var optionalNumberWithDefault: Int? = 0 + + @DirectiveArgumentWrapped + var optionalCustomValueWithDefault: Something? = .something + + @DirectiveArgumentWrapped + var optionalBooleanWithNilDefault: Bool? = nil + + @DirectiveArgumentWrapped + var optionalNumberWithNilDefault: Int? = nil + + @DirectiveArgumentWrapped + var optionalCustomValueWithNilDefault: Something? = nil + + // MARK: Explicit allowed values + + // Non-optional + + @DirectiveArgumentWrapped( + parseArgument: { _, _ in nil }, + allowedValues: ["one", "two", "three"]) + var booleanWithAllowedValues: Bool + + @DirectiveArgumentWrapped( + parseArgument: { _, _ in nil }, + allowedValues: ["one", "two", "three"]) + var numberWithAllowedValues: Int + + @DirectiveArgumentWrapped( + parseArgument: { _, _ in nil }, + allowedValues: ["one", "two", "three"]) + var customValueWithAllowedValues: Something + + @DirectiveArgumentWrapped( + parseArgument: { _, _ in nil }, + allowedValues: ["one", "two", "three"]) + var booleanWithAllowedValuesAndDefaultValue: Bool = false + + @DirectiveArgumentWrapped( + parseArgument: { _, _ in nil }, + allowedValues: ["one", "two", "three"]) + var numberWithAllowedValuesAndDefaultValue: Int = 0 + + @DirectiveArgumentWrapped( + parseArgument: { _, _ in nil }, + allowedValues: ["one", "two", "three"]) + var customValueWithAllowedValuesAndDefaultValue: Something = .something + + // Optional + + @DirectiveArgumentWrapped( + parseArgument: { _, _ in nil }, + allowedValues: ["one", "two", "three"]) + var optionalBooleanWithAllowedValues: Bool? + + @DirectiveArgumentWrapped( + parseArgument: { _, _ in nil }, + allowedValues: ["one", "two", "three"]) + var optionalNumberWithAllowedValues: Int? + + @DirectiveArgumentWrapped( + parseArgument: { _, _ in nil }, + allowedValues: ["one", "two", "three"]) + var optionalCustomValueWithAllowedValues: Something? + + @DirectiveArgumentWrapped( + parseArgument: { _, _ in nil }, + allowedValues: ["one", "two", "three"]) + var optionalBooleanWithAllowedValuesAndDefaultValue: Bool? = false + + @DirectiveArgumentWrapped( + parseArgument: { _, _ in nil }, + allowedValues: ["one", "two", "three"]) + var optionalNumberWithAllowedValuesAndDefaultValue: Int? = 0 + + @DirectiveArgumentWrapped( + parseArgument: { _, _ in nil }, + allowedValues: ["one", "two", "three"]) + var optionalCustomValueWithAllowedValuesAndDefaultValue: Something? = .something + + // MARK: Non-convertible values + + // A custom type that _isn't_ directive-argument-convertible + enum SomethingNonConvertible: String { + case someValue + } + + @DirectiveArgumentWrapped( + parseArgument: { _, _ in .someValue } + ) + var nonConvertibleValueWithCustomParsing: SomethingNonConvertible + + @DirectiveArgumentWrapped( + parseArgument: { _, _ in .someValue } + ) + var nonConvertibleValueWithCustomParsingAndDefaultValue: SomethingNonConvertible = .someValue + + @DirectiveArgumentWrapped( + parseArgument: { _, _ in .someValue } + ) + var optionalNonConvertibleValueWithCustomParsing: SomethingNonConvertible? + + @DirectiveArgumentWrapped( + parseArgument: { _, _ in .someValue } + ) + var optionalNonConvertibleValueWithCustomParsingAndNilDefault: SomethingNonConvertible? = nil + + @DirectiveArgumentWrapped( + parseArgument: { _, _ in .someValue } + ) + var optionalNonConvertibleValueWithCustomParsingAndDefaultValue: SomethingNonConvertible? = .someValue + + + // MARK: - Test assertions + + func testTypeDisplayName() throws { + + // MARK: Values + + // Without default values + + XCTAssertEqual(_boolean.typeDisplayName, "Bool") + XCTAssertEqual(_boolean.allowedValues, ["true", "false"]) + XCTAssertEqual(_boolean.required, true, "Argument without default value is required") + + XCTAssertEqual(_number.typeDisplayName, "Int") + XCTAssertEqual(_number.allowedValues, nil) + XCTAssertEqual(_number.required, true, "Argument without default value is required") + + XCTAssertEqual(_customValue.typeDisplayName, "Something") + XCTAssertEqual(_customValue.allowedValues, ["something"]) + XCTAssertEqual(_customValue.required, true, "Argument without default value is required") + + // With default values + + XCTAssertEqual(_booleanWithDefault.typeDisplayName, "Bool = false") + XCTAssertEqual(_booleanWithDefault.allowedValues, ["true", "false"]) + XCTAssertEqual(_booleanWithDefault.required, false, "Argument has default value to fallback to") + + XCTAssertEqual(_numberWithDefault.typeDisplayName, "Int = 0") + XCTAssertEqual(_numberWithDefault.allowedValues, nil) + XCTAssertEqual(_numberWithDefault.required, false, "Argument has default value to fallback to") + + XCTAssertEqual(_customValueWithDefault.typeDisplayName, "Something = something") + XCTAssertEqual(_customValueWithDefault.allowedValues, ["something"]) + XCTAssertEqual(_customValueWithDefault.required, false, "Argument has default value to fallback to") + + // MARK: Optional values + + XCTAssertEqual(_optionalBoolean.typeDisplayName, "Bool?") + XCTAssertEqual(_optionalBoolean.allowedValues, ["true", "false"]) + XCTAssertEqual(_optionalBoolean.required, false, "Argument with optional type is not required") + + XCTAssertEqual(_optionalNumber.typeDisplayName, "Int?") + XCTAssertEqual(_optionalNumber.allowedValues, nil) + XCTAssertEqual(_optionalNumber.required, false, "Argument with optional type is not required") + + XCTAssertEqual(_optionalCustomValue.typeDisplayName, "Something?") + XCTAssertEqual(_optionalCustomValue.allowedValues, ["something"]) + XCTAssertEqual(_optionalCustomValue.required, false, "Argument with optional type is not required") + + // With nil default values + + XCTAssertEqual(_optionalBooleanWithNilDefault.typeDisplayName, "Bool?") + XCTAssertEqual(_optionalBooleanWithNilDefault.allowedValues, ["true", "false"]) + XCTAssertEqual(_optionalBooleanWithNilDefault.required, false, "Argument with optional type is not required") + + XCTAssertEqual(_optionalNumberWithNilDefault.typeDisplayName, "Int?") + XCTAssertEqual(_optionalNumberWithNilDefault.allowedValues, nil) + XCTAssertEqual(_optionalNumberWithNilDefault.required, false, "Argument with optional type is not required") + + XCTAssertEqual(_optionalCustomValueWithNilDefault.typeDisplayName, "Something?") + XCTAssertEqual(_optionalCustomValueWithNilDefault.allowedValues, ["something"]) + XCTAssertEqual(_optionalCustomValueWithNilDefault.required, false, "Argument with optional type is not required") + + // With default values + + XCTAssertEqual(_optionalBooleanWithDefault.typeDisplayName, "Bool = true") + XCTAssertEqual(_optionalBooleanWithDefault.allowedValues, ["true", "false"]) + XCTAssertEqual(_optionalBooleanWithDefault.required, false, "Argument with optional type is not required") + + XCTAssertEqual(_optionalNumberWithDefault.typeDisplayName, "Int = 0") + XCTAssertEqual(_optionalNumberWithDefault.allowedValues, nil) + XCTAssertEqual(_optionalNumberWithDefault.required, false, "Argument with optional type is not required") + + XCTAssertEqual(_optionalCustomValueWithDefault.typeDisplayName, "Something = something") + XCTAssertEqual(_optionalCustomValueWithDefault.allowedValues, ["something"]) + XCTAssertEqual(_optionalCustomValueWithDefault.required, false, "Argument with optional type is not required") + + // MARK: Explicit allowed values + + // Non-optional + + XCTAssertEqual(_booleanWithAllowedValues.typeDisplayName, "Bool") + XCTAssertEqual(_booleanWithAllowedValues.allowedValues, ["one", "two", "three"], "Argument has explicitly specified allowed values") + XCTAssertEqual(_booleanWithAllowedValues.required, true, "Argument without default value is required") + + XCTAssertEqual(_numberWithAllowedValues.typeDisplayName, "Int") + XCTAssertEqual(_numberWithAllowedValues.allowedValues, ["one", "two", "three"], "Argument has explicitly specified allowed values") + XCTAssertEqual(_numberWithAllowedValues.required, true, "Argument without default value is required") + + XCTAssertEqual(_customValueWithAllowedValues.typeDisplayName, "Something") + XCTAssertEqual(_customValueWithAllowedValues.allowedValues, ["one", "two", "three"], "Argument has explicitly specified allowed values") + XCTAssertEqual(_customValueWithAllowedValues.required, true, "Argument without default value is required") + + XCTAssertEqual(_booleanWithAllowedValuesAndDefaultValue.typeDisplayName, "Bool = false") + XCTAssertEqual(_booleanWithAllowedValuesAndDefaultValue.allowedValues, ["one", "two", "three"], "Argument has explicitly specified allowed values") + XCTAssertEqual(_booleanWithAllowedValuesAndDefaultValue.required, false, "Argument has default value to fallback to") + + XCTAssertEqual(_numberWithAllowedValuesAndDefaultValue.typeDisplayName, "Int = 0") + XCTAssertEqual(_numberWithAllowedValuesAndDefaultValue.allowedValues, ["one", "two", "three"], "Argument has explicitly specified allowed values") + XCTAssertEqual(_numberWithAllowedValuesAndDefaultValue.required, false, "Argument has default value to fallback to") + + XCTAssertEqual(_customValueWithAllowedValuesAndDefaultValue.typeDisplayName, "Something = something") + XCTAssertEqual(_customValueWithAllowedValuesAndDefaultValue.allowedValues, ["one", "two", "three"], "Argument has explicitly specified allowed values") + XCTAssertEqual(_customValueWithAllowedValuesAndDefaultValue.required, false, "Argument has default value to fallback to") + + // Optional + + XCTAssertEqual(_optionalBooleanWithAllowedValues.typeDisplayName, "Bool?") + XCTAssertEqual(_optionalBooleanWithAllowedValues.allowedValues, ["one", "two", "three"], "Argument has explicitly specified allowed values") + XCTAssertEqual(_optionalBooleanWithAllowedValues.required, false, "Argument with optional type is not required") + + XCTAssertEqual(_optionalNumberWithAllowedValues.typeDisplayName, "Int?") + XCTAssertEqual(_optionalNumberWithAllowedValues.allowedValues, ["one", "two", "three"], "Argument has explicitly specified allowed values") + XCTAssertEqual(_optionalNumberWithAllowedValues.required, false, "Argument with optional type is not required") + + XCTAssertEqual(_optionalCustomValueWithAllowedValues.typeDisplayName, "Something?") + XCTAssertEqual(_optionalCustomValueWithAllowedValues.allowedValues, ["one", "two", "three"], "Argument has explicitly specified allowed values") + XCTAssertEqual(_optionalCustomValueWithAllowedValues.required, false, "Argument with optional type is not required") + + + XCTAssertEqual(_optionalBooleanWithAllowedValuesAndDefaultValue.typeDisplayName, "Bool = false") + XCTAssertEqual(_optionalBooleanWithAllowedValuesAndDefaultValue.allowedValues, ["one", "two", "three"], "Argument has explicitly specified allowed values") + XCTAssertEqual(_optionalBooleanWithAllowedValuesAndDefaultValue.required, false, "Argument with optional type is not required") + + XCTAssertEqual(_optionalNumberWithAllowedValuesAndDefaultValue.typeDisplayName, "Int = 0") + XCTAssertEqual(_optionalNumberWithAllowedValuesAndDefaultValue.allowedValues, ["one", "two", "three"], "Argument has explicitly specified allowed values") + XCTAssertEqual(_optionalNumberWithAllowedValuesAndDefaultValue.required, false, "Argument with optional type is not required") + + XCTAssertEqual(_optionalCustomValueWithAllowedValuesAndDefaultValue.typeDisplayName, "Something = something") + XCTAssertEqual(_optionalCustomValueWithAllowedValuesAndDefaultValue.allowedValues, ["one", "two", "three"], "Argument has explicitly specified allowed values") + XCTAssertEqual(_optionalCustomValueWithAllowedValuesAndDefaultValue.required, false, "Argument with optional type is not required") + + // MARK: Non-convertible values + + XCTAssertEqual(_nonConvertibleValueWithCustomParsing.typeDisplayName, "SomethingNonConvertible") + XCTAssertEqual(_nonConvertibleValueWithCustomParsing.allowedValues, nil) + XCTAssertEqual(_nonConvertibleValueWithCustomParsing.required, true, "Argument without default value is required") + + XCTAssertEqual(_nonConvertibleValueWithCustomParsingAndDefaultValue.typeDisplayName, "SomethingNonConvertible = someValue") + XCTAssertEqual(_nonConvertibleValueWithCustomParsingAndDefaultValue.allowedValues, nil) + XCTAssertEqual(_nonConvertibleValueWithCustomParsingAndDefaultValue.required, false, "Argument with default value is not required") + + XCTAssertEqual(_optionalNonConvertibleValueWithCustomParsing.typeDisplayName, "SomethingNonConvertible?") + XCTAssertEqual(_optionalNonConvertibleValueWithCustomParsing.allowedValues, nil) + XCTAssertEqual(_optionalNonConvertibleValueWithCustomParsing.required, false, "Argument with optional type is not required") + + XCTAssertEqual(_optionalNonConvertibleValueWithCustomParsingAndNilDefault.typeDisplayName, "SomethingNonConvertible?") + XCTAssertEqual(_optionalNonConvertibleValueWithCustomParsingAndNilDefault.allowedValues, nil) + XCTAssertEqual(_optionalNonConvertibleValueWithCustomParsingAndNilDefault.required, false, "Argument with optional type is not required") + + XCTAssertEqual(_optionalNonConvertibleValueWithCustomParsingAndDefaultValue.typeDisplayName, "SomethingNonConvertible = someValue") + XCTAssertEqual(_optionalNonConvertibleValueWithCustomParsingAndDefaultValue.allowedValues, nil) + XCTAssertEqual(_optionalNonConvertibleValueWithCustomParsingAndDefaultValue.required, false, "Argument with optional type is not required") + } +}