Skip to content

Commit 17fbad3

Browse files
committed
Improved convenience init generator code
1 parent 09e7d57 commit 17fbad3

File tree

2 files changed

+71
-23
lines changed

2 files changed

+71
-23
lines changed

CodeGeneration/Sources/generate-swift-syntax/LayoutNode+Extensions.swift

Lines changed: 67 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import Utils
1717

1818
extension LayoutNode {
1919

20-
func makeChildParamType(for child: Child, isOptional: Bool = false) -> TypeSyntax {
20+
/// Returns Child parameter type as a ``TypeSyntax``.
21+
func generateChildParameterType(for child: Child, isOptional: Bool = false) -> TypeSyntax {
2122
var paramType: TypeSyntax
2223

2324
if !child.kind.isNodeChoicesEmpty {
@@ -39,17 +40,21 @@ extension LayoutNode {
3940
return paramType
4041
}
4142

42-
/// Generates a memberwise SyntaxNode initializer `SyntaxNodeString`.
43+
/// Generates a convenience memberwise SyntaxNode initializer based on a
44+
/// given ``NodeInitRule``.
4345
///
4446
/// - parameters:
4547
/// - rule: The ``NodeInitRule`` to use for generating the initializer. Applying a rule will make some children non-optional, and set default values for other children.
4648
/// - useDeprecatedChildName: Whether to use the deprecated child name for the initializer parameter.
49+
/// - returns:
50+
/// - ``SyntaxNodeString``: The generated initializer.
4751
func generateInitializerDeclHeader(for rule: NodeInitRule? = nil, useDeprecatedChildName: Bool = false) -> SyntaxNodeString {
4852
if children.isEmpty {
4953
return "public init()"
5054
}
5155

52-
func childParameterName(for child: Child) -> TokenSyntax {
56+
/// Returns the child paramter name.
57+
func generateChildParameterName(for child: Child) -> TokenSyntax {
5358
let parameterName: TokenSyntax
5459

5560
if useDeprecatedChildName, let deprecatedVarName = child.deprecatedVarName {
@@ -60,29 +65,52 @@ extension LayoutNode {
6065
return parameterName
6166
}
6267

63-
func ruleBasedChildIsOptional(for child: Child, with rule: NodeInitRule?) -> Bool? {
68+
/// Returns whether a given child should be optional in the initializer,
69+
/// based on a provided ``NodeInitRule``.
70+
///
71+
/// If the rule is `nil`, this func will return `nil` as well, which means
72+
/// that you should fall back to whether child is optional in the ``Node``
73+
/// definition.
74+
///
75+
func ruleBasedChildIsOptional(for child: Child, with rule: NodeInitRule?) -> Bool {
6476
if let rule = rule {
6577
if rule.nonOptionalChildName == child.name {
6678
return false
6779
} else {
6880
return child.isOptional
6981
}
7082
} else {
71-
return nil
83+
return child.isOptional
7284
}
7385
}
7486

87+
/// Returns a default value for a given child, based on a provided
88+
/// ``NodeInitRule``.
89+
///
90+
/// If the rule should not affect this child, the
91+
/// `child.defualtInitialization` will be returned.
7592
func ruleBasedChildDefaultValue(for child: Child, with rule: NodeInitRule?) -> InitializerClauseSyntax? {
76-
if let rule, let defaultValue = rule.childDefaultValues[child.name] {
77-
return InitializerClauseSyntax(
78-
equal: .equalToken(leadingTrivia: .space, trailingTrivia: .space),
79-
value: ExprSyntax(".\(defaultValue.spec.varOrCaseName)Token()")
80-
)
93+
if ruleBasedShouldOverrideDefault(for: child, with: rule) {
94+
if let rule, let defaultValue = rule.childDefaultValues[child.name] {
95+
return InitializerClauseSyntax(
96+
equal: .equalToken(leadingTrivia: .space, trailingTrivia: .space),
97+
value: ExprSyntax(".\(defaultValue.spec.varOrCaseName)Token()")
98+
)
99+
} else {
100+
return nil
101+
}
81102
} else {
82-
return nil
103+
return child.defaultInitialization
83104
}
105+
84106
}
85107

108+
/// Should the convenience initializer override the default value of a given
109+
/// child?
110+
///
111+
/// Returns `true` if there is a default value in the rule, or if the rule
112+
/// requires this parameter to be non-optional.
113+
/// If the rule is `nil`, it will return false.
86114
func ruleBasedShouldOverrideDefault(for child: Child, with rule: NodeInitRule?) -> Bool {
87115
if let rule {
88116
// If the rule provides a default for this child, override it and set the rule-based default.
@@ -97,33 +125,47 @@ extension LayoutNode {
97125
}
98126
}
99127

100-
func createFunctionParameterSyntax(for child: Child, overrideOptional: Bool? = nil, shouldOverrideDefault: Bool = false, overrideDefaultValue: InitializerClauseSyntax? = nil) -> FunctionParameterSyntax {
101128

102-
let parameterName = childParameterName(for: child)
129+
/// Generates a ``FunctionParameterSyntax`` for a given ``Child`` of this node.
130+
///
131+
/// - parameters:
132+
/// - child: The ``Child`` to generate the parameter for.
133+
/// - isOptional: Is the parameter optional?
134+
///
135+
func generateInitFunctionParameterSyntax(
136+
for child: Child,
137+
isOptional: Bool,
138+
defaultValue: InitializerClauseSyntax? = nil
139+
) -> FunctionParameterSyntax {
140+
let parameterName = generateChildParameterName(for: child)
103141

104142
return FunctionParameterSyntax(
105143
leadingTrivia: .newline,
106144
firstName: child.isUnexpectedNodes ? .wildcardToken(trailingTrivia: .space) : parameterName,
107145
secondName: child.isUnexpectedNodes ? parameterName : nil,
108146
colon: .colonToken(),
109-
type: makeChildParamType(for: child, isOptional: overrideOptional ?? child.isOptional),
110-
defaultValue: shouldOverrideDefault ? overrideDefaultValue : child.defaultInitialization
147+
type: generateChildParameterType(for: child, isOptional: isOptional),
148+
defaultValue: defaultValue
111149
)
112150
}
113151

152+
// Iterate over all children including unexpected, or only over expected children of the Node.
153+
//
114154
// For convenience initializers, we don't need unexpected tokens in the arguments list
115155
// because convenience initializers are meant to be used bo developers manually
116-
// hence there should be no unexpected tokens
156+
// hence there should be no unexpected tokens.
117157
let childrenToIterate = rule != nil ? nonUnexpectedChildren : children
118158

159+
// Iterate over the selected children, and make FunctionParameterSyntax for each of them.
119160
let params = FunctionParameterListSyntax {
120161
FunctionParameterSyntax("leadingTrivia: Trivia? = nil")
121162

122163
for child in childrenToIterate {
123-
createFunctionParameterSyntax(for: child,
124-
overrideOptional: ruleBasedChildIsOptional(for: child, with: rule),
125-
shouldOverrideDefault: ruleBasedShouldOverrideDefault(for: child, with: rule),
126-
overrideDefaultValue: ruleBasedChildDefaultValue(for: child, with: rule))
164+
generateInitFunctionParameterSyntax(
165+
for: child,
166+
isOptional: ruleBasedChildIsOptional(for: child, with: rule),
167+
defaultValue: ruleBasedChildDefaultValue(for: child, with: rule)
168+
)
127169
}
128170

129171
FunctionParameterSyntax("trailingTrivia: Trivia? = nil")
@@ -137,14 +179,18 @@ extension LayoutNode {
137179
"""
138180
}
139181

140-
func generateRuleBasedDefaultValuesDocComment(for rule: NodeInitRule) -> SwiftSyntax.Trivia {
182+
/// Returns a DccC comment for the parameters that get a default value,
183+
/// with their corresponding default values, for a rule-based convenience initializer
184+
/// for a node.
185+
func generateRuleBasedInitParamsDocComment(for rule: NodeInitRule) -> SwiftSyntax.Trivia {
141186
var params = ""
142187
for (childName, defaultValue) in rule.childDefaultValues {
143188
params += " - `\(childName)`: `TokenSyntax.\(defaultValue.spec.varOrCaseName)Token()`\n"
144189
}
145190
return docCommentTrivia(from: params)
146191
}
147192

193+
/// Returns a DocC comment for the full memberwise initializer for this node.
148194
func generateInitializerDocComment() -> SwiftSyntax.Trivia {
149195
func generateParamDocComment(for child: Child) -> String? {
150196
if child.documentationAbstract.isEmpty {

CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxNodesFile.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ func syntaxNode(nodesStartingWith: [Character]) -> SourceFileSyntax {
8484
/// A convenience initializer for ``\(node.kind.syntaxType)``
8585
/// that takes a non-optional value for `\(raw: rule.nonOptionalChildName)` parameter,
8686
/// and adds the following default values:
87-
\(node.generateRuleBasedDefaultValuesDocComment(for: rule))
87+
\(node.generateRuleBasedInitParamsDocComment(for: rule))
8888
\(node.generateInitializerDeclHeader(for: rule))
8989
"""
9090
) {
@@ -107,7 +107,9 @@ func syntaxNode(nodesStartingWith: [Character]) -> SourceFileSyntax {
107107
LabeledExprSyntax(
108108
label: child.varOrCaseName,
109109
colon: .colonToken(),
110-
expression: rule.nonOptionalChildName == child.name ? ExprSyntax("\(child.varOrCaseName.backtickedIfNeeded) as \(node.makeChildParamType(for: child, isOptional: true))") : ExprSyntax("\(child.varOrCaseName.backtickedIfNeeded)"),
110+
expression: rule.nonOptionalChildName == child.name ?
111+
ExprSyntax("\(child.varOrCaseName.backtickedIfNeeded) as \(node.generateChildParameterType(for: child, isOptional: true))") :
112+
ExprSyntax("\(child.varOrCaseName.backtickedIfNeeded)"),
111113
trailingComma: .commaToken()
112114
)
113115
}

0 commit comments

Comments
 (0)