Sources/MockoloFramework/Models/ParsedEntity.swift (146 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 Algorithms /// Metadata containing unique models and potential init params ready to be rendered for output struct ResolvedEntity { var key: String var entity: Entity var uniqueModels: [(String, Model)] var attributes: [String] var inheritedTypes: [String] var declaredInits: [MethodModel] { return uniqueModels.compactMap { (_, model) in guard let model = model as? MethodModel, model.isInitializer else { return nil } return model } } var initParamCandidates: [VariableModel] { return sortedInitVars( in: uniqueModels.compactMap{ $0.1 as? VariableModel } ) } var inheritsActorProtocol: Bool { return inheritedTypes.contains(.actorProtocol) } /// 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 (unprocessed, processed) = models.filter(\.canBeInitParam).partitioned(by: \.processed) // 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 } var requiresSendable: Bool { return inheritedTypes.contains(.sendable) || inheritedTypes.contains(.error) } func model() -> Model { let metadata = entity.metadata return NominalModel(name: metadata?.nameOverride ?? (key + "Mock"), namespaces: entity.entityNode.namespaces, acl: entity.entityNode.accessLevel, declKindOfMockAnnotatedBaseType: entity.entityNode.declKind, declKind: inheritsActorProtocol ? .actor : .class, attributes: attributes, offset: entity.entityNode.offset, inheritedTypeName: (entity.metadata?.module?.withDot ?? "") + key, genericWhereConstraints: entity.entityNode.genericWhereConstraints, initParamCandidates: initParamCandidates, declaredInits: declaredInits, entities: uniqueModels, requiresSendable: requiresSendable) } } struct ResolvedEntityContainer { var entity: ResolvedEntity var paths: [String] } protocol EntityNode { var namespaces: [String] { get } var nameText: String { get } var mayHaveGlobalActor: Bool { get } var accessLevel: String { get } var attributesDescription: String { get } var declKind: NominalTypeDeclKind { get } var inheritedTypes: [String] { get } var genericWhereConstraints: [String] { get } var offset: Int64 { get } var hasBlankInit: Bool { get } func subContainer(metadata: AnnotationMetadata?, declKind: NominalTypeDeclKind, path: String?, isProcessed: Bool) -> EntityNodeSubContainer } struct EntityNodeSubContainer { var attributes: [String] var members: [Model] var hasInit: Bool } 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]? } struct GenerationArguments { var useTemplateFunc: Bool var allowSetCallCount: Bool var mockFinal: Bool var enableFuncArgsHistory: Bool var disableCombineDefaultValues: Bool static let `default` = GenerationArguments( useTemplateFunc: false, allowSetCallCount: false, mockFinal: false, enableFuncArgsHistory: false, disableCombineDefaultValues: false ) } public typealias ImportMap = [String: [String: [String]]] /// Metadata for a type being mocked public final class Entity { let entityNode: EntityNode let filepath: String let metadata: AnnotationMetadata? let isProcessed: Bool var isAnnotated: Bool { return metadata != nil } static func node(with entityNode: EntityNode, filepath: String, isPrivate: Bool, isFinal: Bool, metadata: AnnotationMetadata?, processed: Bool) -> Entity? { guard !isPrivate, !isFinal else {return nil} return Entity(entityNode: entityNode, filepath: filepath, metadata: metadata, isProcessed: processed) } init(entityNode: EntityNode, filepath: String, metadata: AnnotationMetadata?, isProcessed: Bool) { self.entityNode = entityNode self.filepath = filepath self.metadata = metadata self.isProcessed = isProcessed } } enum Modifier: String { case weak = "weak" case dynamic = "dynamic" }