From 8f458e2ed8d866d0abd0d957f8a2ad6a256c5e14 Mon Sep 17 00:00:00 2001 From: Iceman Date: Sat, 26 Oct 2024 20:52:57 +0900 Subject: [PATCH] Add async and throwing property support (#267) * Read accessor and omit setCallCount if protocol has no setter * Remove unnecessary static var handling * Support get async and get throws property accessor * small rename and remove unused code * generate initialize for computed var * computed getter handles with the same way of method * remove deprecated method * Add test for throwing never * small formatting * Update Sources/MockoloFramework/Parsers/SwiftSyntaxExtensions.swift Co-authored-by: Fumiya Tanaka * split syntax representation --------- Co-authored-by: Fumiya Tanaka --- .../Models/ArgumentsHistoryModel.swift | 4 +- .../Models/ClosureModel.swift | 21 ++-- .../MockoloFramework/Models/MethodModel.swift | 17 +-- .../Models/ThrowingKind.swift | 35 ++++++ .../Models/VariableModel.swift | 16 ++- .../Parsers/SwiftSyntaxExtensions.swift | 63 ++++++++++- .../Templates/ClosureTemplate.swift | 7 +- .../Templates/MethodTemplate.swift | 8 +- .../Templates/NominalTemplate.swift | 24 ++-- .../Templates/ThrowingKindTemplate.swift | 30 +++++ .../Templates/VariableTemplate.swift | 105 +++++++++++++++--- .../Utils/StringExtensions.swift | 14 +-- .../MockoloFramework/Utils/TypeParser.swift | 15 ++- Tests/TestActor/FixtureActor.swift | 7 +- Tests/TestCombine/FixtureCombine.swift | 9 +- .../FixtureExistentialAny.swift | 12 +- .../FixtureNonSimpleFuncs.swift | 4 +- .../FixtureModifiersTypes.swift | 11 +- .../FixtureModuleOverrides.swift | 4 +- Tests/TestOverloads/FixtureDuplicates4.swift | 10 +- Tests/TestOverloads/FixtureInheritance.swift | 4 +- Tests/TestPATs/FixturePAT.swift | 4 +- Tests/TestRx/FixtureRxVars.swift | 16 +-- Tests/TestVars/FixtureAsyncThrowsVars.swift | 87 +++++++++++++++ Tests/TestVars/FixtureNonSimpleVars.swift | 26 ++--- Tests/TestVars/VarTests.swift | 10 ++ 26 files changed, 428 insertions(+), 135 deletions(-) create mode 100644 Sources/MockoloFramework/Models/ThrowingKind.swift create mode 100644 Sources/MockoloFramework/Templates/ThrowingKindTemplate.swift create mode 100644 Tests/TestVars/FixtureAsyncThrowsVars.swift diff --git a/Sources/MockoloFramework/Models/ArgumentsHistoryModel.swift b/Sources/MockoloFramework/Models/ArgumentsHistoryModel.swift index 758e7902..a613c80b 100644 --- a/Sources/MockoloFramework/Models/ArgumentsHistoryModel.swift +++ b/Sources/MockoloFramework/Models/ArgumentsHistoryModel.swift @@ -4,7 +4,6 @@ final class ArgumentsHistoryModel: Model { var name: String var type: SwiftType var offset: Int64 = .max - let suffix: String let capturableParamNames: [String] let capturableParamTypes: [SwiftType] let isHistoryAnnotated: Bool @@ -13,7 +12,7 @@ final class ArgumentsHistoryModel: Model { return .argumentsHistory } - init?(name: String, genericTypeParams: [ParamModel], params: [ParamModel], isHistoryAnnotated: Bool, suffix: String) { + init?(name: String, genericTypeParams: [ParamModel], params: [ParamModel], isHistoryAnnotated: Bool) { // Value contains closure is not supported. let capturables = params.filter { !$0.type.hasClosure && !$0.type.isEscaping && !$0.type.isAutoclosure } guard !capturables.isEmpty else { @@ -21,7 +20,6 @@ final class ArgumentsHistoryModel: Model { } self.name = name + .argsHistorySuffix - self.suffix = suffix self.isHistoryAnnotated = isHistoryAnnotated self.capturableParamNames = capturables.map(\.name.safeName) diff --git a/Sources/MockoloFramework/Models/ClosureModel.swift b/Sources/MockoloFramework/Models/ClosureModel.swift index be03462c..4da33906 100644 --- a/Sources/MockoloFramework/Models/ClosureModel.swift +++ b/Sources/MockoloFramework/Models/ClosureModel.swift @@ -24,31 +24,36 @@ final class ClosureModel: Model { let genericTypeNames: [String] let paramNames: [String] let paramTypes: [SwiftType] - let suffix: String + let isAsync: Bool + let throwing: ThrowingKind var modelType: ModelType { return .closure } - - init(name: String, genericTypeParams: [ParamModel], paramNames: [String], paramTypes: [SwiftType], suffix: String, returnType: SwiftType, encloser: String) { + init(name: String, genericTypeParams: [ParamModel], paramNames: [String], paramTypes: [SwiftType], isAsync: Bool, throwing: ThrowingKind, returnType: SwiftType, encloser: String) { self.name = name + .handlerSuffix - self.suffix = suffix + self.isAsync = isAsync + self.throwing = throwing let genericTypeNameList = genericTypeParams.map(\.name) self.genericTypeNames = genericTypeNameList self.paramNames = paramNames self.paramTypes = paramTypes self.funcReturnType = returnType - self.type = SwiftType.toClosureType(with: paramTypes, typeParams: genericTypeNameList, suffix: suffix, returnType: returnType, encloser: encloser) + self.type = SwiftType.toClosureType( + params: paramTypes, + typeParams: genericTypeNameList, + isAsync: isAsync, + throwing: throwing, + returnType: returnType, + encloser: encloser + ) } func render(with identifier: String, encloser: String, useTemplateFunc: Bool = false, useMockObservable: Bool = false, allowSetCallCount: Bool = false, mockFinal: Bool = false, enableFuncArgsHistory: Bool = false, disableCombineDefaultValues: Bool = false) -> String? { return applyClosureTemplate(name: identifier + .handlerSuffix, - type: type, - genericTypeNames: genericTypeNames, paramVals: paramNames, paramTypes: paramTypes, - suffix: suffix, returnDefaultType: funcReturnType) } } diff --git a/Sources/MockoloFramework/Models/MethodModel.swift b/Sources/MockoloFramework/Models/MethodModel.swift index 390506e9..8698eeb3 100644 --- a/Sources/MockoloFramework/Models/MethodModel.swift +++ b/Sources/MockoloFramework/Models/MethodModel.swift @@ -38,7 +38,8 @@ final class MethodModel: Model { var modelDescription: String? = nil var isStatic: Bool let shouldOverride: Bool - let suffix: String + let isAsync: Bool + let throwing: ThrowingKind let kind: MethodKind let funcsWithArgsHistory: [String] let customModifiers: [String : Modifier] @@ -134,8 +135,7 @@ final class MethodModel: Model { let ret = ArgumentsHistoryModel(name: name, genericTypeParams: genericTypeParams, params: params, - isHistoryAnnotated: funcsWithArgsHistory.contains(name), - suffix: suffix) + isHistoryAnnotated: funcsWithArgsHistory.contains(name)) return ret }() @@ -151,7 +151,8 @@ final class MethodModel: Model { genericTypeParams: genericTypeParams, paramNames: paramNames, paramTypes: paramTypes, - suffix: suffix, + isAsync: isAsync, + throwing: throwing, returnType: type, encloser: encloser) @@ -167,8 +168,8 @@ final class MethodModel: Model { genericTypeParams: [ParamModel], genericWhereClause: String?, params: [ParamModel], - throwsOrRethrows: String?, - asyncOrReasync: String?, + isAsync: Bool, + throwing: ThrowingKind, isStatic: Bool, offset: Int64, length: Int64, @@ -178,7 +179,8 @@ final class MethodModel: Model { processed: Bool) { self.name = name.trimmingCharacters(in: .whitespaces) self.type = SwiftType(typeName.trimmingCharacters(in: .whitespaces)) - self.suffix = [asyncOrReasync, throwsOrRethrows].compactMap { $0 }.joined(separator: " ") + self.isAsync = isAsync + self.throwing = throwing self.offset = offset self.length = length self.kind = kind @@ -237,7 +239,6 @@ final class MethodModel: Model { params: params, returnType: type, accessLevel: accessLevel, - suffix: suffix, argsHistory: argsHistory, handler: handler(encloser: encloser)) return result diff --git a/Sources/MockoloFramework/Models/ThrowingKind.swift b/Sources/MockoloFramework/Models/ThrowingKind.swift new file mode 100644 index 00000000..5277e5c8 --- /dev/null +++ b/Sources/MockoloFramework/Models/ThrowingKind.swift @@ -0,0 +1,35 @@ +// +// Copyright (c) 2018. Uber Technologies +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +enum ThrowingKind: Equatable { + case none + case any + case `rethrows` + case typed(errorType: String) + + var hasError: Bool { + switch self { + case .none: + return false + case .any: + return true + case .rethrows: + return true + case .typed(let errorType): + return errorType != .neverType && errorType != "Swift.\(String.neverType)" + } + } +} diff --git a/Sources/MockoloFramework/Models/VariableModel.swift b/Sources/MockoloFramework/Models/VariableModel.swift index a2007fdc..a3139b68 100644 --- a/Sources/MockoloFramework/Models/VariableModel.swift +++ b/Sources/MockoloFramework/Models/VariableModel.swift @@ -1,6 +1,17 @@ import Foundation final class VariableModel: Model { + struct GetterEffects: Equatable { + var isAsync: Bool + var throwing: ThrowingKind + static let empty: GetterEffects = .init(isAsync: false, throwing: .none) + } + + enum MockStorageType { + case stored(needsSetCount: Bool) + case computed(GetterEffects) + } + var name: String var type: SwiftType var offset: Int64 @@ -13,6 +24,7 @@ final class VariableModel: Model { var filePath: String = "" var isStatic = false var shouldOverride = false + let storageType: MockStorageType var rxTypes: [String: String]? var customModifiers: [String: Modifier]? var modelDescription: String? = nil @@ -29,7 +41,7 @@ final class VariableModel: Model { } var underlyingName: String { - if isStatic || type.defaultVal() == nil { + if type.defaultVal() == nil { return "_\(name)" } return name @@ -40,6 +52,7 @@ final class VariableModel: Model { acl: String?, encloserType: DeclType, isStatic: Bool, + storageType: MockStorageType, canBeInitParam: Bool, offset: Int64, rxTypes: [String: String]?, @@ -51,6 +64,7 @@ final class VariableModel: Model { self.type = SwiftType(typeName.trimmingCharacters(in: .whitespaces)) self.offset = offset self.isStatic = isStatic + self.storageType = storageType self.shouldOverride = encloserType == .classType self.canBeInitParam = canBeInitParam self.processed = processed diff --git a/Sources/MockoloFramework/Parsers/SwiftSyntaxExtensions.swift b/Sources/MockoloFramework/Parsers/SwiftSyntaxExtensions.swift index f5c32b44..c4a2ac81 100644 --- a/Sources/MockoloFramework/Parsers/SwiftSyntaxExtensions.swift +++ b/Sources/MockoloFramework/Parsers/SwiftSyntaxExtensions.swift @@ -368,11 +368,44 @@ extension VariableDeclSyntax { typeName = vtype } + let storageType: VariableModel.MockStorageType + switch v.accessorBlock?.accessors { + case .accessors(let accessorDecls): + if accessorDecls.contains(where: { $0.accessorSpecifier.tokenKind == .keyword(.set) }) { + storageType = .stored(needsSetCount: true) + } else if let getterDecl = accessorDecls.first(where: { $0.accessorSpecifier.tokenKind == .keyword(.get) }) { + if getterDecl.body == nil { // is protoccol + var getterEffects = VariableModel.GetterEffects.empty + if getterDecl.effectSpecifiers?.asyncSpecifier != nil { + getterEffects.isAsync = true + } + if let `throws` = getterDecl.effectSpecifiers?.throwsClause { + getterEffects.throwing = .init(`throws`) + } + if getterEffects == .empty { + storageType = .stored(needsSetCount: false) + } else { + storageType = .computed(getterEffects) + } + } else { // is class + storageType = .computed(.empty) + } + } else { + // will never happens + storageType = .stored(needsSetCount: false) // fallback + } + case .getter: + storageType = .computed(.empty) + case nil: + storageType = .stored(needsSetCount: true) + } + let varmodel = VariableModel(name: name, typeName: typeName, acl: acl, encloserType: declType, isStatic: isStatic, + storageType: storageType, canBeInitParam: potentialInitParam, offset: v.offset, rxTypes: metadata?.varTypes, @@ -402,8 +435,8 @@ extension SubscriptDeclSyntax { genericTypeParams: genericTypeParams, genericWhereClause: genericWhereClause, params: params, - throwsOrRethrows: nil, - asyncOrReasync: nil, + isAsync: false, + throwing: .none, isStatic: isStatic, offset: self.offset, length: self.length, @@ -432,8 +465,8 @@ extension FunctionDeclSyntax { genericTypeParams: genericTypeParams, genericWhereClause: genericWhereClause, params: params, - throwsOrRethrows: self.signature.effectSpecifiers?.throwsClause?.throwsSpecifier.text, - asyncOrReasync: self.signature.effectSpecifiers?.asyncSpecifier?.text, + isAsync: self.signature.effectSpecifiers?.asyncSpecifier != nil, + throwing: .init(self.signature.effectSpecifiers?.throwsClause), isStatic: isStatic, offset: self.offset, length: self.length, @@ -473,8 +506,8 @@ extension InitializerDeclSyntax { genericTypeParams: genericTypeParams, genericWhereClause: genericWhereClause, params: params, - throwsOrRethrows: self.signature.effectSpecifiers?.throwsClause?.throwsSpecifier.text, - asyncOrReasync: self.signature.effectSpecifiers?.asyncSpecifier?.text, + isAsync: self.signature.effectSpecifiers?.asyncSpecifier != nil, + throwing: .init(self.signature.effectSpecifiers?.throwsClause), isStatic: false, offset: self.offset, length: self.length, @@ -796,3 +829,21 @@ extension Trivia { return nil } } + +extension ThrowingKind { + fileprivate init(_ syntax: ThrowsClauseSyntax?) { + guard let syntax else { + self = .none + return + } + if syntax.throwsSpecifier.tokenKind == .keyword(.rethrows) { + self = .rethrows + } else { + if let type = syntax.type { + self = .typed(errorType: type.trimmedDescription) + } else { + self = .any + } + } + } +} diff --git a/Sources/MockoloFramework/Templates/ClosureTemplate.swift b/Sources/MockoloFramework/Templates/ClosureTemplate.swift index 3bbc995a..75e0ffcd 100644 --- a/Sources/MockoloFramework/Templates/ClosureTemplate.swift +++ b/Sources/MockoloFramework/Templates/ClosureTemplate.swift @@ -18,11 +18,8 @@ import Foundation extension ClosureModel { func applyClosureTemplate(name: String, - type: SwiftType, - genericTypeNames: [String], paramVals: [String]?, paramTypes: [SwiftType]?, - suffix: String, returnDefaultType: SwiftType) -> String { var handlerParamValsStr = "" @@ -46,8 +43,8 @@ extension ClosureModel { let handlerReturnDefault = renderReturnDefaultStatement(name: name, type: returnDefaultType) let prefix = [ - suffix.hasThrowsOrRethrows ? String.try + " " : nil, - suffix.hasAsync ? String.await + " " : nil, + throwing.hasError ? String.try + " " : nil, + isAsync ? String.await + " " : nil, ].compactMap { $0 }.joined() let returnStr = returnDefaultType.typeName.isEmpty ? "" : "return " diff --git a/Sources/MockoloFramework/Templates/MethodTemplate.swift b/Sources/MockoloFramework/Templates/MethodTemplate.swift index 2276a93f..b1abada3 100644 --- a/Sources/MockoloFramework/Templates/MethodTemplate.swift +++ b/Sources/MockoloFramework/Templates/MethodTemplate.swift @@ -31,7 +31,6 @@ extension MethodModel { params: [ParamModel], returnType: SwiftType, accessLevel: String, - suffix: String, argsHistory: ArgumentsHistoryModel?, handler: ClosureModel?) -> String { var template = "" @@ -59,14 +58,17 @@ extension MethodModel { let handlerVarType = handler.type.typeName // ?? "Any" let handlerReturn = handler.render(with: identifier, encloser: "") ?? "" - let suffixStr = suffix.isEmpty ? "" : "\(suffix) " + let suffixStr = [ + isAsync ? String.async : nil, + throwing.applyThrowingTemplate(), + ].compactMap { $0 }.joined(separator: " ") + " " let returnStr = returnTypeName.isEmpty ? "" : "-> \(returnTypeName)" let staticStr = isStatic ? String.static + " " : "" let keyword = isSubscript ? "" : "func " var body = "" if useTemplateFunc { - let callMockFunc = !suffix.hasThrowsOrRethrows && (handler.type.cast?.isEmpty ?? false) + let callMockFunc = !throwing.hasError && (handler.type.cast?.isEmpty ?? false) if callMockFunc { let handlerParamValsStr = params.map { (arg) -> String in if arg.type.typeName.hasPrefix(String.autoclosure) { diff --git a/Sources/MockoloFramework/Templates/NominalTemplate.swift b/Sources/MockoloFramework/Templates/NominalTemplate.swift index 50a2ee23..e6f5cefb 100644 --- a/Sources/MockoloFramework/Templates/NominalTemplate.swift +++ b/Sources/MockoloFramework/Templates/NominalTemplate.swift @@ -147,8 +147,8 @@ extension NominalModel { if needParamedInit { var paramsAssign = "" let params = initParamCandidates - .map { (element: Model) -> String in - if let val = element.type.defaultVal(with: overrides, overrideKey: element.name, isInitParam: true) { + .map { (element: VariableModel) -> String in + if let val = element.type.defaultVal(with: overrides, overrideKey: element.name, isInitParam: true) { return "\(element.name): \(element.type.typeName) = \(val)" } var prefix = "" @@ -158,13 +158,16 @@ extension NominalModel { } } return "\(element.name): \(prefix)\(element.type.typeName)" - } - .joined(separator: ", ") + } + .joined(separator: ", ") - - paramsAssign = initParamCandidates.map { p in - return "\(2.tab)self.\(p.underlyingName) = \(p.name.safeName)" - + paramsAssign = initParamCandidates.map { (element: VariableModel) in + switch element.storageType { + case .stored: + return "\(2.tab)self.\(element.underlyingName) = \(element.name.safeName)" + case .computed: + return "\(2.tab)self.\(element.name)\(String.handlerSuffix) = { \(element.name.safeName) }" + } }.joined(separator: "\n") initTemplate = """ @@ -195,7 +198,10 @@ extension NominalModel { let genericTypeDeclsStr = m.genericTypeParams.compactMap {$0.render(with: "", encloser: "")}.joined(separator: ", ") let genericTypesStr = genericTypeDeclsStr.isEmpty ? "" : "<\(genericTypeDeclsStr)>" let paramDeclsStr = m.params.compactMap{$0.render(with: "", encloser: "")}.joined(separator: ", ") - let suffixStr = m.suffix.isEmpty ? "" : "\(m.suffix) " + let suffixStr = [ + m.isAsync ? String.async : nil, + m.throwing.applyThrowingTemplate(), + ].compactMap { $0 }.joined(separator: " ") + " " if override { let paramsList = m.params.map { param in diff --git a/Sources/MockoloFramework/Templates/ThrowingKindTemplate.swift b/Sources/MockoloFramework/Templates/ThrowingKindTemplate.swift new file mode 100644 index 00000000..8ec73a9b --- /dev/null +++ b/Sources/MockoloFramework/Templates/ThrowingKindTemplate.swift @@ -0,0 +1,30 @@ +// +// Copyright (c) 2018. Uber Technologies +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +extension ThrowingKind { + func applyThrowingTemplate() -> String? { + switch self { + case .none: + return nil + case .any: + return .throws + case .rethrows: + return .rethrows + case .typed(let errorType): + return "\(String.throws)(\(errorType))" + } + } +} diff --git a/Sources/MockoloFramework/Templates/VariableTemplate.swift b/Sources/MockoloFramework/Templates/VariableTemplate.swift index f6b19b16..0aaa9f97 100644 --- a/Sources/MockoloFramework/Templates/VariableTemplate.swift +++ b/Sources/MockoloFramework/Templates/VariableTemplate.swift @@ -48,7 +48,6 @@ extension VariableModel { } let privateSetSpace = allowSetCallCount ? "" : "\(String.privateSet) " - let setCallCountStmt = "\(underlyingSetCallCount) += 1" let modifierTypeStr: String if let customModifiers = self.customModifiers, @@ -58,27 +57,79 @@ extension VariableModel { modifierTypeStr = "" } - var template = "" - if isStatic || underlyingVarDefaultVal == nil { - let staticSpace = isStatic ? "\(String.static) " : "" - template = """ + let staticSpace = isStatic ? "\(String.static) " : "" + switch storageType { + case .stored(let needSetCount): + let setCallCountVarDecl = needSetCount ? """ \(1.tab)\(acl)\(staticSpace)\(privateSetSpace)var \(underlyingSetCallCount) = 0 - \(1.tab)\(propertyWrapper)\(staticSpace)private var \(underlyingName): \(underlyingType) \(assignVal) { didSet { \(setCallCountStmt) } } + """ : "" + + var accessorBlockItems: [String] = [] + if needSetCount { + let didSetBlock = """ + didSet { \(underlyingSetCallCount) += 1 } + """ + accessorBlockItems.append(didSetBlock) + } + + let accessorBlock: String + switch accessorBlockItems.count { + case 0: accessorBlock = "" + case 1: accessorBlock = " { \(accessorBlockItems[0]) }" + default: accessorBlock = """ + { + \(accessorBlockItems.map { "\(2.tab)\($0)" }.joined(separator: "\n")) + \(1.tab)} + """ + } + + let template: String + if underlyingVarDefaultVal == nil { + template = """ + + \(setCallCountVarDecl) + \(1.tab)\(propertyWrapper)\(staticSpace)private var \(underlyingName): \(underlyingType) \(assignVal)\(accessorBlock) + \(1.tab)\(acl)\(staticSpace)\(overrideStr)\(modifierTypeStr)var \(name): \(type.typeName) { + \(2.tab)get { return \(underlyingName) } + \(2.tab)set { \(underlyingName) = newValue } + \(1.tab)} + """ + } else { + template = """ + + \(setCallCountVarDecl) + \(1.tab)\(propertyWrapper)\(acl)\(staticSpace)\(overrideStr)\(modifierTypeStr)var \(name): \(type.typeName) \(assignVal)\(accessorBlock) + """ + } + + return template + + case .computed(let effects): + let body = (ClosureModel( + name: "", + genericTypeParams: [], + paramNames: [], + paramTypes: [], + isAsync: effects.isAsync, + throwing: effects.throwing, + returnType: type, + encloser: "" + ).render(with: name, encloser: "") ?? "") + .split(separator: "\n") + .map { "\(1.tab)\($0)" } + .joined(separator: "\n") + + return """ + + \(1.tab)\(acl)\(staticSpace)var \(name)\(String.handlerSuffix): (() \(effects.applyTemplate())-> \(type.typeName))? \(1.tab)\(acl)\(staticSpace)\(overrideStr)\(modifierTypeStr)var \(name): \(type.typeName) { - \(2.tab)get { return \(underlyingName) } - \(2.tab)set { \(underlyingName) = newValue } + \(2.tab)get \(effects.applyTemplate()){ + \(body) + \(2.tab)} \(1.tab)} """ - } else { - template = """ - - \(1.tab)\(acl)\(privateSetSpace)var \(underlyingSetCallCount) = 0 - \(1.tab)\(propertyWrapper)\(acl)\(overrideStr)\(modifierTypeStr)var \(name): \(type.typeName) \(assignVal) { didSet { \(setCallCountStmt) } } - """ } - - return template } func applyCombineVariableTemplate(name: String, @@ -304,4 +355,26 @@ extension VariableModel { } } +extension VariableModel.GetterEffects { + fileprivate func applyTemplate() -> String { + var clauses: [String] = [] + if isAsync { + clauses.append(.async) + } + if let throwSyntax = throwing.applyThrowingTemplate() { + clauses.append(throwSyntax) + } + return clauses.map { "\($0) " }.joined() + } + fileprivate var callerMarkers: String { + var clauses: [String] = [] + if throwing.hasError { + clauses.append(.try) + } + if isAsync { + clauses.append(.await) + } + return clauses.map { "\($0) " }.joined() + } +} diff --git a/Sources/MockoloFramework/Utils/StringExtensions.swift b/Sources/MockoloFramework/Utils/StringExtensions.swift index 3bfe3489..0bf2598e 100644 --- a/Sources/MockoloFramework/Utils/StringExtensions.swift +++ b/Sources/MockoloFramework/Utils/StringExtensions.swift @@ -48,6 +48,7 @@ extension String { static let unknownVal = "Unknown" static let prefix = "prefix" static let anyType = "Any" + static let neverType = "Never" static let any = "any" static let some = "some" static let anyObject = "AnyObject" @@ -110,19 +111,6 @@ extension String { /// """ - - var hasThrowsOrRethrows: Bool { - return components(separatedBy: .whitespaces).contains { component in - return component == .throws || component == .rethrows - } - } - - var hasAsync: Bool { - return components(separatedBy: .whitespaces).contains { component in - return component == .async - } - } - var safeName: String { var text = self if let keyword = text.withSyntaxText(Keyword.init), diff --git a/Sources/MockoloFramework/Utils/TypeParser.swift b/Sources/MockoloFramework/Utils/TypeParser.swift index b36d8648..7830dde6 100644 --- a/Sources/MockoloFramework/Utils/TypeParser.swift +++ b/Sources/MockoloFramework/Utils/TypeParser.swift @@ -512,9 +512,14 @@ public final class SwiftType { } - static func toClosureType(with params: [SwiftType], typeParams: [String], suffix: String, returnType: SwiftType, encloser: String) -> SwiftType { - - + static func toClosureType( + params: [SwiftType], + typeParams: [String], + isAsync: Bool, + throwing: ThrowingKind, + returnType: SwiftType, + encloser: String + ) -> SwiftType { let displayableParamTypes = params.map { (subtype: SwiftType) -> String in return subtype.processTypeParams(with: typeParams) } @@ -560,8 +565,8 @@ public final class SwiftType { } let suffixStr = [ - suffix.hasAsync ? String.async + " " : nil, - suffix.hasThrowsOrRethrows ? String.throws + " " : nil, + isAsync ? String.async + " " : nil, + throwing.hasError ? String.throws + " " : nil, ].compactMap { $0 }.joined() let typeStr = "((\(displayableParamStr)) \(suffixStr)-> \(displayableReturnType))?" diff --git a/Tests/TestActor/FixtureActor.swift b/Tests/TestActor/FixtureActor.swift index c4a33e29..6ed40963 100644 --- a/Tests/TestActor/FixtureActor.swift +++ b/Tests/TestActor/FixtureActor.swift @@ -23,8 +23,8 @@ actor FooMock: Foo { } fatalError("fooHandler returns can't have a default value thus its handler must be set") } - private(set) var barSetCallCount = 0 - var bar: Int = 0 { didSet { barSetCallCount += 1 } } + + var bar: Int = 0 } """ @@ -48,8 +48,7 @@ actor FooMock: Foo { } - private(set) var barSetCallCount = 0 - var bar: Int = 0 { didSet { barSetCallCount += 1 } } + var bar: Int = 0 private(set) var bazCallCount = 0 var bazHandler: ((String) async -> (Int))? diff --git a/Tests/TestCombine/FixtureCombine.swift b/Tests/TestCombine/FixtureCombine.swift index 0fb0a254..10ecf7f8 100644 --- a/Tests/TestCombine/FixtureCombine.swift +++ b/Tests/TestCombine/FixtureCombine.swift @@ -225,22 +225,19 @@ public class FooMock: Foo { public init() { } - public private(set) var myPublisherSetCallCount = 0 - private var _myPublisher: AnyPublisher! { didSet { myPublisherSetCallCount += 1 } } + private var _myPublisher: AnyPublisher! public var myPublisher: AnyPublisher { get { return _myPublisher } set { _myPublisher = newValue } } - public private(set) var dictionaryPublisherSetCallCount = 0 - private var _dictionaryPublisher: AnyPublisher, Never>! { didSet { dictionaryPublisherSetCallCount += 1 } } + private var _dictionaryPublisher: AnyPublisher, Never>! public var dictionaryPublisher: AnyPublisher, Never> { get { return _dictionaryPublisher } set { _dictionaryPublisher = newValue } } - public private(set) var noDefaultSubjectValueSetCallCount = 0 - private var _noDefaultSubjectValue: AnyPublisher! { didSet { noDefaultSubjectValueSetCallCount += 1 } } + private var _noDefaultSubjectValue: AnyPublisher! public var noDefaultSubjectValue: AnyPublisher { get { return _noDefaultSubjectValue } set { _noDefaultSubjectValue = newValue } diff --git a/Tests/TestExistentialAny/FixtureExistentialAny.swift b/Tests/TestExistentialAny/FixtureExistentialAny.swift index 321c6d5e..be40c1f5 100644 --- a/Tests/TestExistentialAny/FixtureExistentialAny.swift +++ b/Tests/TestExistentialAny/FixtureExistentialAny.swift @@ -20,29 +20,25 @@ class ExistentialAnyMock: ExistentialAny { } - private(set) var fooSetCallCount = 0 - private var _foo: P! { didSet { fooSetCallCount += 1 } } + private var _foo: P! var foo: P { get { return _foo } set { _foo = newValue } } - private(set) var barSetCallCount = 0 - private var _bar: (any R)! { didSet { barSetCallCount += 1 } } + private var _bar: (any R)! var bar: any R { get { return _bar } set { _bar = newValue } } - private(set) var bazSetCallCount = 0 - private var _baz: (any P & Q)! { didSet { bazSetCallCount += 1 } } + private var _baz: (any P & Q)! var baz: any P & Q { get { return _baz } set { _baz = newValue } } - private(set) var quxSetCallCount = 0 - private var _qux: ((any P) -> any P)! { didSet { quxSetCallCount += 1 } } + private var _qux: ((any P) -> any P)! var qux: (any P) -> any P { get { return _qux } set { _qux = newValue } diff --git a/Tests/TestFuncs/TestBasicFuncs/FixtureNonSimpleFuncs.swift b/Tests/TestFuncs/TestBasicFuncs/FixtureNonSimpleFuncs.swift index 9da2c024..7ecb7d5a 100644 --- a/Tests/TestFuncs/TestBasicFuncs/FixtureNonSimpleFuncs.swift +++ b/Tests/TestFuncs/TestBasicFuncs/FixtureNonSimpleFuncs.swift @@ -370,7 +370,7 @@ import Foundation /// \(String.mockAnnotation) protocol NonSimpleFuncs { -func pass(handler: @autoclosure () -> Int) rethrows -> T +func pass(handler: @autoclosure () -> Int) throws -> T } """ @@ -385,7 +385,7 @@ class NonSimpleFuncsMock: NonSimpleFuncs { private(set) var passCallCount = 0 var passHandler: ((@autoclosure () -> Int) throws -> (Any))? - func pass(handler: @autoclosure () -> Int) rethrows -> T { + func pass(handler: @autoclosure () -> Int) throws -> T { passCallCount += 1 if let passHandler = passHandler { return try passHandler(handler()) as! T diff --git a/Tests/TestModifiersTypes/FixtureModifiersTypes.swift b/Tests/TestModifiersTypes/FixtureModifiersTypes.swift index 0dd1a7af..56ced15b 100644 --- a/Tests/TestModifiersTypes/FixtureModifiersTypes.swift +++ b/Tests/TestModifiersTypes/FixtureModifiersTypes.swift @@ -17,8 +17,7 @@ class FooMock: Foo { } - private(set) var listenerSetCallCount = 0 - weak var listener: AnyObject? = nil { didSet { listenerSetCallCount += 1 } } + weak var listener: AnyObject? = nil private(set) var barFuncCallCount = 0 var barFuncHandler: (([Int]) -> ())? @@ -60,8 +59,8 @@ class FooMock: Foo { } - private(set) var listenerSetCallCount = 0 - dynamic var listener: AnyObject? = nil { didSet { listenerSetCallCount += 1 } } + + dynamic var listener: AnyObject? = nil private(set) var barFuncCallCount = 0 var barFuncHandler: (([Int]) -> ())? @@ -103,8 +102,8 @@ class FooMock: Foo { } - private(set) var listenerSetCallCount = 0 - var listener: AnyObject? = nil { didSet { listenerSetCallCount += 1 } } + + var listener: AnyObject? = nil private(set) var barFuncCallCount = 0 var barFuncHandler: (([Int]) -> ())? diff --git a/Tests/TestModuleNames/FixtureModuleOverrides.swift b/Tests/TestModuleNames/FixtureModuleOverrides.swift index 3f29d1c5..af3d13f5 100644 --- a/Tests/TestModuleNames/FixtureModuleOverrides.swift +++ b/Tests/TestModuleNames/FixtureModuleOverrides.swift @@ -17,8 +17,8 @@ class TaskRoutingMock: Foo.TaskRouting { init(bar: String = "") { self.bar = bar } - private(set) var barSetCallCount = 0 - var bar: String = "" { didSet { barSetCallCount += 1 } } + + var bar: String = "" private(set) var bazCallCount = 0 var bazHandler: (() -> (Double))? func baz() -> Double { diff --git a/Tests/TestOverloads/FixtureDuplicates4.swift b/Tests/TestOverloads/FixtureDuplicates4.swift index b92b9099..cfb8ccbd 100644 --- a/Tests/TestOverloads/FixtureDuplicates4.swift +++ b/Tests/TestOverloads/FixtureDuplicates4.swift @@ -110,7 +110,7 @@ public class FooMock: Foo { let sameNameVarFunc = """ /// \(String.mockAnnotation) -public protocol Bar: class { +public protocol Bar: AnyObject { var talk: Int { get } } @@ -131,8 +131,8 @@ public class BarMock: Bar { self.talk = talk } - public private(set) var talkSetCallCount = 0 - public var talk: Int = 0 { didSet { talkSetCallCount += 1 } } + + public var talk: Int = 0 } public class FooMock: Foo { @@ -144,8 +144,8 @@ public class FooMock: Foo { self.talk = talk } - public private(set) var talkSetCallCount = 0 - public var talk: Int = 0 { didSet { talkSetCallCount += 1 } } + + public var talk: Int = 0 public private(set) var talkDismissCallCount = 0 public var talkDismissHandler: ((Bool) -> ())? public func talk(_ dismiss: Bool) { diff --git a/Tests/TestOverloads/FixtureInheritance.swift b/Tests/TestOverloads/FixtureInheritance.swift index 4ca87227..df98dc43 100644 --- a/Tests/TestOverloads/FixtureInheritance.swift +++ b/Tests/TestOverloads/FixtureInheritance.swift @@ -111,8 +111,8 @@ class TestProtocolMock: TestProtocol { } - private(set) var someBoolSetCallCount = 0 - var someBool: Bool = false { didSet { someBoolSetCallCount += 1 } } + + var someBool: Bool = false private(set) var doSomethingCallCount = 0 var doSomethingHandler: (() -> ())? diff --git a/Tests/TestPATs/FixturePAT.swift b/Tests/TestPATs/FixturePAT.swift index 12f155f3..90bbb151 100644 --- a/Tests/TestPATs/FixturePAT.swift +++ b/Tests/TestPATs/FixturePAT.swift @@ -152,8 +152,8 @@ public class SomeTypeMock: SomeType { self._key = key } public typealias Key = String - public private(set) var keySetCallCount = 0 - private var _key: Key! { didSet { keySetCallCount += 1 } } + + private var _key: Key! public var key: Key { get { return _key } set { _key = newValue } diff --git a/Tests/TestRx/FixtureRxVars.swift b/Tests/TestRx/FixtureRxVars.swift index 2f3e2daa..40aa6f63 100644 --- a/Tests/TestRx/FixtureRxVars.swift +++ b/Tests/TestRx/FixtureRxVars.swift @@ -27,25 +27,25 @@ public class FooMock: Foo { self.someBar = someBar } - public private(set) var someBehaviorSetCallCount = 0 - private var _someBehavior: BehaviorSubject! { didSet { someBehaviorSetCallCount += 1 } } + + private var _someBehavior: BehaviorSubject! public var someBehavior: BehaviorSubject { get { return _someBehavior } set { _someBehavior = newValue } } - public private(set) var someReplySetCallCount = 0 - public var someReply: ReplaySubject = ReplaySubject.create(bufferSize: 1) { didSet { someReplySetCallCount += 1 } } - public private(set) var someVariableSetCallCount = 0 - private var _someVariable: Variable! { didSet { someVariableSetCallCount += 1 } } + public var someReply: ReplaySubject = ReplaySubject.create(bufferSize: 1) + + + private var _someVariable: Variable! public var someVariable: Variable { get { return _someVariable } set { _someVariable = newValue } } - public private(set) var someBarSetCallCount = 0 - public var someBar: Bar = BarMock() { didSet { someBarSetCallCount += 1 } } + + public var someBar: Bar = BarMock() } """ diff --git a/Tests/TestVars/FixtureAsyncThrowsVars.swift b/Tests/TestVars/FixtureAsyncThrowsVars.swift new file mode 100644 index 00000000..c73e579d --- /dev/null +++ b/Tests/TestVars/FixtureAsyncThrowsVars.swift @@ -0,0 +1,87 @@ +import MockoloFramework + +let asyncThrowsVars = """ +/// \(String.mockAnnotation) +public protocol AsyncThrowsVars { + var getOnly: Int { get } + static var getAndSet: Int { get set } + var getAndThrows: MyValue { get throws } + static var getAndAsync: MyValue { get async } + var getAndAsyncAndThrows: Int { get async throws(any Error) } +} +""" + +let asyncThrowsVarsMock = """ +public class AsyncThrowsVarsMock: AsyncThrowsVars { + public init() { } + public init(getOnly: Int = 0, getAndThrows: MyValue, getAndAsyncAndThrows: Int = 0) { + self.getOnly = getOnly + self.getAndThrowsHandler = { getAndThrows } + self.getAndAsyncAndThrowsHandler = { getAndAsyncAndThrows } + } + + + + public var getOnly: Int = 0 + + public static private(set) var getAndSetSetCallCount = 0 + public static var getAndSet: Int = 0 { didSet { getAndSetSetCallCount += 1 } } + + public var getAndThrowsHandler: (() throws -> MyValue)? + public var getAndThrows: MyValue { + get throws { + if let getAndThrowsHandler = getAndThrowsHandler { + return try getAndThrowsHandler() + } + fatalError("getAndThrowsHandler returns can't have a default value thus its handler must be set") + } + } + + public static var getAndAsyncHandler: (() async -> MyValue)? + public static var getAndAsync: MyValue { + get async { + if let getAndAsyncHandler = getAndAsyncHandler { + return await getAndAsyncHandler() + } + fatalError("getAndAsyncHandler returns can't have a default value thus its handler must be set") + } + } + + public var getAndAsyncAndThrowsHandler: (() async throws(any Error) -> Int)? + public var getAndAsyncAndThrows: Int { + get async throws(any Error) { + if let getAndAsyncAndThrowsHandler = getAndAsyncAndThrowsHandler { + return try await getAndAsyncAndThrowsHandler() + } + return 0 + } + } +} +""" + +let throwsNeverVars = """ +/// \(String.mockAnnotation) +protocol P { + var foo: Int { get throws(Never) } +} +""" + +let throwsNeverVarsMock = """ +class PMock: P { + init() { } + init(foo: Int = 0) { + self.fooHandler = { foo } + } + + + var fooHandler: (() throws(Never) -> Int)? + var foo: Int { + get throws(Never) { + if let fooHandler = fooHandler { + return fooHandler() + } + return 0 + } + } +} +""" diff --git a/Tests/TestVars/FixtureNonSimpleVars.swift b/Tests/TestVars/FixtureNonSimpleVars.swift index c69ae23f..4298229a 100644 --- a/Tests/TestVars/FixtureNonSimpleVars.swift +++ b/Tests/TestVars/FixtureNonSimpleVars.swift @@ -13,6 +13,7 @@ public protocol NonSimpleVars { var voidHandler: (() -> ()) { get } var hasDot: ModuleX.SomeType? { get } static var someVal: String { get } + static var someVal2: String { get set } } """ @@ -29,23 +30,22 @@ public class NonSimpleVarsMock: NonSimpleVars { } public private(set) var dictSetCallCount = 0 public var dict: Dictionary = Dictionary() { didSet { dictSetCallCount += 1 } } - public private(set) var closureVarSetCallCount = 0 - public var closureVar: ((_ arg: String) -> Void)? = nil { didSet { closureVarSetCallCount += 1 } } - public private(set) var voidHandlerSetCallCount = 0 - private var _voidHandler: ((() -> ()))! { didSet { voidHandlerSetCallCount += 1 } } + + public var closureVar: ((_ arg: String) -> Void)? = nil + + private var _voidHandler: ((() -> ()))! public var voidHandler: (() -> ()) { get { return _voidHandler } set { _voidHandler = newValue } } - public private(set) var hasDotSetCallCount = 0 - public var hasDot: ModuleX.SomeType? = nil { didSet { hasDotSetCallCount += 1 } } - - public static private(set) var someValSetCallCount = 0 - static private var _someVal: String = "" { didSet { someValSetCallCount += 1 } } - public static var someVal: String { - get { return _someVal } - set { _someVal = newValue } - } + + public var hasDot: ModuleX.SomeType? = nil + + + public static var someVal: String = "" + + public static private(set) var someVal2SetCallCount = 0 + public static var someVal2: String = "" { didSet { someVal2SetCallCount += 1 } } } """ diff --git a/Tests/TestVars/VarTests.swift b/Tests/TestVars/VarTests.swift index dbd272c0..42c8f11c 100644 --- a/Tests/TestVars/VarTests.swift +++ b/Tests/TestVars/VarTests.swift @@ -23,4 +23,14 @@ class VarTests: MockoloTestCase { dstContent: simpleVarsAllowCallCountMock, allowSetCallCount: true) } + + func testAsyncThrows() { + verify(srcContent: asyncThrowsVars, + dstContent: asyncThrowsVarsMock) + } + + func testThrowsNever() { + verify(srcContent: throwsNeverVars, + dstContent: throwsNeverVarsMock) + } }