From ef6a33eb5b886ec7f9c4232a049b8c7d9f8a58e0 Mon Sep 17 00:00:00 2001 From: Jaeyoung Yoon Date: Wed, 26 Apr 2023 14:46:19 +0900 Subject: [PATCH 1/6] =?UTF-8?q?=F0=9F=A4=A1=20Add=20reducerProtocol=20samp?= =?UTF-8?q?les?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Resources/Sources.swift | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/Tests/TCADiagramLibTests/Resources/Sources.swift b/Tests/TCADiagramLibTests/Resources/Sources.swift index 4cf2f33..ced5224 100644 --- a/Tests/TCADiagramLibTests/Resources/Sources.swift +++ b/Tests/TCADiagramLibTests/Resources/Sources.swift @@ -74,3 +74,66 @@ let sources: [String] = [ } """, ] + +let reducerProtocolSampleSource: [String] = [ + """ + public struct SelfLessonDetail: ReducerProtocol { + @Dependency(\\.environmentSelfLessonDetail) private var environment + + public init() {} + + public var body: some ReducerProtocol { + 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: /Action.filter) { + SelfLessonDetailFilter() + } + .ifLet(\\.selection, action: /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 { + } + } + """ +] From 189190eaaa361bae3b82827d4ec5ca195193a954 Mon Sep 17 00:00:00 2001 From: Jaeyoung Yoon Date: Wed, 26 Apr 2023 15:04:29 +0900 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=A7=AA=20gitmoji=20-c?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add reducerProtocol tests --- Tests/TCADiagramLibTests/DiagramTests.swift | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Tests/TCADiagramLibTests/DiagramTests.swift b/Tests/TCADiagramLibTests/DiagramTests.swift index 8c7019e..a26c1c1 100644 --- a/Tests/TCADiagramLibTests/DiagramTests.swift +++ b/Tests/TCADiagramLibTests/DiagramTests.swift @@ -23,4 +23,26 @@ final class DiagramTests: XCTestCase { """ XCTAssertEqual(result, expected) } + + func testReducerProtocolExample() throws { + let result = try Diagram.dump(reducerProtocolSampleSource) + 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) + } } From 5adb939bdd37228c6a4f70e65267f8208a7b6ca8 Mon Sep 17 00:00:00 2001 From: Jaeyoung Yoon Date: Wed, 26 Apr 2023 15:06:30 +0900 Subject: [PATCH 3/6] =?UTF-8?q?=E2=9C=A8=20Parse=20feature=20name=20from?= =?UTF-8?q?=20ReducerProtocol=20decl?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/TCADiagramLib/Internal/Parser.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Sources/TCADiagramLib/Internal/Parser.swift b/Sources/TCADiagramLib/Internal/Parser.swift index bbbae44..0dfe2d5 100644 --- a/Sources/TCADiagramLib/Internal/Parser.swift +++ b/Sources/TCADiagramLib/Internal/Parser.swift @@ -31,6 +31,18 @@ extension SourceFileSyntax { } extension SourceFileSyntax { + + /// ReducerProtocol을 상속한 부분을 찾아 부모 피쳐 이름을 가져옵니다. + private func predicateReducerProtocol(_ node: Syntax) throws -> String? { + if + let node = StructDeclSyntax(node), + node.inheritanceClause?.tokens(viewMode: .fixedUp).contains(where: { $0.tokenKind == .identifier("ReducerProtocol") }) == true + { + return node.identifier.text + } + return nil + } + /// pullback 함수 호출이 있는 부분을 찾아 부모, 자식 피쳐 이름을 가져옵니다. /// /// 1. pullback 호출 부분을 찾습니다(코드 상으로는 마지막 컨디션입니다. 파라미터를 먼저 보는게 속도 측면에서 유리할 것 같아서). From d43b96c5aa0dcf9ec1dd0330173201abe499a2ef Mon Sep 17 00:00:00 2001 From: Jaeyoung Yoon Date: Wed, 26 Apr 2023 15:07:14 +0900 Subject: [PATCH 4/6] =?UTF-8?q?=E2=9C=A8=20Parse=20Scope=20and=20ifLet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/TCADiagramLib/Internal/Parser.swift | 33 +++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/Sources/TCADiagramLib/Internal/Parser.swift b/Sources/TCADiagramLib/Internal/Parser.swift index 0dfe2d5..d82d668 100644 --- a/Sources/TCADiagramLib/Internal/Parser.swift +++ b/Sources/TCADiagramLib/Internal/Parser.swift @@ -43,6 +43,39 @@ extension SourceFileSyntax { return nil } + /// Scope 또는 ifLet 호출을 찾아 자식 피쳐 이름을 가져옵니다. + private func predicateChildReducerProtocol(_ node: Syntax) throws -> ([String], Bool)? { + if + let node = FunctionCallExprSyntax(node), + node.argumentList.contains(where: { syntax in syntax.label?.text == "action" }) + { + if + node.tokens(viewMode: .fixedUp).contains(where: { $0.tokenKind == .identifier("Scope") }), + let child = node.trailingClosure?.statements.first?.description + .firstMatch(of: try Regex("\\s*(.+?)\\(\\)"))?[1] + .substring? + .description { + return ([child], false) + } + + // ifLet은 method chaining으로 연달아서 붙어있기 때문에 + // 매칭되는 모든 리듀서 이름들을 가져와 child 에 저장합니다. + if + node.tokens(viewMode: .fixedUp).contains(where: { $0.tokenKind == .identifier("ifLet") }) { + let childs = node.description + .matches(of: try Regex("ifLet.+{\\s+(.+?)\\(\\)")) + .compactMap { + $0[1].substring?.description + } + .filter { + $0 != "EmptyReducer" + } + return (childs, true) + } + } + return .none + } + /// pullback 함수 호출이 있는 부분을 찾아 부모, 자식 피쳐 이름을 가져옵니다. /// /// 1. pullback 호출 부분을 찾습니다(코드 상으로는 마지막 컨디션입니다. 파라미터를 먼저 보는게 속도 측면에서 유리할 것 같아서). From fda8cbc89c1210df02779cea4a81546d404d417c Mon Sep 17 00:00:00 2001 From: Jaeyoung Yoon Date: Wed, 26 Apr 2023 15:08:08 +0900 Subject: [PATCH 5/6] =?UTF-8?q?=F0=9F=91=94=20Add=20reducerProtocol=20pars?= =?UTF-8?q?ing=20logic=20in=20Parser?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/TCADiagramLib/Internal/Parser.swift | 39 +++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/Sources/TCADiagramLib/Internal/Parser.swift b/Sources/TCADiagramLib/Internal/Parser.swift index d82d668..3120bed 100644 --- a/Sources/TCADiagramLib/Internal/Parser.swift +++ b/Sources/TCADiagramLib/Internal/Parser.swift @@ -6,6 +6,11 @@ extension SourceFileSyntax { actions: inout Set, relations: inout [Relation] ) throws { + + if let reducerProtocolParent = try predicateReducerProtocol(node) { + try travel(parent: reducerProtocolParent, node: node, actions: &actions, relations: &relations) + } + if let (node, parent, child) = try predicatePullbackCall(node) { relations.append( .init( @@ -14,8 +19,6 @@ extension SourceFileSyntax { optional: isOptionalPullback(node) ) ) - } else if false { - // TODO: predicateReducerProtocol } else if let name = try predicateActionDecl(node) { actions.insert(name) } else { @@ -28,6 +31,38 @@ extension SourceFileSyntax { } } } + + /// ReducerProtocol이 선언된 파일에서 child를 가져옵니다. + /// + /// pullback과는 다르게 ReducerProtocol의 Scope나 ifLet에서는 부모피쳐 이름을 찾을수가 없습니다. + /// Reducer 선언부에서 찾은 부모 이름을 유지하면서 자식 피쳐들을 찾아나갑니다. + func travel( + parent: String, + node: Syntax, + actions: inout Set, + relations: inout [Relation] + ) throws { + if let (childs, isOptional) = try predicateChildReducerProtocol(node) { + childs.forEach { child in + relations.append( + .init( + parent: parent, + child: child.firstUppercased, + optional: isOptional + ) + ) + } + } else { + for child in node.children(viewMode: .all) { + try travel( + parent: parent, + node: child, + actions: &actions, + relations: &relations + ) + } + } + } } extension SourceFileSyntax { From be9a818566f86eb64e968950d7b6461d87d22ae6 Mon Sep 17 00:00:00 2001 From: Jaeyoung Yoon Date: Wed, 26 Apr 2023 15:08:28 +0900 Subject: [PATCH 6/6] =?UTF-8?q?=F0=9F=94=A7=20Update=20version=20to=200.3.?= =?UTF-8?q?0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/TCADiagram/TCADiagram.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/TCADiagram/TCADiagram.swift b/Sources/TCADiagram/TCADiagram.swift index 3f3ea9d..0e54c3f 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.2.0" + version: "0.3.0" ) @Option(name: .shortAndLong, help: "Root directory of swift files")