Sources/MockoloFramework/Models/MethodModel.swift (203 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
public enum MethodKind: Equatable {
case funcKind
case initKind(required: Bool, override: Bool)
case subscriptKind
}
final class MethodModel: Model {
var filePath: String = ""
var data: Data? = nil
var name: String
var type: Type
var offset: Int64
let length: Int64
let accessLevel: String
var attributes: [String]? = nil
let genericTypeParams: [ParamModel]
var genericWhereClause: String? = nil
let params: [ParamModel]
let processed: Bool
var modelDescription: String? = nil
var isStatic: Bool
let shouldOverride: Bool
let suffix: String
let kind: MethodKind
let funcsWithArgsHistory: [String]
let customModifiers: [String : Modifier]
var modelType: ModelType {
return .method
}
private var staticKind: String {
return isStatic ? .static : ""
}
/// This is used to uniquely identify methods with the same signature and different generic requirements
var genericWhereClauseToSignatureComponent: String {
guard let genericWhereClause else {
return ""
}
let typeRequirementSyntax = ":"
let typeEqualitySyntax = "=="
var signatureComponents: [String] = []
genericWhereClause.deletingPrefix("where").components(separatedBy: ",").forEach { requirement in
if requirement.contains(typeRequirementSyntax) {
let components = requirement.components(separatedBy: typeRequirementSyntax).map{ $0.trimmingCharacters(in: .whitespaces) }
guard let key = components.first, let value = components.last else {
return
}
let valueDescription = value.replacingOccurrences(of: " ", with: "").replacingOccurrences(of: "&", with: "And")
signatureComponents.append(contentsOf: [key, valueDescription])
} else if requirement.contains(typeEqualitySyntax) {
let components = requirement.components(separatedBy: typeEqualitySyntax).map{ $0.trimmingCharacters(in: .whitespaces) }
guard let key = components.first, let value = components.last else {
return
}
signatureComponents.append(contentsOf: [key, value])
}
}
return signatureComponents.map { component in
var newComponent = component
newComponent.removeAll(where: { $0 == "."})
return newComponent
}.joined()
}
var isInitializer: Bool {
if case .initKind(_, _) = kind {
return true
}
return false
}
var isSubscript: Bool {
if case .subscriptKind = kind {
return true
}
return false
}
lazy var signatureComponents: [String] = {
let paramLabels = self.params.map {$0.label != "_" ? $0.label : ""}
let paramNames = self.params.map(\.name)
let paramTypes = self.params.map(\.type)
let nameString = self.name
var args = zip(paramLabels, paramNames).compactMap { (argLabel: String, argName: String) -> String? in
let val = argLabel.isEmpty ? argName : argLabel
if val.count < 2 || !nameString.lowercased().hasSuffix(val.lowercased()) {
return val.capitalizeFirstLetter
}
return nil
}
let genericTypeNames = self.genericTypeParams.map { $0.name.capitalizeFirstLetter + $0.type.displayName }
args.append(contentsOf: genericTypeNames)
if let genericWhereClause {
args.append(genericWhereClauseToSignatureComponent)
}
args.append(contentsOf: paramTypes.map(\.displayName))
var displayType = self.type.displayName
let capped = min(displayType.count, 32)
displayType.removeLast(displayType.count-capped)
args.append(displayType)
args.append(self.staticKind)
let ret = args.filter{ arg in !arg.isEmpty }
return ret
}()
lazy var argsHistory: ArgumentsHistoryModel? = {
if isInitializer || isSubscript {
return nil
}
let ret = ArgumentsHistoryModel(name: name,
genericTypeParams: genericTypeParams,
params: params,
isHistoryAnnotated: funcsWithArgsHistory.contains(name),
suffix: suffix)
return ret
}()
func handler(encloser: String) -> ClosureModel? {
if isInitializer {
return nil
}
let paramNames = self.params.map(\.name)
let paramTypes = self.params.map(\.type)
let ret = ClosureModel(name: name,
genericTypeParams: genericTypeParams,
paramNames: paramNames,
paramTypes: paramTypes,
suffix: suffix,
returnType: type,
encloser: encloser)
return ret
}
init(name: String,
typeName: String,
kind: MethodKind,
encloserType: DeclType,
acl: String,
genericTypeParams: [ParamModel],
genericWhereClause: String?,
params: [ParamModel],
throwsOrRethrows: String?,
asyncOrReasync: String?,
isStatic: Bool,
offset: Int64,
length: Int64,
funcsWithArgsHistory: [String],
customModifiers: [String: Modifier],
modelDescription: String?,
processed: Bool) {
self.name = name.trimmingCharacters(in: .whitespaces)
self.type = Type(typeName.trimmingCharacters(in: .whitespaces))
self.suffix = [asyncOrReasync, throwsOrRethrows].compactMap { $0 }.joined(separator: " ")
self.offset = offset
self.length = length
self.kind = kind
self.isStatic = isStatic
self.shouldOverride = encloserType == .classType
self.params = params
self.genericTypeParams = genericTypeParams
self.genericWhereClause = genericWhereClause
self.processed = processed
self.funcsWithArgsHistory = funcsWithArgsHistory
self.customModifiers = customModifiers
self.modelDescription = modelDescription
self.accessLevel = acl
}
var fullName: String {
return self.name + self.signatureComponents.joined() + staticKind
}
func name(by level: Int) -> String {
if level <= 0 {
return name
}
let diff = level - self.signatureComponents.count
let postfix = diff > 0 ? String(diff) : self.signatureComponents[level - 1]
return name(by: level - 1) + postfix
}
func render(with identifier: String, encloser: String, useTemplateFunc: Bool, useMockObservable: Bool, allowSetCallCount: Bool = false, mockFinal: Bool = false, enableFuncArgsHistory: Bool, disableCombineDefaultValues: Bool = false) -> String? {
if processed {
var prefix = shouldOverride ? "\(String.override) " : ""
if case .initKind(required: let isRequired, override: _) = self.kind {
if isRequired {
prefix = ""
}
}
if let ret = modelDescription?.trimmingCharacters(in: .newlines) ?? self.data?.toString(offset: offset, length: length) {
return prefix + ret
}
return nil
}
let result = applyMethodTemplate(name: name,
identifier: identifier,
kind: kind,
useTemplateFunc: useTemplateFunc,
allowSetCallCount: allowSetCallCount,
enableFuncArgsHistory: enableFuncArgsHistory,
isStatic: isStatic,
customModifiers: customModifiers,
isOverride: shouldOverride,
genericTypeParams: genericTypeParams,
genericWhereClause: genericWhereClause,
params: params,
returnType: type,
accessLevel: accessLevel,
suffix: suffix,
argsHistory: argsHistory,
handler: handler(encloser: encloser))
return result
}
}