@@ -105,6 +105,169 @@ extension RawUnexpectedNodesSyntax {
105
105
}
106
106
}
107
107
108
+ // MARK: - Converting nodes to unexpected nodes
109
+
110
+ extension RawSyntaxNodeProtocol {
111
+ /// Select one node somewhere inside `self` and return a version of it with all of the other tokens in `self`
112
+ /// converted to unexpected nodes.
113
+ ///
114
+ /// This is the same as ``RawSyntaxNodeProtocol/makeUnexpectedKeepingNodes(of:arena:where:makeMissing:)`` except that
115
+ /// it always keeps, and always returns, exactly one node (the first one that would be encountered in a depth-first
116
+ /// search, or the missing node). The other nodes that would have been kept are converted to unexpected tokens as
117
+ /// usual. See that method for details about the behavior.
118
+ ///
119
+ /// - Parameters:
120
+ /// - keptType: The type of node that should be kept and returned.
121
+ /// - arena: The syntax arena to allocate new or copied nodes within.
122
+ /// - predicate: Should return `true` for the node that should be kept. Allows more selectivity than `keptType`
123
+ /// alone.
124
+ /// - makeMissing: If there are no children which satisfy `keptType` and `predicate`, this closure will be called
125
+ /// to create a substitute node for the unexpected syntax to be attached to. It's typically used to return a "missing syntax"
126
+ /// node, though that's not a technical requirement.
127
+ ///
128
+ /// - Returns: The kept node (or the missing node) with the other tokens in `self` attached to its leading and
129
+ /// trailing unexpected node children.
130
+ func makeUnexpectedKeepingFirstNode< KeptNode: RawSyntaxNodeProtocol > (
131
+ of keptType: KeptNode . Type ,
132
+ arena: SyntaxArena ,
133
+ where predicate: ( KeptNode ) -> Bool ,
134
+ makeMissing: ( ) -> KeptNode
135
+ ) -> KeptNode {
136
+ var alreadyFoundFirst = false
137
+ func compositePredicate( _ node: KeptNode ) -> Bool {
138
+ if alreadyFoundFirst || !predicate( node) {
139
+ return false
140
+ }
141
+ alreadyFoundFirst = true
142
+ return true
143
+ }
144
+
145
+ return makeUnexpectedKeepingNodes (
146
+ of: keptType,
147
+ arena: arena,
148
+ where: compositePredicate,
149
+ makeMissing: makeMissing
150
+ ) . first!
151
+ }
152
+
153
+ /// Select a number of nodes inside `self` and return versions of them with all of the other tokens in`self`
154
+ /// attached to them as unexpected nodes.
155
+ ///
156
+ /// For instance, if you had a `RawIfConfigDeclSyntax` like this:
157
+ ///
158
+ /// ```swift
159
+ /// #if FOO
160
+ /// func x() {}
161
+ /// func y() {}
162
+ /// #elseif BAR
163
+ /// func a() {}
164
+ /// func b() {}
165
+ /// #endif
166
+ /// ```
167
+ ///
168
+ /// Then a call like this:
169
+ ///
170
+ /// ```swift
171
+ /// let functions = directive.makeUnexpectedKeepingNodes(
172
+ /// of: RawFunctionDeclSyntax.self,
173
+ /// arena: parser.arena,
174
+ /// where: { node in true },
175
+ /// makeMissing: { RawFunctionDeclSyntax(...) }
176
+ /// )
177
+ /// ```
178
+ ///
179
+ /// Would return an array of four `RawFunctionDeclSyntax` nodes with the tokens for `#if FOO`, `#elseif BAR`, and
180
+ /// `#endif` added to the nodes for `x()`, `a()`, and `b()` respectively.
181
+ ///
182
+ /// Specifically, this method performs a depth-first recursive search of the children of `self`, collecting nodes of
183
+ /// type `keptType` for which `predicate` returns `true`. These nodes are then modified to add all of the tokens
184
+ /// within `self`, but outside of the kept nodes, to their leading or trailing `RawUnexpectedNodesSyntax` child. If
185
+ /// no kept nodes are found, the `makeMissing` closure is used to create a "missing syntax" node the tokens can be
186
+ /// attached to.
187
+ ///
188
+ /// Tokens are usually added to the leading unexpected node child of the next kept node, except for tokens after the
189
+ /// last kept node, which are added to the trailing unexpected node child of the last kept node.
190
+ ///
191
+ /// Token and collection nodes cannot be kept, as they cannot have unexpected syntax attached to them; however,
192
+ /// parents of such nodes can be kept.
193
+ ///
194
+ /// - Parameters:
195
+ /// - keptType: The type of node that should be kept and returned in the array.
196
+ /// - arena: The syntax arena to allocate new or copied nodes within.
197
+ /// - predicate: Should return `true` for nodes that should be kept. Allows more selectivity than `keptType` alone.
198
+ /// - makeMissing: If there are no children which satisfy `keptType` and `predicate`, this closure will be called
199
+ /// to create a substitute node for the unexpected syntax to be attached to. It's typically used to return a "missing syntax"
200
+ /// node, though that's not a technical requirement.
201
+ ///
202
+ /// - Returns: The kept nodes (or the missing node) with the other tokens in `self` attached to them at appropriate
203
+ /// unexpected node children. Note that there is always at least one node in the array.
204
+ func makeUnexpectedKeepingNodes< KeptNode: RawSyntaxNodeProtocol > (
205
+ of keptType: KeptNode . Type ,
206
+ arena: SyntaxArena ,
207
+ where predicate: ( KeptNode ) -> Bool ,
208
+ makeMissing: ( ) -> KeptNode
209
+ ) -> [ KeptNode ] {
210
+ var keptNodes : [ KeptNode ] = [ ]
211
+ var accumulatedTokens : [ RawTokenSyntax ] = [ ]
212
+
213
+ func attachAccumulatedTokensToLastKeptNode( appending: Bool ) {
214
+ if accumulatedTokens. isEmpty {
215
+ // Nothing to add? Nothing to do.
216
+ return
217
+ }
218
+
219
+ let lastKeptNodeIndex = keptNodes. endIndex - 1
220
+ let lastKeptNode = keptNodes [ lastKeptNodeIndex] . raw. layoutView!
221
+
222
+ let childIndex = appending ? lastKeptNode. children. endIndex - 1 : 0
223
+
224
+ let newUnexpected : RawUnexpectedNodesSyntax
225
+ if let oldUnexpected = lastKeptNode. children [ childIndex] ? . cast ( RawUnexpectedNodesSyntax . self) {
226
+ if appending {
227
+ newUnexpected = RawUnexpectedNodesSyntax ( combining: oldUnexpected, accumulatedTokens, arena: arena) !
228
+ } else {
229
+ newUnexpected = RawUnexpectedNodesSyntax ( combining: accumulatedTokens, oldUnexpected, arena: arena) !
230
+ }
231
+ } else {
232
+ newUnexpected = RawUnexpectedNodesSyntax ( accumulatedTokens, arena: arena) !
233
+ }
234
+
235
+ keptNodes [ lastKeptNodeIndex] = lastKeptNode. replacingChild (
236
+ at: childIndex,
237
+ with: newUnexpected. raw,
238
+ arena: arena
239
+ ) . cast ( KeptNode . self)
240
+
241
+ accumulatedTokens. removeAll ( )
242
+ }
243
+
244
+ func walk( _ raw: RawSyntax ) {
245
+ if let token = RawTokenSyntax ( raw) {
246
+ if !token. isMissing {
247
+ accumulatedTokens. append ( token)
248
+ }
249
+ } else if !raw. kind. isSyntaxCollection, let node = raw. as ( KeptNode . self) , predicate ( node) {
250
+ keptNodes. append ( node)
251
+ attachAccumulatedTokensToLastKeptNode ( appending: false )
252
+ } else {
253
+ for case let child? in raw. layoutView!. children {
254
+ walk ( child)
255
+ }
256
+ }
257
+ }
258
+
259
+ walk ( self . raw)
260
+
261
+ if keptNodes. isEmpty {
262
+ keptNodes. append ( makeMissing ( ) )
263
+ }
264
+
265
+ attachAccumulatedTokensToLastKeptNode ( appending: true )
266
+
267
+ return keptNodes
268
+ }
269
+ }
270
+
108
271
// MARK: - Misc
109
272
110
273
extension SyntaxText {
0 commit comments