@@ -18,15 +18,20 @@ extension SyntaxProtocol {
18
18
/// are inactive according to the given build configuration, leaving only
19
19
/// the code that is active within that build configuration.
20
20
///
21
- /// Returns the syntax node with all inactive regions removed, along with an
22
- /// array containing any diagnostics produced along the way.
23
- ///
24
21
/// If there are errors in the conditions of any configuration
25
22
/// clauses, e.g., `#if FOO > 10`, then the condition will be
26
23
/// considered to have failed and the clauses's elements will be
27
24
/// removed.
25
+ /// - Parameters:
26
+ /// - configuration: the configuration to apply.
27
+ /// - retainFeatureCheckIfConfigs: whether to retain `#if` blocks involving
28
+ /// compiler version checks (e.g., `compiler(>=6.0)`) and `$`-based
29
+ /// feature checks.
30
+ /// - Returns: the syntax node with all inactive regions removed, along with
31
+ /// an array containing any diagnostics produced along the way.
28
32
public func removingInactive(
29
- in configuration: some BuildConfiguration
33
+ in configuration: some BuildConfiguration ,
34
+ retainFeatureCheckIfConfigs: Bool = false
30
35
) -> ( result: Syntax , diagnostics: [ Diagnostic ] ) {
31
36
// First pass: Find all of the active clauses for the #ifs we need to
32
37
// visit, along with any diagnostics produced along the way. This process
@@ -41,7 +46,10 @@ extension SyntaxProtocol {
41
46
42
47
// Second pass: Rewrite the syntax tree by removing the inactive clauses
43
48
// from each #if (along with the #ifs themselves).
44
- let rewriter = ActiveSyntaxRewriter ( configuration: configuration)
49
+ let rewriter = ActiveSyntaxRewriter (
50
+ configuration: configuration,
51
+ retainFeatureCheckIfConfigs: retainFeatureCheckIfConfigs
52
+ )
45
53
return (
46
54
rewriter. rewrite ( Syntax ( self ) ) ,
47
55
visitor. diagnostics
@@ -83,8 +91,12 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
83
91
let configuration : Configuration
84
92
var diagnostics : [ Diagnostic ] = [ ]
85
93
86
- init ( configuration: Configuration ) {
94
+ /// Whether to retain `#if` blocks containing compiler and feature checks.
95
+ var retainFeatureCheckIfConfigs : Bool
96
+
97
+ init ( configuration: Configuration , retainFeatureCheckIfConfigs: Bool ) {
87
98
self . configuration = configuration
99
+ self . retainFeatureCheckIfConfigs = retainFeatureCheckIfConfigs
88
100
}
89
101
90
102
private func dropInactive< List: SyntaxCollection > (
@@ -97,7 +109,9 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
97
109
let element = node [ elementIndex]
98
110
99
111
// Find #ifs within the list.
100
- if let ifConfigDecl = elementAsIfConfig ( element) {
112
+ if let ifConfigDecl = elementAsIfConfig ( element) ,
113
+ ( !retainFeatureCheckIfConfigs || !ifConfigDecl. containsFeatureCheck)
114
+ {
101
115
// Retrieve the active `#if` clause
102
116
let ( activeClause, localDiagnostics) = ifConfigDecl. activeClause ( in: configuration)
103
117
@@ -262,6 +276,12 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
262
276
outerBase: ExprSyntax ? ,
263
277
postfixIfConfig: PostfixIfConfigExprSyntax
264
278
) -> ExprSyntax {
279
+ // If we're supposed to retain #if configs that are feature checks, and
280
+ // this configuration has one, do so.
281
+ if retainFeatureCheckIfConfigs && postfixIfConfig. config. containsFeatureCheck {
282
+ return ExprSyntax ( postfixIfConfig)
283
+ }
284
+
265
285
// Retrieve the active `if` clause.
266
286
let ( activeClause, localDiagnostics) = postfixIfConfig. config. activeClause ( in: configuration)
267
287
@@ -307,3 +327,55 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
307
327
return visit ( rewrittenNode)
308
328
}
309
329
}
330
+
331
+ /// Helper class to find a feature or compiler check.
332
+ fileprivate class FindFeatureCheckVisitor : SyntaxVisitor {
333
+ var foundFeatureCheck = false
334
+
335
+ override func visit( _ node: DeclReferenceExprSyntax ) -> SyntaxVisitorContinueKind {
336
+ // Checks that start with $ are feature checks that should be retained.
337
+ if let identifier = node. simpleIdentifier,
338
+ let initialChar = identifier. name. first,
339
+ initialChar == " $ "
340
+ {
341
+ foundFeatureCheck = true
342
+ return . skipChildren
343
+ }
344
+
345
+ return . visitChildren
346
+ }
347
+
348
+ override func visit( _ node: FunctionCallExprSyntax ) -> SyntaxVisitorContinueKind {
349
+ if let calleeDeclRef = node. calledExpression. as ( DeclReferenceExprSyntax . self) ,
350
+ let calleeName = calleeDeclRef. simpleIdentifier? . name,
351
+ ( calleeName == " compiler " || calleeName == " _compiler_version " )
352
+ {
353
+ foundFeatureCheck = true
354
+ }
355
+
356
+ return . skipChildren
357
+ }
358
+ }
359
+
360
+ extension ExprSyntaxProtocol {
361
+ /// Whether any of the nodes in this expression involve compiler or feature
362
+ /// checks.
363
+ fileprivate var containsFeatureCheck : Bool {
364
+ let visitor = FindFeatureCheckVisitor ( viewMode: . fixedUp)
365
+ visitor. walk ( self )
366
+ return visitor. foundFeatureCheck
367
+ }
368
+ }
369
+
370
+ extension IfConfigDeclSyntax {
371
+ /// Whether any of the clauses in this #if contain a feature check.
372
+ var containsFeatureCheck : Bool {
373
+ return clauses. contains { clause in
374
+ if let condition = clause. condition {
375
+ return condition. containsFeatureCheck
376
+ } else {
377
+ return false
378
+ }
379
+ }
380
+ }
381
+ }
0 commit comments