From 64ca8a982b644f25fba207d194af81c804006b9a Mon Sep 17 00:00:00 2001 From: Ryu0118 Date: Thu, 19 Oct 2023 04:36:43 +0900 Subject: [PATCH] Add Diagnostic to determine whether ViewAction and ReducerAction are enum --- .../Reducer/ReducerMacro+memberMacro.swift | 34 ++++++-- .../Reducer/ReducerMacroDiagnostic.swift | 7 ++ .../ReducerMacroTests.swift | 86 +++++++++++++++++++ 3 files changed, 118 insertions(+), 9 deletions(-) diff --git a/Sources/SimplexArchitectureMacrosPlugin/Reducer/ReducerMacro+memberMacro.swift b/Sources/SimplexArchitectureMacrosPlugin/Reducer/ReducerMacro+memberMacro.swift index 9dd01ff..99d56f0 100644 --- a/Sources/SimplexArchitectureMacrosPlugin/Reducer/ReducerMacro+memberMacro.swift +++ b/Sources/SimplexArchitectureMacrosPlugin/Reducer/ReducerMacro+memberMacro.swift @@ -123,15 +123,31 @@ public struct ReducerMacro: MemberMacro { var viewAction: EnumDeclSyntax? var reducerAction: EnumDeclSyntax? - structDecl.memberBlock.members - .compactMap { $0.decl.as(EnumDeclSyntax.self) } - .forEach { enumDecl in - switch enumDecl.name.text { - case "ViewAction": - viewAction = enumDecl - case "ReducerAction": - reducerAction = enumDecl - default: return + try structDecl.memberBlock.members + .forEach { + guard let hasName = $0.hasName else { + return + } + + let name = hasName.name.text + + if let enumDecl = $0.decl.as(EnumDeclSyntax.self) { + switch name { + case "ViewAction": + viewAction = enumDecl + case "ReducerAction": + reducerAction = enumDecl + default: return + } + } else if name == "ViewAction" || name == "ReducerAction" { + // ViewAction and ReducerAction must be enum + throw DiagnosticsError( + diagnostics: [ + ReducerMacroDiagnostic + .actionMustBeEnum(actionName: name) + .diagnose(at: hasName) + ] + ) } } diff --git a/Sources/SimplexArchitectureMacrosPlugin/Reducer/ReducerMacroDiagnostic.swift b/Sources/SimplexArchitectureMacrosPlugin/Reducer/ReducerMacroDiagnostic.swift index d8ae069..83a9314 100644 --- a/Sources/SimplexArchitectureMacrosPlugin/Reducer/ReducerMacroDiagnostic.swift +++ b/Sources/SimplexArchitectureMacrosPlugin/Reducer/ReducerMacroDiagnostic.swift @@ -8,6 +8,7 @@ public enum ReducerMacroDiagnostic { case notStruct case duplicatedCase case noMatchInheritanceClause + case actionMustBeEnum(actionName: String) } extension ReducerMacroDiagnostic: DiagnosticMessage { @@ -31,6 +32,9 @@ extension ReducerMacroDiagnostic: DiagnosticMessage { case .noMatchInheritanceClause: "The inheritance clause must match between ViewAction and ReducerAction" + + case .actionMustBeEnum(let actionName): + "\(actionName) must be enum" } } @@ -52,6 +56,9 @@ extension ReducerMacroDiagnostic: DiagnosticMessage { case .noMatchInheritanceClause: MessageID(domain: "ReducerMacroDiagnostic", id: "noMatchInheritanceClause") + + case .actionMustBeEnum: + MessageID(domain: "ReducerMacroDiagnostic", id: "actionMustBeEnum") } } } diff --git a/Tests/SimplexArchitectureTests/ReducerMacroTests.swift b/Tests/SimplexArchitectureTests/ReducerMacroTests.swift index 94b5b72..8325ec4 100644 --- a/Tests/SimplexArchitectureTests/ReducerMacroTests.swift +++ b/Tests/SimplexArchitectureTests/ReducerMacroTests.swift @@ -13,6 +13,46 @@ final class ReducerMacroTests: XCTestCase { } } + func testActionMustBeEnum() { + assertMacro { + """ + @Reducer + public struct MyReducer { + public struct ViewAction {} + } + """ + } matches: { + """ + @Reducer + public struct MyReducer { + public struct ViewAction {} + ┬────────────────────────── + ╰─ 🛑 ViewAction must be enum + } + """ + } + + assertMacro { + """ + @Reducer + public struct MyReducer { + public enum ViewAction {} + public struct ReducerAction {} + } + """ + } matches: { + """ + @Reducer + public struct MyReducer { + public enum ViewAction {} + public struct ReducerAction {} + ┬───────────────────────────── + ╰─ 🛑 ReducerAction must be enum + } + """ + } + } + func testNoMatchInheritanceClause() { assertMacro { """ @@ -630,5 +670,51 @@ final class ReducerMacroTests: XCTestCase { } """ } + + assertMacro { + """ + @Reducer + public struct MyReducer { + public enum ViewAction { + case decrement(arg1: String = "", arg2: Int? = nil) + } + public enum ReducerAction { + case decrement(arg1: String = "") + } + } + """ + } matches: { + """ + public struct MyReducer { + public enum ViewAction { + case decrement(arg1: String = "", arg2: Int? = nil) + } + public enum ReducerAction { + case decrement(arg1: String = "") + } + + public enum Action: ActionProtocol { + case decrement(arg1: String = "", arg2: Int? = nil) + + case decrement(arg1: String = "") + public init(viewAction: ViewAction) { + switch viewAction { + case .decrement(let arg1, let arg2): + self = .decrement(arg1: arg1, arg2: arg2) + } + } + public init(reducerAction: ReducerAction) { + switch reducerAction { + case .decrement(let arg1): + self = .decrement(arg1: arg1) + } + } + } + } + + extension MyReducer: ReducerProtocol { + } + """ + } } }