mirror of
https://github.com/realm/SwiftLint.git
synced 2026-06-06 20:18:40 +00:00
284 lines
9.0 KiB
Swift
284 lines
9.0 KiB
Swift
import SwiftLintCore
|
|
import SwiftSyntax
|
|
|
|
@SwiftSyntaxRule(optIn: true)
|
|
struct VariableShadowingRule: Rule {
|
|
var configuration = VariableShadowingConfiguration()
|
|
|
|
static let description = RuleDescription(
|
|
identifier: "variable_shadowing",
|
|
name: "Variable Shadowing",
|
|
description: "Do not shadow variables declared in outer scopes",
|
|
kind: .lint,
|
|
nonTriggeringExamples: [
|
|
Example("""
|
|
var a: String?
|
|
func test(a: String?) {
|
|
print(a)
|
|
}
|
|
""", configuration: ["ignore_parameters": true]),
|
|
Example("""
|
|
var a: String = "hello"
|
|
if let b = a {
|
|
print(b)
|
|
}
|
|
"""),
|
|
Example("""
|
|
var a: String?
|
|
func test() {
|
|
if let b = a {
|
|
print(b)
|
|
}
|
|
}
|
|
"""),
|
|
Example("""
|
|
for i in 1...10 {
|
|
print(i)
|
|
}
|
|
for i in 1...10 {
|
|
print(i)
|
|
}
|
|
"""),
|
|
Example("""
|
|
func test() {
|
|
var a: String = "hello"
|
|
func nested() {
|
|
var b: String = "world"
|
|
print(a, b)
|
|
}
|
|
}
|
|
"""),
|
|
Example("""
|
|
class Test {
|
|
var a: String?
|
|
func test(a: String?) {
|
|
print(a)
|
|
}
|
|
}
|
|
"""),
|
|
Example("""
|
|
let a: Int?
|
|
if let a { print(a) }
|
|
"""),
|
|
Example("""
|
|
let a: Int?
|
|
guard let a else { return }
|
|
"""),
|
|
Example("""
|
|
let a: Int?
|
|
while let a { print(a) }
|
|
"""),
|
|
Example("""
|
|
var a = 1
|
|
if let a = a {
|
|
print(a)
|
|
}
|
|
"""),
|
|
Example("""
|
|
var a = 1
|
|
if let a = self.a {
|
|
print(a)
|
|
}
|
|
"""),
|
|
Example("""
|
|
struct S {
|
|
static let c: Int? = nil
|
|
var a: Int?
|
|
var b: Int {
|
|
if let a = self.a { a }
|
|
else if let c = Self.c { c }
|
|
else { 0 }
|
|
}
|
|
}
|
|
"""),
|
|
],
|
|
triggeringExamples: [
|
|
Example("""
|
|
var foo = 1
|
|
do {
|
|
let ↓foo = 2
|
|
}
|
|
"""),
|
|
Example("""
|
|
var a = 1
|
|
if let ↓a = Optional(2) {
|
|
let ↓a = 3
|
|
print(a)
|
|
}
|
|
"""),
|
|
Example("""
|
|
var i = 1
|
|
for ↓i in 1...3 {
|
|
let ↓i = 2
|
|
print(i)
|
|
}
|
|
"""),
|
|
Example("""
|
|
var a = 1
|
|
func test() {
|
|
do {
|
|
var ↓a = 2
|
|
print(a)
|
|
}
|
|
}
|
|
"""),
|
|
Example("""
|
|
func test() {
|
|
var a = 1
|
|
if var ↓a = Optional(2) {
|
|
var ↓a = 2
|
|
print(a)
|
|
}
|
|
}
|
|
"""),
|
|
Example("""
|
|
func test() {
|
|
var a = 1
|
|
for ↓a in 0..<1 {
|
|
var ↓a = 2
|
|
print(a)
|
|
}
|
|
}
|
|
"""),
|
|
Example("""
|
|
func test() {
|
|
var a = 1
|
|
while true {
|
|
var ↓a = 2
|
|
break
|
|
}
|
|
}
|
|
"""),
|
|
Example("""
|
|
var a: String?
|
|
func test(↓a: String?) {
|
|
let ↓a = ""
|
|
print(a)
|
|
}
|
|
""", configuration: ["ignore_parameters": false]),
|
|
Example("""
|
|
struct S {
|
|
var a = 1
|
|
var b: Int {
|
|
let ↓a = 2
|
|
return a
|
|
}
|
|
}
|
|
"""),
|
|
Example("""
|
|
var a: String?
|
|
while let ↓a = Optional("hello") {}
|
|
"""),
|
|
Example("""
|
|
var a = "outer"
|
|
let (↓a, c) = ("first", "second")
|
|
"""),
|
|
]
|
|
)
|
|
}
|
|
|
|
private extension VariableShadowingRule {
|
|
final class Visitor: DeclaredIdentifiersTrackingVisitor<VariableShadowingConfiguration> {
|
|
init(configuration: ConfigurationType, file: SwiftLintFile) {
|
|
super.init(configuration: configuration, file: file, includeMembers: true)
|
|
}
|
|
|
|
override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {
|
|
if node.parent?.is(MemberBlockItemSyntax.self) == false {
|
|
let isUnmodifiable = node.bindingSpecifier.tokenKind == .keyword(.let)
|
|
for binding in node.bindings where isUnmodifiable || !binding.isIdentity {
|
|
checkForShadowing(in: binding.pattern)
|
|
}
|
|
}
|
|
return super.visit(node)
|
|
}
|
|
|
|
override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind {
|
|
if !configuration.ignoreParameters {
|
|
for param in node.signature.parameterClause.parameters {
|
|
let nameToken = param.secondName ?? param.firstName
|
|
if nameToken.text != "_", hasSeenDeclaration(for: nameToken.text) {
|
|
violations.append(nameToken.positionAfterSkippingLeadingTrivia)
|
|
}
|
|
}
|
|
}
|
|
return super.visit(node)
|
|
}
|
|
|
|
override func visit(_ node: ForStmtSyntax) -> SyntaxVisitorContinueKind {
|
|
checkForShadowing(in: node.pattern)
|
|
return super.visit(node)
|
|
}
|
|
|
|
override func visit(_ node: IfExprSyntax) -> SyntaxVisitorContinueKind {
|
|
for condition in node.conditions {
|
|
if let optBinding = condition.condition.as(OptionalBindingConditionSyntax.self) {
|
|
checkForShadowing(in: optBinding.pattern, binding: optBinding)
|
|
}
|
|
}
|
|
return super.visit(node)
|
|
}
|
|
|
|
override func visit(_ node: WhileStmtSyntax) -> SyntaxVisitorContinueKind {
|
|
for condition in node.conditions {
|
|
if let optBinding = condition.condition.as(OptionalBindingConditionSyntax.self) {
|
|
checkForShadowing(in: optBinding.pattern, binding: optBinding)
|
|
}
|
|
}
|
|
return super.visit(node)
|
|
}
|
|
|
|
override func visit(_ node: GuardStmtSyntax) -> SyntaxVisitorContinueKind {
|
|
for condition in node.conditions {
|
|
if let optBinding = condition.condition.as(OptionalBindingConditionSyntax.self) {
|
|
checkForShadowing(in: optBinding.pattern, binding: optBinding)
|
|
}
|
|
}
|
|
return super.visit(node)
|
|
}
|
|
|
|
// Checking shadowing in both variable declarations and optional bindings.
|
|
// For optional bindings, skips idiomatic patterns (if let a / if let a = a).
|
|
private func checkForShadowing(in pattern: PatternSyntax, binding: OptionalBindingConditionSyntax? = nil) {
|
|
if let identifier = pattern.as(IdentifierPatternSyntax.self) {
|
|
let name = identifier.identifier.text
|
|
if let binding, binding.initializer?.isBindingFor(name: name) != false {
|
|
return
|
|
}
|
|
if hasSeenDeclaration(for: name) {
|
|
violations.append(identifier.identifier.positionAfterSkippingLeadingTrivia)
|
|
}
|
|
} else if let tuple = pattern.as(TuplePatternSyntax.self) {
|
|
tuple.elements.forEach { element in
|
|
checkForShadowing(in: element.pattern, binding: binding)
|
|
}
|
|
} else if let valueBinding = pattern.as(ValueBindingPatternSyntax.self) {
|
|
checkForShadowing(in: valueBinding.pattern, binding: binding)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private extension PatternBindingSyntax {
|
|
var isIdentity: Bool {
|
|
guard let initializer, let identifierPattern = pattern.as(IdentifierPatternSyntax.self) else {
|
|
return false
|
|
}
|
|
return initializer.isBindingFor(name: identifierPattern.identifier.text)
|
|
}
|
|
}
|
|
|
|
private extension InitializerClauseSyntax {
|
|
func isBindingFor(name: String) -> Bool {
|
|
if let identifierExpr = value.as(DeclReferenceExprSyntax.self) {
|
|
return identifierExpr.baseName.text == name
|
|
}
|
|
if let memberAccessExpr = value.as(MemberAccessExprSyntax.self),
|
|
let baseName = memberAccessExpr.base?.as(DeclReferenceExprSyntax.self)?.baseName.text,
|
|
["self", "Self"].contains(baseName) {
|
|
return memberAccessExpr.declName.baseName.text == name
|
|
}
|
|
return false
|
|
}
|
|
}
|