@@ -52,30 +52,69 @@ extension SyntaxProtocol {
52
52
in configuration: some BuildConfiguration ,
53
53
retainFeatureCheckIfConfigs: Bool
54
54
) -> ( result: Syntax , diagnostics: [ Diagnostic ] ) {
55
- // First pass: Find all of the active clauses for the #ifs we need to
56
- // visit, along with any diagnostics produced along the way. This process
57
- // does not change the tree in any way.
58
- let visitor = ActiveSyntaxVisitor ( viewMode: . sourceAccurate, configuration: configuration)
59
- visitor. walk ( self )
60
-
61
- // If there were no active clauses to visit, we're done!
62
- if !visitor. visitedAnyIfClauses {
63
- return ( Syntax ( self ) , visitor. diagnostics)
64
- }
65
-
66
- // Second pass: Rewrite the syntax tree by removing the inactive clauses
55
+ // Rewrite the syntax tree by removing the inactive clauses
67
56
// from each #if (along with the #ifs themselves).
68
57
let rewriter = ActiveSyntaxRewriter (
69
58
configuration: configuration,
70
59
retainFeatureCheckIfConfigs: retainFeatureCheckIfConfigs
71
60
)
72
61
return (
73
62
rewriter. rewrite ( Syntax ( self ) ) ,
74
- visitor . diagnostics
63
+ rewriter . diagnostics
75
64
)
76
65
}
77
66
}
78
67
68
+ extension ConfiguredRegions {
69
+ /// Produce a copy of some syntax node in the configured region that removes
70
+ /// all syntax regions that are inactive according to the build configuration,
71
+ /// leaving only the code that is active within that build configuration.
72
+ ///
73
+ /// If there are errors in the conditions of any configuration
74
+ /// clauses, e.g., `#if FOO > 10`, then the condition will be
75
+ /// considered to have failed and the clauses's elements will be
76
+ /// removed.
77
+ /// - Parameters:
78
+ /// - node: the stnrax node from which inactive regions will be removed.
79
+ /// - Returns: the syntax node with all inactive regions removed.
80
+ public func removingInactive( from node: some SyntaxProtocol ) -> Syntax {
81
+ return removingInactive ( from: node, retainFeatureCheckIfConfigs: false )
82
+ }
83
+
84
+ /// Produce a copy of some syntax node in the configured region that removes
85
+ /// all syntax regions that are inactive according to the build configuration,
86
+ /// leaving only the code that is active within that build configuration.
87
+ ///
88
+ /// If there are errors in the conditions of any configuration
89
+ /// clauses, e.g., `#if FOO > 10`, then the condition will be
90
+ /// considered to have failed and the clauses's elements will be
91
+ /// removed.
92
+ /// - Parameters:
93
+ /// - node: the stnrax node from which inactive regions will be removed.
94
+ /// - retainFeatureCheckIfConfigs: whether to retain `#if` blocks involving
95
+ /// compiler version checks (e.g., `compiler(>=6.0)`) and `$`-based
96
+ /// feature checks.
97
+ /// - Returns: the syntax node with all inactive regions removed.
98
+ @_spi ( Compiler)
99
+ public func removingInactive(
100
+ from node: some SyntaxProtocol ,
101
+ retainFeatureCheckIfConfigs: Bool
102
+ ) -> Syntax {
103
+ // If there are no inactive regions, there's nothing to do.
104
+ if regions. isEmpty {
105
+ return Syntax ( node)
106
+ }
107
+
108
+ // Rewrite the syntax tree by removing the inactive clauses
109
+ // from each #if (along with the #ifs themselves).
110
+ let rewriter = ActiveSyntaxRewriter (
111
+ configuredRegions: self ,
112
+ retainFeatureCheckIfConfigs: retainFeatureCheckIfConfigs
113
+ )
114
+ return rewriter. rewrite ( Syntax ( node) )
115
+ }
116
+ }
117
+
79
118
/// Syntax rewriter that only visits syntax nodes that are active according
80
119
/// to a particular build configuration.
81
120
///
@@ -106,15 +145,22 @@ extension SyntaxProtocol {
106
145
///
107
146
/// For any other target platforms, the resulting tree will be empty (other
108
147
/// than trivia).
109
- class ActiveSyntaxRewriter < Configuration : BuildConfiguration > : SyntaxRewriter {
110
- let configuration : Configuration
111
- var diagnostics : [ Diagnostic ] = [ ]
148
+ class ActiveSyntaxRewriter : SyntaxRewriter {
149
+ let activeClauses : ActiveClauseEvaluator
150
+ var diagnostics : [ Diagnostic ]
112
151
113
152
/// Whether to retain `#if` blocks containing compiler and feature checks.
114
153
var retainFeatureCheckIfConfigs : Bool
115
154
116
- init ( configuration: Configuration , retainFeatureCheckIfConfigs: Bool ) {
117
- self . configuration = configuration
155
+ init ( configuredRegions: ConfiguredRegions , retainFeatureCheckIfConfigs: Bool ) {
156
+ self . activeClauses = . configuredRegions( configuredRegions)
157
+ self . diagnostics = activeClauses. priorDiagnostics
158
+ self . retainFeatureCheckIfConfigs = retainFeatureCheckIfConfigs
159
+ }
160
+
161
+ init ( configuration: some BuildConfiguration , retainFeatureCheckIfConfigs: Bool ) {
162
+ self . activeClauses = . configuration( configuration)
163
+ self . diagnostics = activeClauses. priorDiagnostics
118
164
self . retainFeatureCheckIfConfigs = retainFeatureCheckIfConfigs
119
165
}
120
166
@@ -124,6 +170,19 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
124
170
) -> List {
125
171
var newElements : [ List . Element ] = [ ]
126
172
var anyChanged = false
173
+
174
+ // Note that an element changed at the given index.
175
+ func noteElementChanged( at elementIndex: List . Index ) {
176
+ if anyChanged {
177
+ return
178
+ }
179
+
180
+ // This is the first element that changed, so note that we have
181
+ // changes and add all prior elements to the list of new elements.
182
+ anyChanged = true
183
+ newElements. append ( contentsOf: node [ ..< elementIndex] )
184
+ }
185
+
127
186
for elementIndex in node. indices {
128
187
let element = node [ elementIndex]
129
188
@@ -132,17 +191,9 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
132
191
( !retainFeatureCheckIfConfigs || !ifConfigDecl. containsFeatureCheck)
133
192
{
134
193
// Retrieve the active `#if` clause
135
- let ( activeClause, localDiagnostics ) = ifConfigDecl . activeClause ( in : configuration )
194
+ let activeClause = activeClauses . activeClause ( for : ifConfigDecl , diagnostics : & diagnostics )
136
195
137
- // Add these diagnostics.
138
- diagnostics. append ( contentsOf: localDiagnostics)
139
-
140
- // If this is the first element that changed, note that we have
141
- // changes and add all prior elements to the list of new elements.
142
- if !anyChanged {
143
- anyChanged = true
144
- newElements. append ( contentsOf: node [ ..< elementIndex] )
145
- }
196
+ noteElementChanged ( at: elementIndex)
146
197
147
198
// Extract the elements from the active clause, if there are any.
148
199
guard let elements = activeClause? . elements else {
@@ -161,6 +212,15 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
161
212
continue
162
213
}
163
214
215
+ // Transform the element directly. If it changed, note the changes.
216
+ if let transformedElement = rewrite ( Syntax ( element) ) . as ( List . Element. self) ,
217
+ transformedElement. id != element. id
218
+ {
219
+ noteElementChanged ( at: elementIndex)
220
+ newElements. append ( transformedElement)
221
+ continue
222
+ }
223
+
164
224
if anyChanged {
165
225
newElements. append ( element)
166
226
}
@@ -174,47 +234,39 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
174
234
}
175
235
176
236
override func visit( _ node: CodeBlockItemListSyntax ) -> CodeBlockItemListSyntax {
177
- let rewrittenNode = dropInactive ( node) { element in
237
+ return dropInactive ( node) { element in
178
238
guard case . decl( let declElement) = element. item else {
179
239
return nil
180
240
}
181
241
182
242
return declElement. as ( IfConfigDeclSyntax . self)
183
243
}
184
-
185
- return super. visit ( rewrittenNode)
186
244
}
187
245
188
246
override func visit( _ node: MemberBlockItemListSyntax ) -> MemberBlockItemListSyntax {
189
- let rewrittenNode = dropInactive ( node) { element in
247
+ return dropInactive ( node) { element in
190
248
return element. decl. as ( IfConfigDeclSyntax . self)
191
249
}
192
-
193
- return super. visit ( rewrittenNode)
194
250
}
195
251
196
252
override func visit( _ node: SwitchCaseListSyntax ) -> SwitchCaseListSyntax {
197
- let rewrittenNode = dropInactive ( node) { element in
253
+ return dropInactive ( node) { element in
198
254
if case . ifConfigDecl( let ifConfigDecl) = element {
199
255
return ifConfigDecl
200
256
}
201
257
202
258
return nil
203
259
}
204
-
205
- return super. visit ( rewrittenNode)
206
260
}
207
261
208
262
override func visit( _ node: AttributeListSyntax ) -> AttributeListSyntax {
209
- let rewrittenNode = dropInactive ( node) { element in
263
+ return dropInactive ( node) { element in
210
264
if case . ifConfigDecl( let ifConfigDecl) = element {
211
265
return ifConfigDecl
212
266
}
213
267
214
268
return nil
215
269
}
216
-
217
- return super. visit ( rewrittenNode)
218
270
}
219
271
220
272
/// Apply the given base to the postfix expression.
@@ -234,7 +286,7 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
234
286
return nil
235
287
}
236
288
237
- let newExpr = applyBaseToPostfixExpression ( base: base, postfix: node [ keyPath: keyPath] )
289
+ let newExpr = applyBaseToPostfixExpression ( base: base, postfix: visit ( node [ keyPath: keyPath] ) )
238
290
return ExprSyntax ( node. with ( keyPath, newExpr) )
239
291
}
240
292
@@ -243,7 +295,7 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
243
295
guard let memberBase = memberAccess. base else {
244
296
// If this member access has no base, this is the base we are
245
297
// replacing, terminating the recursion. Do so now.
246
- return ExprSyntax ( memberAccess. with ( \. base, base) )
298
+ return ExprSyntax ( memberAccess. with ( \. base, visit ( base) ) )
247
299
}
248
300
249
301
let newBase = applyBaseToPostfixExpression ( base: base, postfix: memberBase)
@@ -302,10 +354,7 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
302
354
}
303
355
304
356
// Retrieve the active `if` clause.
305
- let ( activeClause, localDiagnostics) = postfixIfConfig. config. activeClause ( in: configuration)
306
-
307
- // Record these diagnostics.
308
- diagnostics. append ( contentsOf: localDiagnostics)
357
+ let activeClause = activeClauses. activeClause ( for: postfixIfConfig. config, diagnostics: & diagnostics)
309
358
310
359
guard case . postfixExpression( let postfixExpr) = activeClause? . elements
311
360
else {
@@ -315,7 +364,7 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
315
364
// only have both in an ill-formed syntax tree that was manually
316
365
// created.
317
366
if let base = postfixIfConfig. base ?? outerBase {
318
- return base
367
+ return visit ( base)
319
368
}
320
369
321
370
// If there was no base, we're in an erroneous syntax tree that would
@@ -330,20 +379,15 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
330
379
331
380
// If there is no base, return the postfix expression.
332
381
guard let base = postfixIfConfig. base ?? outerBase else {
333
- return postfixExpr
382
+ return visit ( postfixExpr)
334
383
}
335
384
336
385
// Apply the base to the postfix expression.
337
386
return applyBaseToPostfixExpression ( base: base, postfix: postfixExpr)
338
387
}
339
388
340
389
override func visit( _ node: PostfixIfConfigExprSyntax ) -> ExprSyntax {
341
- let rewrittenNode = dropInactive ( outerBase: nil , postfixIfConfig: node)
342
- if rewrittenNode == ExprSyntax ( node) {
343
- return rewrittenNode
344
- }
345
-
346
- return visit ( rewrittenNode)
390
+ return dropInactive ( outerBase: nil , postfixIfConfig: node)
347
391
}
348
392
}
349
393
0 commit comments