mirror of
https://github.com/realm/SwiftLint.git
synced 2026-06-06 20:18:40 +00:00
173 lines
5.8 KiB
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
|
|
}
|
|
}
|
|
}
|