Rewrite collection_alignment rule with SwiftSyntax (#4472)

This commit is contained in:
Marcelo Fabri
2022-10-24 10:51:31 -07:00
committed by GitHub
parent 96925d480e
commit fa6db3cca7
3 changed files with 53 additions and 79 deletions
+1
View File
@@ -56,6 +56,7 @@
- `closing_brace`
- `closure_body_length`
- `closure_parameter_position`
- `collection_alignment`
- `comment_spacing`
- `computed_accessors_order`
- `conditional_returns_on_newline`
@@ -1,8 +1,8 @@
public struct CollectionAlignmentConfiguration: RuleConfiguration, Equatable {
private(set) var severityConfiguration = SeverityConfiguration(.warning)
private(set) var alignColons = false
public struct CollectionAlignmentConfiguration: SeverityBasedRuleConfiguration, Equatable {
public private(set) var severityConfiguration = SeverityConfiguration(.warning)
public private(set) var alignColons = false
init() {}
public init() {}
public var consoleDescription: String {
return severityConfiguration.consoleDescription + ", align_colons: \(alignColons)"
@@ -1,6 +1,6 @@
import SourceKittenFramework
import SwiftSyntax
public struct CollectionAlignmentRule: ASTRule, ConfigurationProviderRule, OptInRule {
public struct CollectionAlignmentRule: SwiftSyntaxRule, ConfigurationProviderRule, OptInRule {
public var configuration = CollectionAlignmentConfiguration()
public init() {}
@@ -14,89 +14,62 @@ public struct CollectionAlignmentRule: ASTRule, ConfigurationProviderRule, OptIn
triggeringExamples: Examples(alignColons: false).triggeringExamples
)
public func validate(file: SwiftLintFile, kind: SwiftExpressionKind,
dictionary: SourceKittenDictionary) -> [StyleViolation] {
guard kind == .dictionary || kind == .array else { return [] }
public func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(alignColons: configuration.alignColons, locationConverter: file.locationConverter)
}
}
let keyLocations: [Location]
if kind == .array {
keyLocations = arrayElementLocations(with: file, dictionary: dictionary)
} else {
keyLocations = dictionaryKeyLocations(with: file, dictionary: dictionary)
private extension CollectionAlignmentRule {
final class Visitor: ViolationsSyntaxVisitor {
private let alignColons: Bool
private let locationConverter: SourceLocationConverter
init(alignColons: Bool, locationConverter: SourceLocationConverter) {
self.alignColons = alignColons
self.locationConverter = locationConverter
super.init(viewMode: .sourceAccurate)
}
guard keyLocations.count >= 2 else {
return []
override func visitPost(_ node: ArrayExprSyntax) {
let locations = node.elements.map { element in
locationConverter.location(for: element.positionAfterSkippingLeadingTrivia)
}
violations.append(contentsOf: validate(keyLocations: locations))
}
let firstKeyLocation = keyLocations[0]
let remainingKeyLocations = keyLocations[1...]
let violationLocations = zip(remainingKeyLocations.indices, remainingKeyLocations)
.compactMap { index, location -> Location? in
let previousLocation = keyLocations[index - 1]
guard let previousLine = previousLocation.line,
let locationLine = location.line,
let firstKeyCharacter = firstKeyLocation.character,
let locationCharacter = location.character,
previousLine < locationLine,
firstKeyCharacter != locationCharacter else { return nil }
override func visitPost(_ node: DictionaryElementListSyntax) {
let locations = node.map { element in
let position = alignColons ? element.colon.positionAfterSkippingLeadingTrivia :
element.keyExpression.positionAfterSkippingLeadingTrivia
return locationConverter.location(for: position)
}
violations.append(contentsOf: validate(keyLocations: locations))
}
return location
private func validate(keyLocations: [SourceLocation]) -> [AbsolutePosition] {
guard keyLocations.count >= 2 else {
return []
}
return violationLocations.map {
StyleViolation(ruleDescription: Self.description,
severity: configuration.severityConfiguration.severity,
location: $0)
let firstKeyLocation = keyLocations[0]
let remainingKeyLocations = keyLocations[1...]
return zip(remainingKeyLocations.indices, remainingKeyLocations)
.compactMap { index, location -> AbsolutePosition? in
let previousLocation = keyLocations[index - 1]
guard let previousLine = previousLocation.line,
let locationLine = location.line,
let firstKeyColumn = firstKeyLocation.column,
let locationColumn = location.column,
previousLine < locationLine,
firstKeyColumn != locationColumn else {
return nil
}
return locationConverter.position(ofLine: locationLine, column: locationColumn)
}
}
}
private func arrayElementLocations(with file: SwiftLintFile, dictionary: SourceKittenDictionary) -> [Location] {
return dictionary.elements.compactMap { element -> Location? in
element.offset.map { Location(file: file, byteOffset: $0) }
}
}
private func dictionaryKeyLocations(with file: SwiftLintFile,
dictionary: SourceKittenDictionary) -> [Location] {
var keys: [SourceKittenDictionary] = []
var values: [SourceKittenDictionary] = []
dictionary.elements.enumerated().forEach { index, element in
// in a dictionary, the even elements are keys, and the odd elements are values
if index.isMultiple(of: 2) {
keys.append(element)
} else {
values.append(element)
}
}
return zip(keys, values).compactMap { key, value -> Location? in
guard let keyOffset = key.offset,
let valueOffset = value.offset,
let keyLength = key.length else { return nil }
if configuration.alignColons {
return colonLocation(with: file,
keyOffset: keyOffset,
keyLength: keyLength,
valueOffset: valueOffset)
} else {
return Location(file: file, byteOffset: keyOffset)
}
}
}
private func colonLocation(with file: SwiftLintFile, keyOffset: ByteCount, keyLength: ByteCount,
valueOffset: ByteCount) -> Location? {
let contents = file.stringView
let matchStart = keyOffset + keyLength
let matchLength = valueOffset - matchStart
let byteRange = ByteRange(location: matchStart, length: matchLength)
let range = contents.byteRangeToNSRange(byteRange)
let matches = file.match(pattern: ":", excludingSyntaxKinds: [.comment], range: range)
return matches.first.map { Location(file: file, characterOffset: $0.location) }
}
}
extension CollectionAlignmentRule {