159 lines
6.9 KiB
Swift
159 lines
6.9 KiB
Swift
import SwiftSyntax
|
|
|
|
// MARK: - SelfBindingRule
|
|
|
|
struct SelfBindingRule: SwiftSyntaxCorrectableRule, ConfigurationProviderRule, OptInRule {
|
|
var configuration = SelfBindingConfiguration()
|
|
|
|
init() {}
|
|
|
|
static let description = RuleDescription(
|
|
identifier: "self_binding",
|
|
name: "Self Binding",
|
|
description: "Re-bind `self` to a consistent identifier name.",
|
|
kind: .style,
|
|
nonTriggeringExamples: [
|
|
Example("if let self = self { return }"),
|
|
Example("guard let self = self else else { return }"),
|
|
Example("if let this = this { return }"),
|
|
Example("guard let this = this else else { return }"),
|
|
Example("if let this = self { return }", configuration: ["bind_identifier": "this"]),
|
|
Example("guard let this = self else else { return }", configuration: ["bind_identifier": "this"])
|
|
],
|
|
triggeringExamples: [
|
|
Example("if let ↓`self` = self { return }"),
|
|
Example("guard let ↓`self` = self else else { return }"),
|
|
Example("if let ↓this = self { return }"),
|
|
Example("guard let ↓this = self else else { return }"),
|
|
Example("if let ↓self = self { return }", configuration: ["bind_identifier": "this"]),
|
|
Example("guard let ↓self = self else { return }", configuration: ["bind_identifier": "this"]),
|
|
Example("if let ↓self { return }", configuration: ["bind_identifier": "this"]),
|
|
Example("guard let ↓self else { return }", configuration: ["bind_identifier": "this"])
|
|
],
|
|
corrections: [
|
|
Example("if let ↓`self` = self { return }"):
|
|
Example("if let self = self { return }"),
|
|
Example("guard let ↓`self` = self else else { return }"):
|
|
Example("guard let self = self else else { return }"),
|
|
Example("if let ↓this = self { return }"):
|
|
Example("if let self = self { return }"),
|
|
Example("guard let ↓this = self else else { return }"):
|
|
Example("guard let self = self else else { return }"),
|
|
Example("if let ↓self = self { return }", configuration: ["bind_identifier": "this"]):
|
|
Example("if let this = self { return }", configuration: ["bind_identifier": "this"]),
|
|
Example("if let ↓self { return }", configuration: ["bind_identifier": "this"]):
|
|
Example("if let this = self { return }", configuration: ["bind_identifier": "this"]),
|
|
Example("guard let ↓self else { return }", configuration: ["bind_identifier": "this"]):
|
|
Example("guard let this = self else { return }", configuration: ["bind_identifier": "this"])
|
|
]
|
|
)
|
|
|
|
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
|
|
SelfBindingRuleVisitor(bindIdentifier: configuration.bindIdentifier)
|
|
}
|
|
|
|
func makeRewriter(file: SwiftLintFile) -> ViolationsSyntaxRewriter? {
|
|
SelfBindingRuleRewriter(
|
|
bindIdentifier: configuration.bindIdentifier,
|
|
locationConverter: file.locationConverter,
|
|
disabledRegions: disabledRegions(file: file)
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: - SelfBindingRuleVisitor
|
|
|
|
private final class SelfBindingRuleVisitor: ViolationsSyntaxVisitor {
|
|
private let bindIdentifier: String
|
|
|
|
init(bindIdentifier: String) {
|
|
self.bindIdentifier = bindIdentifier
|
|
super.init(viewMode: .sourceAccurate)
|
|
}
|
|
|
|
override func visitPost(_ node: OptionalBindingConditionSyntax) {
|
|
if let identifierPattern = node.pattern.as(IdentifierPatternSyntax.self),
|
|
identifierPattern.identifier.text != bindIdentifier {
|
|
var hasViolation = false
|
|
if let initializerIdentifier = node.initializer?.value.as(IdentifierExprSyntax.self) {
|
|
hasViolation = initializerIdentifier.identifier.text == "self"
|
|
} else if node.initializer == nil {
|
|
hasViolation = identifierPattern.identifier.text == "self" && bindIdentifier != "self"
|
|
}
|
|
|
|
if hasViolation {
|
|
violations.append(
|
|
ReasonedRuleViolation(
|
|
position: identifierPattern.positionAfterSkippingLeadingTrivia,
|
|
reason: "`self` should always be re-bound to `\(bindIdentifier)`"
|
|
)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - SelfBindingRuleRewriter
|
|
|
|
private final class SelfBindingRuleRewriter: SyntaxRewriter, ViolationsSyntaxRewriter {
|
|
private(set) var correctionPositions: [AbsolutePosition] = []
|
|
private let bindIdentifier: String
|
|
let locationConverter: SourceLocationConverter
|
|
let disabledRegions: [SourceRange]
|
|
|
|
init(bindIdentifier: String, locationConverter: SourceLocationConverter, disabledRegions: [SourceRange]) {
|
|
self.bindIdentifier = bindIdentifier
|
|
self.locationConverter = locationConverter
|
|
self.disabledRegions = disabledRegions
|
|
}
|
|
|
|
override func visit(_ node: OptionalBindingConditionSyntax) -> OptionalBindingConditionSyntax {
|
|
guard
|
|
let identifierPattern = node.pattern.as(IdentifierPatternSyntax.self),
|
|
identifierPattern.identifier.text != bindIdentifier,
|
|
!node.isContainedIn(regions: disabledRegions, locationConverter: locationConverter)
|
|
else {
|
|
return super.visit(node)
|
|
}
|
|
|
|
if let initializerIdentifier = node.initializer?.value.as(IdentifierExprSyntax.self),
|
|
initializerIdentifier.identifier.text == "self" {
|
|
correctionPositions.append(identifierPattern.positionAfterSkippingLeadingTrivia)
|
|
|
|
let newPattern = PatternSyntax(
|
|
identifierPattern
|
|
.with(\.identifier,
|
|
identifierPattern.identifier.withKind(.identifier(bindIdentifier))
|
|
)
|
|
)
|
|
|
|
return super.visit(node.with(\.pattern, newPattern))
|
|
} else if node.initializer == nil, identifierPattern.identifier.text == "self", bindIdentifier != "self" {
|
|
correctionPositions.append(identifierPattern.positionAfterSkippingLeadingTrivia)
|
|
|
|
let newPattern = PatternSyntax(
|
|
identifierPattern
|
|
.with(\.identifier,
|
|
identifierPattern.identifier.withKind(.identifier(bindIdentifier)))
|
|
)
|
|
|
|
let newInitializer = InitializerClauseSyntax(
|
|
value: IdentifierExprSyntax(
|
|
identifier: .keyword(
|
|
.`self`,
|
|
leadingTrivia: .space,
|
|
trailingTrivia: identifierPattern.trailingTrivia ?? .space
|
|
)
|
|
)
|
|
)
|
|
|
|
let newNode = node
|
|
.with(\.pattern, newPattern)
|
|
.with(\.initializer, newInitializer)
|
|
return super.visit(newNode)
|
|
} else {
|
|
return super.visit(node)
|
|
}
|
|
}
|
|
}
|