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