From c2633b17b2f36b9110da02f314ff8a83f2d7419c Mon Sep 17 00:00:00 2001 From: Tiago Pereira Date: Fri, 25 Oct 2024 09:19:48 +0200 Subject: [PATCH] fix: automockable macro --- CHANGELOG.md | 18 ++ Sources/SageSwiftKit/CodableMacros.swift | 1 - Sources/SageSwiftKit/JsonMockable.swift | 1 - Sources/SageSwiftKit/SageSwiftKit.swift | 1 - Sources/SageSwiftKitClient/main.swift | 29 --- .../JsonMockable/Macros/JsonMockable.swift | 4 +- .../MockableMacros/AutoMockableBuilder.swift | 103 +++++++++++ .../ClassMockForFunctionBuilder.swift | 170 ++++++++++++++++++ .../Builders/FunctionMocksClassBuilder.swift | 61 +++++++ .../ProtocolFunctionsConformanceBuilder.swift | 77 ++++++++ .../ProtocolVarsConformanceBuilder.swift | 78 ++++++++ .../Auxiliars/FunctionsMockData.swift | 89 +++++++++ .../Protocols/MockableFunctionBuilder.swift | 83 --------- .../Protocols/MockableProtocol.swift | 86 --------- .../Protocols/MockableVariablesBuilder.swift | 133 -------------- .../MockableMacrosTests.swift | 110 +++--------- 16 files changed, 622 insertions(+), 422 deletions(-) create mode 100644 Sources/SageSwiftKitMacros/MockableMacros/AutoMockableBuilder.swift create mode 100644 Sources/SageSwiftKitMacros/MockableMacros/Auxiliars/Builders/ClassMockForFunctionBuilder.swift create mode 100644 Sources/SageSwiftKitMacros/MockableMacros/Auxiliars/Builders/FunctionMocksClassBuilder.swift create mode 100644 Sources/SageSwiftKitMacros/MockableMacros/Auxiliars/Builders/ProtocolFunctionsConformanceBuilder.swift create mode 100644 Sources/SageSwiftKitMacros/MockableMacros/Auxiliars/Builders/ProtocolVarsConformanceBuilder.swift create mode 100644 Sources/SageSwiftKitMacros/MockableMacros/Auxiliars/FunctionsMockData.swift delete mode 100644 Sources/SageSwiftKitMacros/MockableMacros/Protocols/MockableFunctionBuilder.swift delete mode 100644 Sources/SageSwiftKitMacros/MockableMacros/Protocols/MockableProtocol.swift delete mode 100644 Sources/SageSwiftKitMacros/MockableMacros/Protocols/MockableVariablesBuilder.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index f4694bd..a06dd12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +# [0.2.2] - 2024-11-06 + +### Fixed + +- Allow Automackable to support more cases + +# [0.2.1] - 2024-10-23 + +### Fixed + +- Fix unsuported optionals to StringOrInt and StringOrDouble macros + +# [0.2.0] - 2024-10-23 + +### Added + +- StringToDouble macro +- # [0.1.1] - 2024-10-21 ### Fixed diff --git a/Sources/SageSwiftKit/CodableMacros.swift b/Sources/SageSwiftKit/CodableMacros.swift index 5a75d42..55fbf1b 100644 --- a/Sources/SageSwiftKit/CodableMacros.swift +++ b/Sources/SageSwiftKit/CodableMacros.swift @@ -2,7 +2,6 @@ // Copyright © 2024 Sage. // All Rights Reserved. - import Foundation @attached(peer) diff --git a/Sources/SageSwiftKit/JsonMockable.swift b/Sources/SageSwiftKit/JsonMockable.swift index 043dda7..5f53e70 100644 --- a/Sources/SageSwiftKit/JsonMockable.swift +++ b/Sources/SageSwiftKit/JsonMockable.swift @@ -4,7 +4,6 @@ import Foundation - @attached(member, names: arbitrary) public macro JsonMockable( keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy, diff --git a/Sources/SageSwiftKit/SageSwiftKit.swift b/Sources/SageSwiftKit/SageSwiftKit.swift index 79eba8d..a3c4a0c 100644 --- a/Sources/SageSwiftKit/SageSwiftKit.swift +++ b/Sources/SageSwiftKit/SageSwiftKit.swift @@ -28,6 +28,5 @@ public macro CustomHashable(parameters: [String]) = #externalMacro(module: "Sage @attached(extension, conformances: Equatable, names: arbitrary) public macro CustomEquatable(parameters: [String]) = #externalMacro(module: "SageSwiftKitMacros", type: "CustomEquatable") - @attached(member, names: arbitrary) public macro NodePrinter() = #externalMacro(module: "SageSwiftKitMacros", type: "NodePrinter") diff --git a/Sources/SageSwiftKitClient/main.swift b/Sources/SageSwiftKitClient/main.swift index 6c1cbe0..4465d12 100644 --- a/Sources/SageSwiftKitClient/main.swift +++ b/Sources/SageSwiftKitClient/main.swift @@ -5,13 +5,6 @@ import SageSwiftKit import Foundation import Combine -@AutoMockable(accessLevel: "public") -protocol FooProtocol { - func tmpFunc(var1: @escaping (String?) -> Void, var2: AnyPublisher) -> Void -} - -let mockProtocol: FooProtocolMock = .init() - struct SubObject: Codable { let id: Int let name: String @@ -42,25 +35,3 @@ struct PlayingObject { var identifier: Int } - - -@JsonMockable( - keyDecodingStrategy: .convertFromSnakeCase, - bundle: .main -) -struct User: Decodable { - var example: String - - static var mockA: Self { - get throws { - try getMock(bundle: .main, keyDecodingStrategy: .convertFromSnakeCase, fileName: "fileA") - } - } - - static var mockB: Self { - get throws { - try getMock(bundle: .main, keyDecodingStrategy: .convertFromSnakeCase, fileName: "fileB") - } - } - -} diff --git a/Sources/SageSwiftKitMacros/JsonMockable/Macros/JsonMockable.swift b/Sources/SageSwiftKitMacros/JsonMockable/Macros/JsonMockable.swift index a9becdc..965666a 100644 --- a/Sources/SageSwiftKitMacros/JsonMockable/Macros/JsonMockable.swift +++ b/Sources/SageSwiftKitMacros/JsonMockable/Macros/JsonMockable.swift @@ -59,9 +59,7 @@ public enum JsonMockable: MemberMacro { ) declSyntax.append(DeclSyntax(function)) - - - + let variable = try VariableDeclSyntax( "public static var jsonMock: \(name.tokenSyntax)") { try .init(itemsBuilder: { diff --git a/Sources/SageSwiftKitMacros/MockableMacros/AutoMockableBuilder.swift b/Sources/SageSwiftKitMacros/MockableMacros/AutoMockableBuilder.swift new file mode 100644 index 0000000..0ef2f5e --- /dev/null +++ b/Sources/SageSwiftKitMacros/MockableMacros/AutoMockableBuilder.swift @@ -0,0 +1,103 @@ +// +// Copyright © 2024 Sage. +// All Rights Reserved. + +import Foundation +import SwiftSyntax +import SwiftSyntaxMacros +import SwiftSyntaxBuilder + +public enum AutoMockable: PeerMacro { + static var mocksVarName: String { "mock" } + + public static func expansion( + of node: AttributeSyntax, + providingPeersOf declaration: some DeclSyntaxProtocol, + in context: some MacroExpansionContext + ) throws -> [DeclSyntax] { + guard let protocolSyntax = declaration.as(ProtocolDeclSyntax.self) else { + return [] + } + + let accessLevel = node + .adapter + .findArgument(id: "accessLevel")? + .adapter + .expression(cast: StringLiteralExprSyntax.self)?.representedLiteralValue ?? "internal" + + let procotolName = protocolSyntax.name.text + + guard let members = declaration.as(ProtocolDeclSyntax.self)?.memberBlock.members else { + return [] + } + + let variablesToMock: [VariableDeclSyntax] = members.compactMap { $0.decl.as(VariableDeclSyntax.self) } + + let functionsToMock: [FunctionsMockData] = members + .compactMap { item -> FunctionsMockData? in + guard let casted = item.decl.as(FunctionDeclSyntax.self) else { + return nil + } + + return FunctionsMockData(syntax: casted, accessLevel: accessLevel.tokenSyntax) + } + + return [ + DeclSyntax( + ClassDeclSyntax( + modifiers: .init(itemsBuilder: { + DeclModifierSyntax(name: accessLevel.tokenSyntax) + }), + name: .identifier("\(procotolName)Mock"), + inheritanceClause: .init( + inheritedTypes: .init(itemsBuilder: { + InheritedTypeSyntax( + type: IdentifierTypeSyntax( + name: .identifier(procotolName) + ) + ) + }) + ), + memberBlock: MemberBlockSyntax( + members: try MemberBlockItemListSyntax(itemsBuilder: { + // Classes that has mock data for each function + for funcData in functionsToMock { + ClassMockForFunctionBuilder(funcData: funcData).build() + } + + // Class with all functions mock + FunctionMocksClassBuilder( + functions: functionsToMock, + accessLevel: accessLevel.tokenSyntax + ).build() + + // Variable mocks + FunctionMocksClassBuilder( + functions: functionsToMock, + accessLevel: accessLevel.tokenSyntax + ).buildVarForTheClass() + + // Implementation of each variable + for variable in variablesToMock { + let varConformance = ProtocolVarsConformanceBuilder( + variable: variable, + accessLevel: accessLevel.tokenSyntax + ) + + varConformance.build() + varConformance.buildReturnVar() + } + + // Implementation of each function + for data in functionsToMock { + try ProtocolFunctionsConformanceBuilder( + data: data + ).build() + } + }) + ) + ) + ) + ] + } +} diff --git a/Sources/SageSwiftKitMacros/MockableMacros/Auxiliars/Builders/ClassMockForFunctionBuilder.swift b/Sources/SageSwiftKitMacros/MockableMacros/Auxiliars/Builders/ClassMockForFunctionBuilder.swift new file mode 100644 index 0000000..13edfb0 --- /dev/null +++ b/Sources/SageSwiftKitMacros/MockableMacros/Auxiliars/Builders/ClassMockForFunctionBuilder.swift @@ -0,0 +1,170 @@ +// +// Copyright © 2024 Sage. +// All Rights Reserved. + +import Foundation +import SwiftSyntax +import SwiftSyntaxMacros +import SwiftSyntaxBuilder + +// Creates the mock for each function with its parameters calls retun value etc... +struct ClassMockForFunctionBuilder { + let funcData: FunctionsMockData + + var parametersName: String { "Parameters" } + var callsName: String { "calls" } + + init(funcData: FunctionsMockData) { + self.funcData = funcData + } + + func build() -> ClassDeclSyntax { + ClassDeclSyntax( + modifiers: .init( + itemsBuilder: { + .init(name: funcData.accessLevel) + } + ), + name: funcData.className.tokenSyntax, + memberBlock: .init( + members: .init( + itemsBuilder: { + if let parametersStruct = buildParameters() { + parametersStruct + } + + buildCalls() + buildLastCall() + + buildCalled() + + if let returnValue = funcData.returnValue { + VariableDeclSyntax( + modifiers: .init(itemsBuilder: { + .init(name: funcData.accessLevel) + }), + Keyword.var, + name: "returnValue", + type: TypeAnnotationSyntax( + type: TypeSyntax(stringLiteral: returnValue) + ) + ) + } + + InitializerDeclSyntax( + signature: .init(parameterClause: .init( + parameters: .init(itemsBuilder: { + + }) + )), + body: .init(statements: .init(itemsBuilder: { + + })) + ) + } + ) + ) + ) + } +} + +extension ClassMockForFunctionBuilder { + func buildParameters() -> StructDeclSyntax? { + return StructDeclSyntax( + modifiers: .init(itemsBuilder: { + .init(name: funcData.accessLevel) + }), + name: parametersName.tokenSyntax, + memberBlock: .init(members: .init(itemsBuilder: { + for parameter in funcData.params { + VariableDeclSyntax( + modifiers: .init(itemsBuilder: { + .init(name: funcData.accessLevel) + }), + Keyword.let, + name: PatternSyntax(stringLiteral: parameter.name), + type: TypeAnnotationSyntax( + type: TypeSyntax(stringLiteral: parameter.noEscapingType) + ) + ) + } + })) + ) + } + + func buildCalls() -> VariableDeclSyntax { + return VariableDeclSyntax( + modifiers: .init(itemsBuilder: { + .init(name: funcData.accessLevel) + }), + Keyword.var, + name: .init(stringLiteral: callsName), + type: TypeAnnotationSyntax( + type: TypeSyntax(stringLiteral: "[\(parametersName)]") + ), + initializer: InitializerClauseSyntax( + value: ArrayExprSyntax(elements: .init(itemsBuilder: { + + })) + ) + ) + } + + func buildCalled() -> VariableDeclSyntax { + return VariableDeclSyntax( + modifiers: .init(itemsBuilder: { + .init(name: funcData.accessLevel) + }), + bindingSpecifier: .keyword(.var), + bindings: .init(itemsBuilder: { + PatternBindingSyntax( + pattern: IdentifierPatternSyntax(identifier: "called"), + typeAnnotation: TypeAnnotationSyntax(type: IdentifierTypeSyntax(name: "Bool")), + accessorBlock: AccessorBlockSyntax( + leftBrace: .leftBraceToken(), + accessors: .init(CodeBlockItemListSyntax(itemsBuilder: { + ReturnStmtSyntax( + returnKeyword: .keyword(.return), + expression: DeclReferenceExprSyntax( + baseName: "self.lastCall != nil".tokenSyntax + ) + ) + })), + rightBrace: .rightBraceToken() + ) + ) + }) + ) + } + + func buildLastCall() -> VariableDeclSyntax { + return VariableDeclSyntax( + modifiers: .init(itemsBuilder: { + .init(name: funcData.accessLevel) + }), + bindingSpecifier: .keyword(.var), + bindings: .init(itemsBuilder: { + PatternBindingSyntax( + pattern: IdentifierPatternSyntax(identifier: "lastCall"), + typeAnnotation: TypeAnnotationSyntax( + type: OptionalTypeSyntax( + wrappedType: IdentifierTypeSyntax(name: parametersName.tokenSyntax) + ) + ), + accessorBlock: AccessorBlockSyntax( + leftBrace: .leftBraceToken(), + accessors: .init(CodeBlockItemListSyntax(itemsBuilder: { + ReturnStmtSyntax( + returnKeyword: .keyword(.return), + expression: DeclReferenceExprSyntax( + baseName: "self.calls.last".tokenSyntax + ) + ) + })), + rightBrace: .rightBraceToken() + ) + ) + }) + ) + } +} diff --git a/Sources/SageSwiftKitMacros/MockableMacros/Auxiliars/Builders/FunctionMocksClassBuilder.swift b/Sources/SageSwiftKitMacros/MockableMacros/Auxiliars/Builders/FunctionMocksClassBuilder.swift new file mode 100644 index 0000000..e22b47b --- /dev/null +++ b/Sources/SageSwiftKitMacros/MockableMacros/Auxiliars/Builders/FunctionMocksClassBuilder.swift @@ -0,0 +1,61 @@ +// +// Copyright © 2024 Sage. +// All Rights Reserved. + +import Foundation +import SwiftSyntax +import SwiftSyntaxMacros +import SwiftSyntaxBuilder + +struct FunctionMocksClassBuilder { + let functions: [FunctionsMockData] + let accessLevel: TokenSyntax + + init( + functions: [FunctionsMockData], + accessLevel: TokenSyntax + ) { + self.functions = functions + self.accessLevel = accessLevel + } + + func build() -> ClassDeclSyntax { + .init( + modifiers: .init(itemsBuilder: { + .init(name: accessLevel) + }), + name: "FunctionMocks", + memberBlock: .init( + members: .init(itemsBuilder: { + for function in functions { + buildFuncCall(function: function) + } + }) + ) + ) + } + + func buildVarForTheClass() -> VariableDeclSyntax { + VariableDeclSyntax( + modifiers: .init(itemsBuilder: { + .init(name: accessLevel) + }), + Keyword.var, + name: .init(stringLiteral: AutoMockable.mocksVarName), + initializer: InitializerClauseSyntax(value: ExprSyntax(stringLiteral: "FunctionMocks()")) + ) + } +} + +extension FunctionMocksClassBuilder { + private func buildFuncCall(function: FunctionsMockData) -> VariableDeclSyntax { + return .init( + modifiers: .init(itemsBuilder: { + .init(name: accessLevel) + }), + Keyword.var, + name: .init(stringLiteral: function.nameToIdentify), + initializer: InitializerClauseSyntax(value: ExprSyntax(stringLiteral: "\(function.className)()")) + ) + } +} diff --git a/Sources/SageSwiftKitMacros/MockableMacros/Auxiliars/Builders/ProtocolFunctionsConformanceBuilder.swift b/Sources/SageSwiftKitMacros/MockableMacros/Auxiliars/Builders/ProtocolFunctionsConformanceBuilder.swift new file mode 100644 index 0000000..2fa631d --- /dev/null +++ b/Sources/SageSwiftKitMacros/MockableMacros/Auxiliars/Builders/ProtocolFunctionsConformanceBuilder.swift @@ -0,0 +1,77 @@ +// +// Copyright © 2024 Sage. +// All Rights Reserved. + +import Foundation +import SwiftSyntax +import SwiftSyntaxMacros +import SwiftSyntaxBuilder + +struct ProtocolFunctionsConformanceBuilder { + let data: FunctionsMockData + + init(data: FunctionsMockData) { + self.data = data + } + + var mockEntity: String { + "self.\(AutoMockable.mocksVarName).\(data.nameToIdentify)" + } + + func build() throws -> FunctionDeclSyntax { + return FunctionDeclSyntax( + attributes: data.syntax.attributes, + modifiers: .init(itemsBuilder: { + .init(name: data.accessLevel) + }), + name: data.name, + signature: data.syntax.signature, + body: .init(statements: .init(itemsBuilder: { + buildCall() + + if data.returnValue != nil { + buildReturn() + } + })) + ) + } + + private func buildCall() -> FunctionCallExprSyntax { + FunctionCallExprSyntax( + calledExpression: DeclReferenceExprSyntax( + baseName: "\(mockEntity).calls.append".tokenSyntax + ), + leftParen: .leftParenToken(), + arguments: .init(itemsBuilder: { + LabeledExprSyntax(expression: FunctionCallExprSyntax( + calledExpression: MemberAccessExprSyntax( + period: .periodToken(), + declName: DeclReferenceExprSyntax(baseName: "init".tokenSyntax) + ), + leftParen: .leftParenToken(), + arguments: .init(itemsBuilder: { + for param in data.params { + LabeledExprSyntax( + label: param.name, + expression: DeclReferenceExprSyntax( + baseName: param.name.tokenSyntax + ) + ) + } + }), + rightParen: .rightParenToken() + )) + }), + rightParen: .rightParenToken() + ) + } + + private func buildReturn() -> ReturnStmtSyntax { + return ReturnStmtSyntax( + returnKeyword: .keyword(.return), + expression: DeclReferenceExprSyntax( + baseName: "\(mockEntity).returnValue".tokenSyntax + ) + ) + } +} diff --git a/Sources/SageSwiftKitMacros/MockableMacros/Auxiliars/Builders/ProtocolVarsConformanceBuilder.swift b/Sources/SageSwiftKitMacros/MockableMacros/Auxiliars/Builders/ProtocolVarsConformanceBuilder.swift new file mode 100644 index 0000000..aac76cd --- /dev/null +++ b/Sources/SageSwiftKitMacros/MockableMacros/Auxiliars/Builders/ProtocolVarsConformanceBuilder.swift @@ -0,0 +1,78 @@ +// +// Copyright © 2024 Sage. +// All Rights Reserved. + +import Foundation +import SwiftSyntax +import SwiftSyntaxMacros +import SwiftSyntaxBuilder + +struct ProtocolVarsConformanceBuilder { + let variable: VariableDeclSyntax + let accessLevel: TokenSyntax + + var name: String { + variable.bindings.first!.pattern.as(IdentifierPatternSyntax.self)!.identifier.text + } + + var varReturnName: String { + name+"Return" + } + + var typeReturnVar: TypeAnnotationSyntax? { + guard let myType = variable.bindings.first?.typeAnnotation else { + return nil + } + + guard let typeSyntax = myType.type.as(IdentifierTypeSyntax.self) else { + return myType + } + + return TypeAnnotationSyntax(type: TypeSyntax(stringLiteral: "\(typeSyntax.name.text)!")) + } + + init( + variable: VariableDeclSyntax, + accessLevel: TokenSyntax + ) { + self.variable = variable + self.accessLevel = accessLevel + } + + func build() -> VariableDeclSyntax { + return VariableDeclSyntax( + modifiers: .init(itemsBuilder: { + .init(name: accessLevel) + }), + bindingSpecifier: .keyword(.var), + bindings: .init(itemsBuilder: { + .init( + pattern: IdentifierPatternSyntax(identifier: name.tokenSyntax), + typeAnnotation: variable.bindings.first?.typeAnnotation, + accessorBlock: AccessorBlockSyntax( + accessors: .getter( + .init(itemsBuilder: { + DeclReferenceExprSyntax(baseName: varReturnName.tokenSyntax) + }) + ) + ) + ) + }) + ) + } + + func buildReturnVar() -> VariableDeclSyntax { + return VariableDeclSyntax( + modifiers: .init(itemsBuilder: { + .init(name: accessLevel) + }), + bindingSpecifier: .keyword(.var), + bindings: .init(itemsBuilder: { + .init( + pattern: IdentifierPatternSyntax(identifier: varReturnName.tokenSyntax), + typeAnnotation: typeReturnVar + ) + }) + ) + } +} diff --git a/Sources/SageSwiftKitMacros/MockableMacros/Auxiliars/FunctionsMockData.swift b/Sources/SageSwiftKitMacros/MockableMacros/Auxiliars/FunctionsMockData.swift new file mode 100644 index 0000000..74f3b73 --- /dev/null +++ b/Sources/SageSwiftKitMacros/MockableMacros/Auxiliars/FunctionsMockData.swift @@ -0,0 +1,89 @@ +// +// Copyright © 2024 Sage. +// All Rights Reserved. +import Foundation +import SwiftSyntax +import SwiftSyntaxMacros +import SwiftSyntaxBuilder + +struct ParamsMockData { + let firstName: TokenSyntax + let secondName: TokenSyntax? + let type: TypeSyntaxProtocol + + init(firstName: TokenSyntax, secondName: TokenSyntax?, type: TypeSyntaxProtocol) { + self.firstName = firstName + self.secondName = secondName + self.type = type + } + + var name: String { secondName?.text ?? firstName.text } + + var noEscapingType: String { + type.description.replacingOccurrences(of: "@escaping", with: "") + } +} + +struct FunctionsMockData { + let syntax: FunctionDeclSyntax + let name: TokenSyntax + let params: [ParamsMockData] + let returnType: ReturnClauseSyntax? + let accessLevel: TokenSyntax + + var mocksVarName: String { "mock" } + + init(syntax: FunctionDeclSyntax, accessLevel: TokenSyntax) { + self.syntax = syntax + self.name = syntax.name + self.params = syntax.signature + .as(FunctionSignatureSyntax.self)? + .parameterClause.as(FunctionParameterClauseSyntax.self)? + .parameters.compactMap { param -> ParamsMockData? in + guard let casted = param.as(FunctionParameterSyntax.self) else { + return nil + } + + return .init( + firstName: casted.firstName, + secondName: casted.secondName, + type: casted.type + ) + } ?? [] + + self.returnType = syntax.signature.returnClause + self.accessLevel = accessLevel + } +} + +extension FunctionsMockData { + var className: String { + nameToIdentify.prefix(1).uppercased()+nameToIdentify.dropFirst() + } + + var nameToIdentify: String { + let startPattern = name.text + + return params.reduce(startPattern, { acc, param -> String in + let name = param.name + + return acc+"_\(name.prefix(1).uppercased()+param.name.dropFirst())" + }) + } + + var returnValue: String? { + guard let returnType else { + return nil + } + + if let optionalType = returnType.type.as(OptionalTypeSyntax.self) { + return optionalType.description + } + + if let optionalType = returnType.type.as(ImplicitlyUnwrappedOptionalTypeSyntax.self) { + return optionalType.description + } + + return ImplicitlyUnwrappedOptionalTypeSyntax(wrappedType: returnType.type).description + } +} diff --git a/Sources/SageSwiftKitMacros/MockableMacros/Protocols/MockableFunctionBuilder.swift b/Sources/SageSwiftKitMacros/MockableMacros/Protocols/MockableFunctionBuilder.swift deleted file mode 100644 index 69d329c..0000000 --- a/Sources/SageSwiftKitMacros/MockableMacros/Protocols/MockableFunctionBuilder.swift +++ /dev/null @@ -1,83 +0,0 @@ -// -// Copyright © 2024 Sage. -// All Rights Reserved. - -import Foundation -import SwiftSyntax - -struct MockableFunctionBuilder { - let functionSyntax: FunctionDeclSyntax - let variables: MockableVariablesBuilder - let accessLevel: String - - init( - functionSyntax: FunctionDeclSyntax, - accessLevel: String - ) { - self.functionSyntax = functionSyntax - self.variables = MockableVariablesBuilder(functionSyntax: functionSyntax, accessLevel: accessLevel) - self.accessLevel = accessLevel - } - - func build() throws -> FunctionDeclSyntax { - try .init( - .init(stringLiteral: "\(accessLevel) \(functionSyntax.description)"), - bodyBuilder: { - calledIncrease - argumentsRegistration - - if let item = try returnItem() { - item - } - }) - } - - private var calledIncrease: CodeBlockItemSyntax { - let varName = variables.callsCountName - - return .init(stringLiteral: "self.\(varName) += 1") - } - - private var variablesName: [String] { - guard let params = functionSyntax.signature.parameterClause.as(FunctionParameterClauseSyntax.self)?.parameters else { - return [] - } - - var names: [String] = [] - - for parameter in params { - names.append(parameter.secondName?.text ?? parameter.firstName.text) - } - - return names - } - - private var argumentsRegistration: CodeBlockItemSyntax { - let allParameters = variables.allParameters ?? [] - let varName = variables.parametersNameCall - - let callVariables: String = variablesName.reduce("", { acc, name in - let callVariable = "\(name):\(name)" - - if acc.isEmpty { - return callVariable - } else { - return acc+", "+callVariable - } - }) - - if allParameters.count == 1 { - return .init(stringLiteral: "self.\(varName).append(\(variablesName.first ?? ""))") - } - - return .init(stringLiteral: "self.\(varName).append((\(callVariables)))") - } - - private func returnItem() throws -> CodeBlockItemSyntax? { - if try variables.returnVariable() == nil { return nil } - - return .init( - stringLiteral: "return self.\(variables.returnName)" - ) - } -} diff --git a/Sources/SageSwiftKitMacros/MockableMacros/Protocols/MockableProtocol.swift b/Sources/SageSwiftKitMacros/MockableMacros/Protocols/MockableProtocol.swift deleted file mode 100644 index 24be2f9..0000000 --- a/Sources/SageSwiftKitMacros/MockableMacros/Protocols/MockableProtocol.swift +++ /dev/null @@ -1,86 +0,0 @@ -// -// Copyright © 2024 Sage. -// All Rights Reserved. - -import Foundation -import SwiftSyntax -import SwiftSyntaxMacros -import SwiftSyntaxBuilder - -public enum AutoMockable: PeerMacro { - public static func expansion( - of node: AttributeSyntax, - providingPeersOf declaration: some DeclSyntaxProtocol, - in context: some MacroExpansionContext - ) throws -> [DeclSyntax] { - var declarations: [DeclSyntax] = [] - - let accessLevel = node - .adapter - .findArgument(id: "accessLevel")? - .adapter - .string() ?? "" - - guard let protocolSyntax = declaration.as(ProtocolDeclSyntax.self) else { - return declarations - } - - let functions = self.getProtocolFunctions(declaration: protocolSyntax) - - let procotolName = protocolSyntax.name.text - - let mockClass = ClassDeclSyntaxBuilder( - declaration: "\(accessLevel) class \(procotolName)Mock: \(procotolName)" - ) - - do { - mockClass.bodyItems.append( - try InitializerDeclSyntax( - .init(stringLiteral: "\(accessLevel) init()"), - bodyBuilder: {} - ) - ) - - for protocolFunction in functions { - let varsBuilder = MockableVariablesBuilder( - functionSyntax: protocolFunction, - accessLevel: accessLevel - ) - - mockClass.appendItem(item: try varsBuilder.callsCountVar()) - mockClass.appendItem(item: try varsBuilder.calledVar()) - - if let parameters = try varsBuilder.parametersVar() { - mockClass.appendItem(item: parameters) - } - - if let variableSybtax = try varsBuilder.parametersCallsVar() { - mockClass.appendItem(item: variableSybtax) - } - - if let returnVariable = try varsBuilder.returnVariable() { - mockClass.appendItem(item: returnVariable) - } - } - - for protocolFunction in functions { - let builder = MockableFunctionBuilder( - functionSyntax: protocolFunction, - accessLevel: accessLevel - ) - - mockClass.appendItem(item: try builder.build()) - } - } catch { - context.addDiagnostics(from: error, node: node) - } - - declarations.append(DeclSyntax(try mockClass.build())) - - return declarations - } - - private static func getProtocolFunctions(declaration: ProtocolDeclSyntax) -> [FunctionDeclSyntax] { - return declaration.memberBlock.members.compactMap { $0.decl.as(FunctionDeclSyntax.self) } - } -} diff --git a/Sources/SageSwiftKitMacros/MockableMacros/Protocols/MockableVariablesBuilder.swift b/Sources/SageSwiftKitMacros/MockableMacros/Protocols/MockableVariablesBuilder.swift deleted file mode 100644 index 3b1a9e4..0000000 --- a/Sources/SageSwiftKitMacros/MockableMacros/Protocols/MockableVariablesBuilder.swift +++ /dev/null @@ -1,133 +0,0 @@ -// -// Copyright © 2024 Sage. -// All Rights Reserved. - -import Foundation -import SwiftSyntax - -struct MockableVariablesBuilder { - let functionSyntax: FunctionDeclSyntax - let accessLevel: String - - init( - functionSyntax: FunctionDeclSyntax, - accessLevel: String - ) { - self.functionSyntax = functionSyntax - self.accessLevel = accessLevel - } - - private var name: String { - let name = functionSyntax.name.text - - let params = allParameters ?? [] - - return params.reduce(name, { acc, param -> String in - return acc+"_\(param.firstName.text)" - }) - } - - var callsCountName: String { "\(name)CallsCount" } - var calledName: String { "\(name)Called" } - var parametersName: String { "\(name)Parameters" } - var parametersNameCall: String { "\(name)ParametersCalls" } - var returnName: String { "\(name)ReturnValue" } - - func callsCountVar() throws -> VariableDeclSyntax { - try .init( - .init(stringLiteral: "\(accessLevel) var \(callsCountName): Int = 0") - ) - } - - func calledVar() throws -> VariableDeclSyntax { - try .init( - .init(stringLiteral: "\(accessLevel) var \(calledName): Bool"), - accessor: { - .init(stringLiteral: "\(callsCountName) > 0") - } - ) - } - - var allParameters: [FunctionParameterSyntax]? { - guard let clause = functionSyntax.signature.parameterClause.as(FunctionParameterClauseSyntax.self) else { - return nil - } - - return clause.parameters.compactMap { $0.as(FunctionParameterSyntax.self) - } - } - - var parametersTupple: String? { - guard let allParameters else { return nil } - - let parameterTupple: (FunctionParameterSyntax) -> String = { (param: FunctionParameterSyntax) -> String in - let name = param.secondName?.text ?? param.firstName.text - - let description = param.type.description.replacingOccurrences(of: "@escaping", with: "") - - return "\(name): \(description)" - } - - if allParameters.count == 1 { - return allParameters[0].type.description - } - - return allParameters.reduce("", { acc, param in - let variableValue = parameterTupple(param) - - if acc.isEmpty { - return variableValue - } - - return acc+", "+variableValue - }) - } - - func parametersVar() throws -> VariableDeclSyntax? { - guard let parametersTupple, let allParameters else { return nil } - - if allParameters.count == 1 { - return try .init( - .init(stringLiteral: "\(accessLevel) var \(parametersName): \(parametersTupple)?"), - accessor: { - .init(stringLiteral: "self.\(parametersNameCall).last") - } - ) - } - - return try .init( - .init(stringLiteral: "\(accessLevel) var \(parametersName): (\(parametersTupple))?"), - accessor: { - .init(stringLiteral: "self.\(parametersNameCall).last") - } - ) - } - - func parametersCallsVar() throws -> VariableDeclSyntax? { - guard let parametersTupple else { return nil } - - return try .init( - .init(stringLiteral: "\(accessLevel) var \(parametersNameCall): [(\(parametersTupple))] = []") - ) - } - - func returnVariable() throws -> VariableDeclSyntax? { - guard let returnValue = functionSyntax.signature.returnClause else { - return nil - } - - if returnValue.description.contains("Void") { - return nil - } - - if returnValue.description.contains("?") { - return try .init( - .init(stringLiteral: "\(accessLevel) var \(returnName): \(returnValue.type.description)") - ) - } - - return try .init( - .init(stringLiteral: "\(accessLevel) var \(returnName): \(returnValue.type.description)!") - ) - } -} diff --git a/Tests/SageSwiftKitTests/MockableMacrosTests/MockableMacrosTests.swift b/Tests/SageSwiftKitTests/MockableMacrosTests/MockableMacrosTests.swift index 2faa4b6..7a71596 100644 --- a/Tests/SageSwiftKitTests/MockableMacrosTests/MockableMacrosTests.swift +++ b/Tests/SageSwiftKitTests/MockableMacrosTests/MockableMacrosTests.swift @@ -27,28 +27,37 @@ final class MockableMacrosTests: XCTestCase { expandedSource: """ protocol PlayingObject { var value: String? { get } - + func tmpFunc(value: String) -> Int } - class PlayingObjectMock: PlayingObject { - init() { + internal class PlayingObjectMock: PlayingObject { + internal class TmpFunc_Value { + internal struct Parameters { + internal let value: String + } + internal var calls: [Parameters] = [] + internal var lastCall: Parameters? { + return self.calls.last + } + internal var called: Bool { + return self.lastCall != nil + } + internal var returnValue: Int! + init() { + } } - var tmpFunc_valueCallsCount: Int = 0 - var tmpFunc_valueCalled: Bool { - tmpFunc_valueCallsCount > 0 + internal class FunctionMocks { + internal var tmpFunc_Value = TmpFunc_Value() } - var tmpFunc_valueParameters: String? { - self.tmpFunc_valueParametersCalls.last + internal var mock = FunctionMocks() + internal var value: String? { + valueReturn } - var tmpFunc_valueParametersCalls: [(String)] = [] - var tmpFunc_valueReturnValue: Int! - - - func tmpFunc(value: String) -> Int { - self.tmpFunc_valueCallsCount += 1 - self.tmpFunc_valueParametersCalls.append(value) - return self.tmpFunc_valueReturnValue + internal var valueReturn: String? + internal func tmpFunc(value: String) -> Int { + self.mock.tmpFunc_Value.calls.append(.init(value: value)) + return self.mock.tmpFunc_Value.returnValue } } """, @@ -58,73 +67,4 @@ final class MockableMacrosTests: XCTestCase { throw XCTSkip("macros are only supported when running tests for the host platform") #endif } - - enum TestError: Error { - case noValue - } - - @AutoMockable - public protocol ProtocolToTest { - func fetchPeriodsList( - employeeId: Int, - startDate: Date?, - endDate: Date? - ) -> AnyPublisher<[Int], TestError> - - func fetchDetailPeriod( - employeeId: Int, - period: Double - ) -> AnyPublisher - - func fetchDetailPeriod( - employeeId: Int, - date: Date - ) -> AnyPublisher - - func detailPeriodCache(date: Date) -> TestError? - func fetchCommentsAndChangelog( - employeeId: Int64, - date: Date - ) -> AnyPublisher - - func saveWorkDay( - employeeId: Int64, - date: Date, - from: Date, - to: Date, - breakStart: Date?, - breakEnd: Date?, - breakLength: Int, - comments: [Bool] - ) -> AnyPublisher - func cancelTimesheet(employeeId: Int64, period: Bool) -> AnyPublisher - - func submitTimesheet( - employeeId: Int64, - period: Bool, - overtime: Int? - ) -> AnyPublisher - - func fetchGeofencingZones( - employeeId: Int64, - cache: Bool - ) -> AnyPublisher - - func fetchCurrentUser() -> AnyPublisher - - func updatePermission( - employeeId: Int64, - consent: Bool?, - deviceEnable: Bool? - ) -> AnyPublisher - } - - func testProtocol() { - let mockProtocol: ProtocolToTestMock = ProtocolToTestMock() - - XCTAssertEqual(mockProtocol.detailPeriodCache(date: Date()), nil) - mockProtocol.detailPeriodCache_dateReturnValue = .noValue - - XCTAssertEqual(mockProtocol.detailPeriodCache(date: Date()), .noValue) - } }