@@ -3,7 +3,7 @@ import SwiftSyntax
33
44@SwiftSyntaxRule
55struct VariableShadowingRule : Rule {
6- var configuration = SeverityConfiguration < Self > ( . warning )
6+ var configuration = VariableShadowingConfiguration ( )
77
88 static let description = RuleDescription (
99 identifier: " variable_shadowing " ,
@@ -16,7 +16,7 @@ struct VariableShadowingRule: Rule {
1616 func test(a: String?) {
1717 print(a)
1818 }
19- """ ) ,
19+ """ , configuration : [ " ignore_parameters " : true ] ) ,
2020 Example ( """
2121 var a: String = " hello "
2222 if let b = a {
@@ -157,14 +157,14 @@ struct VariableShadowingRule: Rule {
157157 """ ) ,
158158 Example ( """
159159 var a = 1
160- if let a = Optional(2) {
160+ if let ↓ a = Optional(2) {
161161 let ↓a = 3
162162 print(a)
163163 }
164164 """ ) ,
165165 Example ( """
166166 var i = 1
167- for i in 1...3 {
167+ for ↓ i in 1...3 {
168168 let ↓i = 2
169169 print(i)
170170 }
@@ -205,41 +205,95 @@ struct VariableShadowingRule: Rule {
205205 }
206206 }
207207 """ ) ,
208+ Example ( """
209+ var a = 1
210+ if let ↓a = Optional(2) {}
211+ """ ) ,
212+ Example ( """
213+ var i = 1
214+ for ↓i in 1...3 {}
215+ """ ) ,
216+ Example ( """
217+ var a: String?
218+ func test(↓a: String?) {
219+ print(a)
220+ }
221+ """ , configuration: [ " ignore_parameters " : false ] ) ,
208222 ]
209223 )
210224}
211225
212226private extension VariableShadowingRule {
213- final class Visitor : DeclaredIdentifiersTrackingVisitor < ConfigurationType > {
227+ final class Visitor : DeclaredIdentifiersTrackingVisitor < VariableShadowingConfiguration > {
214228 override func visit( _ node: VariableDeclSyntax ) -> SyntaxVisitorContinueKind {
215- // Early exit for member blocks (class/struct properties)
216229 if node. parent? . is ( MemberBlockItemSyntax . self) == false {
217- // Check for shadowing BEFORE adding to scope
218230 node. bindings. forEach { binding in
219231 checkForShadowing ( in: binding. pattern)
220232 }
221233 }
222234 return super. visit ( node)
223235 }
224236
237+ override func visit( _ node: FunctionDeclSyntax ) -> SyntaxVisitorContinueKind {
238+ if !configuration. ignoreParameters {
239+ for param in node. signature. parameterClause. parameters {
240+ let nameToken = param. secondName ?? param. firstName
241+ if nameToken. text != " _ " , isShadowingAnyScope ( nameToken. text) {
242+ violations. append ( nameToken. positionAfterSkippingLeadingTrivia)
243+ }
244+ }
245+ }
246+ return super. visit ( node)
247+ }
248+
249+ override func visit( _ node: ForStmtSyntax ) -> SyntaxVisitorContinueKind {
250+ checkForBindingShadowing ( in: node. pattern)
251+ return super. visit ( node)
252+ }
253+
254+ override func visit( _ node: IfExprSyntax ) -> SyntaxVisitorContinueKind {
255+ for condition in node. conditions {
256+ if let optBinding = condition. condition. as ( OptionalBindingConditionSyntax . self) {
257+ checkForBindingShadowing ( in: optBinding. pattern)
258+ }
259+ }
260+ return super. visit ( node)
261+ }
262+
263+ // Used for VariableDecl: the new identifier is added to the *current* scope,
264+ // so we only check ancestor scopes (dropLast).
225265 private func checkForShadowing( in pattern: PatternSyntax ) {
226- // Handle direct identifier patterns
227266 if let identifier = pattern. as ( IdentifierPatternSyntax . self) {
228267 let identifierText = identifier. identifier. text
229268 if isShadowingOuterScope ( identifierText) {
230269 violations. append ( identifier. identifier. positionAfterSkippingLeadingTrivia)
231270 }
232271 } else if let tuple = pattern. as ( TuplePatternSyntax . self) {
233- // Recurse into tuple patterns: e.g., (a, b)
234272 tuple. elements. forEach { element in
235273 checkForShadowing ( in: element. pattern)
236274 }
237275 } else if let valueBinding = pattern. as ( ValueBindingPatternSyntax . self) {
238- // Recurse into optional binding patterns: e.g., `if let a`, `while var (a, b)`
239276 checkForShadowing ( in: valueBinding. pattern)
240277 }
241278 }
242279
280+ // Used for if-let / for-loop bindings: the new identifier is added to a *child* scope,
281+ // so we check all current scopes.
282+ private func checkForBindingShadowing( in pattern: PatternSyntax ) {
283+ if let identifier = pattern. as ( IdentifierPatternSyntax . self) {
284+ let identifierText = identifier. identifier. text
285+ if isShadowingAnyScope ( identifierText) {
286+ violations. append ( identifier. identifier. positionAfterSkippingLeadingTrivia)
287+ }
288+ } else if let tuple = pattern. as ( TuplePatternSyntax . self) {
289+ tuple. elements. forEach { element in
290+ checkForBindingShadowing ( in: element. pattern)
291+ }
292+ } else if let valueBinding = pattern. as ( ValueBindingPatternSyntax . self) {
293+ checkForBindingShadowing ( in: valueBinding. pattern)
294+ }
295+ }
296+
243297 private func isShadowingOuterScope( _ identifier: String ) -> Bool {
244298 guard scope. count > 1 else { return false }
245299
@@ -249,5 +303,11 @@ private extension VariableShadowingRule {
249303 }
250304 return false
251305 }
306+
307+ /// Checks all scope levels including the current one. Used for parameter checking
308+ /// since parameters are declared into a child scope, not the current one.
309+ private func isShadowingAnyScope( _ identifier: String ) -> Bool {
310+ scope. contains { $0. contains { $0. declares ( id: identifier) } }
311+ }
252312 }
253313}
0 commit comments