Skip to content

Commit b58e1eb

Browse files
authored
Merge pull request #32 from k-kohey/refactor/swift-syntax-builder
Refactor EventGen with SwiftSyntaxBuilder
2 parents 1b99789 + 632aac6 commit b58e1eb

File tree

5 files changed

+239
-76
lines changed

5 files changed

+239
-76
lines changed

.github/workflows/swift.yml

+5-3
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,17 @@ on:
1111

1212
jobs:
1313
build:
14-
runs-on: macos-12
14+
runs-on: macos-13
1515

1616
steps:
1717
- uses: actions/checkout@v3
1818
- name: Build
1919
run: |
2020
swift build -v
21-
swift build -v --package-path EventGen
21+
# EventGen requires Xcode14.3
22+
# swift build -v --package-path EventGen
2223
- name: Run tests
2324
run: |
2425
swift test -v
25-
swift test -v --package-path EventGen
26+
# EventGen requires Xcode14.3
27+
# swift test -v --package-path EventGen

EventGen/Package.resolved

+18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
{
22
"pins" : [
3+
{
4+
"identity" : "sqlite.swift",
5+
"kind" : "remoteSourceControl",
6+
"location" : "https://github.com/stephencelis/SQLite.swift.git",
7+
"state" : {
8+
"revision" : "7a2e3cd27de56f6d396e84f63beefd0267b55ccb",
9+
"version" : "0.14.1"
10+
}
11+
},
312
{
413
"identity" : "swift-argument-parser",
514
"kind" : "remoteSourceControl",
@@ -25,6 +34,15 @@
2534
"state" : {
2635
"revision" : "87ae1a8fa9180b85630c7b41ddd5aa40ffc87ce3"
2736
}
37+
},
38+
{
39+
"identity" : "swift-syntax",
40+
"kind" : "remoteSourceControl",
41+
"location" : "https://github.com/apple/swift-syntax.git",
42+
"state" : {
43+
"revision" : "cd793adf5680e138bf2bcbaacc292490175d0dcd",
44+
"version" : "508.0.0"
45+
}
2846
}
2947
],
3048
"version" : 2

EventGen/Package.swift

+4-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ let package = Package(
1212
dependencies: [
1313
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.0.0"),
1414
.package(url: "https://github.com/apple/swift-markdown", revision: "87ae1a8fa9180b85630c7b41ddd5aa40ffc87ce3"),
15-
.package(name: "Parchment", path: "../")
15+
.package(name: "Parchment", path: "../"),
16+
.package(url: "https://github.com/apple/swift-syntax.git", exact: "508.0.0")
1617
],
1718
targets: [
1819
.executableTarget(
@@ -26,7 +27,8 @@ let package = Package(
2627
name: "EventGenKit",
2728
dependencies: [
2829
.product(name: "Markdown", package: "swift-markdown"),
29-
.product(name: "Parchment", package: "Parchment")
30+
.product(name: "Parchment", package: "Parchment"),
31+
.product(name: "SwiftSyntaxBuilder", package: "swift-syntax")
3032
]
3133
),
3234
.testTarget(

EventGen/Sources/EventGenKit/SwiftGenerator.swift

+199-55
Original file line numberDiff line numberDiff line change
@@ -7,105 +7,249 @@
77

88
import Foundation
99
import Parchment
10+
import SwiftSyntax
11+
import SwiftSyntaxBuilder
12+
13+
struct GeneratedEvent: Loggable {
14+
let eventName: String
15+
let parameters: [String : Sendable]
16+
17+
static func token<T>(_ keyPath: KeyPath<Self, T>) -> TokenSyntax {
18+
switch keyPath {
19+
case \.eventName:
20+
return .identifier("eventName")
21+
case \.parameters:
22+
return .identifier("parameters")
23+
default:
24+
fatalError()
25+
}
26+
}
27+
28+
static func identifierPattern<T>(
29+
_ keyPath: KeyPath<Self, T>
30+
) -> IdentifierPatternSyntax {
31+
switch keyPath {
32+
case \.eventName:
33+
return "eventName"
34+
case \.parameters:
35+
return "parameters"
36+
default:
37+
fatalError()
38+
}
39+
}
40+
}
1041

1142
public struct SwiftGenerator {
1243
enum Error: Swift.Error {
1344
case argumentsIsEmpty
1445
}
1546

16-
public init() {}
47+
public init() {
48+
49+
}
1750

1851
public func run(with definisions: [EventDefinision]) throws -> String {
1952
guard !definisions.isEmpty else { throw Error.argumentsIsEmpty }
2053
return generate(with: definisions)
2154
}
2255

2356
private func generate(with definisions: [EventDefinision]) -> String {
24-
"""
25-
// This file is automatically generated by eventgen.
26-
// Do not edit this file.
57+
SourceFileSyntax {
58+
ImportDeclSyntax(path: [.init(name: "Parchment")])
59+
generatedEventStructDecl
60+
extensionDecl(with: definisions)
61+
}.formatted().description
62+
}
2763

28-
struct GeneratedEvent: \(Loggable.self) {
29-
let eventName: String
30-
let paramerters: [String: Sendasble]
64+
private var generatedEventStructDecl: StructDeclSyntax {
65+
StructDeclSyntax(
66+
identifier: "\(GeneratedEvent.self)",
67+
inheritanceClause: TypeInheritanceClauseSyntax {
68+
InheritedTypeSyntax(
69+
typeName: SimpleTypeIdentifierSyntax(
70+
stringLiteral: "\(Loggable.self)"
71+
)
72+
)
73+
}
74+
) {
75+
VariableDeclSyntax(
76+
.let,
77+
name: GeneratedEvent.identifierPattern(\.eventName),
78+
type: TypeAnnotationSyntax(
79+
type: SimpleTypeIdentifierSyntax(stringLiteral: "\(String.self)")
80+
)
81+
)
82+
VariableDeclSyntax(
83+
.let,
84+
name: GeneratedEvent.identifierPattern(\.parameters),
85+
type: TypeAnnotationSyntax(
86+
type: DictionaryTypeSyntax(
87+
keyType: SimpleTypeIdentifierSyntax(
88+
stringLiteral: "\(String.self)"
89+
),
90+
valueType: SimpleTypeIdentifierSyntax(
91+
stringLiteral: "Sendable"
92+
)
93+
)
94+
)
95+
)
3196
}
97+
}
3298

33-
extension GeneratedEvent {
34-
\(indented: generateFunc(with: definisions.filter { !$0.properties.isEmpty }))
35-
\(indented: generateProperty(with: definisions.filter { $0.properties.isEmpty }))
99+
private func extensionDecl(with definisions: [EventDefinision]) -> ExtensionDeclSyntax {
100+
ExtensionDeclSyntax(
101+
extendedType: SimpleTypeIdentifierSyntax(
102+
stringLiteral: "\(GeneratedEvent.self)"
103+
)
104+
) {
105+
for definision in definisions {
106+
if definision.properties.isEmpty {
107+
propertyEventDecl(with: definision)
108+
} else {
109+
functionEventDecl(with: definision)
110+
}
111+
112+
}
36113
}
37-
"""
38114
}
39115

40-
private func generateFunc(with definision: EventDefinision) -> String {
41-
"""
42-
\(generateCodeDocument(with: definision))
43-
static func \(definision.name)\(generateTupple(with: definision.properties)) -> Self {
44-
.init(
45-
eventName: "\(definision.name)",
46-
parameters: \(generateParamertersDictonary(with: definision))
116+
private func propertyEventDecl(with definision: EventDefinision) -> VariableDeclSyntax {
117+
VariableDeclSyntax(
118+
leadingTrivia: .init(
119+
pieces: generateCodeDocument(with: definision)
120+
),
121+
modifiers: ModifierListSyntax {
122+
DeclModifierSyntax(name: .static)
123+
},
124+
name: .init(stringLiteral: definision.name),
125+
type: TypeAnnotationSyntax(
126+
type: SimpleTypeIdentifier(stringLiteral: "Self")
47127
)
128+
) {
129+
generatedEventInitialization(with: definision)
48130
}
49-
"""
50131
}
51132

52-
private func generateProperty(with definision: EventDefinision) -> String {
53-
"""
54-
\(generateCodeDocument(with: definision))
55-
static var \(definision.name): Self {
56-
.init(eventName: \(definision.name), parameters: [:])
133+
private func functionEventDecl(
134+
with definision: EventDefinision
135+
) -> FunctionDeclSyntax {
136+
FunctionDeclSyntax(
137+
leadingTrivia: .init(
138+
pieces: generateCodeDocument(with: definision)
139+
),
140+
modifiers: ModifierListSyntax {
141+
DeclModifierSyntax(name: .static)
142+
},
143+
identifier: .identifier(definision.name),
144+
signature: FunctionSignatureSyntax(
145+
input: ParameterClauseSyntax {
146+
for property in definision.properties {
147+
FunctionParameterSyntax(
148+
firstName: .identifier(property.name),
149+
colon: .colon,
150+
type: typeSyntax(
151+
property.type, isNullable: property.nullable
152+
)
153+
)
154+
}
155+
},
156+
output: ReturnClause(
157+
returnType: SimpleTypeIdentifierSyntax(
158+
stringLiteral: "Self"
159+
)
160+
)
161+
)
162+
) {
163+
functionEventBodyDecl(with: definision)
57164
}
58-
"""
59165
}
60166

61-
private func generateFunc(with definisions: [EventDefinision]) -> String {
62-
definisions.map(generateFunc(with:)).joined(separator: "\n")
167+
private func functionEventBodyDecl(with definision: EventDefinision) -> CodeBlockItemListSyntax {
168+
CodeBlockItemListSyntax {
169+
generatedEventInitialization(with: definision)
170+
}
63171
}
64172

65-
private func generateProperty(with definisions: [EventDefinision]) -> String {
66-
definisions.map(generateProperty(with:)).joined(separator: "\n")
173+
private func generatedEventInitialization(
174+
with definision: EventDefinision
175+
) -> FunctionCallExprSyntax {
176+
FunctionCallExprSyntax(
177+
callee: IdentifierExprSyntax(stringLiteral: "\(GeneratedEvent.self)")
178+
) {
179+
TupleExprElementSyntax(
180+
label: GeneratedEvent.token(\.eventName),
181+
colon: .colon,
182+
expression: StringLiteralExprSyntax(
183+
content: definision.name
184+
)
185+
)
186+
TupleExprElementSyntax(
187+
label: GeneratedEvent.token(\.parameters),
188+
colon: .colon,
189+
expression: DictionaryExprSyntax {
190+
for property in definision.properties {
191+
DictionaryElementSyntax.init(
192+
keyExpression: StringLiteralExprSyntax(
193+
content: property.name
194+
),
195+
valueExpression: IdentifierExpr(stringLiteral: property.name)
196+
)
197+
}
198+
}
199+
)
200+
}
67201
}
68202

69-
private func generateParamertersDictonary(with definision: EventDefinision) -> String {
70-
"[\(definision.properties.map { "\($0.name): \($0.name)" }.joined(separator: ", "))]"
203+
private func generateCodeDocument(with definision: EventDefinision) -> [TriviaPiece] {
204+
if !definision.properties.isEmpty {
205+
var result: [TriviaPiece] = [
206+
.docLineComment("/// \(definision.description)"),
207+
.newlines(1),
208+
.docLineComment("/// - Parameters:"),
209+
.newlines(1)
210+
]
211+
212+
for property in definision.properties {
213+
result.append(
214+
.docLineComment("/// - \(property.name): \(property.description)")
215+
)
216+
result.append(.newlines(1))
217+
}
218+
219+
return result
220+
} else {
221+
return [
222+
.docLineComment("/// \(definision.description)"),
223+
.newlines(1)
224+
]
225+
}
71226
}
72227

73-
private func generateTupple(with fields: [Field]) -> String {
228+
private func typeSyntax(_ typeString: String, isNullable: Bool) -> TypeSyntaxProtocol {
74229
func type(_ typeString: String, isNullable: Bool) -> String {
75-
var result: String
76230
switch typeString {
77231
case "string":
78-
result = "\(String.self)"
232+
return "\(String.self)"
79233
case "int":
80-
result = "\(Int.self)"
234+
return "\(Int.self)"
81235
case "double":
82-
result = "\(Double.self)"
236+
return "\(Double.self)"
83237
case "boolean":
84-
result = "\(Bool.self)"
238+
return "\(Bool.self)"
85239
default:
86240
fatalError("Detect unsupported type \(typeString)")
87241
}
88-
89-
if isNullable {
90-
result += "?"
91-
}
92-
93-
return result
94242
}
95-
return "(" + fields.map { "\($0.name): \(type($0.type, isNullable: $0.nullable))" }.joined(separator: ", ") + ")"
96-
}
97243

98-
private func generateCodeDocument(with definision: EventDefinision) -> String {
99-
if !definision.properties.isEmpty {
100-
return """
101-
/// \(definision.description)
102-
/// - Parameters:
103-
\(definision.properties.map { "/// - \($0.name): \($0.description)" }.joined(separator: "\n"))
104-
"""
244+
let simpleSyntax = SimpleTypeIdentifierSyntax(
245+
stringLiteral: type(
246+
typeString, isNullable: isNullable
247+
)
248+
)
249+
if isNullable {
250+
return simpleSyntax
105251
} else {
106-
return """
107-
/// \(definision.description)
108-
"""
252+
return OptionalTypeSyntax(wrappedType: simpleSyntax)
109253
}
110254
}
111255
}

0 commit comments

Comments
 (0)