diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/TCADiagram-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/TCADiagram-Package.xcscheme new file mode 100644 index 0000000..b9885e2 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/TCADiagram-Package.xcscheme @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/tca-diagram-lib.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/tca-diagram-lib.xcscheme new file mode 100644 index 0000000..d4edfda --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/tca-diagram-lib.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/tca-diagram.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/tca-diagram.xcscheme new file mode 100644 index 0000000..c745657 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/tca-diagram.xcscheme @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sources/TCADiagram/TCADiagram.swift b/Sources/TCADiagram/TCADiagram.swift index b6e6a2d..3f3ea9d 100644 --- a/Sources/TCADiagram/TCADiagram.swift +++ b/Sources/TCADiagram/TCADiagram.swift @@ -8,7 +8,7 @@ import TCADiagramLib struct TCADiagram: ParsableCommand { static var configuration: CommandConfiguration = .init( commandName: "tca-diagram", - version: "0.1.1" + version: "0.2.0" ) @Option(name: .shortAndLong, help: "Root directory of swift files") diff --git a/Sources/TCADiagramLib/Internal/Parser.swift b/Sources/TCADiagramLib/Internal/Parser.swift index b87a5e7..db4acdb 100644 --- a/Sources/TCADiagramLib/Internal/Parser.swift +++ b/Sources/TCADiagramLib/Internal/Parser.swift @@ -14,6 +14,8 @@ extension SourceFileSyntax { optional: isOptionalPullback(node) ) ) + } else if false { + // TODO: predicateReducerProtocol } else if let name = try predicateActionDecl(node) { actions.insert(name) } else { @@ -37,20 +39,61 @@ extension SourceFileSyntax { private func predicatePullbackCall(_ node: Syntax) throws -> (FunctionCallExprSyntax, String, String)? { if let node = FunctionCallExprSyntax(node), - let action = node.argumentList.first(where: { syntax in syntax.label?.text == "action" })?.expression, - let child = node.description.firstMatch(of: try Regex("\\s+(.+?)Reducer"))?[1].substring?.description, - let parent = "\(action)".firstMatch(of: try Regex("\\/(.+?)Action.+"))?[1].substring?.description, - node.tokens(viewMode: .fixedUp).map(\.text).contains("pullback") + let action = node.argumentList.first(where: { syntax in syntax.label?.text == "action" })?.expression { - return (node, parent, child) + let child = node.description.firstMatch(of: try Regex("\\s+(.+?)Reducer"))?[1].substring?.description + let parent = "\(action)".firstMatch(of: try Regex("\\/(.+?)Action.+"))?[1].substring?.description + switch (child, parent) { + case (.some("Any"), .some(let parent)): + if + let child = node.description + .firstMatch(of: try Regex("(?s)\\s+AnyReducer\\s+\\{.+?in\\s+(.+?)\\("))?[1] + .substring? + .description, + node.tokens(viewMode: .fixedUp).map(\.text).contains("pullback") + { + return (node, parent, child) + } + return .none + + case (.some(let child), .some(let parent)): + if node.tokens(viewMode: .fixedUp).map(\.text).contains("pullback") { + return (node, parent, child) + } + return .none + + default: + return .none + } } return .none } /// `enum`으로 정의된 액션을 찾아 피쳐 이름을 가져옵니다. private func predicateActionDecl(_ node: Syntax) throws -> String? { - if let node = EnumDeclSyntax(node), node.identifier.text.hasSuffix("Action") { - return node.identifier.text.replacing("Action", with: "") + if let node = EnumDeclSyntax(node) { + if node.identifier.text == "Action" { + var parent = node.parent + while parent != nil { + if + let ext = ExtensionDeclSyntax(parent), + let name = ext.children(viewMode: .fixedUp) + .compactMap(SimpleTypeIdentifierSyntax.init) + .first? + .name + .text + { + return name + } else { + parent = parent?.parent + } + } + return .none + } else if node.identifier.text.hasSuffix("Action") { + return node.identifier.text.replacing("Action", with: "") + } else { + return .none + } } return .none } diff --git a/Tests/TCADiagramLibTests/DiagramTests.swift b/Tests/TCADiagramLibTests/DiagramTests.swift index 8ec598f..8c7019e 100644 --- a/Tests/TCADiagramLibTests/DiagramTests.swift +++ b/Tests/TCADiagramLibTests/DiagramTests.swift @@ -10,6 +10,7 @@ final class DiagramTests: XCTestCase { ```mermaid %%{ init : { "theme" : "default", "flowchart" : { "curve" : "monotoneY" }}}%% graph LR + EmailSignUp ---> SignUpAgreement SelfLessonDetail ---> Payment SelfLessonDetail -- optional --> SantaWeb SelfLessonDetail -- optional --> SelfLessonDetailFilter @@ -17,6 +18,7 @@ final class DiagramTests: XCTestCase { Payment(Payment: 1) SantaWeb(SantaWeb: 1) SelfLessonDetailFilter(SelfLessonDetailFilter: 1) + SignUpAgreement(SignUpAgreement: 1) ``` """ XCTAssertEqual(result, expected) diff --git a/Tests/TCADiagramLibTests/Resources/Sources.swift b/Tests/TCADiagramLibTests/Resources/Sources.swift index 744e619..973ff5d 100644 --- a/Tests/TCADiagramLibTests/Resources/Sources.swift +++ b/Tests/TCADiagramLibTests/Resources/Sources.swift @@ -45,5 +45,32 @@ let sources: [String] = [ } enum SelfLessonDetailFilterAction: Equatable { } + """, """ + public let emailSignUpReducer = EmailSignUpReducer + .combine( + AnyReducer { _ in + SignUpAgreement() + } + .pullback( + state: \\.signUpAgreement, + action: /EmailSignUpAction.signUpAgreement, + environment: { _ in } + ), + .init { state, action, environment in + switch action { + default: + return .none + } + } + ) + """, + """ + enum EmailSignUpAction { + } + extension SignUpAgreement { + public enum Action: Equatable { + } + } + """, ]