Files
SwiftLint/Source/SwiftLintBuiltInRules/Rules/Style/RedundantSelfRule.swift

173 lines
5.8 KiB
Swift

@_spi(Diagnostics)
import SwiftParser
@_spi(RawSyntax)
import SwiftSyntax
@SwiftSyntaxRule(correctable: true, optIn: true)
struct RedundantSelfRule: Rule {
var configuration = RedundantSelfConfiguration()
static let description = RuleDescription(
identifier: "redundant_self",
name: "Redundant Self",
description: "Explicit use of 'self' is not required",
kind: .style,
nonTriggeringExamples: RedundantSelfRuleExamples.nonTriggeringExamples,
triggeringExamples: RedundantSelfRuleExamples.triggeringExamples,
corrections: RedundantSelfRuleExamples.corrections,
deprecatedAliases: ["redundant_self_in_closure"]
)
}
private enum TypeDeclarationKind {
case likeStruct, likeClass, `extension`
}
private enum ClosureExprType {
case anonymousCall, functionArgument
}
private enum SelfCaptureKind {
case strong, weak, uncaptured
}
private extension RedundantSelfRule {
final class Visitor: DeclaredIdentifiersTrackingVisitor<ConfigurationType> {
private var typeDeclarations = Stack<TypeDeclarationKind>()
private var closureExprScopes = Stack<(ClosureExprType, SelfCaptureKind)>()
private var initializerScopes = Stack<Bool>()
override var skippableDeclarations: [any DeclSyntaxProtocol.Type] { [ProtocolDeclSyntax.self] }
override func visit(_: ActorDeclSyntax) -> SyntaxVisitorContinueKind {
typeDeclarations.push(.likeClass)
return .visitChildren
}
override func visitPost(_: ActorDeclSyntax) {
typeDeclarations.pop()
}
override func visit(_: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
typeDeclarations.push(.likeClass)
return .visitChildren
}
override func visitPost(_: ClassDeclSyntax) {
typeDeclarations.pop()
}
override func visit(_ node: ClosureExprSyntax) -> SyntaxVisitorContinueKind {
let captureType: SelfCaptureKind =
if let selfItem = node.signature?.capture?.items.first(where: \.capturesSelf) {
selfItem.capturesWeakly ? .weak : .strong
} else {
.uncaptured
}
let exprType: ClosureExprType =
if node.keyPathInParent == \FunctionCallExprSyntax.calledExpression {
.anonymousCall
} else {
.functionArgument
}
closureExprScopes.push((exprType, captureType))
return .visitChildren
}
override func visitPost(_: ClosureExprSyntax) {
closureExprScopes.pop()
}
override func visit(_: EnumDeclSyntax) -> SyntaxVisitorContinueKind {
typeDeclarations.push(.likeStruct)
return .visitChildren
}
override func visitPost(_: EnumDeclSyntax) {
typeDeclarations.pop()
}
override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {
if node.extendedType.isOptionalType {
typeDeclarations.push(.extension)
}
return .visitChildren
}
override func visitPost(_ node: ExtensionDeclSyntax) {
if node.extendedType.isOptionalType {
typeDeclarations.pop()
}
}
override func visit(_: InitializerDeclSyntax) -> SyntaxVisitorContinueKind {
initializerScopes.push(true)
return .visitChildren
}
override func visitPost(_: InitializerDeclSyntax) {
initializerScopes.pop()
}
override func visitPost(_ node: MemberAccessExprSyntax) {
if configuration.keepInInitializers, initializerScopes.peek() == true {
return
}
if closureExprScopes.isNotEmpty, !isSelfRedundant {
return
}
if configuration.onlyInClosures, closureExprScopes.isEmpty {
return
}
if typeDeclarations.peek() == .extension, node.isBaseSelf, hasSeenDeclaration(for: "self") {
return
}
let declName = node.declName.baseName.text
if !hasSeenDeclaration(for: declName), node.isBaseSelf, declName != "init" {
violations.append(
at: node.positionAfterSkippingLeadingTrivia,
correction: .init(
start: node.positionAfterSkippingLeadingTrivia,
end: node.endPositionBeforeTrailingTrivia,
replacement: node.declName.baseName.needsEscaping
? "`\(declName)`"
: declName
)
)
}
}
override func visit(_: StructDeclSyntax) -> SyntaxVisitorContinueKind {
typeDeclarations.push(.likeStruct)
return .visitChildren
}
override func visitPost(_: StructDeclSyntax) {
typeDeclarations.pop()
}
private var isSelfRedundant: Bool {
if typeDeclarations.peek() == .likeStruct {
return true
}
guard let (closureType, selfCapture) = closureExprScopes.peek() else {
return false
}
return closureType == .anonymousCall
|| selfCapture == .strong && SwiftVersion.current >= .fiveDotThree
|| selfCapture == .weak && SwiftVersion.current >= .fiveDotEight
}
}
}
private extension TokenSyntax {
var needsEscaping: Bool {
[UInt8](text.utf8).withUnsafeBufferPointer {
if let keyword = Keyword(SyntaxText(baseAddress: $0.baseAddress, count: text.count)) {
return TokenKind.keyword(keyword).isLexerClassifiedKeyword
}
return false
}
}
}