diff --git a/.gitignore b/.gitignore index 3b29812..03a7043 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,36 @@ DerivedData/ .swiftpm/config/registries.json .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata .netrc + +# Created by https://www.toptal.com/developers/gitignore/api/xcode,swiftpm,swiftpackagemanager +# Edit at https://www.toptal.com/developers/gitignore?templates=xcode,swiftpm,swiftpackagemanager + +### SwiftPackageManager ### +Packages +.build/ +xcuserdata +DerivedData/ +*.xcodeproj + + +### SwiftPM ### + + +### Xcode ### +## User settings +xcuserdata/ + +## Xcode 8 and earlier +*.xcscmblueprint +*.xccheckout + +### Xcode Patch ### +*.xcodeproj/* +!*.xcodeproj/project.pbxproj +!*.xcodeproj/xcshareddata/ +!*.xcodeproj/project.xcworkspace/ +!*.xcworkspace/contents.xcworkspacedata +/*.gcno +**/xcshareddata/WorkspaceSettings.xcsettings + +# End of https://www.toptal.com/developers/gitignore/api/xcode,swiftpm,swiftpackagemanager diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/TCADiagram-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/TCADiagram-Package.xcscheme index b9885e2..b2bfe19 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/TCADiagram-Package.xcscheme +++ b/.swiftpm/xcode/xcshareddata/xcschemes/TCADiagram-Package.xcscheme @@ -1,7 +1,7 @@ + LastUpgradeVersion = "1520" + version = "1.7"> @@ -48,41 +48,14 @@ ReferencedContainer = "container:"> - - - - - - - - + shouldUseLaunchSchemeArgsEnv = "YES" + shouldAutocreateTestPlan = "YES"> diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/tca-diagram-lib.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/tca-diagram-lib.xcscheme index d4edfda..055609b 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/tca-diagram-lib.xcscheme +++ b/.swiftpm/xcode/xcshareddata/xcschemes/tca-diagram-lib.xcscheme @@ -1,7 +1,7 @@ + LastUpgradeVersion = "1520" + version = "1.7"> @@ -26,9 +26,8 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES"> - - + shouldUseLaunchSchemeArgsEnv = "YES" + shouldAutocreateTestPlan = "YES"> + LastUpgradeVersion = "1520" + version = "1.7"> @@ -40,7 +40,8 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES" + shouldAutocreateTestPlan = "YES"> @@ -63,8 +64,7 @@ ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" - allowLocationSimulation = "YES" - viewDebuggingEnabled = "No"> + allowLocationSimulation = "YES"> - - - - - - , relations: inout [Relation] ) throws { - if let (childs, isOptional) = try predicateChildReducerProtocol(node) { - childs.forEach { child in + if let (children, isOptional) = try predicateChildReducerProtocol(node) { + children.forEach { child in relations.append( .init( parent: parent, @@ -67,17 +67,32 @@ extension SourceFileSyntax { extension SourceFileSyntax { - /// Get parent name from feature with superclass of ReducerProtocol + /// Get parent name from feature. private func predicateReducerProtocol(_ node: Syntax) throws -> String? { if - let node = StructDeclSyntax(node), - node.inheritanceClause?.tokens(viewMode: .fixedUp) - .contains(where: { - $0.tokenKind == .identifier("ReducerProtocol") - || $0.tokenKind == .identifier("Reducer") - }) == true + let node = StructDeclSyntax(node) { - return node.identifier.text + /// Has @Reducer macro + if + node.attributes?.contains(where: { element in + element.tokens(viewMode: .fixedUp).contains { el in + el.tokenKind == .identifier("Reducer") + } + }) == true + { + debugPrint(node.identifier.text) + return node.identifier.text + } + /// superclass of ReducerProtocol or Reducer + if + node.inheritanceClause?.tokens(viewMode: .fixedUp) + .contains(where: { + $0.tokenKind == .identifier("ReducerProtocol") + || $0.tokenKind == .identifier("Reducer") + }) == true + { + return node.identifier.text + } } return nil } @@ -93,15 +108,17 @@ extension SourceFileSyntax { let child = node.trailingClosure?.statements.first?.description .firstMatch(of: try Regex("\\s*(.+?)\\(\\)"))?[1] .substring? - .description { + .description + { return ([child], false) } // ifLet can be in "method chaining" // therefore find all reducer names that match and save in child if - node.tokens(viewMode: .fixedUp).contains(where: { $0.tokenKind == .identifier("ifLet") }) { - let childs = node.description + node.tokens(viewMode: .fixedUp).contains(where: { $0.tokenKind == .identifier("ifLet") }) + { + let children = node.description .matches(of: try Regex("ifLet.+{\\s+(.+?)\\(\\)")) .compactMap { $0[1].substring?.description @@ -109,7 +126,7 @@ extension SourceFileSyntax { .filter { $0 != "EmptyReducer" } - return (childs, true) + return (children, true) } } return .none @@ -185,7 +202,7 @@ extension SourceFileSyntax { /// check if `pullback` chains `optional()`. private func isOptionalPullback(_ node: FunctionCallExprSyntax) -> Bool { var stack: [Syntax] = node.children(viewMode: .fixedUp).reversed() - while(!stack.isEmpty) { + while !stack.isEmpty { let node = stack.removeFirst() if let node = FunctionCallExprSyntax(node), diff --git a/Tests/TCADiagramLibTests/DiagramTests.swift b/Tests/TCADiagramLibTests/DiagramTests.swift index 0aa0094..e9d27db 100644 --- a/Tests/TCADiagramLibTests/DiagramTests.swift +++ b/Tests/TCADiagramLibTests/DiagramTests.swift @@ -21,7 +21,7 @@ final class DiagramTests: XCTestCase { SignUpAgreement(SignUpAgreement: 1) ``` """ - XCTAssertEqual(result, expected) + XCTAssertEqual(result, expected) } func testReducerProtocolExample() throws { @@ -67,4 +67,26 @@ final class DiagramTests: XCTestCase { """ XCTAssertEqual(result, expected) } + + func testReducerMacroExample() throws { + let result = try Diagram.dump(reducerMacroSampleSource) + let expected = """ + ```mermaid + %%{ init : { "theme" : "default", "flowchart" : { "curve" : "monotoneY" }}}%% + graph LR + SelfLessonDetail -- optional --> DoubleIfLetChild + SelfLessonDetail ---> DoubleScopeChild + SelfLessonDetail ---> Payment + SelfLessonDetail -- optional --> SantaWeb + SelfLessonDetail -- optional --> SelfLessonDetailFilter + + DoubleIfLetChild(DoubleIfLetChild: 1) + DoubleScopeChild(DoubleScopeChild: 1) + Payment(Payment: 1) + SantaWeb(SantaWeb: 1) + SelfLessonDetailFilter(SelfLessonDetailFilter: 1) + ``` + """ + XCTAssertEqual(result, expected) + } } diff --git a/Tests/TCADiagramLibTests/Resources/Sources.swift b/Tests/TCADiagramLibTests/Resources/Sources.swift index f1f0741..b7c8d2c 100644 --- a/Tests/TCADiagramLibTests/Resources/Sources.swift +++ b/Tests/TCADiagramLibTests/Resources/Sources.swift @@ -200,3 +200,67 @@ let reducerSampleSource: [String] = [ } """ ] + +let reducerMacroSampleSource: [String] = [ + """ + @Reducer + public struct SelfLessonDetail { + @Dependency(\\.environmentSelfLessonDetail) private var environment + + public init() {} + + public var body: some Reducer { + BindingReducer() + Scope(state: \\State.payment, action: /Action.payment) { + Payment() + } + + Scope(state: \\.subState, action: .self) { + Scope( + state: /State.SubState.promotionWeb, + action: /Action.promotionWeb + ) { + DoubleScopeChild() + } + } + + Reduce { state, action in + switch action { + case default: + return .none + } + } + .ifLet(\\.filter, action: \\.filter) { + SelfLessonDetailFilter() + } + .ifLet(\\.selection, action: \\.web) { + SantaWeb() + } + .ifLet(\\SelfLessonDetail.State.selection, action: /SelfLessonDetail.Action.webView) { + EmptyReducer() + .ifLet(\\Identified.value, action: .self) { + DoubleIfLetChild() + } + } + } + } + """, + """ + extension SelfLessonDetail { + public enum Action: Equatable { + } + } + extension Payment { + public enum Action: Equatable { + } + } + extension SantaWeb { + public enum Action: Equatable { + } + } + extension SelfLessonDetailFilter { + public enum Action: Equatable { + } + } + """ +]