mirror of
https://github.com/realm/SwiftLint.git
synced 2026-06-06 20:18:40 +00:00
Rewrite computed_accessors_order with SwiftSyntax (#4165)
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import Foundation
|
||||
import SourceKittenFramework
|
||||
import SwiftSyntax
|
||||
|
||||
public struct ComputedAccessorsOrderRule: ConfigurationProviderRule {
|
||||
public struct ComputedAccessorsOrderRule: ConfigurationProviderRule, SourceKitFreeRule {
|
||||
public var configuration = ComputedAccessorsOrderRuleConfiguration()
|
||||
|
||||
public init() {}
|
||||
@@ -16,116 +15,71 @@ public struct ComputedAccessorsOrderRule: ConfigurationProviderRule {
|
||||
)
|
||||
|
||||
public func validate(file: SwiftLintFile) -> [StyleViolation] {
|
||||
let getTokens = findKeywordTokens(keyword: "get", file: file)
|
||||
|
||||
let violatingLocations = getTokens.compactMap { getToken -> (ByteCount, SwiftDeclarationKind?)? in
|
||||
// the last element is the deepest structure
|
||||
guard let dict = declarations(forByteOffset: getToken.offset,
|
||||
structureDictionary: file.structureDictionary).last else {
|
||||
return nil
|
||||
ComputedAccessorsOrderRuleVisitor(expectedOrder: configuration.order)
|
||||
.walk(file: file, handler: \.violationPositions)
|
||||
.sorted { $0.position < $1.position }
|
||||
.map { violation in
|
||||
StyleViolation(
|
||||
ruleDescription: Self.description,
|
||||
severity: configuration.severityConfiguration.severity,
|
||||
location: Location(file: file, position: violation.position),
|
||||
reason: reason(for: violation.kind)
|
||||
)
|
||||
}
|
||||
|
||||
guard let range = dict.byteRange.map(file.stringView.byteRangeToNSRange) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let setTokens = findKeywordTokens(keyword: "set", file: file, range: range)
|
||||
let setToken = setTokens.first { token in
|
||||
// the last element is the deepest structure
|
||||
guard let setDict = declarations(forByteOffset: token.offset,
|
||||
structureDictionary: file.structureDictionary).last else {
|
||||
return false
|
||||
}
|
||||
|
||||
return setDict.offset == dict.offset
|
||||
}
|
||||
|
||||
let tokensInOrder = [getToken, setToken].compactMap { $0?.offset }.sorted()
|
||||
let expectedOrder: [ByteCount]
|
||||
switch configuration.order {
|
||||
case .getSet:
|
||||
expectedOrder = [getToken, setToken].compactMap { $0?.offset }
|
||||
case .setGet:
|
||||
expectedOrder = [setToken, getToken].compactMap { $0?.offset }
|
||||
}
|
||||
|
||||
guard tokensInOrder != expectedOrder else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let kind = dict.declarationKind
|
||||
return (tokensInOrder[0], kind)
|
||||
}
|
||||
|
||||
return violatingLocations.map { offset, kind in
|
||||
let reason = kind.map { kind -> String in
|
||||
let kindString = kind == .functionSubscript ? "subscripts" : "properties"
|
||||
let orderString: String
|
||||
switch configuration.order {
|
||||
case .getSet:
|
||||
orderString = "getter and then the setter"
|
||||
case .setGet:
|
||||
orderString = "setter and then the getter"
|
||||
}
|
||||
return "Computed \(kindString) should declare first the \(orderString)."
|
||||
}
|
||||
|
||||
return StyleViolation(ruleDescription: Self.description,
|
||||
severity: configuration.severityConfiguration.severity,
|
||||
location: Location(file: file, byteOffset: offset),
|
||||
reason: reason)
|
||||
}
|
||||
}
|
||||
|
||||
private func findKeywordTokens(keyword: String,
|
||||
file: SwiftLintFile,
|
||||
range: NSRange? = nil) -> [SwiftLintSyntaxToken] {
|
||||
let pattern = "\\b\(keyword)\\b"
|
||||
return file.rangesAndTokens(matching: pattern, range: range).compactMap { _, tokens in
|
||||
guard tokens.count == 1,
|
||||
let token = tokens.last,
|
||||
token.kind == .keyword else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return token
|
||||
private func reason(for kind: ComputedAccessorsOrderRuleVisitor.ViolationKind) -> String {
|
||||
let kindString = kind == .subscript ? "subscripts" : "properties"
|
||||
let orderString: String
|
||||
switch configuration.order {
|
||||
case .getSet:
|
||||
orderString = "getter and then the setter"
|
||||
case .setGet:
|
||||
orderString = "setter and then the getter"
|
||||
}
|
||||
return "Computed \(kindString) should declare first the \(orderString)."
|
||||
}
|
||||
}
|
||||
|
||||
private extension ComputedAccessorsOrderRule {
|
||||
func declarations(forByteOffset byteOffset: ByteCount,
|
||||
structureDictionary: SourceKittenDictionary) -> [SourceKittenDictionary] {
|
||||
var results = [SourceKittenDictionary]()
|
||||
let allowedKinds = SwiftDeclarationKind.variableKinds.subtracting([.varParameter])
|
||||
.union([.functionSubscript])
|
||||
private final class ComputedAccessorsOrderRuleVisitor: SyntaxVisitor {
|
||||
enum ViolationKind {
|
||||
case `subscript`, property
|
||||
}
|
||||
|
||||
func parse(dictionary: SourceKittenDictionary,
|
||||
parentKind: SwiftDeclarationKind?,
|
||||
into results: inout [SourceKittenDictionary]) {
|
||||
// Only accepts declarations which contains a body and contains the
|
||||
// searched byteOffset
|
||||
guard let kind = dictionary.declarationKind,
|
||||
let byteRange = dictionary.byteRange,
|
||||
byteRange.contains(byteOffset)
|
||||
else {
|
||||
return
|
||||
}
|
||||
private(set) var violationPositions: [(position: AbsolutePosition, kind: ViolationKind)] = []
|
||||
private let expectedOrder: ComputedAccessorsOrderRuleConfiguration.Order
|
||||
|
||||
if parentKind != .protocol && allowedKinds.contains(kind) {
|
||||
results.append(dictionary)
|
||||
}
|
||||
init(expectedOrder: ComputedAccessorsOrderRuleConfiguration.Order) {
|
||||
self.expectedOrder = expectedOrder
|
||||
}
|
||||
|
||||
for dictionary in dictionary.substructure {
|
||||
parse(dictionary: dictionary, parentKind: kind, into: &results)
|
||||
}
|
||||
override func visitPost(_ node: AccessorBlockSyntax) {
|
||||
guard let firstAccessor = node.accessors.first,
|
||||
let order = node.order,
|
||||
order != expectedOrder else {
|
||||
return
|
||||
}
|
||||
|
||||
let dict = structureDictionary
|
||||
for dictionary in dict.substructure {
|
||||
parse(dictionary: dictionary, parentKind: nil, into: &results)
|
||||
}
|
||||
|
||||
return results
|
||||
let kind: ViolationKind = node.parent?.as(SubscriptDeclSyntax.self) == nil ? .property : .subscript
|
||||
violationPositions.append((firstAccessor.positionAfterSkippingLeadingTrivia, kind))
|
||||
}
|
||||
}
|
||||
|
||||
private extension AccessorBlockSyntax {
|
||||
var order: ComputedAccessorsOrderRuleConfiguration.Order? {
|
||||
guard accessors.count == 2, accessors.map(\.body).allSatisfy({ $0 != nil }) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let tokens = accessors.map(\.accessorKind.tokenKind)
|
||||
if tokens == [.contextualKeyword("get"), .contextualKeyword("set")] {
|
||||
return .getSet
|
||||
}
|
||||
|
||||
if tokens == [.contextualKeyword("set"), .contextualKeyword("get")] {
|
||||
return .setGet
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user