Sources/MockoloFramework/Templates/MethodTemplate.swift (207 lines of code) (raw):
//
// 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.
//
import Foundation
extension MethodModel {
func applyMethodTemplate(overloadingResolvedName: String,
arguments: GenerationArguments,
isOverride: Bool,
handler: ClosureModel?,
context: RenderContext) -> String {
if case .initKind = kind {
return "" // ClassTemplate needs to handle this as it needs a context of all the vars
}
guard let handler, let enclosingType = context.enclosingType else { return "" }
return Renderer(
model: self,
context: context,
arguments: arguments,
overloadingResolvedName: overloadingResolvedName,
isOverride: isOverride,
handler: handler,
enclosingType: enclosingType
)
.render()
}
struct Renderer {
var model: MethodModel
var context: RenderContext
var arguments: GenerationArguments
var overloadingResolvedName: String
var isOverride: Bool
var handler: ClosureModel
var enclosingType: SwiftType
func render() -> String {
let body: String
if arguments.useTemplateFunc
&& !model.throwing.hasError
&& (handler.type(enclosingType: enclosingType, requiresSendable: context.requiresSendable).cast == nil) {
let handlerParamValsStr = model.params.map { (arg) -> String in
if arg.type.typeName.hasPrefix(String.autoclosure) {
return arg.name.safeName + "()"
}
return arg.name.safeName
}.joined(separator: ", ")
let defaultVal = model.returnType?.defaultVal()
var mockReturn = ".error"
if model.returnType?.isVoid ?? true {
mockReturn = ".void"
} else if let val = defaultVal {
mockReturn = ".val(\(val))"
}
body = """
\(2.tab)mockFunc(&\(callCountVarName))(\"\(model.name)\", \(handlerVarName)?(\(handlerParamValsStr)), \(mockReturn))
"""
} else {
let handlerReturn = handler.render(context: context, arguments: arguments)
if context.requiresSendable {
let paramNamesStr: String?
if let argsHistory = model.argsHistory, argsHistory.enable(force: arguments.enableFuncArgsHistory) {
paramNamesStr = argsHistory.capturableParamLabels.joined(separator: ", ")
} else {
paramNamesStr = nil
}
body = [
paramNamesStr.map { "\(2.tab)warnIfNotSendable(\($0))" },
"\(2.tab)let \(handlerVarName) = \(stateVarName).withLock { state in",
"\(3.tab)state.callCount += 1",
paramNamesStr.map { "\(3.tab)state.argValues.append(.init((\($0))))" },
"\(3.tab)return state.handler",
"\(2.tab)}",
handlerReturn,
].compactMap { $0 }.joined(separator: "\n")
} else {
let argsHistoryCaptureCall: String?
if let argsHistory = model.argsHistory, argsHistory.enable(force: arguments.enableFuncArgsHistory) {
let argsHistoryCapture = argsHistory.render(context: context, arguments: arguments) ?? ""
argsHistoryCaptureCall = argsHistoryCapture
} else {
argsHistoryCaptureCall = nil
}
body = [
"\(2.tab)\(callCountVarName) += 1",
argsHistoryCaptureCall.map { "\(2.tab)\($0)" },
handlerReturn,
].compactMap { $0 }.joined(separator: "\n")
}
}
let wrapped = model.isSubscript ? """
\(2.tab)get {
\(body)
\(2.tab)}
\(2.tab)set { }
""" : body
let overrideStr = isOverride ? String.override.withSpace : ""
let modifierTypeStr: String
if let customModifier: Modifier = model.customModifiers[model.name] {
modifierTypeStr = customModifier.rawValue + " "
} else {
modifierTypeStr = ""
}
let keyword = model.isSubscript ? "" : "func "
let genericTypeDeclsStr = model.genericTypeParams.render(context: context, arguments: arguments)
let genericTypesStr = genericTypeDeclsStr.isEmpty ? "" : "<\(genericTypeDeclsStr)>"
let paramDeclsStr = model.params.render(context: context, arguments: arguments)
let suffixStr = applyFunctionSuffixTemplate(
isAsync: model.isAsync,
throwing: model.throwing
)
let genericWhereStr = model.genericWhereClause.map { "\($0) " } ?? ""
let functionDecl = """
\(1.tab)\(declModifiers)\(overrideStr)\(modifierTypeStr)\(keyword)\(model.name)\(genericTypesStr)(\(paramDeclsStr)) \(suffixStr)\(returnClause)\(genericWhereStr){
\(wrapped)
\(1.tab)}
"""
let decls: [String?] = [
stateVarDecl,
callCountVarDecl,
argsHistoryVarDecl,
handlerVarDecl,
functionDecl,
]
return "\n" + decls.compactMap { $0 }.joined(separator: "\n")
}
var declModifiers: String {
let acl = model.accessLevel.isEmpty ? "" : model.accessLevel.withSpace
let staticModifier = model.isStatic ? String.static.withSpace : ""
return acl + staticModifier
}
var returnClause: String {
if let returnType = model.returnType {
return "-> \(returnType.typeName) "
} else {
return ""
}
}
var handlerVarName: String {
return overloadingResolvedName + .handlerSuffix
}
var stateVarName: String {
return overloadingResolvedName + .stateSuffix
}
var callCountVarName: String {
return overloadingResolvedName + .callCountSuffix
}
var argsHistoryVarName: String {
return overloadingResolvedName + .argsHistorySuffix
}
var stateVarDecl: String? {
guard context.requiresSendable else { return nil }
let handlerType = handler.type(enclosingType: enclosingType, requiresSendable: context.requiresSendable).type.typeName
let argumentsTupleType: String
if let argsHistory = model.argsHistory, argsHistory.enable(force: arguments.enableFuncArgsHistory) {
argumentsTupleType = argsHistory.capturedValueType.typeName
} else {
argumentsTupleType = .neverType
}
return "\(1.tab)private let \(stateVarName) = MockoloMutex(MockoloHandlerState<\(argumentsTupleType), \(handlerType)>())"
}
var callCountVarDecl: String {
if !context.requiresSendable {
let privateSetSpace = arguments.allowSetCallCount ? "" : "\(String.privateSet) "
return "\(1.tab)\(declModifiers)\(privateSetSpace)var \(callCountVarName) = 0"
} else {
if arguments.allowSetCallCount {
return """
\(1.tab)\(declModifiers)var \(callCountVarName): Int {
\(2.tab)get { \(stateVarName).withLock(\\.callCount) }
\(2.tab)set { \(stateVarName).withLock { $0.callCount = newValue } }
\(1.tab)}
"""
} else {
return """
\(1.tab)\(declModifiers)var \(callCountVarName): Int {
\(2.tab)return \(stateVarName).withLock(\\.callCount)
\(1.tab)}
"""
}
}
}
var argsHistoryVarDecl: String? {
if let argsHistory = model.argsHistory, argsHistory.enable(force: arguments.enableFuncArgsHistory) {
let capturedValueType = argsHistory.capturedValueType.typeName
if !context.requiresSendable {
return "\(1.tab)\(declModifiers)var \(argsHistoryVarName) = [\(capturedValueType)]()"
} else {
return """
\(1.tab)\(declModifiers)var \(argsHistoryVarName): [\(capturedValueType)] {
\(2.tab)return \(stateVarName).withLock(\\.argValues).map(\\.value)
\(1.tab)}
"""
}
}
return nil
}
var handlerVarDecl: String {
let handlerType = handler.type(enclosingType: enclosingType, requiresSendable: context.requiresSendable).type.typeName // ?? "Any"
let handlerVarType = "(\(handlerType))?"
if !context.requiresSendable {
return "\(1.tab)\(declModifiers)var \(handlerVarName): \(handlerVarType)"
} else {
return """
\(1.tab)\(declModifiers)var \(handlerVarName): \(handlerVarType) {
\(2.tab)get { \(stateVarName).withLock(\\.handler) }
\(2.tab)set { \(stateVarName).withLock { $0.handler = newValue } }
\(1.tab)}
"""
}
}
}
}