diff --git a/Package.resolved b/Package.resolved index 5042d9d6..6d1a89e4 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,14 @@ { "pins" : [ + { + "identity" : "swift-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-algorithms.git", + "state" : { + "revision" : "f6919dfc309e7f1b56224378b11e28bab5bccc42", + "version" : "1.2.0" + } + }, { "identity" : "swift-argument-parser", "kind" : "remoteSourceControl", @@ -9,6 +18,15 @@ "version" : "1.5.0" } }, + { + "identity" : "swift-numerics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-numerics.git", + "state" : { + "revision" : "0a5bc04095a675662cf24757cc0640aa2204253b", + "version" : "1.0.2" + } + }, { "identity" : "swift-syntax", "kind" : "remoteSourceControl", diff --git a/Package.swift b/Package.swift index 3e529900..938b1e91 100644 --- a/Package.swift +++ b/Package.swift @@ -13,6 +13,7 @@ let package = Package( dependencies: [ .package(url: "https://github.com/swiftlang/swift-syntax.git", from: "600.0.1"), .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.5.0"), + .package(url: "https://github.com/apple/swift-algorithms.git", from: "1.2.0"), ], targets: [ .executableTarget( @@ -26,6 +27,7 @@ let package = Package( dependencies: [ .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftParser", package: "swift-syntax"), + .product(name: "Algorithms", package: "swift-algorithms"), ] ), .testTarget( diff --git a/Sources/MockoloFramework/Models/ArgumentsHistoryModel.swift b/Sources/MockoloFramework/Models/ArgumentsHistoryModel.swift index a613c80b..f4ee3b82 100644 --- a/Sources/MockoloFramework/Models/ArgumentsHistoryModel.swift +++ b/Sources/MockoloFramework/Models/ArgumentsHistoryModel.swift @@ -1,9 +1,9 @@ import Foundation final class ArgumentsHistoryModel: Model { - var name: String - var type: SwiftType - var offset: Int64 = .max + let name: String + let type: SwiftType + let offset: Int64 = .max let capturableParamNames: [String] let capturableParamTypes: [SwiftType] let isHistoryAnnotated: Bool diff --git a/Sources/MockoloFramework/Models/ClosureModel.swift b/Sources/MockoloFramework/Models/ClosureModel.swift index b6295812..10c6960d 100644 --- a/Sources/MockoloFramework/Models/ClosureModel.swift +++ b/Sources/MockoloFramework/Models/ClosureModel.swift @@ -17,9 +17,10 @@ import Foundation final class ClosureModel: Model { - var name: String var type: SwiftType - var offset: Int64 = .max + let name: String = "" // closure type cannot have a name + let offset: Int64 = .max + let funcReturnType: SwiftType let genericTypeNames: [String] let paramNames: [String] @@ -30,11 +31,10 @@ final class ClosureModel: Model { var modelType: ModelType { return .closure } - - init(name: String, genericTypeParams: [ParamModel], paramNames: [String], paramTypes: [SwiftType], isAsync: Bool, throwing: ThrowingKind, returnType: SwiftType, encloser: String) { + + init(genericTypeParams: [ParamModel], paramNames: [String], paramTypes: [SwiftType], isAsync: Bool, throwing: ThrowingKind, returnType: SwiftType, encloser: String) { // In the mock's call handler, rethrows is unavailable. let throwing = throwing.coerceRethrowsToThrows - self.name = name + .handlerSuffix self.isAsync = isAsync self.throwing = throwing let genericTypeNameList = genericTypeParams.map(\.name) diff --git a/Sources/MockoloFramework/Models/IfMacroModel.swift b/Sources/MockoloFramework/Models/IfMacroModel.swift index 0fd9a2cb..b49ef9bb 100644 --- a/Sources/MockoloFramework/Models/IfMacroModel.swift +++ b/Sources/MockoloFramework/Models/IfMacroModel.swift @@ -14,14 +14,11 @@ // limitations under the License. // -import Foundation - final class IfMacroModel: Model { - var name: String - var offset: Int64 - var type: SwiftType + let name: String + let offset: Int64 let entities: [Model] - + var modelType: ModelType { return .macro } @@ -36,7 +33,6 @@ final class IfMacroModel: Model { self.name = name self.entities = entities self.offset = offset - self.type = SwiftType(name) } func render(with identifier: String, encloser: String, useTemplateFunc: Bool, useMockObservable: Bool, allowSetCallCount: Bool = false, mockFinal: Bool = false, enableFuncArgsHistory: Bool = false, disableCombineDefaultValues: Bool = false) -> String? { diff --git a/Sources/MockoloFramework/Models/MethodModel.swift b/Sources/MockoloFramework/Models/MethodModel.swift index 8698eeb3..527fad81 100644 --- a/Sources/MockoloFramework/Models/MethodModel.swift +++ b/Sources/MockoloFramework/Models/MethodModel.swift @@ -23,24 +23,21 @@ public enum MethodKind: Equatable { } final class MethodModel: Model { - var filePath: String = "" - var data: Data? = nil - var name: String - var type: SwiftType - var offset: Int64 - let length: Int64 + let name: String + let returnType: SwiftType let accessLevel: String - var attributes: [String]? = nil + let kind: MethodKind + let offset: Int64 + let length: Int64 let genericTypeParams: [ParamModel] - var genericWhereClause: String? = nil + let genericWhereClause: String? let params: [ParamModel] let processed: Bool - var modelDescription: String? = nil - var isStatic: Bool + let modelDescription: String? + let isStatic: Bool let shouldOverride: Bool let isAsync: Bool let throwing: ThrowingKind - let kind: MethodKind let funcsWithArgsHistory: [String] let customModifiers: [String : Modifier] var modelType: ModelType { @@ -118,7 +115,7 @@ final class MethodModel: Model { args.append(genericWhereClauseToSignatureComponent) } args.append(contentsOf: paramTypes.map(\.displayName)) - var displayType = self.type.displayName + var displayType = self.returnType.displayName let capped = min(displayType.count, 32) displayType.removeLast(displayType.count-capped) args.append(displayType) @@ -145,21 +142,15 @@ final class MethodModel: Model { 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, - isAsync: isAsync, - throwing: throwing, - returnType: type, - encloser: encloser) - - return ret + return ClosureModel(genericTypeParams: genericTypeParams, + paramNames: params.map(\.name), + paramTypes: params.map(\.type), + isAsync: isAsync, + throwing: throwing, + returnType: returnType, + encloser: encloser) } - init(name: String, typeName: String, kind: MethodKind, @@ -178,7 +169,7 @@ final class MethodModel: Model { modelDescription: String?, processed: Bool) { self.name = name.trimmingCharacters(in: .whitespaces) - self.type = SwiftType(typeName.trimmingCharacters(in: .whitespaces)) + self.returnType = SwiftType(typeName.trimmingCharacters(in: .whitespaces)) self.isAsync = isAsync self.throwing = throwing self.offset = offset @@ -219,7 +210,7 @@ final class MethodModel: Model { } } - if let ret = modelDescription?.trimmingCharacters(in: .newlines) ?? self.data?.toString(offset: offset, length: length) { + if let ret = modelDescription?.trimmingCharacters(in: .newlines) { return prefix + ret } return nil @@ -237,7 +228,7 @@ final class MethodModel: Model { genericTypeParams: genericTypeParams, genericWhereClause: genericWhereClause, params: params, - returnType: type, + returnType: returnType, accessLevel: accessLevel, argsHistory: argsHistory, handler: handler(encloser: encloser)) diff --git a/Sources/MockoloFramework/Models/Model.swift b/Sources/MockoloFramework/Models/Model.swift index 18d84ae0..9b5c8e13 100644 --- a/Sources/MockoloFramework/Models/Model.swift +++ b/Sources/MockoloFramework/Models/Model.swift @@ -28,31 +28,21 @@ public enum ModelType { } /// Represents a model for an entity such as var, func, class, etc. -public protocol Model { +protocol Model: AnyObject { /// Identifier - var name: String { get set } + var name: String { get } /// Fully qualified identifier var fullName: String { get } - var underlyingName: String { get } - /// Type of this model var modelType: ModelType { get } /// Indicates whether mock generation for this model has been processed var processed: Bool { get } - /// Indicates whether this model maps to an init method - var isInitializer: Bool { get } - - var isStatic: Bool { get } - - /// Decl(e.g. class/struct/protocol/enum) or return type (e.g. var/func) - var type: SwiftType { get set } - /// Offset where this type is declared - var offset: Int64 { get set } + var offset: Int64 { get } /// Applies a corresponding template to this model to output mocks func render(with identifier: String, @@ -69,27 +59,9 @@ public protocol Model { /// @param level The verbosity level /// @returns a unique name given the verbosity (default is name) func name(by level: Int) -> String - - - func isEqual(_ other: Model) -> Bool - - func isLessThan(_ other: Model) -> Bool } extension Model { - func isEqual(_ other: Model) -> Bool { - return self.fullName == other.fullName && - self.type.typeName == other.type.typeName && - self.modelType == other.modelType - } - - func isLessThan(_ other: Model) -> Bool { - if self.offset == other.offset { - return self.name < other.name - } - return self.offset < other.offset - } - func name(by level: Int) -> String { return name } @@ -98,20 +70,7 @@ extension Model { return name } - var underlyingName: String { - return name.safeName - } - - var isStatic: Bool { - return false - } - var processed: Bool { return false } - - var isInitializer: Bool { - return false - } } - diff --git a/Sources/MockoloFramework/Models/NominalModel.swift b/Sources/MockoloFramework/Models/NominalModel.swift index a4f63781..58b0ec3d 100644 --- a/Sources/MockoloFramework/Models/NominalModel.swift +++ b/Sources/MockoloFramework/Models/NominalModel.swift @@ -14,8 +14,6 @@ // limitations under the License. // -import Foundation - final class NominalModel: Model { enum NominalTypeDeclKind: String { case `class` @@ -23,9 +21,9 @@ final class NominalModel: Model { } let namespaces: [String] - var name: String - var offset: Int64 - var type: SwiftType + let name: String + let offset: Int64 + let type: SwiftType let attribute: String let accessLevel: String let identifier: String diff --git a/Sources/MockoloFramework/Models/ParamModel.swift b/Sources/MockoloFramework/Models/ParamModel.swift index b3fd0f88..51bb6bf8 100644 --- a/Sources/MockoloFramework/Models/ParamModel.swift +++ b/Sources/MockoloFramework/Models/ParamModel.swift @@ -17,19 +17,26 @@ import Foundation final class ParamModel: Model { - var name: String - var offset: Int64 - var length: Int64 - var type: SwiftType + internal init(label: String, name: String, type: SwiftType, isGeneric: Bool, inInit: Bool, needsVarDecl: Bool, offset: Int64, length: Int64) { + self.label = label + self.name = name + self.type = type + self.isGeneric = isGeneric + self.inInit = inInit + self.needsVarDecl = needsVarDecl + self.offset = offset + self.length = length + } + let label: String + let name: String + let type: SwiftType let isGeneric: Bool let inInit: Bool - let needVarDecl: Bool + let needsVarDecl: Bool + let offset: Int64 + let length: Int64 - var isStatic: Bool { - return false - } - var modelType: ModelType { return .parameter } @@ -38,18 +45,6 @@ final class ParamModel: Model { return label + "_" + name } - init(label: String = "", name: String, typeName: String, isGeneric: Bool = false, inInit: Bool = false, needVarDecl: Bool, offset: Int64, length: Int64) { - self.name = name.trimmingCharacters(in: .whitespaces) - self.type = SwiftType(typeName.trimmingCharacters(in: .whitespaces)) - let labelStr = label.trimmingCharacters(in: .whitespaces) - self.label = name != labelStr ? labelStr : "" - self.offset = offset - self.length = length - self.isGeneric = isGeneric - self.inInit = inInit - self.needVarDecl = needVarDecl - } - var underlyingName: String { return "_\(name)" } @@ -76,7 +71,7 @@ final class ParamModel: Model { /// } /// ``` func asInitVarDecl(eraseType: Bool) -> String? { - if self.inInit, self.needVarDecl { + if self.inInit, self.needsVarDecl { let type: SwiftType if eraseType { type = SwiftType(.anyType) diff --git a/Sources/MockoloFramework/Models/ParsedEntity.swift b/Sources/MockoloFramework/Models/ParsedEntity.swift index 7cf10edf..aee5f429 100644 --- a/Sources/MockoloFramework/Models/ParsedEntity.swift +++ b/Sources/MockoloFramework/Models/ParsedEntity.swift @@ -26,15 +26,11 @@ struct ResolvedEntity { var inheritsActorProtocol: Bool 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} + return uniqueModels.compactMap { (_, model) in + guard let model = model as? MethodModel, + model.isInitializer else { return nil } + return model + } } var initParamCandidates: [VariableModel] { @@ -61,7 +57,6 @@ struct ResolvedEntity { return result } - func model() -> Model { return NominalModel(identifier: key, namespaces: entity.entityNode.namespaces, @@ -81,7 +76,6 @@ struct ResolvedEntity { struct ResolvedEntityContainer { let entity: ResolvedEntity let paths: [String] - let imports: [(String, Data, Int64)] } protocol EntityNode { @@ -179,7 +173,6 @@ public final class Entity { } enum Modifier: String { - case none = "" case weak = "weak" case dynamic = "dynamic" } diff --git a/Sources/MockoloFramework/Models/TypeAliasModel.swift b/Sources/MockoloFramework/Models/TypeAliasModel.swift index c51ad0f2..f55e069e 100644 --- a/Sources/MockoloFramework/Models/TypeAliasModel.swift +++ b/Sources/MockoloFramework/Models/TypeAliasModel.swift @@ -17,20 +17,17 @@ import Foundation final class TypeAliasModel: Model { - var filePath: String = "" - var name: String - var type: SwiftType - var offset: Int64 = .max - var length: Int64 - var typeOffset: Int64 = 0 - var typeLength: Int64 = 0 + let name: String + let type: SwiftType + let offset: Int64 + let length: Int64 let accessLevel: String let processed: Bool - var useDescription: Bool = false - var modelDescription: String? = nil + let useDescription: Bool + let modelDescription: String? let overrideTypes: [String: String]? - var addAcl: Bool = false - + let addAcl: Bool + var modelType: ModelType { return .typeAlias } diff --git a/Sources/MockoloFramework/Models/VariableModel.swift b/Sources/MockoloFramework/Models/VariableModel.swift index a3139b68..410a111b 100644 --- a/Sources/MockoloFramework/Models/VariableModel.swift +++ b/Sources/MockoloFramework/Models/VariableModel.swift @@ -7,27 +7,27 @@ final class VariableModel: Model { static let empty: GetterEffects = .init(isAsync: false, throwing: .none) } - enum MockStorageType { + enum MockStorageKind { case stored(needsSetCount: Bool) case computed(GetterEffects) } - var name: String - var type: SwiftType - var offset: Int64 + let name: String + let type: SwiftType + let offset: Int64 let accessLevel: String let attributes: [String]? let encloserType: DeclType /// Indicates whether this model can be used as a parameter to an initializer - var canBeInitParam: Bool + let canBeInitParam: Bool let processed: Bool - var filePath: String = "" - var isStatic = false - var shouldOverride = false - let storageType: MockStorageType - var rxTypes: [String: String]? - var customModifiers: [String: Modifier]? - var modelDescription: String? = nil + let isStatic: Bool + let shouldOverride: Bool + let storageKind: MockStorageKind + let rxTypes: [String: String]? + let customModifiers: [String: Modifier]? + let modelDescription: String? + var combineType: CombineType? var wrapperAliasModel: VariableModel? var propertyWrapper: String? @@ -48,11 +48,11 @@ final class VariableModel: Model { } init(name: String, - typeName: String, + type: SwiftType, acl: String?, encloserType: DeclType, isStatic: Bool, - storageType: MockStorageType, + storageKind: MockStorageKind, canBeInitParam: Bool, offset: Int64, rxTypes: [String: String]?, @@ -60,11 +60,11 @@ final class VariableModel: Model { modelDescription: String?, combineType: CombineType?, processed: Bool) { - self.name = name.trimmingCharacters(in: .whitespaces) - self.type = SwiftType(typeName.trimmingCharacters(in: .whitespaces)) + self.name = name + self.type = type self.offset = offset self.isStatic = isStatic - self.storageType = storageType + self.storageKind = storageKind self.shouldOverride = encloserType == .classType self.canBeInitParam = canBeInitParam self.processed = processed diff --git a/Sources/MockoloFramework/Operations/Generator.swift b/Sources/MockoloFramework/Operations/Generator.swift index e020c94c..fa4c67b3 100644 --- a/Sources/MockoloFramework/Operations/Generator.swift +++ b/Sources/MockoloFramework/Operations/Generator.swift @@ -57,7 +57,6 @@ public func generate(sourceDirs: [String], var parentMocks = [String: Entity]() var protocolMap = [String: Entity]() var annotatedProtocolMap = [String: Entity]() - var pathToContentMap = [(String, Data, Int64)]() var pathToImportsMap = ImportMap() var relevantPaths = [String]() @@ -129,7 +128,6 @@ public func generate(sourceDirs: [String], annotatedProtocolMap: annotatedProtocolMap, inheritanceMap: parentMocks, completion: { container in - pathToContentMap.append(contentsOf: container.imports) resolvedEntities.append(container.entity) relevantPaths.append(contentsOf: container.paths) }) @@ -157,7 +155,6 @@ public func generate(sourceDirs: [String], log("Write the mock results and import lines to", outputFilePath, level: .info) let imports = handleImports(pathToImportsMap: pathToImportsMap, - pathToContentMap: pathToContentMap, customImports: customImports, excludeImports: excludeImports, testableImports: testableImports, diff --git a/Sources/MockoloFramework/Operations/ImportsHandler.swift b/Sources/MockoloFramework/Operations/ImportsHandler.swift index e3b63125..d927dd37 100644 --- a/Sources/MockoloFramework/Operations/ImportsHandler.swift +++ b/Sources/MockoloFramework/Operations/ImportsHandler.swift @@ -8,7 +8,6 @@ import Foundation func handleImports(pathToImportsMap: ImportMap, - pathToContentMap: [(String, Data, Int64)], customImports: [String]?, excludeImports: [String]?, testableImports: [String]?, @@ -36,16 +35,6 @@ func handleImports(pathToImportsMap: ImportMap, } } - for (_, filecontent, offset) in pathToContentMap { - let v = filecontent.findImportLines(at: offset) - if let ex = excludeImports { - let filtered = v.filter{ !ex.contains($0.moduleNameInImport) } - importLines[defaultKey]?.append(contentsOf: filtered) - } else { - importLines[defaultKey]?.append(contentsOf: v) - } - } - if let customImports = customImports { importLines[defaultKey]?.append(contentsOf: customImports.map {$0.asImport}) } diff --git a/Sources/MockoloFramework/Operations/UniqueModelGenerator.swift b/Sources/MockoloFramework/Operations/UniqueModelGenerator.swift index cc7c8c7a..78a53ca6 100644 --- a/Sources/MockoloFramework/Operations/UniqueModelGenerator.swift +++ b/Sources/MockoloFramework/Operations/UniqueModelGenerator.swift @@ -14,7 +14,7 @@ // limitations under the License. // -import Foundation +import Algorithms /// Performs uniquifying operations on models of an entity @@ -34,8 +34,7 @@ private func generateUniqueModels(key: String, entity: Entity, protocolMap: [String: Entity], inheritanceMap: [String: Entity]) -> ResolvedEntityContainer { - - let (models, processedModels, attributes, inheritedTypes, paths, pathToContentList) = lookupEntities(key: key, declType: entity.entityNode.declType, protocolMap: protocolMap, inheritanceMap: inheritanceMap) + let (models, processedModels, attributes, inheritedTypes, paths) = lookupEntities(key: key, declType: entity.entityNode.declType, protocolMap: protocolMap, inheritanceMap: inheritanceMap) let processedFullNames = processedModels.compactMap {$0.fullName} @@ -52,13 +51,15 @@ private func generateUniqueModels(key: String, var processedLookup = Dictionary() processedElements.forEach { (key, val) in processedLookup[key] = val } - - let nonMethodModels = models.filter {$0.modelType != .method} - let methodModels = models.filter {$0.modelType == .method} + + let (nonMethodModels, methodModels) = models.partitioned(by: { $0.modelType == .method }) let orderedModels = [nonMethodModels, methodModels].flatMap {$0} - let x = uniqueEntities(in: orderedModels, exclude: processedLookup, fullnames: processedFullNames) - let unmockedUniqueEntities = x.filter {!$0.value.processed} - + let unmockedUniqueEntities = uniqueEntities( + in: orderedModels, + exclude: processedLookup, + fullnames: processedFullNames + ).filter { !$0.value.processed } + let processedElementsMap = Dictionary(grouping: processedModels) { element in element.fullName } .compactMap { (key, value) in value.first } .map { element in (element.fullName, element) } @@ -80,5 +81,5 @@ private func generateUniqueModels(key: String, inheritsActorProtocol: inheritedTypes.contains(.actorProtocol) ) - return ResolvedEntityContainer(entity: resolvedEntity, paths: paths, imports: pathToContentList) + return ResolvedEntityContainer(entity: resolvedEntity, paths: paths) } diff --git a/Sources/MockoloFramework/Parsers/SwiftSyntaxExtensions.swift b/Sources/MockoloFramework/Parsers/SwiftSyntaxExtensions.swift index c3200af7..e1f7e928 100644 --- a/Sources/MockoloFramework/Parsers/SwiftSyntaxExtensions.swift +++ b/Sources/MockoloFramework/Parsers/SwiftSyntaxExtensions.swift @@ -1,8 +1,17 @@ // -// SwiftSyntaxExtensions.swift -// MockoloFramework +// Copyright (c) 2018. Uber Technologies // -// Created by Ellie Shin on 10/29/19. +// 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 @@ -30,13 +39,6 @@ extension SyntaxProtocol { } } - -extension AttributeListSyntax { - var trimmedDescription: String? { - return self.trimmed.description.trimmingCharacters(in: .whitespacesAndNewlines) - } -} - extension DeclModifierListSyntax { var acl: String { for modifier in self { @@ -58,31 +60,31 @@ extension DeclModifierListSyntax { } var isStatic: Bool { - return self.tokens(viewMode: .sourceAccurate).filter {$0.tokenKind == .keyword(.static) }.count > 0 + return self.tokens(viewMode: .sourceAccurate).contains {$0.tokenKind == .keyword(.static) } } var isRequired: Bool { - return self.tokens(viewMode: .sourceAccurate).filter {$0.text == String.required }.count > 0 + return self.tokens(viewMode: .sourceAccurate).contains {$0.text == String.required } } var isConvenience: Bool { - return self.tokens(viewMode: .sourceAccurate).filter {$0.text == String.convenience }.count > 0 + return self.tokens(viewMode: .sourceAccurate).contains {$0.text == String.convenience } } var isOverride: Bool { - return self.tokens(viewMode: .sourceAccurate).filter {$0.text == String.override }.count > 0 + return self.tokens(viewMode: .sourceAccurate).contains {$0.text == String.override } } var isFinal: Bool { - return self.tokens(viewMode: .sourceAccurate).filter {$0.text == String.final }.count > 0 + return self.tokens(viewMode: .sourceAccurate).contains {$0.text == String.final } } var isPrivate: Bool { - return self.tokens(viewMode: .sourceAccurate).filter {$0.tokenKind == .keyword(.private) || $0.tokenKind == .keyword(.fileprivate) }.count > 0 + return self.tokens(viewMode: .sourceAccurate).contains {$0.tokenKind == .keyword(.private) || $0.tokenKind == .keyword(.fileprivate) } } var isPublic: Bool { - return self.tokens(viewMode: .sourceAccurate).filter {$0.tokenKind == .keyword(.public) }.count > 0 + return self.tokens(viewMode: .sourceAccurate).contains {$0.tokenKind == .keyword(.public) } } } @@ -288,11 +290,7 @@ extension ProtocolDeclSyntax: EntityNode { } var attributesDescription: String { - self.attributes.trimmedDescription ?? "" - } - - var offset: Int64 { - return Int64(self.position.utf8Offset) + self.attributes.trimmedDescription } func annotationMetadata(with annotation: String) -> AnnotationMetadata? { @@ -334,11 +332,7 @@ extension ClassDeclSyntax: EntityNode { } var attributesDescription: String { - self.attributes.trimmedDescription ?? "" - } - - var offset: Int64 { - return Int64(self.position.utf8Offset) + self.attributes.trimmedDescription } var isFinal: Bool { @@ -420,16 +414,16 @@ extension VariableDeclSyntax { var potentialInitParam = false // Get the type info and whether it can be a var param for an initializer - if let vtype = v.typeAnnotation?.type.description.trimmingCharacters(in: .whitespaces) { + if let vtype = v.typeAnnotation?.type.trimmedDescription { potentialInitParam = name.canBeInitParam(type: vtype, isStatic: isStatic) typeName = vtype } - let storageType: VariableModel.MockStorageType + let storageKind: VariableModel.MockStorageKind switch v.accessorBlock?.accessors { case .accessors(let accessorDecls): if accessorDecls.contains(where: { $0.accessorSpecifier.tokenKind == .keyword(.set) }) { - storageType = .stored(needsSetCount: true) + storageKind = .stored(needsSetCount: true) } else if let getterDecl = accessorDecls.first(where: { $0.accessorSpecifier.tokenKind == .keyword(.get) }) { if getterDecl.body == nil { // is protoccol var getterEffects = VariableModel.GetterEffects.empty @@ -440,37 +434,36 @@ extension VariableDeclSyntax { getterEffects.throwing = .init(`throws`) } if getterEffects == .empty { - storageType = .stored(needsSetCount: false) + storageKind = .stored(needsSetCount: false) } else { - storageType = .computed(getterEffects) + storageKind = .computed(getterEffects) } } else { // is class - storageType = .computed(.empty) + storageKind = .computed(.empty) } } else { // will never happens - storageType = .stored(needsSetCount: false) // fallback + storageKind = .stored(needsSetCount: false) // fallback } case .getter: - storageType = .computed(.empty) + storageKind = .computed(.empty) case nil: - storageType = .stored(needsSetCount: true) + storageKind = .stored(needsSetCount: true) } - let varmodel = VariableModel(name: name, - typeName: typeName, - acl: acl, - encloserType: declType, - isStatic: isStatic, - storageType: storageType, - canBeInitParam: potentialInitParam, - offset: v.offset, - rxTypes: metadata?.varTypes, - customModifiers: metadata?.modifiers, - modelDescription: self.description, - combineType: metadata?.combineTypes?[name] ?? metadata?.combineTypes?["all"], - processed: processed) - return varmodel + return VariableModel(name: name, + type: SwiftType(typeName), + acl: acl, + encloserType: declType, + isStatic: isStatic, + storageKind: storageKind, + canBeInitParam: potentialInitParam, + offset: v.offset, + rxTypes: metadata?.varTypes, + customModifiers: metadata?.modifiers, + modelDescription: self.description, + combineType: metadata?.combineTypes?[name] ?? metadata?.combineTypes?["all"], + processed: processed) } return varmodels } @@ -581,14 +574,13 @@ extension GenericParameterSyntax { func model(inInit: Bool) -> ParamModel { return ParamModel(label: "", name: self.name.text, - typeName: self.inheritedType?.description ?? "", + type: SwiftType(self.inheritedType?.trimmedDescription ?? ""), isGeneric: true, inInit: inInit, - needVarDecl: false, + needsVarDecl: false, offset: self.offset, length: self.length) } - } extension FunctionParameterSyntax { @@ -609,18 +601,17 @@ extension FunctionParameterSyntax { } } - // Variadic args are not detected in the parser so need to manually look up - var type = self.type.description - if self.description.contains(type + "...") { + var type = self.type.trimmedDescription + if ellipsis != nil { type.append("...") } return ParamModel(label: label, name: name, - typeName: type, + type: SwiftType(type), isGeneric: false, inInit: inInit, - needVarDecl: declType == .protocolType, + needsVarDecl: declType == .protocolType, offset: self.offset, length: self.length) } diff --git a/Sources/MockoloFramework/Templates/MethodTemplate.swift b/Sources/MockoloFramework/Templates/MethodTemplate.swift index a5a5cc0c..8b628877 100644 --- a/Sources/MockoloFramework/Templates/MethodTemplate.swift +++ b/Sources/MockoloFramework/Templates/MethodTemplate.swift @@ -40,10 +40,7 @@ extension MethodModel { let acl = accessLevel.isEmpty ? "" : accessLevel+" " let genericTypeDeclsStr = genericTypeParams.compactMap {$0.render(with: "", encloser: "")}.joined(separator: ", ") let genericTypesStr = genericTypeDeclsStr.isEmpty ? "" : "<\(genericTypeDeclsStr)>" - var genericWhereStr = "" - if let clause = genericWhereClause { - genericWhereStr = " \(clause)" - } + let genericWhereStr = genericWhereClause.map { "\($0) " } ?? "" let paramDeclsStr = params.compactMap{$0.render(with: "", encloser: "")}.joined(separator: ", ") switch kind { @@ -62,7 +59,7 @@ extension MethodModel { isAsync: isAsync, throwing: throwing ) - let returnStr = returnTypeName.isEmpty ? "" : "-> \(returnTypeName)" + let returnStr = returnTypeName.isEmpty ? "" : "-> \(returnTypeName) " let staticStr = isStatic ? String.static + " " : "" let keyword = isSubscript ? "" : "func " var body = "" @@ -77,7 +74,7 @@ extension MethodModel { return arg.name.safeName }.joined(separator: ", ") - let defaultVal = type.defaultVal() // ?? "nil" + let defaultVal = returnType.defaultVal() // ?? "nil" var mockReturn = ".error" if returnType.typeName.isEmpty { @@ -150,7 +147,7 @@ extension MethodModel { template = """ \(template) \(1.tab)\(acl)\(staticStr)var \(handlerVarName): \(handlerVarType) - \(1.tab)\(acl)\(staticStr)\(overrideStr)\(modifierTypeStr)\(keyword)\(name)\(genericTypesStr)(\(paramDeclsStr)) \(suffixStr)\(returnStr)\(genericWhereStr) { + \(1.tab)\(acl)\(staticStr)\(overrideStr)\(modifierTypeStr)\(keyword)\(name)\(genericTypesStr)(\(paramDeclsStr)) \(suffixStr)\(returnStr)\(genericWhereStr){ \(wrapped) \(1.tab)} """ diff --git a/Sources/MockoloFramework/Templates/NominalTemplate.swift b/Sources/MockoloFramework/Templates/NominalTemplate.swift index df4ba05f..b9e874da 100644 --- a/Sources/MockoloFramework/Templates/NominalTemplate.swift +++ b/Sources/MockoloFramework/Templates/NominalTemplate.swift @@ -47,7 +47,7 @@ extension NominalModel { if model.modelType == .variable, model.name == String.hasBlankInit { return nil } - if model.modelType == .method, model.isInitializer, !model.processed { + if model.modelType == .method, let model = model as? MethodModel, model.isInitializer, !model.processed { return nil } if let ret = model.render(with: uniqueId, encloser: name, useTemplateFunc: useTemplateFunc, useMockObservable: useMockObservable, allowSetCallCount: allowSetCallCount, mockFinal: mockFinal, enableFuncArgsHistory: enableFuncArgsHistory, disableCombineDefaultValues: disableCombineDefaultValues) { @@ -170,7 +170,7 @@ extension NominalModel { .joined(separator: ", ") paramsAssign = initParamCandidates.map { (element: VariableModel) in - switch element.storageType { + switch element.storageKind { case .stored: return "\(2.tab)self.\(element.underlyingName) = \(element.name.safeName)" case .computed: @@ -270,21 +270,14 @@ extension NominalModel { /// @param models Potentially contains typealias models /// @returns A map of typealiases with multiple possible types func typealiasWhitelist(`in` models: [(String, Model)]) -> [String: [String]]? { - let typealiasModels = models.filter{$0.1.modelType == .typeAlias} - var aliasMap = [String: [String]]() - typealiasModels.forEach { (arg: (key: String, value: Model)) in - - let alias = arg.value - if aliasMap[alias.name] == nil { - aliasMap[alias.name] = [alias.type.typeName] - } else { - if let val = aliasMap[alias.name], !val.contains(alias.type.typeName) { - aliasMap[alias.name]?.append(alias.type.typeName) - } + var aliasMap = [String: Set]() + for (_, model) in models { + if let alias = model as? TypeAliasModel { + aliasMap[alias.name, default: []].insert(alias.type.typeName) } } let aliasDupes = aliasMap.filter {$0.value.count > 1} - return aliasDupes.isEmpty ? nil : aliasDupes + return aliasDupes.isEmpty ? nil : aliasDupes.mapValues {$0.sorted()} } // Finds all combine properties that are attempting to use a property wrapper alias diff --git a/Sources/MockoloFramework/Templates/VariableTemplate.swift b/Sources/MockoloFramework/Templates/VariableTemplate.swift index 3a444666..4ab33225 100644 --- a/Sources/MockoloFramework/Templates/VariableTemplate.swift +++ b/Sources/MockoloFramework/Templates/VariableTemplate.swift @@ -59,7 +59,7 @@ extension VariableModel { let staticSpace = isStatic ? "\(String.static) " : "" - switch storageType { + switch storageKind { case .stored(let needSetCount): let setCallCountVarDecl = needSetCount ? """ \(1.tab)\(acl)\(staticSpace)\(privateSetSpace)var \(underlyingSetCallCount) = 0 @@ -107,7 +107,6 @@ extension VariableModel { case .computed(let effects): let body = (ClosureModel( - name: "", genericTypeParams: [], paramNames: [], paramTypes: [], diff --git a/Sources/MockoloFramework/Utils/FunctionSuffixTemplate.swift b/Sources/MockoloFramework/Utils/FunctionSuffixTemplate.swift index 9629e3e1..645a5f06 100644 --- a/Sources/MockoloFramework/Utils/FunctionSuffixTemplate.swift +++ b/Sources/MockoloFramework/Utils/FunctionSuffixTemplate.swift @@ -18,8 +18,12 @@ /// /// Suffix consists of async and throws. func applyFunctionSuffixTemplate(isAsync: Bool, throwing: ThrowingKind) -> String { - [ - isAsync ? String.async : nil, - throwing.applyThrowingTemplate(), - ].compactMap { $0 }.joined(separator: " ") + " " + var keywords = [String]() + if isAsync { + keywords.append(.async) + } + if let throwing = throwing.applyThrowingTemplate() { + keywords.append(throwing) + } + return keywords.map { "\($0) " }.joined() } diff --git a/Sources/MockoloFramework/Utils/InheritanceResolver.swift b/Sources/MockoloFramework/Utils/InheritanceResolver.swift index 368ad580..0692495e 100644 --- a/Sources/MockoloFramework/Utils/InheritanceResolver.swift +++ b/Sources/MockoloFramework/Utils/InheritanceResolver.swift @@ -14,7 +14,7 @@ // limitations under the License. // -import Foundation +import Algorithms /// Used to resolve inheritance, uniquify duplicate entities, and compute potential init params. @@ -27,7 +27,7 @@ import Foundation func lookupEntities(key: String, declType: DeclType, protocolMap: [String: Entity], - inheritanceMap: [String: Entity]) -> ([Model], [Model], [String], Set, [String], [(String, Data, Int64)]) { + inheritanceMap: [String: Entity]) -> ([Model], [Model], [String], Set, [String]) { // Used to keep track of types to be mocked var models = [Model]() @@ -37,8 +37,6 @@ func lookupEntities(key: String, var attributes = [String]() // Gather inherited types declared in current or parent protocols var inheritedTypes = Set() - // Gather filepaths and contents used for imports - var pathToContents = [(String, Data, Int64)]() // Gather filepaths used for imports var paths = [String]() @@ -57,13 +55,12 @@ func lookupEntities(key: String, // If the protocol inherits other protocols, look up their entities as well. for parent in current.entityNode.inheritedTypes { if parent != .class, parent != .anyType, parent != .anyObject { - let (parentModels, parentProcessedModels, parentAttributes, parentInheritedTypes, parentPaths, parentPathToContents) = lookupEntities(key: parent, declType: declType, protocolMap: protocolMap, inheritanceMap: inheritanceMap) + let (parentModels, parentProcessedModels, parentAttributes, parentInheritedTypes, parentPaths) = lookupEntities(key: parent, declType: declType, protocolMap: protocolMap, inheritanceMap: inheritanceMap) models.append(contentsOf: parentModels) processedModels.append(contentsOf: parentProcessedModels) attributes.append(contentsOf: parentAttributes) inheritedTypes.formUnion(parentInheritedTypes) paths.append(contentsOf: parentPaths) - pathToContents.append(contentsOf:parentPathToContents) } } } @@ -77,7 +74,7 @@ func lookupEntities(key: String, paths.append(parentMock.filepath) } - return (models, processedModels, attributes, inheritedTypes, paths, pathToContents) + return (models, processedModels, attributes, inheritedTypes, paths) } @@ -96,8 +93,8 @@ private func uniquifyDuplicates(group: [String: [Model]], var bufferNameByLevelVisited = [String: Model]() var bufferFullNameVisited = [String]() - group.forEach { (key: String, models: [Model]) in - if let nameByLevelVisited = nameByLevelVisited, nameByLevelVisited[key] != nil { + for (key, models) in group { + if let nameByLevelVisited, nameByLevelVisited[key] != nil { // An entity with the given key already exists, so look up a more verbose name for these entities let subgroup = Dictionary(grouping: models, by: { (modelElement: Model) -> String in return modelElement.name(by: level + 1) @@ -106,14 +103,11 @@ private func uniquifyDuplicates(group: [String: [Model]], bufferFullNameVisited.append(contentsOf: fullNameVisited) } let subresult = uniquifyDuplicates(group: subgroup, level: level+1, nameByLevelVisited: bufferNameByLevelVisited, fullNameVisited: bufferFullNameVisited) - bufferNameByLevelVisited.merge(subresult, uniquingKeysWith: { (bufferElement: Model, subresultElement: Model) -> Model in - return subresultElement - }) + bufferNameByLevelVisited.merge(subresult, uniquingKeysWith: { $1 }) } else { // Check if full name has been looked up - let visited = models.filter {fullNameVisited.contains($0.fullName)}.compactMap{$0} - let unvisited = models.filter {!fullNameVisited.contains($0.fullName)}.compactMap{$0} - + let (unvisited, visited) = models.partitioned(by: { fullNameVisited.contains($0.fullName) }) + if let first = unvisited.first { // If not, add it to the fullname map to keep track of duplicates if !visited.isEmpty { @@ -131,9 +125,7 @@ private func uniquifyDuplicates(group: [String: [Model]], }) let subresult = uniquifyDuplicates(group: subgroup, level: level+1, nameByLevelVisited: bufferNameByLevelVisited, fullNameVisited: bufferFullNameVisited) - bufferNameByLevelVisited.merge(subresult, uniquingKeysWith: { (bufferElement: Model, addedElement: Model) -> Model in - return addedElement - }) + bufferNameByLevelVisited.merge(subresult, uniquingKeysWith: { $1 }) } } }