SwiftLint/Source/SwiftLintFramework/Rules/Lint/UnusedSetterValueRule.swift

203 lines
5.8 KiB
Swift

import SwiftSyntax
struct UnusedSetterValueRule: ConfigurationProviderRule, SwiftSyntaxRule {
var configuration = SeverityConfiguration(.warning)
init() {}
static let description = RuleDescription(
identifier: "unused_setter_value",
name: "Unused Setter Value",
description: "Setter value is not used",
kind: .lint,
nonTriggeringExamples: [
Example("""
var aValue: String {
get {
return Persister.shared.aValue
}
set {
Persister.shared.aValue = newValue
}
}
"""),
Example("""
var aValue: String {
set {
Persister.shared.aValue = newValue
}
get {
return Persister.shared.aValue
}
}
"""),
Example("""
var aValue: String {
get {
return Persister.shared.aValue
}
set(value) {
Persister.shared.aValue = value
}
}
"""),
Example("""
override var aValue: String {
get {
return Persister.shared.aValue
}
set { }
}
"""),
Example("""
protocol Foo {
var bar: Bool { get set }
""", excludeFromDocumentation: true),
Example("""
override var accessibilityValue: String? {
get {
let index = Int(self.value)
guard steps.indices.contains(index) else { return "" }
return ""
}
set {}
}
""", excludeFromDocumentation: true)
],
triggeringExamples: [
Example("""
var aValue: String {
get {
return Persister.shared.aValue
}
↓set {
Persister.shared.aValue = aValue
}
}
"""),
Example("""
var aValue: String {
↓set {
Persister.shared.aValue = aValue
}
get {
return Persister.shared.aValue
}
}
"""),
Example("""
var aValue: String {
get {
return Persister.shared.aValue
}
↓set {
Persister.shared.aValue = aValue
}
}
"""),
Example("""
var aValue: String {
get {
let newValue = Persister.shared.aValue
return newValue
}
↓set {
Persister.shared.aValue = aValue
}
}
"""),
Example("""
var aValue: String {
get {
return Persister.shared.aValue
}
↓set(value) {
Persister.shared.aValue = aValue
}
}
"""),
Example("""
override var aValue: String {
get {
return Persister.shared.aValue
}
↓set {
Persister.shared.aValue = aValue
}
}
""")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
}
private extension UnusedSetterValueRule {
final class Visitor: ViolationsSyntaxVisitor {
override var skippableDeclarations: [DeclSyntaxProtocol.Type] { [ProtocolDeclSyntax.self] }
override func visitPost(_ node: AccessorDeclSyntax) {
guard node.accessorKind.tokenKind == .contextualKeyword("set") else {
return
}
let variableName = node.parameter?.name.text ?? "newValue"
let visitor = NewValueUsageVisitor(variableName: variableName)
if !visitor.walk(tree: node, handler: \.isVariableUsed) {
if (Syntax(node).closestVariableOrSubscript()?.modifiers).containsOverride,
let body = node.body, body.statements.isEmpty {
return
}
violations.append(node.positionAfterSkippingLeadingTrivia)
}
}
}
final class NewValueUsageVisitor: SyntaxVisitor {
let variableName: String
private(set) var isVariableUsed = false
init(variableName: String) {
self.variableName = variableName
super.init(viewMode: .sourceAccurate)
}
override func visitPost(_ node: IdentifierExprSyntax) {
if node.identifier.text == variableName {
isVariableUsed = true
}
}
}
}
private extension Syntax {
func closestVariableOrSubscript() -> Either<SubscriptDeclSyntax, VariableDeclSyntax>? {
if let subscriptDecl = self.as(SubscriptDeclSyntax.self) {
return .left(subscriptDecl)
} else if let variableDecl = self.as(VariableDeclSyntax.self) {
return .right(variableDecl)
}
return parent?.closestVariableOrSubscript()
}
}
private enum Either<L, R> {
case left(L)
case right(R)
}
private extension Either<SubscriptDeclSyntax, VariableDeclSyntax> {
var modifiers: ModifierListSyntax? {
switch self {
case .left(let left):
return left.modifiers
case .right(let right):
return right.modifiers
}
}
}