@@ -52,27 +52,64 @@ extension SyntaxProtocol {
5252 in configuration: some BuildConfiguration ,
5353 retainFeatureCheckIfConfigs: Bool
5454 ) -> ( 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+ }
6065
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)
64104 }
65105
66- // Second pass: Rewrite the syntax tree by removing the inactive clauses
106+ // Rewrite the syntax tree by removing the inactive clauses
67107 // from each #if (along with the #ifs themselves).
68108 let rewriter = ActiveSyntaxRewriter (
69- configuration : configuration ,
109+ configuredRegions : self ,
70110 retainFeatureCheckIfConfigs: retainFeatureCheckIfConfigs
71111 )
72- return (
73- rewriter. rewrite ( Syntax ( self ) ) ,
74- visitor. diagnostics
75- )
112+ return rewriter. rewrite ( Syntax ( node) )
76113 }
77114}
78115
@@ -106,15 +143,16 @@ extension SyntaxProtocol {
106143///
107144/// For any other target platforms, the resulting tree will be empty (other
108145/// 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 ]
112149
113150 /// Whether to retain `#if` blocks containing compiler and feature checks.
114151 var retainFeatureCheckIfConfigs : Bool
115152
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
118156 self . retainFeatureCheckIfConfigs = retainFeatureCheckIfConfigs
119157 }
120158
@@ -124,6 +162,19 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
124162 ) -> List {
125163 var newElements : [ List . Element ] = [ ]
126164 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+
127178 for elementIndex in node. indices {
128179 let element = node [ elementIndex]
129180
@@ -132,17 +183,9 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
132183 ( !retainFeatureCheckIfConfigs || !ifConfigDecl. containsFeatureCheck)
133184 {
134185 // 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)
139187
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)
146189
147190 // Extract the elements from the active clause, if there are any.
148191 guard let elements = activeClause? . elements else {
@@ -161,6 +204,15 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
161204 continue
162205 }
163206
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+
164216 if anyChanged {
165217 newElements. append ( element)
166218 }
@@ -174,47 +226,39 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
174226 }
175227
176228 override func visit( _ node: CodeBlockItemListSyntax ) -> CodeBlockItemListSyntax {
177- let rewrittenNode = dropInactive ( node) { element in
229+ return dropInactive ( node) { element in
178230 guard case . decl( let declElement) = element. item else {
179231 return nil
180232 }
181233
182234 return declElement. as ( IfConfigDeclSyntax . self)
183235 }
184-
185- return super. visit ( rewrittenNode)
186236 }
187237
188238 override func visit( _ node: MemberBlockItemListSyntax ) -> MemberBlockItemListSyntax {
189- let rewrittenNode = dropInactive ( node) { element in
239+ return dropInactive ( node) { element in
190240 return element. decl. as ( IfConfigDeclSyntax . self)
191241 }
192-
193- return super. visit ( rewrittenNode)
194242 }
195243
196244 override func visit( _ node: SwitchCaseListSyntax ) -> SwitchCaseListSyntax {
197- let rewrittenNode = dropInactive ( node) { element in
245+ return dropInactive ( node) { element in
198246 if case . ifConfigDecl( let ifConfigDecl) = element {
199247 return ifConfigDecl
200248 }
201249
202250 return nil
203251 }
204-
205- return super. visit ( rewrittenNode)
206252 }
207253
208254 override func visit( _ node: AttributeListSyntax ) -> AttributeListSyntax {
209- let rewrittenNode = dropInactive ( node) { element in
255+ return dropInactive ( node) { element in
210256 if case . ifConfigDecl( let ifConfigDecl) = element {
211257 return ifConfigDecl
212258 }
213259
214260 return nil
215261 }
216-
217- return super. visit ( rewrittenNode)
218262 }
219263
220264 /// Apply the given base to the postfix expression.
@@ -234,7 +278,7 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
234278 return nil
235279 }
236280
237- let newExpr = applyBaseToPostfixExpression ( base: base, postfix: node [ keyPath: keyPath] )
281+ let newExpr = applyBaseToPostfixExpression ( base: base, postfix: visit ( node [ keyPath: keyPath] ) )
238282 return ExprSyntax ( node. with ( keyPath, newExpr) )
239283 }
240284
@@ -243,7 +287,7 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
243287 guard let memberBase = memberAccess. base else {
244288 // If this member access has no base, this is the base we are
245289 // replacing, terminating the recursion. Do so now.
246- return ExprSyntax ( memberAccess. with ( \. base, base) )
290+ return ExprSyntax ( memberAccess. with ( \. base, visit ( base) ) )
247291 }
248292
249293 let newBase = applyBaseToPostfixExpression ( base: base, postfix: memberBase)
@@ -302,10 +346,7 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
302346 }
303347
304348 // 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)
309350
310351 guard case . postfixExpression( let postfixExpr) = activeClause? . elements
311352 else {
@@ -315,7 +356,7 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
315356 // only have both in an ill-formed syntax tree that was manually
316357 // created.
317358 if let base = postfixIfConfig. base ?? outerBase {
318- return base
359+ return visit ( base)
319360 }
320361
321362 // If there was no base, we're in an erroneous syntax tree that would
@@ -330,20 +371,15 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
330371
331372 // If there is no base, return the postfix expression.
332373 guard let base = postfixIfConfig. base ?? outerBase else {
333- return postfixExpr
374+ return visit ( postfixExpr)
334375 }
335376
336377 // Apply the base to the postfix expression.
337378 return applyBaseToPostfixExpression ( base: base, postfix: postfixExpr)
338379 }
339380
340381 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)
347383 }
348384}
349385
0 commit comments