diff --git a/Sources/SurfCore/Closures/Closures.swift b/Sources/SurfCore/Closures/Closures.swift new file mode 100644 index 0000000..da6803c --- /dev/null +++ b/Sources/SurfCore/Closures/Closures.swift @@ -0,0 +1,11 @@ +// +// Copyright © Surf. All rights reserved. +// + +import Foundation + +/// Замыкание без параметра +public typealias EmptyClosure = () -> Void + +/// Замыкание с одним параметром +public typealias Closure = (T) -> Void diff --git a/Sources/SurfMacros/Implementation/Signals/CompletionHandlerMacro.swift b/Sources/SurfMacros/Implementation/Signals/CompletionHandlerMacro.swift new file mode 100644 index 0000000..ddbe907 --- /dev/null +++ b/Sources/SurfMacros/Implementation/Signals/CompletionHandlerMacro.swift @@ -0,0 +1,133 @@ +import SwiftCompilerPlugin +import SwiftSyntax +import SwiftSyntaxBuilder +import SwiftSyntaxMacros +import SurfMacrosSupport + +public struct CompletionHandlerMacro: PeerMacro { + + // MARK: - Names + + private enum Names { + static let completion = "completion" + static let emptyClosure = "EmptyClosure" + + static var `protocol` = "" + + static var `class`: String { + return `protocol` + "Handler" + } + + } + + // MARK: - Macro + + public static func expansion( + of node: AttributeSyntax, + providingPeersOf declaration: some DeclSyntaxProtocol, + in context: some MacroExpansionContext + ) throws -> [DeclSyntax] { + guard let protocolDecl = declaration.as(ProtocolDeclSyntax.self) else { + throw DeclarationError.wrongAttaching(expected: .protocol) + } + Names.protocol = protocolDecl.name.text + try SignalsMacroGroupSupport.checkProtocolDeclaration(protocolDecl) + let protocolFuncDecls = protocolDecl.memberBlock.functionDecls + let handlerClass = createHandlerClass(with: protocolFuncDecls) + return [.init(handlerClass)] + } + +} + +// MARK: - Creations + +private extension CompletionHandlerMacro { + + static func createHandlerClass(with protocolFuncDecls: [FunctionDeclSyntax]) -> ClassDeclSyntax { + let privateModifier = DeclModifierSyntax(name: .keyword(.private)) + let protocolIdentifier = createProtocolIdentifier() + let memberBlock = createMemberBlock(with: protocolFuncDecls) + return .init( + modifiers: [privateModifier], + name: .identifier(Names.class), + inheritanceClause: .init(inheritedTypes: [.init(type: protocolIdentifier)]), + memberBlock: memberBlock + ) + } + + static func createProtocolIdentifier() -> IdentifierTypeSyntax { + return .init(name: .identifier(Names.protocol)) + } + + static func createMemberBlock(with protocolFuncDecls: [FunctionDeclSyntax]) -> MemberBlockSyntax { + let itemList = MemberBlockItemListSyntax { + createCompletionProperty() + createInit() + for funcDecl in protocolFuncDecls { + SignalsMacroGroupSupport.createFuncDecl( + from: funcDecl, + with: createFuncBody() + ) + } + } + return .init(members: itemList) + } + + static func createCompletionProperty() -> VariableDeclSyntax { + let privateModifier = DeclModifierSyntax(name: .keyword(.private)) + let type = createCompletionType() + let patternBinding = PatternBindingSyntax( + pattern: IdentifierPatternSyntax(identifier: .identifier(Names.completion)), + typeAnnotation: .init(type: type) + ) + return .init( + modifiers: [privateModifier], + bindingSpecifier: .keyword(.let), + bindings: [patternBinding] + ) + } + + static func createInit() -> InitializerDeclSyntax { + let signature = createInitSignature() + let body = createInitBody() + return .init(signature: signature, body: body) + } + + static func createInitSignature() -> FunctionSignatureSyntax { + let type = createCompletionType() + let defaultNil = InitializerClauseSyntax(value: NilLiteralExprSyntax()) + let completionParameter = FunctionParameterSyntax( + firstName: .identifier(Names.completion), + type: type, + defaultValue: defaultNil + ) + return .init(parameterClause: .init(parameters: [completionParameter])) + } + + static func createInitBody() -> CodeBlockSyntax { + let selfCompletion = MemberAccessExprSyntax( + base: DeclReferenceExprSyntax(baseName: .keyword(.`self`)), + declName: .init(baseName: .identifier(Names.completion)) + ) + let completion = DeclReferenceExprSyntax(baseName: .identifier(Names.completion)) + let assigmentOperator = InfixOperatorExprSyntax( + leftOperand: selfCompletion, + operator: AssignmentExprSyntax(), + rightOperand: completion + ) + return .init(statements: [.init(item: .expr(.init(assigmentOperator)))]) + } + + static func createCompletionType() -> TypeSyntaxProtocol { + return OptionalTypeSyntax(wrappedType: IdentifierTypeSyntax(name: .identifier(Names.emptyClosure))) + } + + static func createFuncBody() -> CodeBlockSyntax { + let completion = DeclReferenceExprSyntax(baseName: .identifier(Names.completion)) + let completionCall = FunctionCallExprSyntax( + calledExpression: OptionalChainingExprSyntax(expression: completion) + ) + return .init(statements: [.init(item: .expr(.init(completionCall)))]) + } + +} diff --git a/Sources/SurfMacros/Implementation/Signals/MulticastError.swift b/Sources/SurfMacros/Implementation/Signals/MulticastError.swift deleted file mode 100644 index 12a7e1d..0000000 --- a/Sources/SurfMacros/Implementation/Signals/MulticastError.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// File.swift -// -// -// Created by pavlov on 24.05.2024. -// - -import Foundation - -enum MulticastError: Error, CustomStringConvertible { - case wrongFunctionFormat - - var description: String { - switch self { - case .wrongFunctionFormat: - return """ - The only allowed format of a function is the following: - func (, ...) - """ - } - } - -} diff --git a/Sources/SurfMacros/Implementation/Signals/MulticastMacro.swift b/Sources/SurfMacros/Implementation/Signals/MulticastMacro.swift index cde20e4..8f218b1 100644 --- a/Sources/SurfMacros/Implementation/Signals/MulticastMacro.swift +++ b/Sources/SurfMacros/Implementation/Signals/MulticastMacro.swift @@ -33,7 +33,7 @@ public struct MulticastMacro: PeerMacro { guard let protocolDecl = declaration.as(ProtocolDeclSyntax.self) else { throw DeclarationError.wrongAttaching(expected: .protocol) } - try checkProtocolDeclaration(protocolDecl) + try SignalsMacroGroupSupport.checkProtocolDeclaration(protocolDecl) Names.protocol = protocolDecl.name.text let protocolFuncDecls = protocolDecl.memberBlock.functionDecls let signalsClass = createSignalsClass(with: protocolFuncDecls) @@ -42,16 +42,6 @@ public struct MulticastMacro: PeerMacro { } -// MARK: - Checks - -private extension MulticastMacro { - - static func checkProtocolDeclaration(_ declaration: ProtocolDeclSyntax) throws { - try checkMembers(of: declaration) - } - -} - // MARK: - Creations private extension MulticastMacro { @@ -75,36 +65,6 @@ private extension MulticastMacro { private extension MulticastMacro { - static func checkMembers(of decl: ProtocolDeclSyntax) throws { - try decl.memberBlock.members.forEach { member in - if let funcDecl = member.decl.as(FunctionDeclSyntax.self), - !isAppropriateFuncDecl(funcDecl) { - throw MulticastError.wrongFunctionFormat - } else if member.decl.is(VariableDeclSyntax.self) { - throw DeclarationError.unexpectedVariable - } else if member.decl.is(AssociatedTypeDeclSyntax.self) { - throw DeclarationError.unexpectedAssociatedType - } - } - } - - static func isAppropriateFuncDecl(_ decl: FunctionDeclSyntax) -> Bool { - if !decl.modifiers.contains(where: isStatic), - case .identifier = decl.name.tokenKind, - decl.signature.returnClause == nil, - decl.signature.effectSpecifiers == nil, - decl.genericWhereClause == nil, - decl.genericParameterClause == nil, - decl.attributes.isEmpty { - return true - } - return false - } - - static func isStatic(_ modifier: DeclModifierSyntax) -> Bool { - modifier.name.tokenKind == .keyword(.static) - } - static func createPublicModifier() -> DeclModifierSyntax { return .init(name: .keyword(.public)) } @@ -182,6 +142,16 @@ private extension MulticastMacro { } static func transformIntoSignalsClassFuncDecl(_ funcDecl: FunctionDeclSyntax) -> FunctionDeclSyntax { + let publicModifier = createPublicModifier() + let body = createFuncBody(withCalling: funcDecl) + return SignalsMacroGroupSupport.createFuncDecl( + from: funcDecl, + with: body, + modifiers: [publicModifier] + ) + } + + static func createFuncBody(withCalling funcDecl: FunctionDeclSyntax) -> CodeBlockSyntax { let funcCall = transformIntoCall(functionDecl: funcDecl) let trailingClosure = ClosureExprSyntax(statements: [.init(item: .expr(.init(funcCall)))]) let forEachCall = FunctionCallExprSyntax( @@ -192,12 +162,7 @@ private extension MulticastMacro { trailingClosure: trailingClosure, argumentsBuilder: {} ) - let publicModifier = createPublicModifier() - - var signalsClassFuncDecl = funcDecl.trimmed - signalsClassFuncDecl.body = .init(statements: [.init(item: .expr(.init(forEachCall)))]) - signalsClassFuncDecl.modifiers = [publicModifier] - return signalsClassFuncDecl + return .init(statements: [.init(item: .expr(.init(forEachCall)))]) } static func transformIntoCall(functionDecl: FunctionDeclSyntax) -> FunctionCallExprSyntax { diff --git a/Sources/SurfMacros/Implementation/Signals/SignalsPlugin.swift b/Sources/SurfMacros/Implementation/Signals/SignalsPlugin.swift index b4457d5..3dda2d5 100644 --- a/Sources/SurfMacros/Implementation/Signals/SignalsPlugin.swift +++ b/Sources/SurfMacros/Implementation/Signals/SignalsPlugin.swift @@ -5,6 +5,7 @@ import SwiftSyntaxMacros struct SignalsPlugin { static let providingMacros: [Macro.Type] = [ - MulticastMacro.self + MulticastMacro.self, + CompletionHandlerMacro.self ] } diff --git a/Sources/SurfMacros/Implementation/Signals/Support/SignalsMacroGroupSupport.swift b/Sources/SurfMacros/Implementation/Signals/Support/SignalsMacroGroupSupport.swift new file mode 100644 index 0000000..238d70a --- /dev/null +++ b/Sources/SurfMacros/Implementation/Signals/Support/SignalsMacroGroupSupport.swift @@ -0,0 +1,82 @@ +// +// SignalsMacroGroupSupport.swift +// +// +// Created by pavlov on 01.07.2024. +// + +import SwiftCompilerPlugin +import SwiftSyntax +import SwiftSyntaxBuilder +import SwiftSyntaxMacros +import SurfMacrosSupport + +struct SignalsMacroGroupSupport {} + +// MARK: - Checks + +extension SignalsMacroGroupSupport { + + static func checkProtocolDeclaration(_ declaration: ProtocolDeclSyntax) throws { + try checkMembers(of: declaration) + } + +} + +// MARK: - Creations + +extension SignalsMacroGroupSupport { + + static func createFuncDecl( + from protocolFuncDecl: FunctionDeclSyntax, + with body: CodeBlockSyntax, + modifiers: DeclModifierListSyntax = [] + ) -> FunctionDeclSyntax { + var funcDecl = protocolFuncDecl.trimmed + funcDecl.body = body + funcDecl.modifiers = modifiers + return funcDecl + } + +} + +// MARK: - Private Methods + +private extension SignalsMacroGroupSupport { + + static func checkMembers(of decl: ProtocolDeclSyntax) throws { + try decl.memberBlock.members.forEach { member in + if let funcDecl = member.decl.as(FunctionDeclSyntax.self), + !isAppropriateFuncDecl(funcDecl) { + throw CustomError( + description: """ + The only allowed format of a function is the following: + func (, ...) + """ + ) + } else if member.decl.is(VariableDeclSyntax.self) { + throw DeclarationError.unexpectedVariable + } else if member.decl.is(AssociatedTypeDeclSyntax.self) { + throw DeclarationError.unexpectedAssociatedType + } + } + } + + static func isAppropriateFuncDecl(_ decl: FunctionDeclSyntax) -> Bool { + if !decl.modifiers.contains(where: isStatic), + case .identifier = decl.name.tokenKind, + decl.signature.returnClause == nil, + decl.signature.effectSpecifiers == nil, + decl.genericWhereClause == nil, + decl.genericParameterClause == nil, + decl.attributes.isEmpty { + return true + } + return false + } + + static func isStatic(_ modifier: DeclModifierSyntax) -> Bool { + modifier.name.tokenKind == .keyword(.static) + } + +} diff --git a/Sources/SurfMacros/Macros/Signals/CompletionHandler.swift b/Sources/SurfMacros/Macros/Signals/CompletionHandler.swift new file mode 100644 index 0000000..5fdc5eb --- /dev/null +++ b/Sources/SurfMacros/Macros/Signals/CompletionHandler.swift @@ -0,0 +1,2 @@ +@attached(peer, names: suffixed(Handler)) +public macro CompletionHandler() = #externalMacro(module: "SurfMacroBody", type: "CompletionHandlerMacro") diff --git a/Tests/SurfMacros/Signals/CompletionHandlerTests.swift b/Tests/SurfMacros/Signals/CompletionHandlerTests.swift new file mode 100644 index 0000000..2bad47a --- /dev/null +++ b/Tests/SurfMacros/Signals/CompletionHandlerTests.swift @@ -0,0 +1,70 @@ +// swiftlint:disable all + +import SwiftSyntaxMacros +import SwiftSyntaxMacrosTestSupport +import XCTest + +#if canImport(SurfMacroBody) +import SurfMacroBody +import SurfMacrosSupport + +private let macro = "CompletionHandler" +private let type = CompletionHandlerMacro.self +private let testMacros: [String: Macro.Type] = [macro: type] +#endif + +final class CompletionHandlerMacroTests: XCTestCase { + + func testAllWrongProtocolFormats() { + let runner = ProtocolableMacroTests(macro: macro, type: type) + runner.testWhenAttachedTypeIsNotProtocol() + runner.testWhenFuncHasAttribute() + runner.testWhenFuncIsAsync() + runner.testWhenFuncIsGenericWithWhereKeyword() + runner.testWhenFuncIsGenericWithoutWhereKeyword() + runner.testWhenFuncIsStatic() + runner.testWhenFuncReturnIsNotVoid() + runner.testWhenFuncThrows() + runner.testWhenThereIsAssociated() + runner.testWhenThereIsVariable() + } + + func testWithAllPossibleArgumentFormats() { + assertMacroExpansion( + """ + @\(macro) + protocol BatSignal { + func call(robin: Robin) + func call(for robin: Robin) + func call(_ robin: Robin) + } + """, + expandedSource: """ + protocol BatSignal { + func call(robin: Robin) + func call(for robin: Robin) + func call(_ robin: Robin) + } + + private class BatSignalHandler: BatSignal { + private let completion: EmptyClosure? + init(completion: EmptyClosure? = nil) { + self.completion = completion + } + func call(robin: Robin) { + completion?() + } + func call(for robin: Robin) { + completion?() + } + func call(_ robin: Robin) { + completion?() + } + } + """, + macros: testMacros + ) + } + +} +// swiftlint:enable all diff --git a/Tests/SurfMacros/Signals/MulticastTests.swift b/Tests/SurfMacros/Signals/MulticastTests.swift index e6fb404..60c14e3 100644 --- a/Tests/SurfMacros/Signals/MulticastTests.swift +++ b/Tests/SurfMacros/Signals/MulticastTests.swift @@ -8,305 +8,26 @@ import XCTest import SurfMacroBody import SurfMacrosSupport -private let testMacros: [String: Macro.Type] = ["Multicast": MulticastMacro.self] +private let macro = "Multicast" +private let type = MulticastMacro.self +private let testMacros: [String: Macro.Type] = [macro: type] #endif final class MulticastMacroTests: XCTestCase { - - func testWhenAttachedTypeIsNotProtocol() { - let attached: (String) -> String = { - """ - @Multicast - \($0) BatSignal {} - """ - } - let expandedSource: (String) -> String = { - """ - \($0) BatSignal {} - """ - } - let diagnostic = DiagnosticSpec( - message: "Macro can be attached to protocol only", - line: 1, - column: 1 - ) - let wrongDecls: [Decls] = [.class, .enum, .struct] - - wrongDecls - .map { $0.rawValue } - .forEach { - assertMacroExpansion( - attached($0), - expandedSource: expandedSource($0), - diagnostics: [diagnostic], - macros: testMacros - ) - } - } - - func testWhenThereIsAssociated() { - assertMacroExpansion( - """ - @Multicast - protocol BatSignal { - associatedtype Item - - func methodA() - } - """, - expandedSource: """ - protocol BatSignal { - associatedtype Item - - func methodA() - } - """, - diagnostics: [.init(message: "There should not be any associated types", line: 1, column: 1)], - macros: testMacros - ) - } - - func testWhenThereIsVariable() { - assertMacroExpansion( - """ - @Multicast - protocol BatSignal { - var robinsName: String { get } - - func methodA() - } - """, - expandedSource: """ - protocol BatSignal { - var robinsName: String { get } - - func methodA() - } - """, - diagnostics: [.init(message: "There should not be any variables", line: 1, column: 1)], - macros: testMacros - ) - } - - func testWhenFuncIsStatic() { - assertMacroExpansion( - """ - @Multicast - protocol BatSignal { - static func methodA() - - func methodB() - } - """, - expandedSource: """ - protocol BatSignal { - static func methodA() - func methodB() - } - """, - diagnostics: [ - .init( - message: """ - The only allowed format of a function is the following: - func (, ...) - """, - line: 1, - column: 1 - ) - ], - macros: testMacros - ) + func testAllWrongProtocolFormats() { + let runner = ProtocolableMacroTests(macro: macro, type: type) + runner.testWhenAttachedTypeIsNotProtocol() + runner.testWhenFuncHasAttribute() + runner.testWhenFuncIsAsync() + runner.testWhenFuncIsGenericWithWhereKeyword() + runner.testWhenFuncIsGenericWithoutWhereKeyword() + runner.testWhenFuncIsStatic() + runner.testWhenFuncReturnIsNotVoid() + runner.testWhenFuncThrows() + runner.testWhenThereIsAssociated() + runner.testWhenThereIsVariable() } - - func testWhenFuncThrows() { - assertMacroExpansion( - """ - @Multicast - protocol BatSignal { - func methodA() throws - - func methodB() - } - """, - expandedSource: """ - protocol BatSignal { - func methodA() throws - - func methodB() - } - """, - diagnostics: [ - .init( - message: """ - The only allowed format of a function is the following: - func (, ...) - """, - line: 1, - column: 1 - ) - ], - macros: testMacros - ) - } - - func testWhenFuncIsAsync() { - assertMacroExpansion( - """ - @Multicast - protocol BatSignal { - func methodA() async - - func methodB() - } - """, - expandedSource: """ - protocol BatSignal { - func methodA() async - - func methodB() - } - """, - diagnostics: [ - .init( - message: """ - The only allowed format of a function is the following: - func (, ...) - """, - line: 1, - column: 1 - ) - ], - macros: testMacros - ) - } - - func testWhenFuncHasAttribute() { - assertMacroExpansion( - """ - @Multicast - protocol BatSignal { - @MainActor - func methodA() - - func methodB() - } - """, - expandedSource: """ - protocol BatSignal { - @MainActor - func methodA() - - func methodB() - } - """, - diagnostics: [ - .init( - message: """ - The only allowed format of a function is the following: - func (, ...) - """, - line: 1, - column: 1 - ) - ], - macros: testMacros - ) - } - - func testWhenFuncReturnIsNotVoid() { - assertMacroExpansion( - """ - @Multicast - protocol BatSignal { - func methodA() -> String - - func methodB() - } - """, - expandedSource: """ - protocol BatSignal { - func methodA() -> String - - func methodB() - } - """, - diagnostics: [ - .init( - message: """ - The only allowed format of a function is the following: - func (, ...) - """, - line: 1, - column: 1 - ) - ], - macros: testMacros - ) - } - - func testWhenFuncIsGenericWithoutWhereKeyword() { - assertMacroExpansion( - """ - @Multicast - protocol BatSignal { - func methodA(input: T) - - func methodB() - } - """, - expandedSource: """ - protocol BatSignal { - func methodA(input: T) - - func methodB() - } - """, - diagnostics: [ - .init( - message: """ - The only allowed format of a function is the following: - func (, ...) - """, - line: 1, - column: 1 - ) - ], - macros: testMacros - ) - } - - func testWhenFuncIsGenericWithWhereKeyword() { - assertMacroExpansion( - """ - @Multicast - protocol BatSignal { - func methodA(input: T) where T: Codable - - func methodB() - } - """, - expandedSource: """ - protocol BatSignal { - func methodA(input: T) where T: Codable - - func methodB() - } - """, - diagnostics: [ - .init( - message: """ - The only allowed format of a function is the following: - func (, ...) - """, - line: 1, - column: 1 - ) - ], - macros: testMacros - ) - } - func testWithAllPossibleArgumentFormats() { assertMacroExpansion( diff --git a/Tests/SurfMacros/Signals/Support/ProtocolableMacroTests.swift b/Tests/SurfMacros/Signals/Support/ProtocolableMacroTests.swift new file mode 100644 index 0000000..7ca8b70 --- /dev/null +++ b/Tests/SurfMacros/Signals/Support/ProtocolableMacroTests.swift @@ -0,0 +1,366 @@ +// +// ProtocolableMacroTests.swift +// +// +// Created by pavlov on 01.07.2024. +// + +import SwiftSyntaxMacros +import SwiftSyntaxMacrosTestSupport +import SurfMacrosSupport +import XCTest + +final class ProtocolableMacroTests { + + private let macro: String + private let type: Macro.Type + + init(macro: String, type: Macro.Type) { + self.macro = macro + self.type = type + } + + // MARK: - Tests + + func testWhenAttachedTypeIsNotProtocol(file: StaticString = #file, line: UInt = #line) { + let originalSource: (String) -> String = { + """ + @\(self.macro) + \($0) BatSignal {} + """ + } + let expandedSource: (String) -> String = { + """ + \($0) BatSignal {} + """ + } + let diagnosticMessage = "Macro can be attached to protocol only" + let wrongDecls: [Decls] = [.class, .enum, .struct] + + wrongDecls + .map { $0.rawValue } + .forEach { + launchTest( + originalSource($0), + expandedSource: expandedSource($0), + diagnosticMessage: diagnosticMessage, + file: file, + line: line + ) + } + } + + func testWhenThereIsAssociated(file: StaticString = #file, line: UInt = #line) { + let originalSource = """ + @\(self.macro) + protocol BatSignal { + associatedtype Item + + func methodA() + } + """ + + let expandedSource = """ + protocol BatSignal { + associatedtype Item + + func methodA() + } + """ + + let diagnosticsMessage = "There should not be any associated types" + + launchTest( + originalSource, + expandedSource: expandedSource, + diagnosticMessage: diagnosticsMessage, + file: file, + line: line + ) + } + + func testWhenThereIsVariable(file: StaticString = #file, line: UInt = #line) { + let originalSource = """ + @\(self.macro) + protocol BatSignal { + var robinsName: String { get } + + func methodA() + } + """ + + let expandedSource = """ + protocol BatSignal { + var robinsName: String { get } + + func methodA() + } + """ + + let diagnosticMessage = "There should not be any variables" + + launchTest( + originalSource, + expandedSource: expandedSource, + diagnosticMessage: diagnosticMessage, + file: file, + line: line + ) + } + + func testWhenFuncIsStatic(file: StaticString = #file, line: UInt = #line) { + let originalSource = """ + @\(self.macro) + protocol BatSignal { + static func methodA() + + func methodB() + } + """ + + let expandedSource = """ + protocol BatSignal { + static func methodA() + + func methodB() + } + """ + + let diagnosticMessage = """ + The only allowed format of a function is the following: + func (, ...) + """ + + launchTest( + originalSource, + expandedSource: expandedSource, + diagnosticMessage: diagnosticMessage, + file: file, + line: line + ) + } + + func testWhenFuncThrows(file: StaticString = #file, line: UInt = #line) { + let originalSource = """ + @\(self.macro) + protocol BatSignal { + func methodA() throws + + func methodB() + } + """ + + let expandedSource = """ + protocol BatSignal { + func methodA() throws + + func methodB() + } + """ + + let diagnosticMessage = """ + The only allowed format of a function is the following: + func (, ...) + """ + + launchTest( + originalSource, + expandedSource: expandedSource, + diagnosticMessage: diagnosticMessage, + file: file, + line: line + ) + } + + func testWhenFuncIsAsync(file: StaticString = #file, line: UInt = #line) { + let originalSource = """ + @\(self.macro) + protocol BatSignal { + func methodA() async + + func methodB() + } + """ + + let expandedSource = """ + protocol BatSignal { + func methodA() async + + func methodB() + } + """ + + let diagnosticMessage = """ + The only allowed format of a function is the following: + func (, ...) + """ + + launchTest( + originalSource, + expandedSource: expandedSource, + diagnosticMessage: diagnosticMessage, + file: file, + line: line + ) + } + + func testWhenFuncHasAttribute(file: StaticString = #file, line: UInt = #line) { + let originalSource = """ + @\(self.macro) + protocol BatSignal { + @MainActor + func methodA() + + func methodB() + } + """ + + let expandedSource = """ + protocol BatSignal { + @MainActor + func methodA() + + func methodB() + } + """ + + let diagnosticMessage = """ + The only allowed format of a function is the following: + func (, ...) + """ + + launchTest( + originalSource, + expandedSource: expandedSource, + diagnosticMessage: diagnosticMessage, + file: file, + line: line + ) + } + + func testWhenFuncReturnIsNotVoid(file: StaticString = #file, line: UInt = #line) { + let originalSource = """ + @\(self.macro) + protocol BatSignal { + func methodA() -> String + + func methodB() + } + """ + + let expandedSource = """ + protocol BatSignal { + func methodA() -> String + + func methodB() + } + """ + + let diagnosticMessage = """ + The only allowed format of a function is the following: + func (, ...) + """ + + launchTest( + originalSource, + expandedSource: expandedSource, + diagnosticMessage: diagnosticMessage, + file: file, + line: line + ) + } + + func testWhenFuncIsGenericWithoutWhereKeyword(file: StaticString = #file, line: UInt = #line) { + let originalSource = """ + @\(self.macro) + protocol BatSignal { + func methodA(input: T) + + func methodB() + } + """ + + let expandedSource = """ + protocol BatSignal { + func methodA(input: T) + + func methodB() + } + """ + + let diagnosticMessage = """ + The only allowed format of a function is the following: + func (, ...) + """ + + launchTest( + originalSource, + expandedSource: expandedSource, + diagnosticMessage: diagnosticMessage, + file: file, + line: line + ) + } + + func testWhenFuncIsGenericWithWhereKeyword(file: StaticString = #file, line: UInt = #line) { + let originalSource = """ + @\(self.macro) + protocol BatSignal { + func methodA(input: T) where T: Codable + + func methodB() + } + """ + + let expandedSource = """ + protocol BatSignal { + func methodA(input: T) where T: Codable + + func methodB() + } + """ + + let diagnosticMessage = """ + The only allowed format of a function is the following: + func (, ...) + """ + + launchTest( + originalSource, + expandedSource: expandedSource, + diagnosticMessage: diagnosticMessage, + file: file, + line: line + ) + } + +} + +// MARK: - Private Methods + +private extension ProtocolableMacroTests { + + func launchTest( + _ originalSource: String, + expandedSource: String, + diagnosticMessage: String, + file: StaticString, + line: UInt + ) { + assertMacroExpansion( + originalSource, + expandedSource: expandedSource, + diagnostics: [ + .init( + message: diagnosticMessage, + line: 1, + column: 1 + ) + ], + macros: [macro: type], + file: file, + line: line + ) + } + +} diff --git a/support/macro_generator/templates/implementation.liquid b/support/macro_generator/templates/implementation.liquid index 36bf0b7..b14edd7 100644 --- a/support/macro_generator/templates/implementation.liquid +++ b/support/macro_generator/templates/implementation.liquid @@ -6,5 +6,24 @@ import SwiftSyntaxMacros //select a Macro protocol based on your macroDefinition //public struct {{ macro_type }}: { + + // MARK: - Names + + private enum Names {} + + // MARK: - Macro + // implement the expansion function here, based on the chosen Macro protocol or protocols //} + +// MARK: - Checks + +// private extension {{ macro_type }} {} + +// MARK: - Getters + +// private extension {{ macro_type }} {} + +// MARK: - Creations + +// private extension {{ macro_type }} {}