From 2786d5ec072d60979fdb3eee71450b197159675a Mon Sep 17 00:00:00 2001 From: Rafael Escaleira Date: Mon, 14 Oct 2024 10:32:37 -0400 Subject: [PATCH] Add macros --- .../xcschemes/RefdsDesignPatterns.xcscheme | 67 +++++++++++++++++++ Package.swift | 18 +++-- Sources/RefdsRedux/RefdsReduxAction.swift | 3 - .../RefdsRedux/RefdsReduxActionProtocol.swift | 6 ++ Sources/RefdsRedux/RefdsReduxMiddleware.swift | 8 --- .../RefdsReduxMiddlewareProtocol.swift | 18 +++++ Sources/RefdsRedux/RefdsReduxReducer.swift | 8 --- .../RefdsReduxReducerProtocol.swift | 17 +++++ Sources/RefdsRedux/RefdsReduxState.swift | 3 - .../RefdsRedux/RefdsReduxStateProtocol.swift | 6 ++ Sources/RefdsRedux/RefdsReduxStore.swift | 39 ----------- .../RefdsRedux/RefdsReduxStoreProtocol.swift | 38 +++++++++++ .../RefdsReduxActionMacro.swift | 19 ++++++ .../RefdsReduxMacroPlugin.swift | 15 +++++ .../RefdsReduxMiddlewareMacro.swift | 28 ++++++++ .../RefdsReduxReducerMacro.swift | 30 +++++++++ .../RefdsReduxStateMacro.swift | 19 ++++++ .../RefdsReduxStoreMacro.swift | 29 ++++++++ 18 files changed, 304 insertions(+), 67 deletions(-) create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/RefdsDesignPatterns.xcscheme delete mode 100644 Sources/RefdsRedux/RefdsReduxAction.swift create mode 100644 Sources/RefdsRedux/RefdsReduxActionProtocol.swift delete mode 100644 Sources/RefdsRedux/RefdsReduxMiddleware.swift create mode 100644 Sources/RefdsRedux/RefdsReduxMiddlewareProtocol.swift delete mode 100644 Sources/RefdsRedux/RefdsReduxReducer.swift create mode 100644 Sources/RefdsRedux/RefdsReduxReducerProtocol.swift delete mode 100644 Sources/RefdsRedux/RefdsReduxState.swift create mode 100644 Sources/RefdsRedux/RefdsReduxStateProtocol.swift delete mode 100644 Sources/RefdsRedux/RefdsReduxStore.swift create mode 100644 Sources/RefdsRedux/RefdsReduxStoreProtocol.swift create mode 100644 Sources/RefdsReduxMacros/RefdsReduxActionMacro.swift create mode 100644 Sources/RefdsReduxMacros/RefdsReduxMacroPlugin.swift create mode 100644 Sources/RefdsReduxMacros/RefdsReduxMiddlewareMacro.swift create mode 100644 Sources/RefdsReduxMacros/RefdsReduxReducerMacro.swift create mode 100644 Sources/RefdsReduxMacros/RefdsReduxStateMacro.swift create mode 100644 Sources/RefdsReduxMacros/RefdsReduxStoreMacro.swift diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/RefdsDesignPatterns.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/RefdsDesignPatterns.xcscheme new file mode 100644 index 0000000..2f93b8e --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/RefdsDesignPatterns.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Package.swift b/Package.swift index 10033b4..ff035f9 100644 --- a/Package.swift +++ b/Package.swift @@ -2,6 +2,7 @@ // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription +import CompilerPluginSupport let package = Package( name: "RefdsDesignPatterns", @@ -21,16 +22,21 @@ let package = Package( targets: ["RefdsRedux"]), ], dependencies: [ - .package(url: "https://github.com/rafaelesantos/refds-shared.git", branch: "main") + .package(url: "https://github.com/rafaelesantos/refds-shared.git", branch: "main"), + .package(url: "https://github.com/swiftlang/swift-syntax.git", branch: "main") ], targets: [ + .macro( + name: "RefdsReduxMacros", + dependencies: [ + .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), + .product(name: "SwiftCompilerPlugin", package: "swift-syntax") + ]), .target( name: "RefdsRedux", dependencies: [ - .product( - name: "RefdsShared", - package: "refds-shared" - ) - ]), + "RefdsReduxMacros", + .product(name: "RefdsShared", package: "refds-shared") + ]) ] ) diff --git a/Sources/RefdsRedux/RefdsReduxAction.swift b/Sources/RefdsRedux/RefdsReduxAction.swift deleted file mode 100644 index 5a98a89..0000000 --- a/Sources/RefdsRedux/RefdsReduxAction.swift +++ /dev/null @@ -1,3 +0,0 @@ -import Foundation - -public protocol RefdsReduxAction: Sendable {} diff --git a/Sources/RefdsRedux/RefdsReduxActionProtocol.swift b/Sources/RefdsRedux/RefdsReduxActionProtocol.swift new file mode 100644 index 0000000..c39b62b --- /dev/null +++ b/Sources/RefdsRedux/RefdsReduxActionProtocol.swift @@ -0,0 +1,6 @@ +import Foundation + +@attached(extension, conformances: RefdsReduxActionProtocol) +public macro RefdsReduxAction() = #externalMacro(module: "RefdsReduxMacros", type: "RefdsReduxActionMacro") + +public protocol RefdsReduxActionProtocol: Sendable {} diff --git a/Sources/RefdsRedux/RefdsReduxMiddleware.swift b/Sources/RefdsRedux/RefdsReduxMiddleware.swift deleted file mode 100644 index 52f0254..0000000 --- a/Sources/RefdsRedux/RefdsReduxMiddleware.swift +++ /dev/null @@ -1,8 +0,0 @@ -import Foundation - -public protocol RefdsReduxMiddleware { - func middleware( - state: State, - action: RefdsReduxAction - ) -> AsyncStream -} diff --git a/Sources/RefdsRedux/RefdsReduxMiddlewareProtocol.swift b/Sources/RefdsRedux/RefdsReduxMiddlewareProtocol.swift new file mode 100644 index 0000000..ac08e65 --- /dev/null +++ b/Sources/RefdsRedux/RefdsReduxMiddlewareProtocol.swift @@ -0,0 +1,18 @@ +import Foundation + +@attached(extension, conformances: RefdsReduxMiddlewareProtocol) +public macro RefdsReduxMiddleware< + State: RefdsReduxStateProtocol +>() = #externalMacro( + module: "RefdsReduxMacros", + type: "RefdsReduxMiddlewareMacro" +) + +public protocol RefdsReduxMiddlewareProtocol { + associatedtype State: RefdsReduxStateProtocol + + func middleware( + state: State, + action: RefdsReduxActionProtocol + ) async -> AsyncStream +} diff --git a/Sources/RefdsRedux/RefdsReduxReducer.swift b/Sources/RefdsRedux/RefdsReduxReducer.swift deleted file mode 100644 index 393665e..0000000 --- a/Sources/RefdsRedux/RefdsReduxReducer.swift +++ /dev/null @@ -1,8 +0,0 @@ -import Foundation - -public actor RefdsReduxReducer { - func reduce( - state: State, - action: RefdsReduxAction - ) -> State { return state } -} diff --git a/Sources/RefdsRedux/RefdsReduxReducerProtocol.swift b/Sources/RefdsRedux/RefdsReduxReducerProtocol.swift new file mode 100644 index 0000000..d9300ef --- /dev/null +++ b/Sources/RefdsRedux/RefdsReduxReducerProtocol.swift @@ -0,0 +1,17 @@ +import Foundation + +@attached(extension, conformances: RefdsReduxReducerProtocol) +public macro RefdsReduxReducer< + State: RefdsReduxStateProtocol, + Action: RefdsReduxActionProtocol +>() = #externalMacro(module: "RefdsReduxMacros", type: "RefdsReduxReducerMacro") + +public protocol RefdsReduxReducerProtocol { + associatedtype State: RefdsReduxStateProtocol + associatedtype Action: RefdsReduxActionProtocol + + func reduce( + state: State, + action: Action + ) async -> State +} diff --git a/Sources/RefdsRedux/RefdsReduxState.swift b/Sources/RefdsRedux/RefdsReduxState.swift deleted file mode 100644 index d2795e2..0000000 --- a/Sources/RefdsRedux/RefdsReduxState.swift +++ /dev/null @@ -1,3 +0,0 @@ -import Foundation - -public protocol RefdsReduxState: Sendable {} diff --git a/Sources/RefdsRedux/RefdsReduxStateProtocol.swift b/Sources/RefdsRedux/RefdsReduxStateProtocol.swift new file mode 100644 index 0000000..3f0d82f --- /dev/null +++ b/Sources/RefdsRedux/RefdsReduxStateProtocol.swift @@ -0,0 +1,6 @@ +import Foundation + +@attached(extension, conformances: RefdsReduxStateProtocol) +public macro RefdsReduxState() = #externalMacro(module: "RefdsReduxMacros", type: "RefdsReduxStateMacro") + +public protocol RefdsReduxStateProtocol: Sendable, Equatable {} diff --git a/Sources/RefdsRedux/RefdsReduxStore.swift b/Sources/RefdsRedux/RefdsReduxStore.swift deleted file mode 100644 index e2868a9..0000000 --- a/Sources/RefdsRedux/RefdsReduxStore.swift +++ /dev/null @@ -1,39 +0,0 @@ -import Foundation -import SwiftUI - -@MainActor -public class RefdsReduxStore: ObservableObject { - @Published public var state: RefdsReduxState - public let reducer: RefdsReduxReducer - public let middlewares: [RefdsReduxMiddleware] - - public init( - state: RefdsReduxState, - reducer: RefdsReduxReducer, - middlewares: [RefdsReduxMiddleware] = [] - ) { - self._state = Published(initialValue: state) - self.reducer = reducer - self.middlewares = middlewares - } - - public func dispatch(action: RefdsReduxAction) async { - let stateReduced = await reducer.reduce( - state: state, - action: action - ) - - withAnimation(.easeInOut) { state = stateReduced } - - for middleware in middlewares { - let actions = middleware.middleware( - state: stateReduced, - action: action - ) - - for await action in actions { - await dispatch(action: action) - } - } - } -} diff --git a/Sources/RefdsRedux/RefdsReduxStoreProtocol.swift b/Sources/RefdsRedux/RefdsReduxStoreProtocol.swift new file mode 100644 index 0000000..e8a6842 --- /dev/null +++ b/Sources/RefdsRedux/RefdsReduxStoreProtocol.swift @@ -0,0 +1,38 @@ +import Foundation +import SwiftUI + +@attached(extension, conformances: RefdsReduxStoreProtocol) +public macro RefdsReduxStore< + State: RefdsReduxStateProtocol +>() = #externalMacro( + module: "RefdsReduxMacros", + type: "RefdsReduxStoreMacro" +) + +@MainActor +public protocol RefdsReduxStoreProtocol: ObservableObject { + associatedtype State: RefdsReduxStateProtocol + + var state: State { get set } + var reducer: (State, RefdsReduxActionProtocol) async -> State { get } + var middlewares: [(State, RefdsReduxActionProtocol) async -> AsyncStream] { get } + + func dispatch(action: RefdsReduxActionProtocol) async +} + +public extension RefdsReduxStoreProtocol { + func dispatch(action: RefdsReduxActionProtocol) async { + let middlewares = self.middlewares + let stateReduced = await reducer(state, action) + + withAnimation(.easeInOut) { state = stateReduced } + + for middleware in middlewares { + let actions = await middleware(state, action) + + for await action in actions { + await dispatch(action: action) + } + } + } +} diff --git a/Sources/RefdsReduxMacros/RefdsReduxActionMacro.swift b/Sources/RefdsReduxMacros/RefdsReduxActionMacro.swift new file mode 100644 index 0000000..ce248ec --- /dev/null +++ b/Sources/RefdsReduxMacros/RefdsReduxActionMacro.swift @@ -0,0 +1,19 @@ +import SwiftCompilerPlugin +import SwiftSyntax +import SwiftSyntaxBuilder +import SwiftSyntaxMacros + +public struct RefdsReduxActionMacro: ExpressionMacro { + public static func expansion( + of node: some FreestandingMacroExpansionSyntax, + in context: some MacroExpansionContext + ) -> ExprSyntax { + guard let enumDecl = node.as(EnumDeclSyntax.self) else { + fatalError("@RefdsReduxAction must be applied to an enum.") + } + let protocolConformance = """ + extension \(enumDecl.name.text): RefdsReduxActionProtocol {} + """ + return ExprSyntax(stringLiteral: protocolConformance) + } +} diff --git a/Sources/RefdsReduxMacros/RefdsReduxMacroPlugin.swift b/Sources/RefdsReduxMacros/RefdsReduxMacroPlugin.swift new file mode 100644 index 0000000..d6f4156 --- /dev/null +++ b/Sources/RefdsReduxMacros/RefdsReduxMacroPlugin.swift @@ -0,0 +1,15 @@ +import SwiftCompilerPlugin +import SwiftSyntax +import SwiftSyntaxBuilder +import SwiftSyntaxMacros + +@main +struct RefdsReduxMacroPlugin: CompilerPlugin { + let providingMacros: [Macro.Type] = [ + RefdsReduxActionMacro.self, + RefdsReduxStateMacro.self, + RefdsReduxReducerMacro.self, + RefdsReduxMiddlewareMacro.self, + RefdsReduxStoreMacro.self + ] +} diff --git a/Sources/RefdsReduxMacros/RefdsReduxMiddlewareMacro.swift b/Sources/RefdsReduxMacros/RefdsReduxMiddlewareMacro.swift new file mode 100644 index 0000000..9578651 --- /dev/null +++ b/Sources/RefdsReduxMacros/RefdsReduxMiddlewareMacro.swift @@ -0,0 +1,28 @@ +import SwiftCompilerPlugin +import SwiftSyntax +import SwiftSyntaxBuilder +import SwiftSyntaxMacros + +public struct RefdsReduxMiddlewareMacro: ExpressionMacro { + public static func expansion( + of node: some FreestandingMacroExpansionSyntax, + in context: some MacroExpansionContext + ) -> ExprSyntax { + guard let actorDecl = node.as(ActorDeclSyntax.self) else { + fatalError("@RefdsReduxState must be applied to an actor.") + } + let arguments = node.arguments + guard arguments.count == 1, + let stateType = arguments.first?.expression.description else { + fatalError("@RefdsReduxMiddleware must be applied with State type, e.g. @RefdsReduxMiddleware(State).") + } + + let protocolConformance = """ + extension \(actorDecl.name.text): RefdsReduxMiddlewareProtocol { + typealias State = \(stateType) + } + """ + + return ExprSyntax(stringLiteral: protocolConformance) + } +} diff --git a/Sources/RefdsReduxMacros/RefdsReduxReducerMacro.swift b/Sources/RefdsReduxMacros/RefdsReduxReducerMacro.swift new file mode 100644 index 0000000..cd59b7b --- /dev/null +++ b/Sources/RefdsReduxMacros/RefdsReduxReducerMacro.swift @@ -0,0 +1,30 @@ +import SwiftCompilerPlugin +import SwiftSyntax +import SwiftSyntaxBuilder +import SwiftSyntaxMacros + +public struct RefdsReduxReducerMacro: ExpressionMacro { + public static func expansion( + of node: some FreestandingMacroExpansionSyntax, + in context: some MacroExpansionContext + ) -> ExprSyntax { + guard let actorDecl = node.as(ActorDeclSyntax.self) else { + fatalError("@RefdsReduxState must be applied to an actor.") + } + let arguments = node.arguments + guard arguments.count == 2, + let stateType = arguments.first?.expression.description, + let actionType = arguments.last?.expression.description else { + fatalError("@RefdsReduxReducer must be applied with State and Action types, e.g. @RefdsReduxReducer(State, Action).") + } + + let protocolConformance = """ + extension \(actorDecl.name.text): RefdsReduxReducerProtocol { + typealias State = \(stateType) + typealias Action = \(actionType) + } + """ + + return ExprSyntax(stringLiteral: protocolConformance) + } +} diff --git a/Sources/RefdsReduxMacros/RefdsReduxStateMacro.swift b/Sources/RefdsReduxMacros/RefdsReduxStateMacro.swift new file mode 100644 index 0000000..5324253 --- /dev/null +++ b/Sources/RefdsReduxMacros/RefdsReduxStateMacro.swift @@ -0,0 +1,19 @@ +import SwiftCompilerPlugin +import SwiftSyntax +import SwiftSyntaxBuilder +import SwiftSyntaxMacros + +public struct RefdsReduxStateMacro: ExpressionMacro { + public static func expansion( + of node: some FreestandingMacroExpansionSyntax, + in context: some MacroExpansionContext + ) -> ExprSyntax { + guard let structDecl = node.as(StructDeclSyntax.self) else { + fatalError("@RefdsReduxState must be applied to an struct.") + } + let protocolConformance = """ + extension \(structDecl.name.text): RefdsReduxStateProtocol {} + """ + return ExprSyntax(stringLiteral: protocolConformance) + } +} diff --git a/Sources/RefdsReduxMacros/RefdsReduxStoreMacro.swift b/Sources/RefdsReduxMacros/RefdsReduxStoreMacro.swift new file mode 100644 index 0000000..265edad --- /dev/null +++ b/Sources/RefdsReduxMacros/RefdsReduxStoreMacro.swift @@ -0,0 +1,29 @@ +import SwiftCompilerPlugin +import SwiftSyntax +import SwiftSyntaxBuilder +import SwiftSyntaxMacros + +public struct RefdsReduxStoreMacro: ExpressionMacro { + public static func expansion( + of node: some FreestandingMacroExpansionSyntax, + in context: some MacroExpansionContext + ) -> ExprSyntax { + guard let enumDecl = node.as(EnumDeclSyntax.self) else { + fatalError("@RefdsReduxStore must be applied to an enum.") + } + + let arguments = node.arguments + guard arguments.count == 1, + let stateType = arguments.first?.expression.description else { + fatalError("@RefdsReduxStore must be applied with State type, e.g. @RefdsReduxStore(State).") + } + + let protocolConformance = """ + extension \(enumDecl.name.text): RefdsReduxStoreProtocol { + typealias State = \(stateType) + } + """ + + return ExprSyntax(stringLiteral: protocolConformance) + } +}