Sources/MockoloFramework/Models/ParsedEntity.swift (133 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
/// Metadata containing unique models and potential init params ready to be rendered for output
struct ResolvedEntity {
let key: String
let entity: Entity
let uniqueModels: [(String, Model)]
let attributes: [String]
var declaredInits: [MethodModel] {
return uniqueModels.filter {$0.1.isInitializer}.compactMap{ $0.1 as? MethodModel }
}
var hasDeclaredEmptyInit: Bool {
return !declaredInits.filter { $0.params.isEmpty }.isEmpty
}
var declaredInitParams: [ParamModel] {
return declaredInits.map { $0.params }.flatMap{$0}
}
var initParamCandidates: [VariableModel] {
return sortedInitVars(
in: uniqueModels.compactMap{ $0.1 as? VariableModel }
)
}
/// Returns models that can be used as parameters to an initializer
/// @param models The models of the current entity including unprocessed (ones to generate) and
/// processed (already mocked by a previous run if any) models.
/// @returns A list of init parameter models
private func sortedInitVars(`in` models: [VariableModel]) -> [VariableModel] {
let processed = models.filter {$0.processed && $0.canBeInitParam}
let unprocessed = models.filter {!$0.processed && $0.canBeInitParam}
// Named params in init should be unique. Add a duplicate param check to ensure it.
let curVarsSorted = unprocessed.sorted(path: \.offset, fallback: \.name)
let curVarNames = curVarsSorted.map(\.name)
let parentVars = processed.filter {!curVarNames.contains($0.name)}
let parentVarsSorted = parentVars.sorted(path: \.offset, fallback: \.name)
let result = [curVarsSorted, parentVarsSorted].flatMap{$0}
return result
}
func model() -> Model {
return ClassModel(identifier: key,
acl: entity.entityNode.accessLevel,
declType: entity.entityNode.declType,
attributes: attributes,
offset: entity.entityNode.offset,
metadata: entity.metadata,
initParamCandidates: initParamCandidates,
declaredInits: declaredInits,
entities: uniqueModels)
}
}
struct ResolvedEntityContainer {
let entity: ResolvedEntity
let paths: [String]
let imports: [(String, Data, Int64)]
}
protocol EntityNode {
var nameText: String { get }
var accessLevel: String { get }
var attributesDescription: String { get }
var declType: DeclType { get }
var inheritedTypes: [String] { get }
var offset: Int64 { get }
var hasBlankInit: Bool { get }
func subContainer(metadata: AnnotationMetadata?, declType: DeclType, path: String?, data: Data?, isProcessed: Bool) -> EntityNodeSubContainer
}
final class EntityNodeSubContainer {
let attributes: [String]
let members: [Model]
let hasInit: Bool
init(attributes: [String], members: [Model], hasInit: Bool) {
self.attributes = attributes
self.members = members
self.hasInit = hasInit
}
}
public enum CombineType {
case passthroughSubject
case currentValueSubject
case property(wrapper: String, name: String)
var typeName: String {
switch self {
case .passthroughSubject:
return .passthroughSubject
case .currentValueSubject:
return .currentValueSubject
case .property:
return ""
}
}
}
/// Contains arguments to annotation
/// e.g. @mockable(module: prefix = Foo; typealias: T = Any; U = String; rx: barStream = PublishSubject; history: bazFunc = true; modifiers: someVar = weak; combine: fooPublisher = CurrentValueSubject; otherPublisher = @Published otherProperty, override: name = FooMock)
struct AnnotationMetadata {
var nameOverride: String?
var module: String?
var typeAliases: [String: String]?
var varTypes: [String: String]?
var funcsWithArgsHistory: [String]?
var modifiers: [String: Modifier]?
var combineTypes: [String: CombineType]?
}
public typealias ImportMap = [String: [String: [String]]]
/// Metadata for a type being mocked
public final class Entity {
var filepath: String = ""
var data: Data? = nil
let entityNode: EntityNode
let isProcessed: Bool
let metadata: AnnotationMetadata?
var isAnnotated: Bool {
return metadata != nil
}
static func node(with entityNode: EntityNode,
filepath: String,
data: Data? = nil,
isPrivate: Bool,
isFinal: Bool,
metadata: AnnotationMetadata?,
processed: Bool) -> Entity? {
guard !isPrivate, !isFinal else {return nil}
let node = Entity(entityNode: entityNode,
filepath: filepath,
data: data,
metadata: metadata,
isProcessed: processed)
return node
}
init(entityNode: EntityNode,
filepath: String = "",
data: Data? = nil,
metadata: AnnotationMetadata?,
isProcessed: Bool) {
self.entityNode = entityNode
self.filepath = filepath
self.data = data
self.metadata = metadata
self.isProcessed = isProcessed
}
}
enum Modifier: String {
case none = ""
case weak = "weak"
case dynamic = "dynamic"
}