@@ -52,30 +52,69 @@ 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 )
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
6756 // from each #if (along with the #ifs themselves).
6857 let rewriter = ActiveSyntaxRewriter (
6958 configuration: configuration,
7059 retainFeatureCheckIfConfigs: retainFeatureCheckIfConfigs
7160 )
7261 return (
7362 rewriter. rewrite ( Syntax ( self ) ) ,
74- visitor . diagnostics
63+ rewriter . diagnostics
7564 )
7665 }
7766}
7867
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+
79118/// Syntax rewriter that only visits syntax nodes that are active according
80119/// to a particular build configuration.
81120///
@@ -106,15 +145,22 @@ extension SyntaxProtocol {
106145///
107146/// For any other target platforms, the resulting tree will be empty (other
108147/// 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 ]
112151
113152 /// Whether to retain `#if` blocks containing compiler and feature checks.
114153 var retainFeatureCheckIfConfigs : Bool
115154
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
118164 self . retainFeatureCheckIfConfigs = retainFeatureCheckIfConfigs
119165 }
120166
@@ -124,6 +170,19 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
124170 ) -> List {
125171 var newElements : [ List . Element ] = [ ]
126172 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+
127186 for elementIndex in node. indices {
128187 let element = node [ elementIndex]
129188
@@ -132,17 +191,9 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
132191 ( !retainFeatureCheckIfConfigs || !ifConfigDecl. containsFeatureCheck)
133192 {
134193 // Retrieve the active `#if` clause
135- let ( activeClause, localDiagnostics ) = ifConfigDecl . activeClause ( in : configuration )
194+ let activeClause = activeClauses . activeClause ( for : ifConfigDecl , diagnostics : & diagnostics )
136195
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)
146197
147198 // Extract the elements from the active clause, if there are any.
148199 guard let elements = activeClause? . elements else {
@@ -161,6 +212,15 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
161212 continue
162213 }
163214
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+
164224 if anyChanged {
165225 newElements. append ( element)
166226 }
@@ -174,47 +234,39 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
174234 }
175235
176236 override func visit( _ node: CodeBlockItemListSyntax ) -> CodeBlockItemListSyntax {
177- let rewrittenNode = dropInactive ( node) { element in
237+ return dropInactive ( node) { element in
178238 guard case . decl( let declElement) = element. item else {
179239 return nil
180240 }
181241
182242 return declElement. as ( IfConfigDeclSyntax . self)
183243 }
184-
185- return super. visit ( rewrittenNode)
186244 }
187245
188246 override func visit( _ node: MemberBlockItemListSyntax ) -> MemberBlockItemListSyntax {
189- let rewrittenNode = dropInactive ( node) { element in
247+ return dropInactive ( node) { element in
190248 return element. decl. as ( IfConfigDeclSyntax . self)
191249 }
192-
193- return super. visit ( rewrittenNode)
194250 }
195251
196252 override func visit( _ node: SwitchCaseListSyntax ) -> SwitchCaseListSyntax {
197- let rewrittenNode = dropInactive ( node) { element in
253+ return dropInactive ( node) { element in
198254 if case . ifConfigDecl( let ifConfigDecl) = element {
199255 return ifConfigDecl
200256 }
201257
202258 return nil
203259 }
204-
205- return super. visit ( rewrittenNode)
206260 }
207261
208262 override func visit( _ node: AttributeListSyntax ) -> AttributeListSyntax {
209- let rewrittenNode = dropInactive ( node) { element in
263+ return dropInactive ( node) { element in
210264 if case . ifConfigDecl( let ifConfigDecl) = element {
211265 return ifConfigDecl
212266 }
213267
214268 return nil
215269 }
216-
217- return super. visit ( rewrittenNode)
218270 }
219271
220272 /// Apply the given base to the postfix expression.
@@ -234,7 +286,7 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
234286 return nil
235287 }
236288
237- let newExpr = applyBaseToPostfixExpression ( base: base, postfix: node [ keyPath: keyPath] )
289+ let newExpr = applyBaseToPostfixExpression ( base: base, postfix: visit ( node [ keyPath: keyPath] ) )
238290 return ExprSyntax ( node. with ( keyPath, newExpr) )
239291 }
240292
@@ -243,7 +295,7 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
243295 guard let memberBase = memberAccess. base else {
244296 // If this member access has no base, this is the base we are
245297 // replacing, terminating the recursion. Do so now.
246- return ExprSyntax ( memberAccess. with ( \. base, base) )
298+ return ExprSyntax ( memberAccess. with ( \. base, visit ( base) ) )
247299 }
248300
249301 let newBase = applyBaseToPostfixExpression ( base: base, postfix: memberBase)
@@ -302,10 +354,7 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
302354 }
303355
304356 // 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)
309358
310359 guard case . postfixExpression( let postfixExpr) = activeClause? . elements
311360 else {
@@ -315,7 +364,7 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
315364 // only have both in an ill-formed syntax tree that was manually
316365 // created.
317366 if let base = postfixIfConfig. base ?? outerBase {
318- return base
367+ return visit ( base)
319368 }
320369
321370 // If there was no base, we're in an erroneous syntax tree that would
@@ -330,20 +379,15 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
330379
331380 // If there is no base, return the postfix expression.
332381 guard let base = postfixIfConfig. base ?? outerBase else {
333- return postfixExpr
382+ return visit ( postfixExpr)
334383 }
335384
336385 // Apply the base to the postfix expression.
337386 return applyBaseToPostfixExpression ( base: base, postfix: postfixExpr)
338387 }
339388
340389 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)
347391 }
348392}
349393
0 commit comments