Skip to content

Commit fb23103

Browse files
committed
Add higher-level APIs for querying active code state
`IfConfigDeclSyntax.activeClause(in:)` determines which clause is active within an `#if` syntax node. `SyntaxProtocol.isActive(in:)` determines whether a given syntax node is active in the program, based on the nested stack of `#if` configurations.
1 parent 2e1b554 commit fb23103

File tree

1 file changed

+80
-0
lines changed

1 file changed

+80
-0
lines changed

Sources/SwiftIfConfig/IfConfigEvaluation.swift

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,3 +191,83 @@ extension IfConfigState {
191191
self = result ? .active : .inactive
192192
}
193193
}
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

Comments
 (0)