Sources/MockoloFramework/Templates/VariableTemplate.swift (280 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 VariableModel {
func applyVariableTemplate(name: String,
type: SwiftType,
encloser: String,
isStatic: Bool,
customModifiers: [String: Modifier]?,
allowSetCallCount: Bool,
shouldOverride: Bool,
accessLevel: String,
context: RenderContext,
arguments: GenerationArguments) -> String {
let underlyingSetCallCount = "\(name)\(String.setCallCountSuffix)"
let underlyingVarDefaultVal = type.defaultVal()
var underlyingType = type.typeName
if underlyingVarDefaultVal == nil {
underlyingType = type.underlyingType
}
let propertyWrapper = propertyWrapper != nil ? "\(propertyWrapper!) " : ""
let overrideStr = shouldOverride ? "\(String.override) " : ""
var acl = accessLevel
if !acl.isEmpty {
acl = acl + " "
}
var assignVal = ""
if !shouldOverride, let val = underlyingVarDefaultVal {
assignVal = " = \(val)"
}
let privateSetSpace = allowSetCallCount ? "" : "\(String.privateSet) "
let modifierTypeStr: String
if let customModifiers = self.customModifiers,
let customModifier: Modifier = customModifiers[name] {
modifierTypeStr = customModifier.rawValue + " "
} else {
modifierTypeStr = ""
}
let staticSpace = isStatic ? "\(String.static) " : ""
switch storageKind {
case .stored(let needSetCount):
let setCallCountVarDecl = needSetCount ? """
\(1.tab)\(acl)\(staticSpace)\(privateSetSpace)var \(underlyingSetCallCount) = 0
""" : ""
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(
genericTypeParams: [],
params: [],
isAsync: effects.isAsync,
throwing: effects.throwing,
returnType: type
).render(context: .init(
overloadingResolvedName: name, // var cannot overload. this is ok
enclosingType: context.enclosingType,
annotatedTypeKind: context.annotatedTypeKind
), arguments: arguments) ?? "")
.addingIndent(1)
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 \(effects.applyTemplate()){
\(body)
\(2.tab)}
\(1.tab)}
"""
}
}
func applyCombineVariableTemplate(name: String,
type: SwiftType,
encloser: String,
shouldOverride: Bool,
isStatic: Bool,
accessLevel: String) -> String? {
let typeName = type.typeName
guard
typeName.starts(with: String.anyPublisherLeftAngleBracket),
let range = typeName.range(of: String.anyPublisherLeftAngleBracket),
let lastIdx = typeName.lastIndex(of: ">")
else {
return nil
}
let typeParamStr = typeName[range.upperBound..<lastIdx]
var subjectTypeStr = ""
var errorTypeStr = ""
if let lastCommaIndex = typeParamStr.lastIndex(of: ",") {
subjectTypeStr = String(typeParamStr[..<lastCommaIndex])
let nextIndex = typeParamStr.index(after: lastCommaIndex)
errorTypeStr = String(typeParamStr[nextIndex..<typeParamStr.endIndex]).trimmingCharacters(in: .whitespaces)
}
let subjectType = SwiftType(subjectTypeStr)
let subjectDefaultValue = subjectType.defaultVal()
let staticSpace = isStatic ? "\(String.static) " : ""
let acl = accessLevel.isEmpty ? "" : accessLevel + " "
let thisStr = isStatic ? encloser : "self"
let overrideStr = shouldOverride ? "\(String.override) " : ""
switch combineType {
case .property(_, var wrapperPropertyName):
// Using a property wrapper to back this publisher, such as @Published
var template = "\n"
var isWrapperPropertyOptionalOrForceUnwrapped = false
if let publishedAliasModel = wrapperAliasModel, let type = publishedAliasModel.type {
// If the property required by the protocol/class cannot be optional, the wrapper property will be the underlyingProperty
// i.e. @Published var _myType: MyType!
let wrapperPropertyDefaultValue = type.defaultVal()
if wrapperPropertyDefaultValue == nil {
wrapperPropertyName = "_\(wrapperPropertyName)"
}
isWrapperPropertyOptionalOrForceUnwrapped = wrapperPropertyDefaultValue == nil || type.isOptional
}
var mapping = ""
if !subjectType.isOptional, isWrapperPropertyOptionalOrForceUnwrapped {
// If the wrapper property is of type: MyType?/MyType!, but the publisher is of type MyType
mapping = ".map { $0! }"
} else if subjectType.isOptional, !isWrapperPropertyOptionalOrForceUnwrapped {
// If the wrapper property is of type: MyType, but the publisher is of type MyType?
mapping = ".map { $0 }"
}
let setErrorType = ".setFailureType(to: \(errorTypeStr).self)"
template += """
\(1.tab)\(acl)\(staticSpace)\(overrideStr)var \(name): \(typeName) { return \(thisStr).$\(wrapperPropertyName)\(mapping)\(setErrorType).\(String.eraseToAnyPublisher)() }
"""
return template
default:
// Using a combine subject to back this publisher
var combineSubjectType = combineType ?? .passthroughSubject
var defaultValue: String? = ""
if case .currentValueSubject = combineSubjectType {
defaultValue = subjectDefaultValue
}
// Unable to generate default value for this CurrentValueSubject. Default back to PassthroughSubject.
//
if defaultValue == nil {
combineSubjectType = .passthroughSubject
}
let underlyingSubjectName = "\(name)\(String.subjectSuffix)"
let template = """
\(1.tab)\(acl)\(staticSpace)\(overrideStr)var \(name): \(typeName) { return \(thisStr).\(underlyingSubjectName).\(String.eraseToAnyPublisher)() }
\(1.tab)\(acl)\(staticSpace)\(String.privateSet) var \(underlyingSubjectName) = \(combineSubjectType.typeName)<\(typeParamStr)>(\(defaultValue ?? ""))
"""
return template
}
}
func applyRxVariableTemplate(name: String,
type: SwiftType,
encloser: String,
rxTypes: [String: String]?,
shouldOverride: Bool,
allowSetCallCount: Bool,
isStatic: Bool,
accessLevel: String) -> String? {
let staticSpace = isStatic ? "\(String.static) " : ""
let privateSetSpace = allowSetCallCount ? "" : "\(String.privateSet) "
if let rxTypes = rxTypes, !rxTypes.isEmpty {
let (subjectType, _, subjectVal) = type.parseRxVar(overrides: rxTypes, overrideKey: name, isInitParam: true)
if let underlyingSubjectType = subjectType {
let underlyingSubjectName = "\(name)\(String.subjectSuffix)"
let underlyingSetCallCount = "\(underlyingSubjectName)\(String.setCallCountSuffix)"
var defaultValAssignStr = ""
if let underlyingSubjectTypeDefaultVal = subjectVal {
defaultValAssignStr = " = \(underlyingSubjectTypeDefaultVal)"
} else {
defaultValAssignStr = ": \(underlyingSubjectType)!"
}
let acl = accessLevel.isEmpty ? "" : accessLevel + " "
let overrideStr = shouldOverride ? "\(String.override) " : ""
let setCallCountStmt = "\(underlyingSetCallCount) += 1"
let fallbackName = "\(String.underlyingVarPrefix)\(name)"
var fallbackType = type.typeName
if type.isIUO || type.isOptional {
fallbackType.removeLast()
}
let template = """
\(1.tab)\(acl)\(staticSpace)\(privateSetSpace)var \(underlyingSetCallCount) = 0
\(1.tab)\(staticSpace)var \(fallbackName): \(fallbackType)? { didSet { \(setCallCountStmt) } }
\(1.tab)\(acl)\(staticSpace)var \(underlyingSubjectName)\(defaultValAssignStr) { didSet { \(setCallCountStmt) } }
\(1.tab)\(acl)\(staticSpace)\(overrideStr)var \(name): \(type.typeName) {
\(2.tab)get { return \(fallbackName) ?? \(underlyingSubjectName) }
\(2.tab)set { if let val = newValue as? \(underlyingSubjectType) { \(underlyingSubjectName) = val } else { \(fallbackName) = newValue } }
\(1.tab)}
"""
return template
}
}
let typeName = type.typeName
if let range = typeName.range(of: String.observableLeftAngleBracket), let lastIdx = typeName.lastIndex(of: ">") {
let typeParamStr = typeName[range.upperBound..<lastIdx]
let underlyingSubjectName = "\(name)\(String.subjectSuffix)"
let underlyingSetCallCount = "\(underlyingSubjectName)\(String.setCallCountSuffix)"
let publishSubjectName = underlyingSubjectName
let publishSubjectType = "\(String.publishSubject)<\(typeParamStr)>"
let behaviorSubjectName = "\(name)\(String.behaviorSubject)"
let behaviorSubjectType = "\(String.behaviorSubject)<\(typeParamStr)>"
let replaySubjectName = "\(name)\(String.replaySubject)"
let replaySubjectType = "\(String.replaySubject)<\(typeParamStr)>"
let acl = accessLevel.isEmpty ? "" : accessLevel + " "
let overrideStr = shouldOverride ? "\(String.override) " : ""
let whichSubject = "\(underlyingSubjectName)Kind"
let fallbackName = "_\(name)"
let fallbackType = typeName[typeName.startIndex..<typeName.index(after: lastIdx)]
let setCallCountStmt = "\(underlyingSetCallCount) += 1"
let template = """
\(1.tab)\(staticSpace)private var \(whichSubject) = 0
\(1.tab)\(acl)\(staticSpace)\(privateSetSpace)var \(underlyingSetCallCount) = 0
\(1.tab)\(acl)\(staticSpace)var \(publishSubjectName) = \(publishSubjectType)() { didSet { \(setCallCountStmt) } }
\(1.tab)\(acl)\(staticSpace)var \(replaySubjectName) = \(replaySubjectType).create(bufferSize: 1) { didSet { \(setCallCountStmt) } }
\(1.tab)\(acl)\(staticSpace)var \(behaviorSubjectName): \(behaviorSubjectType)! { didSet { \(setCallCountStmt) } }
\(1.tab)\(acl)\(staticSpace)var \(fallbackName): \(fallbackType)! { didSet { \(setCallCountStmt) } }
\(1.tab)\(acl)\(staticSpace)\(overrideStr)var \(name): \(typeName) {
\(2.tab)get {
\(3.tab)if \(whichSubject) == 0 {
\(4.tab)return \(publishSubjectName)
\(3.tab)} else if \(whichSubject) == 1 {
\(4.tab)return \(behaviorSubjectName)
\(3.tab)} else if \(whichSubject) == 2 {
\(4.tab)return \(replaySubjectName)
\(3.tab)} else {
\(4.tab)return \(fallbackName)
\(3.tab)}
\(2.tab)}
\(2.tab)set {
\(3.tab)if let val = newValue as? \(publishSubjectType) {
\(4.tab)\(publishSubjectName) = val
\(4.tab)\(whichSubject) = 0
\(3.tab)} else if let val = newValue as? \(behaviorSubjectType) {
\(4.tab)\(behaviorSubjectName) = val
\(4.tab)\(whichSubject) = 1
\(3.tab)} else if let val = newValue as? \(replaySubjectType) {
\(4.tab)\(replaySubjectName) = val
\(4.tab)\(whichSubject) = 2
\(3.tab)} else {
\(4.tab)\(fallbackName) = newValue
\(4.tab)\(whichSubject) = 3
\(3.tab)}
\(2.tab)}
\(1.tab)}
"""
return template
}
return nil
}
}
extension VariableModel.GetterEffects {
fileprivate func applyTemplate() -> String {
return applyFunctionSuffixTemplate(isAsync: isAsync, throwing: throwing)
}
fileprivate var callerMarkers: String {
var clauses: [String] = []
if throwing.hasError {
clauses.append(.try)
}
if isAsync {
clauses.append(.await)
}
return clauses.map { "\($0) " }.joined()
}
}