SwiftLint/Source/SwiftLintFramework/Rules/Style/PreferSelfInStaticReference...

390 lines
13 KiB
Swift

import SwiftSyntax
struct PreferSelfInStaticReferencesRule: SwiftSyntaxRule, CorrectableRule, ConfigurationProviderRule, OptInRule {
var configuration = SeverityConfiguration(.warning)
init() {}
static var description = RuleDescription(
identifier: "prefer_self_in_static_references",
name: "Prefer Self in Static References",
description: "Use `Self` to refer to the surrounding type name",
kind: .style,
nonTriggeringExamples: [
Example("""
class C {
static let primes = [2, 3, 5, 7]
func isPrime(i: Int) -> Bool { Self.primes.contains(i) }
"""),
Example("""
struct T {
static let i = 0
}
struct S {
static let i = 0
}
extension T {
static let j = S.i + T.i
static let k = { T.j }()
}
"""),
Example("""
class `Self` {
static let i = 0
func f() -> Int { Self.i }
}
"""),
Example("""
class C {
static private(set) var i = 0, j = C.i
static let k = { C.i }()
let h = C.i
@GreaterThan(C.j) var k: Int
func f() {
_ = [Int: C]()
_ = [C]()
}
}
""", excludeFromDocumentation: true),
Example("""
struct S {
struct T {
struct R {
static let i = 3
}
}
struct R {
static let j = S.T.R.i
}
static let j = Self.T.R.i + Self.R.j
let h = Self.T.R.i + Self.R.j
}
""", excludeFromDocumentation: true),
Example("""
class C {
static let s = 2
func f(i: Int = C.s) -> Int {
func g(@GreaterEqualThan(C.s) j: Int = C.s) -> Int { j }
return i + Self.s
}
func g() -> Any { C.self }
}
""", excludeFromDocumentation: true),
Example("""
class Record<T> {
static func get() -> Record<T> { Record<T>() }
}
""", excludeFromDocumentation: true),
Example("""
@objc class C: NSObject {
@objc var s = ""
@objc func f() { _ = #keyPath(C.s) }
}
""", excludeFromDocumentation: true)
],
triggeringExamples: [
Example("""
final class CheckCellView: NSTableCellView {
@IBOutlet var checkButton: NSButton!
override func awakeFromNib() {
checkButton.action = #selector(↓CheckCellView.check(_:))
}
@objc func check(_ button: AnyObject?) {}
}
"""),
Example("""
class C {
struct S {
static let i = 2
let h = ↓S.i
}
static let i = 1
let h = C.i
var j: Int { ↓C.i }
func f() -> Int { ↓C.i + h }
}
"""),
Example("""
struct S {
let j: Int
static let i = 1
static func f() -> Int { ↓S.i }
func g() -> Any { ↓S.self }
func h() -> S { ↓S(j: 2) }
func i() -> KeyPath<S, Int> { \\↓S.j }
func j(@Wrap(-↓S.i, ↓S.i) n: Int = ↓S.i) {}
}
"""),
Example("""
struct S {
struct T {
static let i = 3
}
struct R {
static let j = S.T.i
}
static let h = ↓S.T.i + ↓S.R.j
}
"""),
Example("""
enum E {
case A
static func f() -> E { ↓E.A }
static func g() -> E { ↓E.f() }
}
"""),
Example("""
extension E {
class C {
static var i = 2
var j: Int { ↓C.i }
var k: Int {
get { ↓C.i }
set { ↓C.i = newValue }
}
}
}
""", excludeFromDocumentation: true),
Example("""
class C {
var c: C { C() }
}
final class D {
var d: D { ↓D() }
}
""", excludeFromDocumentation: true)
],
corrections: [
Example("""
final class CheckCellView: NSTableCellView {
@IBOutlet var checkButton: NSButton!
override func awakeFromNib() {
checkButton.action = #selector(↓CheckCellView.check(_:))
}
@objc func check(_ button: AnyObject?) {}
}
"""):
Example("""
final class CheckCellView: NSTableCellView {
@IBOutlet var checkButton: NSButton!
override func awakeFromNib() {
checkButton.action = #selector(Self.check(_:))
}
@objc func check(_ button: AnyObject?) {}
}
"""),
Example("""
struct S {
static let i = 1
static let j = ↓S.i
let k = ↓S . j
static func f(_ l: Int = ↓S.i) -> Int { l*↓S.j }
func g() { ↓S.i + ↓S.f() + k }
}
"""): Example("""
struct S {
static let i = 1
static let j = Self.i
let k = Self . j
static func f(_ l: Int = Self.i) -> Int { l*Self.j }
func g() { Self.i + Self.f() + k }
}
""")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
func correct(file: SwiftLintFile) -> [Correction] {
let ranges = Visitor(viewMode: .sourceAccurate)
.walk(file: file, handler: \.corrections)
.compactMap { file.stringView.NSRange(start: $0.start, end: $0.end) }
.filter { file.ruleEnabled(violatingRange: $0, for: self) != nil }
.reversed()
var corrections = [Correction]()
var contents = file.contents
for range in ranges {
let contentsNSString = contents.bridge()
contents = contentsNSString.replacingCharacters(in: range, with: "Self")
let location = Location(file: file, characterOffset: range.location)
corrections.append(Correction(ruleDescription: Self.description, location: location))
}
file.write(contents)
return corrections
}
}
private class Visitor: ViolationsSyntaxVisitor {
private enum ParentDeclBehavior {
case likeClass(name: String, isFinal: Bool)
case likeStruct(String)
case skipReferences
var parentName: String? {
switch self {
case let .likeClass(name, _): return name
case let .likeStruct(name): return name
case .skipReferences: return nil
}
}
}
private enum VariableDeclBehavior {
case handleReferences
case skipReferences
}
private var parentDeclScopes = [ParentDeclBehavior]()
private var variableDeclScopes = [VariableDeclBehavior]()
private(set) var corrections = [(start: AbsolutePosition, end: AbsolutePosition)]()
override func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind {
parentDeclScopes.append(.likeClass(name: node.identifier.text, isFinal: node.modifiers.isFinal))
return .skipChildren
}
override func visitPost(_ node: ActorDeclSyntax) {
_ = parentDeclScopes.popLast()
}
override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
parentDeclScopes.append(.likeClass(name: node.identifier.text, isFinal: node.modifiers.isFinal))
return .visitChildren
}
override func visitPost(_ node: ClassDeclSyntax) {
_ = parentDeclScopes.popLast()
}
override func visit(_ node: CodeBlockSyntax) -> SyntaxVisitorContinueKind {
variableDeclScopes.append(.handleReferences)
return .visitChildren
}
override func visitPost(_ node: CodeBlockSyntax) {
_ = variableDeclScopes.popLast()
}
override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {
parentDeclScopes.append(.likeStruct(node.identifier.text))
return .visitChildren
}
override func visitPost(_ node: EnumDeclSyntax) {
_ = parentDeclScopes.popLast()
}
override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {
parentDeclScopes.append(.skipReferences)
return .visitChildren
}
override func visitPost(_ node: ExtensionDeclSyntax) {
_ = parentDeclScopes.popLast()
}
override func visit(_ node: MemberAccessExprSyntax) -> SyntaxVisitorContinueKind {
if case .likeClass = parentDeclScopes.last {
if node.name.tokenKind == .keyword(.self) {
return .skipChildren
}
}
return .visitChildren
}
override func visitPost(_ node: IdentifierExprSyntax) {
guard let parent = node.parent,
!parent.is(SpecializeExprSyntax.self),
!parent.is(DictionaryElementSyntax.self),
!parent.is(ArrayElementSyntax.self) else {
return
}
if parent.is(FunctionCallExprSyntax.self), case .likeClass(_, false) = parentDeclScopes.last {
return
}
addViolation(on: node.identifier)
}
override func visit(_ node: MemberDeclBlockSyntax) -> SyntaxVisitorContinueKind {
if case .likeClass = parentDeclScopes.last {
variableDeclScopes.append(.skipReferences)
} else {
variableDeclScopes.append(.handleReferences)
}
return .visitChildren
}
override func visitPost(_ node: MemberDeclBlockSyntax) {
_ = variableDeclScopes.popLast()
}
override func visit(_ node: MacroExpansionExprSyntax) -> SyntaxVisitorContinueKind {
if case .likeClass = parentDeclScopes.last, case .identifier("selector") = node.macro.tokenKind {
return .visitChildren
}
return .skipChildren
}
override func visit(_ node: ParameterClauseSyntax) -> SyntaxVisitorContinueKind {
if case .likeStruct = parentDeclScopes.last {
return .visitChildren
}
return .skipChildren
}
override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {
parentDeclScopes.append(.skipReferences)
return .skipChildren
}
override func visitPost(_ node: ProtocolDeclSyntax) {
_ = parentDeclScopes.popLast()
}
override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
parentDeclScopes.append(.likeStruct(node.identifier.text))
return .visitChildren
}
override func visitPost(_ node: StructDeclSyntax) {
_ = parentDeclScopes.popLast()
}
override func visitPost(_ node: SimpleTypeIdentifierSyntax) {
if node.parent?.is(KeyPathExprSyntax.self) == true {
addViolation(on: node.name)
}
}
override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {
if node.bindings.onlyElement?.accessor != nil {
// Computed property
return .visitChildren
}
if case .handleReferences = variableDeclScopes.last {
return .visitChildren
}
return .skipChildren
}
private func addViolation(on node: TokenSyntax) {
if let parentName = parentDeclScopes.last?.parentName, node.tokenKind == .identifier(parentName) {
violations.append(node.positionAfterSkippingLeadingTrivia)
corrections.append(
(start: node.positionAfterSkippingLeadingTrivia, end: node.endPositionBeforeTrailingTrivia)
)
}
}
}