diff --git a/Sources/MockoloFramework/Models/ClassModel.swift b/Sources/MockoloFramework/Models/ClassModel.swift index 6815095b..88ef63be 100644 --- a/Sources/MockoloFramework/Models/ClassModel.swift +++ b/Sources/MockoloFramework/Models/ClassModel.swift @@ -25,7 +25,7 @@ final class ClassModel: Model { let identifier: String let declType: DeclType let entities: [(String, Model)] - let initParamCandidates: [Model] + let initParamCandidates: [VariableModel] let declaredInits: [MethodModel] let metadata: AnnotationMetadata? @@ -39,7 +39,7 @@ final class ClassModel: Model { attributes: [String], offset: Int64, metadata: AnnotationMetadata?, - initParamCandidates: [Model], + initParamCandidates: [VariableModel], declaredInits: [MethodModel], entities: [(String, Model)]) { self.identifier = identifier @@ -55,7 +55,32 @@ final class ClassModel: Model { self.accessLevel = acl } - func render(with identifier: String, encloser: String, useTemplateFunc: Bool, useMockObservable: Bool, allowSetCallCount: Bool = false, mockFinal: Bool = false, enableFuncArgsHistory: Bool = false, disableCombineDefaultValues: Bool = false) -> String? { - return applyClassTemplate(name: name, identifier: self.identifier, accessLevel: accessLevel, attribute: attribute, declType: declType, metadata: metadata, useTemplateFunc: useTemplateFunc, useMockObservable: useMockObservable, allowSetCallCount: allowSetCallCount, mockFinal: mockFinal, enableFuncArgsHistory: enableFuncArgsHistory, disableCombineDefaultValues: disableCombineDefaultValues, initParamCandidates: initParamCandidates, declaredInits: declaredInits, entities: entities) + func render( + with identifier: String, + encloser: String, + useTemplateFunc: Bool, + useMockObservable: Bool, + allowSetCallCount: Bool = false, + mockFinal: Bool = false, + enableFuncArgsHistory: Bool = false, + disableCombineDefaultValues: Bool = false + ) -> String? { + return applyClassTemplate( + name: name, + identifier: self.identifier, + accessLevel: accessLevel, + attribute: attribute, + declType: declType, + metadata: metadata, + useTemplateFunc: useTemplateFunc, + useMockObservable: useMockObservable, + allowSetCallCount: allowSetCallCount, + mockFinal: mockFinal, + enableFuncArgsHistory: enableFuncArgsHistory, + disableCombineDefaultValues: disableCombineDefaultValues, + initParamCandidates: initParamCandidates, + declaredInits: declaredInits, + entities: entities + ) } } diff --git a/Sources/MockoloFramework/Models/Model.swift b/Sources/MockoloFramework/Models/Model.swift index 2743c8e0..46c9e7a5 100644 --- a/Sources/MockoloFramework/Models/Model.swift +++ b/Sources/MockoloFramework/Models/Model.swift @@ -36,9 +36,6 @@ public protocol Model { /// Indicates whether mock generation for this model has been processed var processed: Bool { get } - /// Indicates whether this model can be used as a parameter to an initializer - var canBeInitParam: Bool { get } - /// Indicates whether this model maps to an init method var isInitializer: Bool { get } @@ -106,10 +103,6 @@ extension Model { return false } - var canBeInitParam: Bool { - return false - } - var isInitializer: Bool { return false } diff --git a/Sources/MockoloFramework/Models/ParamModel.swift b/Sources/MockoloFramework/Models/ParamModel.swift index 006ce77b..a1ca813d 100644 --- a/Sources/MockoloFramework/Models/ParamModel.swift +++ b/Sources/MockoloFramework/Models/ParamModel.swift @@ -53,10 +53,37 @@ final class ParamModel: Model { var underlyingName: String { return "_\(name)" } - - var asVarDecl: String? { + + /// - Parameters: + /// - eraseType: + /// If other initializers in decl has same name as this param and type is different from each other, + /// please pass `True` to this parameter. Default value is `false`. + /// + /// ``` + /// protocol A { + /// init(param: String) + /// init(param: any Sequence) + /// } + /// class B: A { + /// var param: Any! // NOTE: type erasing + /// init () {} + /// required init(param: String) { + /// self.param = param + /// } + /// required init(param: any Sequence) { + /// self.param = param + /// } + /// } + /// ``` + func asInitVarDecl(eraseType: Bool) -> String? { if self.inInit, self.needVarDecl { - return applyVarTemplate(name: name, type: type) + let type: `Type` + if eraseType { + type = Type(.any) + } else { + type = self.type + } + return applyVarTemplate(type: type) } return nil } diff --git a/Sources/MockoloFramework/Models/ParsedEntity.swift b/Sources/MockoloFramework/Models/ParsedEntity.swift index 6f5e525a..782ecdab 100644 --- a/Sources/MockoloFramework/Models/ParsedEntity.swift +++ b/Sources/MockoloFramework/Models/ParsedEntity.swift @@ -35,15 +35,17 @@ struct ResolvedEntity { return declaredInits.map { $0.params }.flatMap{$0} } - var initParamCandidates: [Model] { - return sortedInitVars(in: uniqueModels.map{$0.1}) + 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: [Model]) -> [Model] { + private func sortedInitVars(`in` models: [VariableModel]) -> [VariableModel] { let processed = models.filter {$0.processed && $0.canBeInitParam} let unprocessed = models.filter {!$0.processed && $0.canBeInitParam} diff --git a/Sources/MockoloFramework/Models/VariableModel.swift b/Sources/MockoloFramework/Models/VariableModel.swift index 25d5b312..8b5b2451 100644 --- a/Sources/MockoloFramework/Models/VariableModel.swift +++ b/Sources/MockoloFramework/Models/VariableModel.swift @@ -7,6 +7,7 @@ final class VariableModel: Model { 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 processed: Bool var filePath: String = "" diff --git a/Sources/MockoloFramework/Templates/ClassTemplate.swift b/Sources/MockoloFramework/Templates/ClassTemplate.swift index 61ce05f0..8d3bfc1c 100644 --- a/Sources/MockoloFramework/Templates/ClassTemplate.swift +++ b/Sources/MockoloFramework/Templates/ClassTemplate.swift @@ -29,7 +29,7 @@ extension ClassModel { mockFinal: Bool, enableFuncArgsHistory: Bool, disableCombineDefaultValues: Bool, - initParamCandidates: [Model], + initParamCandidates: [VariableModel], declaredInits: [MethodModel], entities: [(String, Model)]) -> String { @@ -101,14 +101,16 @@ extension ClassModel { return template } - private func extraInitsIfNeeded(initParamCandidates: [Model], - declaredInits: [MethodModel], - acl: String, - declType: DeclType, - overrides: [String: String]?) -> String { + private func extraInitsIfNeeded( + initParamCandidates: [VariableModel], + declaredInits: [MethodModel], + acl: String, + declType: DeclType, + overrides: [String: String]? + ) -> String { let declaredInitParamsPerInit = declaredInits.map { $0.params } - + var needParamedInit = false var needBlankInit = false @@ -169,13 +171,18 @@ extension ClassModel { } let extraInitParamNames = initParamCandidates.map{$0.name} - let extraVarsToDecl = declaredInitParamsPerInit.flatMap{$0}.compactMap { (p: ParamModel) -> String? in - if !extraInitParamNames.contains(p.name) { - return p.asVarDecl + let extraVarsToDecl = Dictionary( + grouping: declaredInitParamsPerInit.flatMap { + $0.filter { !extraInitParamNames.contains($0.name) } + }, + by: \.name + ) + .compactMap { (name: String, params: [ParamModel]) in + let shouldErase = params.contains { params[0].type.typeName != $0.type.typeName } + return params[0].asInitVarDecl(eraseType: shouldErase) } - return nil - } - .joined(separator: "\n") + .sorted() + .joined(separator: "\n") let declaredInitStr = declaredInits.compactMap { (m: MethodModel) -> String? in if case let .initKind(required, override) = m.kind, !m.processed { diff --git a/Sources/MockoloFramework/Templates/ParamTemplate.swift b/Sources/MockoloFramework/Templates/ParamTemplate.swift index d35713bd..43e94c6c 100644 --- a/Sources/MockoloFramework/Templates/ParamTemplate.swift +++ b/Sources/MockoloFramework/Templates/ParamTemplate.swift @@ -34,9 +34,8 @@ extension ParamModel { } return result } - - func applyVarTemplate(name: String, - type: Type) -> String { + + func applyVarTemplate(type: `Type`) -> String { assert(!type.isUnknown) let vardecl = "\(1.tab)private var \(underlyingName): \(type.underlyingType)" return vardecl diff --git a/Sources/MockoloFramework/Utils/TypeParser.swift b/Sources/MockoloFramework/Utils/TypeParser.swift index aef0c5c1..26105336 100644 --- a/Sources/MockoloFramework/Utils/TypeParser.swift +++ b/Sources/MockoloFramework/Utils/TypeParser.swift @@ -22,7 +22,7 @@ fileprivate var validIdentifierChars: CharacterSet = { return valid }() -public final class Type { +public final class `Type` { let typeName: String let cast: String? var cachedDefaultVal: String? diff --git a/Tests/TestInit/FixtureInit.swift b/Tests/TestInit/FixtureInit.swift index e69a2b49..161d1fe3 100644 --- a/Tests/TestInit/FixtureInit.swift +++ b/Tests/TestInit/FixtureInit.swift @@ -268,3 +268,61 @@ public class ForcastUpdatingMock: ForcastUpdating { } """ +let initWithSameParamNameButDifferentType = """ +/// \(String.mockAnnotation) +protocol MyProtocol { + init(param: Any) + init(param: String) + init(with param: [Character]) +} +""" + +let initWithSameParamNameButDifferentTypeMock = """ + + + +class MyProtocolMock: MyProtocol { + private var _param: Any! + init() { } + required init(param: Any) { + self._param = param + } + required init(param: String = "") { + self._param = param + } + required init(with param: [Character] = [Character]()) { + self._param = param + } + + +} +""" + +let multipleInitsWithSameParamName = """ +/// \(String.mockAnnotation) +protocol MyProtocol { + init(param: String, anotherParam: Int) + init(param: String, anotherParam: String) +} +""" + +let multipleInitsWithSameParamNameMock = """ + + + +class MyProtocolMock: MyProtocol { + private var _anotherParam: Any! + private var _param: String! + init() { } + required init(param: String = "", anotherParam: Int = 0) { + self._param = param + self._anotherParam = anotherParam + } + required init(param: String = "", anotherParam: String = "") { + self._param = param + self._anotherParam = anotherParam + } + + +} +""" diff --git a/Tests/TestInit/InitTests.swift b/Tests/TestInit/InitTests.swift index 55acbe0f..91b532f2 100644 --- a/Tests/TestInit/InitTests.swift +++ b/Tests/TestInit/InitTests.swift @@ -27,4 +27,18 @@ class InitTests: MockoloTestCase { verify(srcContent: keywordParams, dstContent: keywordParamsMock) } + + func testInitiWithSameParamName() { + verify( + srcContent: multipleInitsWithSameParamName, + dstContent: multipleInitsWithSameParamNameMock + ) + } + + func testInitiWithSameParamNameButDifferentType() { + verify( + srcContent: initWithSameParamNameButDifferentType, + dstContent: initWithSameParamNameButDifferentTypeMock + ) + } }