@@ -105,6 +105,169 @@ extension RawUnexpectedNodesSyntax {
105105 }
106106}
107107
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+
108271// MARK: - Misc
109272
110273extension SyntaxText {
0 commit comments