Skip to content

Commit e99d669

Browse files
committed
Added Configuration for the function shadowing
1 parent eb602f2 commit e99d669

4 files changed

Lines changed: 95 additions & 10 deletions

File tree

Source/SwiftLintBuiltInRules/Rules/Lint/VariableShadowingRule.swift

Lines changed: 70 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import SwiftSyntax
33

44
@SwiftSyntaxRule
55
struct 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

212226
private 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
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import SwiftLintCore
2+
3+
@AutoConfigParser
4+
struct VariableShadowingConfiguration: SeverityBasedRuleConfiguration {
5+
@ConfigurationElement(key: "severity")
6+
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
7+
@ConfigurationElement(key: "ignore_parameters")
8+
private(set) var ignoreParameters = true
9+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
@testable import SwiftLintBuiltInRules
2+
import TestHelpers
3+
import XCTest
4+
5+
final class VariableShadowingRuleTests: SwiftLintTestCase {
6+
func testWithIgnoreParametersTrue() {
7+
let configuration = ["ignore_parameters": true]
8+
verifyRule(VariableShadowingRule.description, ruleConfiguration: configuration)
9+
}
10+
11+
func testWithIgnoreParametersFalse() {
12+
let configuration = ["ignore_parameters": false]
13+
verifyRule(VariableShadowingRule.description, ruleConfiguration: configuration)
14+
}
15+
}

Tests/IntegrationTests/Resources/default_rule_configurations.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1385,6 +1385,7 @@ valid_ibinspectable:
13851385
correctable: false
13861386
variable_shadowing:
13871387
severity: warning
1388+
ignore_parameters: true
13881389
meta:
13891390
opt-in: false
13901391
correctable: false

0 commit comments

Comments
 (0)