@@ -191,3 +191,83 @@ extension IfConfigState {
191
191
self = result ? . active : . inactive
192
192
}
193
193
}
194
+
195
+ extension IfConfigDeclSyntax {
196
+ /// Given a particular build configuration, determine which clause (if any) is the "active" clause.
197
+ ///
198
+ /// For example, for code like the following:
199
+ /// ```
200
+ /// #if A
201
+ /// func f()
202
+ /// #elseif B
203
+ /// func g()
204
+ /// #endif
205
+ /// ```
206
+ ///
207
+ /// If the `A` configuration option was passed on the command line (e.g. via `-DA`), the first clause
208
+ /// (containing `func f()`) would be returned. If not, and if the `B`configuration was passed on the
209
+ /// command line, the second clause (containing `func g()`) would be returned. If neither was
210
+ /// passed, this function will return `nil` to indicate that none of the regions are active.
211
+ ///
212
+ /// If an error occurred while processing any of the `#if` clauses, this function will throw that error.
213
+ public func activeClause( in configuration: some BuildConfiguration ) throws -> IfConfigClauseSyntax ? {
214
+ for clause in clauses {
215
+ // If there is no condition, we have reached an unconditional clause. Return it.
216
+ guard let condition = clause. condition else {
217
+ return clause
218
+ }
219
+
220
+ // If this condition evaluates true, return this clause.
221
+ if try evaluateIfConfig ( condition: condition, configuration: configuration) {
222
+ return clause
223
+ }
224
+ }
225
+
226
+ return nil
227
+ }
228
+ }
229
+
230
+ extension SyntaxProtocol {
231
+ /// Determine whether the given syntax node is active within the given build configuration.
232
+ ///
233
+ /// This function evaluates the enclosing stack of `#if` conditions to determine whether the
234
+ /// given node is active in the program when it is compiled with the given build configuration.
235
+ ///
236
+ /// For example, given code like the following:
237
+ /// #if DEBUG
238
+ /// #if A
239
+ /// func f()
240
+ /// #elseif B
241
+ /// func g()
242
+ /// #endif
243
+ /// #endif
244
+ ///
245
+ /// a call to `isActive` on the syntax node for the function `g` would return `true` when the
246
+ /// configuration options `DEBUG` and `B` are provided, but `A` is not.
247
+ public func isActive( in configuration: some BuildConfiguration ) throws -> Bool {
248
+ var currentNode : Syntax = Syntax ( self )
249
+ var currentClause = currentNode. as ( IfConfigClauseSyntax . self)
250
+
251
+ while let parent = currentNode. parent {
252
+ // If the parent is an `#if` configuration, check whether our current
253
+ // clause is active. If not, we're in an inactive region.
254
+ if let parentIfConfig = parent. as ( IfConfigDeclSyntax . self) {
255
+ if try currentClause != nil && parentIfConfig. activeClause ( in: configuration) != currentClause {
256
+ return false
257
+ }
258
+
259
+ currentClause = nil
260
+ }
261
+
262
+ // If the parent node is an if configuration clause, store it.
263
+ if let parentClause = parent. as ( IfConfigClauseSyntax . self) {
264
+ currentClause = parentClause
265
+ }
266
+
267
+ currentNode = parent
268
+ }
269
+
270
+ // No more enclosing nodes; this code is active.
271
+ return true
272
+ }
273
+ }
0 commit comments