From dd6a47ee9e034774882f05fbce3357d88308a2bf Mon Sep 17 00:00:00 2001 From: maximkrouk Date: Sun, 31 Dec 2023 03:13:25 +0100 Subject: [PATCH] feat: RoutingController macro improvements --- .../RoutingControllerMacro.swift | 46 ++++++------- .../RoutingControllerMacroTests.swift | 68 ++++++++++++++++--- 2 files changed, 76 insertions(+), 38 deletions(-) diff --git a/Sources/CombineNavigationMacros/RoutingControllerMacro/RoutingControllerMacro.swift b/Sources/CombineNavigationMacros/RoutingControllerMacro/RoutingControllerMacro.swift index 36f51be..d5eab97 100644 --- a/Sources/CombineNavigationMacros/RoutingControllerMacro/RoutingControllerMacro.swift +++ b/Sources/CombineNavigationMacros/RoutingControllerMacro/RoutingControllerMacro.swift @@ -118,48 +118,40 @@ extension RoutingControllerMacro: ExtensionMacro { return decl } - - typealias StackDestination = (identifier: String, idType: String) - let stackDestinations: [StackDestination] = try navigationDestinations.compactMap { decl in + let stackDestinations: [String] = navigationDestinations.compactMap { decl in guard let attribute = decl.attributes.first?.attribute, - attribute.name.name.hasSuffix("StackDestination"), + attribute.name.name.contains("StackDestination"), let identifier = decl.bindings.first?.identifier - else { return StackDestination?.none } - - let idType: String = if let genericArgument = attribute.name.genericArguments?.first { - genericArgument.description - } else if let typeDecl = decl.bindings.first?.type?._syntax.as(DictionaryTypeSyntax.self) { - typeDecl.key.description - } else { - throw DiagnosticsError(diagnostics: [ - .requiresDictionaryLiteralForStackDestination(attribute._syntax) - ]) - } + else { return .none } - return StackDestination(identifier, idType) + return identifier } - - - if let firstStackDestination = stackDestinations.first { + if !stackDestinations.isEmpty { let erasedIDType = "some Hashable" - let commonIDType = stackDestinations.allSatisfy { destination in - destination.idType == firstStackDestination.idType - } ? firstStackDestination.idType : nil + + let castedIDDecl: DeclSyntax = """ + func controller( + for s: StackDestination + ) -> UIViewController? { + return (id as? ID).flatMap { + s.wrappedValue[$0] + } + } + """ var stackDestinationsCoalecing: String { - let subscriptCall = commonIDType.map { _ in "[id]" } ?? "[id as! $0.idType]" return stackDestinations - .map { "\($0.identifier)\(subscriptCall)" } + .map { "controller(for: _\($0))" } .joined(separator: "\n\t?? ") } - let inputType = commonIDType ?? erasedIDType - let destinationsStructSubscriptDecl: DeclSyntax = """ - \npublic subscript(_ id: \(raw: inputType)) -> UIViewController? { + \npublic subscript(_ id: \(raw: erasedIDType)) -> UIViewController? { + \(castedIDDecl) + return \(raw: stackDestinationsCoalecing) } """ diff --git a/Tests/CombineNavigationMacrosTests/RoutingControllerMacroTests.swift b/Tests/CombineNavigationMacrosTests/RoutingControllerMacroTests.swift index ca504b8..6030434 100644 --- a/Tests/CombineNavigationMacrosTests/RoutingControllerMacroTests.swift +++ b/Tests/CombineNavigationMacrosTests/RoutingControllerMacroTests.swift @@ -174,9 +174,17 @@ final class RoutingControllerMacroTests: XCTestCase { @StackDestination var secondDetailController: [Int: CocoaViewController] - public subscript(_ id: Int) -> UIViewController? { - return firstDetailController[id] - ?? secondDetailController[id] + public subscript(_ id: some Hashable) -> UIViewController? { + func controller( + for s: StackDestination + ) -> UIViewController? { + return (id as? ID).flatMap { + s.wrappedValue[$0] + } + } + + return controller(for: _firstDetailController) + ?? controller(for: _secondDetailController) } } @@ -202,14 +210,43 @@ final class RoutingControllerMacroTests: XCTestCase { var firstDetailController: Dictionary } """ - } diagnostics: { + } expansion: { """ - @RoutingController final class CustomController { @StackDestination - ╰─ 🛑 `@StackDestination` requires explicit wrapper type or dictionary type literal declaration for value. var firstDetailController: Dictionary } + + extension CustomController: CombineNavigation.RoutingController { + /// Container for captured destinations without referring to self + /// + /// > Generated by `CombineNavigationMacros.RoutingController` macro + /// + /// Use in `navigationDestination`/`navigationStack` methods to map + /// routes to specific destinations using `destinations` method + public struct Destinations { + @StackDestination + var firstDetailController: Dictionary + + public subscript(_ id: some Hashable) -> UIViewController? { + func controller( + for s: StackDestination + ) -> UIViewController? { + return (id as? ID).flatMap { + s.wrappedValue[$0] + } + } + + return controller(for: _firstDetailController) + } + } + + public func _makeDestinations() -> Destinations { + return Destinations( + firstDetailController: $firstDetailController + ) + } + } """ } } @@ -272,7 +309,7 @@ final class RoutingControllerMacroTests: XCTestCase { @CustomStackDestination var firstDetailController: [Int: CocoaViewController] - @CustomTreeDestinationOf + @CustomStackDestinationOf var secondDetailController } """ @@ -282,7 +319,7 @@ final class RoutingControllerMacroTests: XCTestCase { @CustomStackDestination var firstDetailController: [Int: CocoaViewController] - @CustomTreeDestinationOf + @CustomStackDestinationOf var secondDetailController } @@ -298,11 +335,20 @@ final class RoutingControllerMacroTests: XCTestCase { var firstDetailController: [Int: CocoaViewController] - @CustomTreeDestinationOf + @CustomStackDestinationOf var secondDetailController - public subscript(_ id: Int) -> UIViewController? { - return firstDetailController[id] + public subscript(_ id: some Hashable) -> UIViewController? { + func controller( + for s: StackDestination + ) -> UIViewController? { + return (id as? ID).flatMap { + s.wrappedValue[$0] + } + } + + return controller(for: _firstDetailController) + ?? controller(for: _secondDetailController) } }