WIP allow non-optional values to be passed as matchers for optionals.
This commit is contained in:
parent
ba434e6e91
commit
417075a94a
|
@ -78,7 +78,7 @@ public struct Generator {
|
|||
private func matchableGenerics(where parameters: [MethodParameter]) -> String {
|
||||
guard parameters.isEmpty == false else { return "" }
|
||||
|
||||
let whereClause = parameters.enumerated().map { "M\($0 + 1).MatchedType == \(genericSafeType(from: $1.typeWithoutAttributes))" }.joined(separator: ", ")
|
||||
let whereClause = parameters.enumerated().map { "M\($0 + 1).MatchedType == \(genericSafeType(from: $1.type.withoutAttributes.unoptionaled.sugarized))" }.joined(separator: ", ")
|
||||
return " where \(whereClause)"
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ extension Templates {
|
|||
|
||||
{% for method in container.methods %}
|
||||
@discardableResult
|
||||
func {{method.name}}{{method.parameters|matchableGenericNames}}({{method.parameters|matchableParameterSignature}}) -> Cuckoo.__DoNotUse<{{method.returnType|genericSafe}}>{{method.parameters|matchableGenericWhere}} {
|
||||
func {{method.name}}{{method.parameters|matchableGenericNames}}({{method.parameters|matchableParameterSignature}}) -> Cuckoo.__DoNotUse<({{method.inputTypes|genericSafe}}), {{method.returnType|genericSafe}}>{{method.parameters|matchableGenericWhere}} {
|
||||
{{method.parameters|parameterMatchers}}
|
||||
return cuckoo_manager.verify("{{method.fullyQualifiedName}}", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation)
|
||||
}
|
||||
|
|
|
@ -85,7 +85,12 @@ public struct Tokenizer {
|
|||
}
|
||||
|
||||
let accessibility = (dictionary[Key.Accessibility.rawValue] as? String).flatMap { Accessibility(rawValue: $0) } ?? .Internal
|
||||
let type = dictionary[Key.TypeName.rawValue] as? String
|
||||
let type: WrappableType?
|
||||
if let stringType = dictionary[Key.TypeName.rawValue] as? String {
|
||||
type = WrappableType(parsing: stringType)
|
||||
} else {
|
||||
type = nil
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case Kinds.ProtocolDeclaration.rawValue:
|
||||
|
@ -147,7 +152,7 @@ public struct Tokenizer {
|
|||
|
||||
return InstanceVariable(
|
||||
name: name,
|
||||
type: type ?? "__UnknownType",
|
||||
type: type ?? .type("__UnknownType"),
|
||||
accessibility: accessibility,
|
||||
setterAccessibility: setterAccessibility,
|
||||
range: range!,
|
||||
|
@ -176,11 +181,20 @@ public struct Tokenizer {
|
|||
returnSignature = " " + returnSignature
|
||||
}
|
||||
|
||||
let returnTypeString: String
|
||||
if let range = returnSignature.range(of: "->") {
|
||||
returnTypeString = String(returnSignature[range.upperBound...]).trimmed
|
||||
} else {
|
||||
returnTypeString = "Void"
|
||||
}
|
||||
let returnType = WrappableType(parsing: returnTypeString)
|
||||
|
||||
// When bodyRange != nil, we need to create .ClassMethod instead of .ProtocolMethod
|
||||
if let bodyRange = bodyRange {
|
||||
return ClassMethod(
|
||||
name: name,
|
||||
accessibility: accessibility,
|
||||
returnType: returnType,
|
||||
returnSignature: returnSignature,
|
||||
range: range!,
|
||||
nameRange: nameRange!,
|
||||
|
@ -191,6 +205,7 @@ public struct Tokenizer {
|
|||
return ProtocolMethod(
|
||||
name: name,
|
||||
accessibility: accessibility,
|
||||
returnType: returnType,
|
||||
returnSignature: returnSignature,
|
||||
range: range!,
|
||||
nameRange: nameRange!,
|
||||
|
@ -255,7 +270,9 @@ public struct Tokenizer {
|
|||
isInout = false
|
||||
}
|
||||
|
||||
return MethodParameter(label: parameterLabel, name: name, type: inoutSeparatedType, range: range!, nameRange: nameRange!, isInout: isInout)
|
||||
let wrappableType = WrappableType(parsing: inoutSeparatedType)
|
||||
|
||||
return MethodParameter(label: parameterLabel, name: name, type: wrappableType, range: range!, nameRange: nameRange!, isInout: isInout)
|
||||
|
||||
default:
|
||||
stderrPrint("Unknown method parameter. Dictionary: \(dictionary) \(file.path ?? "")")
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
public struct ClassMethod: Method {
|
||||
public var name: String
|
||||
public var accessibility: Accessibility
|
||||
public var returnType: WrappableType
|
||||
public var returnSignature: String
|
||||
public var range: CountableRange<Int>
|
||||
public var nameRange: CountableRange<Int>
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
public struct Initializer: Method, HasAccessibility {
|
||||
public var name: String
|
||||
public var accessibility: Accessibility
|
||||
public var returnType: WrappableType
|
||||
public var returnSignature: String
|
||||
public var range: CountableRange<Int>
|
||||
public var nameRange: CountableRange<Int>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
public struct InstanceVariable: Token, HasAccessibility {
|
||||
public var name: String
|
||||
public var type: String
|
||||
public var type: WrappableType
|
||||
public var accessibility: Accessibility
|
||||
public var setterAccessibility: Accessibility?
|
||||
public var range: CountableRange<Int>
|
||||
|
@ -33,7 +33,7 @@ public struct InstanceVariable: Token, HasAccessibility {
|
|||
let readOnlyString = readOnly ? "ReadOnly" : ""
|
||||
return [
|
||||
"name": name,
|
||||
"type": type,
|
||||
"type": type.sugarized,
|
||||
"accessibility": accessibility.sourceName,
|
||||
"isReadOnly": readOnly,
|
||||
"stubType": overriding ? "ClassToBeStubbed\(readOnlyString)Property" : "ProtocolToBeStubbed\(readOnlyString)Property",
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
public protocol Method: Token, HasAccessibility {
|
||||
var name: String { get }
|
||||
var accessibility: Accessibility { get }
|
||||
var returnType: WrappableType { get }
|
||||
var returnSignature: String { get }
|
||||
var range: CountableRange<Int> { get }
|
||||
var nameRange: CountableRange<Int> { get }
|
||||
|
@ -16,6 +17,7 @@ public protocol Method: Token, HasAccessibility {
|
|||
var isOptional: Bool { get }
|
||||
var isOverriding: Bool { get }
|
||||
var hasClosureParams: Bool { get }
|
||||
var hasOptionalParams: Bool { get }
|
||||
var attributes: [Attribute] { get }
|
||||
}
|
||||
|
||||
|
@ -33,7 +35,7 @@ public extension Method {
|
|||
}
|
||||
|
||||
var fullyQualifiedName: String {
|
||||
let parameterTypes = parameters.map { ($0.isInout ? "inout " : "") + $0.type }
|
||||
let parameterTypes = parameters.map { ($0.isInout ? "inout " : "") + $0.type.sugarized }
|
||||
let nameParts = name.components(separatedBy: ":")
|
||||
let lastNamePart = nameParts.last ?? ""
|
||||
|
||||
|
@ -46,20 +48,12 @@ public extension Method {
|
|||
return returnSignature.trimmed.hasPrefix("throws")
|
||||
}
|
||||
|
||||
var returnType: String {
|
||||
if let range = returnSignature.range(of: "->") {
|
||||
var type = String(returnSignature[range.upperBound...]).trimmed
|
||||
while type.hasSuffix("?") {
|
||||
type = "Optional<\(type[..<type.index(before: type.endIndex)])>"
|
||||
}
|
||||
return type
|
||||
} else {
|
||||
return "Void"
|
||||
}
|
||||
}
|
||||
|
||||
var hasClosureParams: Bool {
|
||||
return parameters.filter { $0.isClosure }.count > 0
|
||||
return parameters.contains { $0.isClosure }
|
||||
}
|
||||
|
||||
var hasOptionalParams: Bool {
|
||||
return parameters.contains { $0.isOptional }
|
||||
}
|
||||
|
||||
public func isEqual(to other: Token) -> Bool {
|
||||
|
@ -80,13 +74,13 @@ public extension Method {
|
|||
let stubFunctionPrefix = isOverriding ? "Class" : "Protocol"
|
||||
let stubFunction: String
|
||||
if isThrowing {
|
||||
if returnType == "Void" {
|
||||
if returnType.sugarized == "Void" {
|
||||
stubFunction = "Cuckoo.\(stubFunctionPrefix)StubNoReturnThrowingFunction"
|
||||
} else {
|
||||
stubFunction = "Cuckoo.\(stubFunctionPrefix)StubThrowingFunction"
|
||||
}
|
||||
} else {
|
||||
if returnType == "Void" {
|
||||
if returnType.sugarized == "Void" {
|
||||
stubFunction = "Cuckoo.\(stubFunctionPrefix)StubNoReturnFunction"
|
||||
} else {
|
||||
stubFunction = "Cuckoo.\(stubFunctionPrefix)StubFunction"
|
||||
|
@ -109,7 +103,7 @@ public extension Method {
|
|||
"parameterNames": parameters.map { $0.name }.joined(separator: ", "),
|
||||
"escapingParameterNames": escapingParameterNames,
|
||||
"isInit": isInit,
|
||||
"returnType": returnType,
|
||||
"returnType": returnType.sugarizedExplicitOnly,
|
||||
"isThrowing": isThrowing,
|
||||
"fullyQualifiedName": fullyQualifiedName,
|
||||
"call": call,
|
||||
|
@ -120,6 +114,7 @@ public extension Method {
|
|||
"inputTypes": parameters.map { $0.typeWithoutAttributes }.joined(separator: ", "),
|
||||
"isOptional": isOptional,
|
||||
"hasClosureParams": hasClosureParams,
|
||||
"hasOptionalParams": hasOptionalParams,
|
||||
"attributes": attributes.filter { $0.isSupported },
|
||||
]
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
public struct MethodParameter: Token, Equatable {
|
||||
public var label: String?
|
||||
public var name: String
|
||||
public var type: String
|
||||
public var type: WrappableType
|
||||
public var range: CountableRange<Int>
|
||||
public var nameRange: CountableRange<Int>
|
||||
public var isInout: Bool
|
||||
|
@ -23,7 +23,7 @@ public struct MethodParameter: Token, Equatable {
|
|||
}
|
||||
|
||||
public var typeWithoutAttributes: String {
|
||||
return type.replacingOccurrences(of: "@escaping", with: "").replacingOccurrences(of: "@autoclosure", with: "").trimmed
|
||||
return type.withoutAttributes.sugarized.trimmed
|
||||
}
|
||||
|
||||
public func isEqual(to other: Token) -> Bool {
|
||||
|
@ -34,9 +34,13 @@ public struct MethodParameter: Token, Equatable {
|
|||
public var isClosure: Bool {
|
||||
return typeWithoutAttributes.hasPrefix("(") && typeWithoutAttributes.range(of: "->") != nil
|
||||
}
|
||||
|
||||
public var isOptional: Bool {
|
||||
return type.isOptional
|
||||
}
|
||||
|
||||
public var isEscaping: Bool {
|
||||
return isClosure && (type.hasPrefix("@escaping") || type.hasSuffix(")?"))
|
||||
return isClosure && (type.containsAttribute(named: "@escaping") || type.isOptional)
|
||||
}
|
||||
|
||||
public func serialize() -> [String : Any] {
|
||||
|
@ -47,6 +51,7 @@ public struct MethodParameter: Token, Equatable {
|
|||
"labelAndName": labelAndName,
|
||||
"typeWithoutAttributes": typeWithoutAttributes,
|
||||
"isClosure": isClosure,
|
||||
"isOptional": isOptional,
|
||||
"isEscaping": isEscaping
|
||||
]
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
public struct ProtocolMethod: Method {
|
||||
public var name: String
|
||||
public var accessibility: Accessibility
|
||||
public var returnType: WrappableType
|
||||
public var returnSignature: String
|
||||
public var range: CountableRange<Int>
|
||||
public var nameRange: CountableRange<Int>
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
//
|
||||
// WrappableType.swift
|
||||
// CuckooGeneratorFramework
|
||||
//
|
||||
// Created by Matyáš Kříž on 13/03/2019.
|
||||
//
|
||||
|
||||
public enum WrappableType {
|
||||
indirect case optional(WrappableType)
|
||||
indirect case implicitlyUnwrappedOptional(WrappableType)
|
||||
indirect case attributed(WrappableType, attributes: [String])
|
||||
case type(String)
|
||||
|
||||
public var sugarized: String {
|
||||
switch self {
|
||||
case .optional(let wrapped):
|
||||
return "\(wrapped.sugarized)?"
|
||||
case .implicitlyUnwrappedOptional(let wrapped):
|
||||
return "\(wrapped.sugarized)!"
|
||||
case .attributed(let wrapped, let attributes):
|
||||
return "\(attributes.joined(separator: " ")) \(wrapped.sugarized)"
|
||||
case .type(let type):
|
||||
return type
|
||||
}
|
||||
}
|
||||
|
||||
public var sugarizedExplicitOnly: String {
|
||||
switch self {
|
||||
case .optional(let wrapped), .implicitlyUnwrappedOptional(let wrapped):
|
||||
return "\(wrapped.sugarizedExplicitOnly)?"
|
||||
case .attributed(let wrapped, let attributes):
|
||||
return "\(attributes.joined(separator: " ")) \(wrapped.sugarizedExplicitOnly)"
|
||||
case .type(let type):
|
||||
return type
|
||||
}
|
||||
}
|
||||
|
||||
public var desugarized: String {
|
||||
switch self {
|
||||
case .optional(let wrapped), .implicitlyUnwrappedOptional(let wrapped):
|
||||
return "Optional<\(wrapped.desugarized)>"
|
||||
case .attributed(let wrapped, let attributes):
|
||||
return "\(attributes.joined(separator: " ")) \(wrapped.desugarized)"
|
||||
case .type(let type):
|
||||
return type
|
||||
}
|
||||
}
|
||||
|
||||
public var unoptionaled: WrappableType {
|
||||
switch self {
|
||||
case .optional(let wrapped), .implicitlyUnwrappedOptional(let wrapped):
|
||||
return wrapped.unoptionaled
|
||||
case .attributed(let wrapped, let attributes):
|
||||
return .attributed(wrapped.unoptionaled, attributes: attributes)
|
||||
case .type:
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
public var unwrapped: WrappableType {
|
||||
switch self {
|
||||
case .optional(let wrapped), .implicitlyUnwrappedOptional(let wrapped):
|
||||
return wrapped
|
||||
case .attributed(let wrapped, let attributes):
|
||||
return .attributed(wrapped.unwrapped, attributes: attributes)
|
||||
case .type:
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
public var withoutAttributes: WrappableType {
|
||||
switch self {
|
||||
case .optional(let wrapped):
|
||||
return .optional(wrapped.withoutAttributes)
|
||||
case .implicitlyUnwrappedOptional(let wrapped):
|
||||
return .implicitlyUnwrappedOptional(wrapped.withoutAttributes)
|
||||
case .attributed(let wrapped, let attributes):
|
||||
return wrapped
|
||||
case .type(let typeString):
|
||||
return .type(typeString)
|
||||
}
|
||||
}
|
||||
|
||||
public var isOptional: Bool {
|
||||
switch self {
|
||||
case .optional, .implicitlyUnwrappedOptional:
|
||||
return true
|
||||
case .attributed(let wrapped, let attributes):
|
||||
return wrapped.isOptional
|
||||
case .type:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public init(parsing value: String) {
|
||||
let trimmedValue = value.trimmed
|
||||
let optionalPrefix = "Optional<"
|
||||
if trimmedValue.hasPrefix("@") {
|
||||
let (attributes, resultString) = ["@autoclosure", "@escaping", "@noescape"]
|
||||
.reduce(([], trimmedValue)) { acc, next -> ([String], String) in
|
||||
var (attributes, resultString) = acc
|
||||
guard let range = resultString.range(of: next) else { return acc }
|
||||
resultString.removeSubrange(range)
|
||||
attributes.append(next)
|
||||
return (attributes, resultString)
|
||||
}
|
||||
self = .attributed(WrappableType(parsing: resultString), attributes: attributes)
|
||||
} else if trimmedValue.hasSuffix("?") {
|
||||
if trimmedValue.contains("->") && !trimmedValue.hasSuffix(")?") {
|
||||
self = .type(trimmedValue)
|
||||
} else {
|
||||
self = .optional(WrappableType(parsing: String(trimmedValue.dropLast())))
|
||||
}
|
||||
} else if trimmedValue.hasPrefix(optionalPrefix) {
|
||||
self = .optional(WrappableType(parsing: String(trimmedValue.dropFirst(optionalPrefix.count).dropLast())))
|
||||
} else if trimmedValue.hasSuffix("!") {
|
||||
self = .implicitlyUnwrappedOptional(WrappableType(parsing: String(trimmedValue.dropLast())))
|
||||
} else {
|
||||
self = .type(trimmedValue)
|
||||
}
|
||||
}
|
||||
|
||||
public func containsAttribute(named attribute: String) -> Bool {
|
||||
switch self {
|
||||
case .optional(let wrapped), .implicitlyUnwrappedOptional(let wrapped):
|
||||
return wrapped.containsAttribute(named: attribute)
|
||||
case .attributed(let wrapped, let attributes):
|
||||
return attributes.contains(attribute.trimmed)
|
||||
case .type(let typeString):
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension WrappableType: CustomStringConvertible {
|
||||
public var description: String {
|
||||
return sugarized
|
||||
}
|
||||
}
|
||||
|
||||
extension WrappableType: Equatable {
|
||||
public static func ==(lhs: WrappableType, rhs: WrappableType) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.optional(let lhsWrapped), .optional(let rhsWrapped)),
|
||||
(.implicitlyUnwrappedOptional(let lhsWrapped), .implicitlyUnwrappedOptional(let rhsWrapped)):
|
||||
return lhsWrapped == rhsWrapped
|
||||
case (.attributed(let lhsWrapped, let lhsAttributes), .attributed(let rhsWrapped, let rhsAttributes)):
|
||||
return lhsWrapped == rhsWrapped && lhsAttributes == rhsAttributes
|
||||
case (.type(let lhsType), .type(let rhsType)):
|
||||
return lhsType == rhsType
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,6 +15,8 @@ public protocol Matchable {
|
|||
|
||||
/// Matcher for this instance. This should be an equalTo type of a matcher, but it is not required.
|
||||
var matcher: ParameterMatcher<MatchedType> { get }
|
||||
|
||||
var optionalMatcher: ParameterMatcher<MatchedType?> { get }
|
||||
}
|
||||
|
||||
public extension Matchable {
|
||||
|
@ -31,92 +33,61 @@ public extension Matchable {
|
|||
}
|
||||
}
|
||||
|
||||
extension Bool: Matchable {
|
||||
public var matcher: ParameterMatcher<Bool> {
|
||||
extension Optional: Matchable where Wrapped: Matchable, Wrapped.MatchedType == Wrapped {
|
||||
public typealias MatchedType = Wrapped?
|
||||
|
||||
public var matcher: ParameterMatcher<Wrapped?> {
|
||||
return ParameterMatcher<Wrapped?> { other in
|
||||
switch (self, other) {
|
||||
case (.none, .none):
|
||||
return true
|
||||
case (.some(let lhs), .some(let rhs)):
|
||||
return lhs.matcher.matches(rhs)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Matchable where Self == MatchedType {
|
||||
public var optionalMatcher: ParameterMatcher<MatchedType?> {
|
||||
return Optional(self).matcher
|
||||
}
|
||||
}
|
||||
|
||||
extension Matchable where Self: Equatable {
|
||||
public var matcher: ParameterMatcher<Self> {
|
||||
return equal(to: self)
|
||||
}
|
||||
}
|
||||
|
||||
extension String: Matchable {
|
||||
public var matcher: ParameterMatcher<String> {
|
||||
return equal(to: self)
|
||||
}
|
||||
}
|
||||
extension Bool: Matchable {}
|
||||
|
||||
extension Float: Matchable {
|
||||
public var matcher: ParameterMatcher<Float> {
|
||||
return equal(to: self)
|
||||
}
|
||||
}
|
||||
extension String: Matchable {}
|
||||
|
||||
extension Double: Matchable {
|
||||
public var matcher: ParameterMatcher<Double> {
|
||||
return equal(to: self)
|
||||
}
|
||||
}
|
||||
extension Float: Matchable {}
|
||||
|
||||
extension Character: Matchable {
|
||||
public var matcher: ParameterMatcher<Character> {
|
||||
return equal(to: self)
|
||||
}
|
||||
}
|
||||
extension Double: Matchable {}
|
||||
|
||||
extension Int: Matchable {
|
||||
public var matcher: ParameterMatcher<Int> {
|
||||
return equal(to: self)
|
||||
}
|
||||
}
|
||||
extension Character: Matchable {}
|
||||
|
||||
extension Int8: Matchable {
|
||||
public var matcher: ParameterMatcher<Int8> {
|
||||
return equal(to: self)
|
||||
}
|
||||
}
|
||||
extension Int: Matchable {}
|
||||
|
||||
extension Int16: Matchable {
|
||||
public var matcher: ParameterMatcher<Int16> {
|
||||
return equal(to: self)
|
||||
}
|
||||
}
|
||||
extension Int8: Matchable {}
|
||||
|
||||
extension Int32: Matchable {
|
||||
public var matcher: ParameterMatcher<Int32> {
|
||||
return equal(to: self)
|
||||
}
|
||||
}
|
||||
extension Int16: Matchable {}
|
||||
|
||||
extension Int64: Matchable {
|
||||
public var matcher: ParameterMatcher<Int64> {
|
||||
return equal(to: self)
|
||||
}
|
||||
}
|
||||
extension Int32: Matchable {}
|
||||
|
||||
extension UInt: Matchable {
|
||||
public var matcher: ParameterMatcher<UInt> {
|
||||
return equal(to: self)
|
||||
}
|
||||
}
|
||||
extension Int64: Matchable {}
|
||||
|
||||
extension UInt8: Matchable {
|
||||
public var matcher: ParameterMatcher<UInt8> {
|
||||
return equal(to: self)
|
||||
}
|
||||
}
|
||||
extension UInt: Matchable {}
|
||||
|
||||
extension UInt16: Matchable {
|
||||
public var matcher: ParameterMatcher<UInt16> {
|
||||
return equal(to: self)
|
||||
}
|
||||
}
|
||||
extension UInt8: Matchable {}
|
||||
|
||||
extension UInt32: Matchable {
|
||||
public var matcher: ParameterMatcher<UInt32> {
|
||||
return equal(to: self)
|
||||
}
|
||||
}
|
||||
extension UInt16: Matchable {}
|
||||
|
||||
extension UInt64: Matchable {
|
||||
public var matcher: ParameterMatcher<UInt64> {
|
||||
return equal(to: self)
|
||||
}
|
||||
}
|
||||
extension UInt32: Matchable {}
|
||||
|
||||
extension UInt64: Matchable {}
|
||||
|
|
|
@ -17,6 +17,12 @@ public struct ParameterMatcher<T>: Matchable {
|
|||
public var matcher: ParameterMatcher<T> {
|
||||
return self
|
||||
}
|
||||
|
||||
public var optionalMatcher: ParameterMatcher<Optional<T>> {
|
||||
return ParameterMatcher<Optional<T>> { other in
|
||||
other.map(self.matchesFunction) ?? false
|
||||
}
|
||||
}
|
||||
|
||||
public func matches(_ input: T) -> Bool {
|
||||
return matchesFunction(input)
|
||||
|
|
|
@ -82,7 +82,7 @@ public class MockManager {
|
|||
return stub
|
||||
}
|
||||
|
||||
public func verify<IN, OUT>(_ method: String, callMatcher: CallMatcher, parameterMatchers: [ParameterMatcher<IN>], sourceLocation: SourceLocation) -> __DoNotUse<OUT> {
|
||||
public func verify<IN, OUT>(_ method: String, callMatcher: CallMatcher, parameterMatchers: [ParameterMatcher<IN>], sourceLocation: SourceLocation) -> __DoNotUse<IN, OUT> {
|
||||
var calls: [StubCall] = []
|
||||
var indexesToRemove: [Int] = []
|
||||
for (i, stubCall) in stubCalls.enumerated() {
|
||||
|
|
|
@ -20,6 +20,12 @@ public func wrap<M: Matchable, IN>(matchable: M, mapping: @escaping (IN) -> M.Ma
|
|||
}
|
||||
}
|
||||
|
||||
public func wrap<M: Matchable, IN, O>(matchable: M, mapping: @escaping (IN) -> M.MatchedType?) -> ParameterMatcher<IN> where M.MatchedType == O {
|
||||
return ParameterMatcher {
|
||||
return matchable.optionalMatcher.matches(mapping($0))
|
||||
}
|
||||
}
|
||||
|
||||
public typealias SourceLocation = (file: StaticString, line: UInt)
|
||||
|
||||
public func escapingStub<IN, OUT>(for closure: (IN) -> OUT) -> (IN) -> OUT {
|
||||
|
|
|
@ -13,12 +13,12 @@ public struct VerifyProperty<T> {
|
|||
private let sourceLocation: SourceLocation
|
||||
|
||||
@discardableResult
|
||||
public func get() -> __DoNotUse<T> {
|
||||
public func get() -> __DoNotUse<Void, T> {
|
||||
return manager.verify(getterName(name), callMatcher: callMatcher, parameterMatchers: [] as [ParameterMatcher<Void>], sourceLocation: sourceLocation)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func set<M: Matchable>(_ matcher: M) -> __DoNotUse<Void> where M.MatchedType == T {
|
||||
public func set<M: Matchable>(_ matcher: M) -> __DoNotUse<T, Void> where M.MatchedType == T {
|
||||
return manager.verify(setterName(name), callMatcher: callMatcher, parameterMatchers: [matcher.matcher], sourceLocation: sourceLocation)
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ public struct VerifyReadOnlyProperty<T> {
|
|||
private let sourceLocation: SourceLocation
|
||||
|
||||
@discardableResult
|
||||
public func get() -> __DoNotUse<T> {
|
||||
public func get() -> __DoNotUse<Void, T> {
|
||||
return manager.verify(getterName(name), callMatcher: callMatcher, parameterMatchers: [] as [ParameterMatcher<Void>], sourceLocation: sourceLocation)
|
||||
}
|
||||
|
||||
|
|
|
@ -7,4 +7,4 @@
|
|||
//
|
||||
|
||||
/// Marker struct for use as a return type in verification.
|
||||
public struct __DoNotUse<T> { }
|
||||
public struct __DoNotUse<IN, OUT> { }
|
||||
|
|
|
@ -191,7 +191,7 @@ class ClassTest: XCTestCase {
|
|||
}
|
||||
|
||||
func testInout() {
|
||||
let mock = MockInoutMethod()
|
||||
let mock = MockInoutMethodClass()
|
||||
stub(mock) { mock in
|
||||
when(mock.inoutko(param: anyInt())).then { param in
|
||||
print(param)
|
||||
|
@ -201,6 +201,23 @@ class ClassTest: XCTestCase {
|
|||
var integer = 12
|
||||
mock.inoutko(param: &integer)
|
||||
}
|
||||
|
||||
func testOptionals() {
|
||||
let mock = MockOptionalParamsClass()
|
||||
|
||||
stub(mock) { mock in
|
||||
// when(mock.clashingFunction(param1: anyInt(), param2: "Henlo Fren")).thenDoNothing()
|
||||
// when(mock.clashingFunction(param1: anyInt(), param2: Optional("What's the question?"))).then { print("What's 6 times 9? \($0.0)") }
|
||||
// mock.function(param: "string")
|
||||
when(mock.function(param: "string")).thenDoNothing()
|
||||
}
|
||||
|
||||
// mock.clashingFunction(param1: Optional(22), param2: "Henlo Fren")
|
||||
// mock.clashingFunction(param1: 42, param2: Optional("What's the question?"))
|
||||
|
||||
// verify(mock).clashingFunction(param1: anyInt(), param2: "Henlo Fren")
|
||||
// verify(mock).clashingFunction(param1: 42, param2: Optional("What's the question?"))
|
||||
}
|
||||
|
||||
private enum TestError: Error {
|
||||
case unknown
|
||||
|
|
|
@ -223,7 +223,7 @@ class FinalFields {
|
|||
}
|
||||
}
|
||||
|
||||
class InoutMethod {
|
||||
class InoutMethodClass {
|
||||
func inoutko(param: inout Int) {
|
||||
|
||||
}
|
||||
|
@ -236,3 +236,23 @@ class InoutMethod {
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
class OptionalParamsClass {
|
||||
func function(param: String?) {
|
||||
|
||||
}
|
||||
|
||||
// the next two methods are exactly the same except for parameter types
|
||||
// this is not ambiguous for Swift, however, mocking this in a way so
|
||||
// that the stubbing and verifying calls wouldn't be ambiguous would require
|
||||
// some serious amount of hacks, so we decided to postpone this feature for now
|
||||
// see `clashingFunction` calls in tests to see how to disambiguate if you're ever
|
||||
// in need of two almost identical methods
|
||||
func clashingFunction(param1: Int?, param2: String) {
|
||||
|
||||
}
|
||||
|
||||
func clashingFunction(param1: Int, param2: String?) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -145,17 +145,17 @@ class StubbingTest: XCTestCase {
|
|||
when(stub.subSubMethod()).thenReturn("not nil")
|
||||
|
||||
// sub-class
|
||||
when(stub.withImplicitlyUnwrappedOptional(i: anyInt())).thenReturn("implicit unwrapped return")
|
||||
when(stub.withThrows()).thenReturn(10)
|
||||
when(stub.withNamedTuple(tuple: any())).thenReturn(11)
|
||||
when(stub.subclassMethod()).thenReturn(12)
|
||||
when(stub.withOptionalClosureAndReturn(anyString(), closure: isNil())).thenReturn(2)
|
||||
when(stub.withClosureAndParam(anyString(), closure: anyClosure())).thenReturn(3)
|
||||
when(stub.withMultClosures(closure: anyClosure(), closureB: anyClosure(), closureC: anyClosure())).thenReturn(4)
|
||||
when(stub.withThrowingClosure(closure: anyThrowingClosure())).thenReturn("throwing closure")
|
||||
when(stub.withThrowingClosureThrows(closure: anyThrowingClosure())).thenReturn("closure throwing")
|
||||
when(stub.withThrowingEscapingClosure(closure: anyThrowingClosure())).thenReturn("escaping closure")
|
||||
when(stub.withThrowingOptionalClosureThrows(closure: anyOptionalThrowingClosure())).thenReturn("optional closure throwing")
|
||||
// when(stub.withImplicitlyUnwrappedOptional(i: anyInt())).thenReturn("implicit unwrapped return")
|
||||
// when(stub.withThrows()).thenReturn(10)
|
||||
// when(stub.withNamedTuple(tuple: any())).thenReturn(11)
|
||||
// when(stub.subclassMethod()).thenReturn(12)
|
||||
// when(stub.withOptionalClosureAndReturn(anyString(), closure: isNil())).thenReturn(2)
|
||||
// when(stub.withClosureAndParam(anyString(), closure: anyClosure())).thenReturn(3)
|
||||
// when(stub.withMultClosures(closure: anyClosure(), closureB: anyClosure(), closureC: anyClosure())).thenReturn(4)
|
||||
// when(stub.withThrowingClosure(closure: anyThrowingClosure())).thenReturn("throwing closure")
|
||||
// when(stub.withThrowingClosureThrows(closure: anyThrowingClosure())).thenReturn("closure throwing")
|
||||
// when(stub.withThrowingEscapingClosure(closure: anyThrowingClosure())).thenReturn("escaping closure")
|
||||
// when(stub.withThrowingOptionalClosureThrows(closure: anyOptionalThrowingClosure())).thenReturn("optional closure throwing")
|
||||
when(stub.methodWithParameter(anyString())).thenReturn("parameter string")
|
||||
when(stub.methodWithParameter(anyInt())).thenReturn("parameter int")
|
||||
|
||||
|
@ -181,9 +181,9 @@ class StubbingTest: XCTestCase {
|
|||
when(stub.withEscape(anyString(), action: anyClosure())).then { _ in
|
||||
callWithEscape = true
|
||||
}
|
||||
when(stub.withOptionalClosure(anyString(), closure: anyClosure())).then { _ in
|
||||
callWithOptionalClosure = true
|
||||
}
|
||||
// when(stub.withOptionalClosure(anyString(), closure: anyClosure())).then { _ in
|
||||
// callWithOptionalClosure = true
|
||||
// }
|
||||
when(stub.withLabelAndUnderscore(labelA: anyString(), anyString())).then { _ in
|
||||
callWithLabelAndUnderscore = true
|
||||
}
|
||||
|
@ -266,13 +266,13 @@ class StubbingTest: XCTestCase {
|
|||
verify(mock, times(1)).withThrows()
|
||||
verify(mock, times(1)).withNamedTuple(tuple: any())
|
||||
verify(mock, times(1)).subclassMethod()
|
||||
verify(mock, times(1)).withOptionalClosureAndReturn(anyString(), closure: isNil())
|
||||
// verify(mock, times(1)).withOptionalClosureAndReturn(anyString(), closure: isNil())
|
||||
verify(mock, times(1)).withClosureAndParam(anyString(), closure: anyClosure())
|
||||
verify(mock, times(1)).withMultClosures(closure: anyClosure(), closureB: anyClosure(), closureC: anyClosure())
|
||||
verify(mock, times(1)).withThrowingClosure(closure: anyThrowingClosure())
|
||||
verify(mock, times(1)).withThrowingClosureThrows(closure: anyThrowingClosure())
|
||||
verify(mock, times(1)).withThrowingEscapingClosure(closure: anyThrowingClosure())
|
||||
verify(mock, times(1)).withThrowingOptionalClosureThrows(closure: anyOptionalThrowingClosure())
|
||||
// verify(mock, times(1)).withThrowingOptionalClosureThrows(closure: anyOptionalThrowingClosure())
|
||||
verify(mock, times(1)).methodWithParameter(anyString())
|
||||
verify(mock, times(1)).methodWithParameter(anyInt())
|
||||
|
||||
|
|
Loading…
Reference in New Issue