258 lines
8.3 KiB
Swift
258 lines
8.3 KiB
Swift
import SwiftSyntax
|
|
|
|
struct CollectionAlignmentRule: SwiftSyntaxRule, ConfigurationProviderRule, OptInRule {
|
|
var configuration = CollectionAlignmentConfiguration()
|
|
|
|
static var description = RuleDescription(
|
|
identifier: "collection_alignment",
|
|
name: "Collection Element Alignment",
|
|
description: "All elements in a collection literal should be vertically aligned",
|
|
kind: .style,
|
|
nonTriggeringExamples: Examples(alignColons: false).nonTriggeringExamples,
|
|
triggeringExamples: Examples(alignColons: false).triggeringExamples
|
|
)
|
|
|
|
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
|
|
Visitor(alignColons: configuration.alignColons, locationConverter: file.locationConverter)
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
override func visitPost(_ node: ArrayExprSyntax) {
|
|
let locations = node.elements.map { element in
|
|
locationConverter.location(for: element.positionAfterSkippingLeadingTrivia)
|
|
}
|
|
violations.append(contentsOf: validate(keyLocations: locations))
|
|
}
|
|
|
|
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))
|
|
}
|
|
|
|
private func validate(keyLocations: [SourceLocation]) -> [AbsolutePosition] {
|
|
guard keyLocations.count >= 2 else {
|
|
return []
|
|
}
|
|
|
|
let firstKeyLocation = keyLocations[0]
|
|
let remainingKeyLocations = keyLocations[1...]
|
|
|
|
return zip(remainingKeyLocations.indices, remainingKeyLocations)
|
|
.compactMap { index, location -> AbsolutePosition? in
|
|
let previousLocation = keyLocations[index - 1]
|
|
let previousLine = previousLocation.line
|
|
let locationLine = location.line
|
|
let firstKeyColumn = firstKeyLocation.column
|
|
let locationColumn = location.column
|
|
guard previousLine < locationLine, firstKeyColumn != locationColumn else {
|
|
return nil
|
|
}
|
|
|
|
return locationConverter.position(ofLine: locationLine, column: locationColumn)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
extension CollectionAlignmentRule {
|
|
struct Examples {
|
|
private let alignColons: Bool
|
|
|
|
init(alignColons: Bool) {
|
|
self.alignColons = alignColons
|
|
}
|
|
|
|
var triggeringExamples: [Example] {
|
|
let examples = alignColons ? alignColonsTriggeringExamples : alignLeftTriggeringExamples
|
|
return examples + sharedTriggeringExamples
|
|
}
|
|
|
|
var nonTriggeringExamples: [Example] {
|
|
let examples = alignColons ? alignColonsNonTriggeringExamples : alignLeftNonTriggeringExamples
|
|
return examples + sharedNonTriggeringExamples
|
|
}
|
|
|
|
private var alignColonsTriggeringExamples: [Example] {
|
|
return [
|
|
Example("""
|
|
doThings(arg: [
|
|
"foo": 1,
|
|
"bar": 2,
|
|
"fizz"↓: 2,
|
|
"buzz"↓: 2
|
|
])
|
|
"""),
|
|
Example("""
|
|
let abc = [
|
|
"alpha": "a",
|
|
"beta"↓: "b",
|
|
"gamma": "c",
|
|
"delta": "d",
|
|
"epsilon"↓: "e"
|
|
]
|
|
"""),
|
|
Example("""
|
|
var weirdColons = [
|
|
"a" : 1,
|
|
"b" ↓:2,
|
|
"c" : 3
|
|
]
|
|
""")
|
|
]
|
|
}
|
|
|
|
private var alignColonsNonTriggeringExamples: [Example] {
|
|
return [
|
|
Example("""
|
|
doThings(arg: [
|
|
"foo": 1,
|
|
"bar": 2,
|
|
"fizz": 2,
|
|
"buzz": 2
|
|
])
|
|
"""),
|
|
Example("""
|
|
let abc = [
|
|
"alpha": "a",
|
|
"beta": "b",
|
|
"gamma": "g",
|
|
"delta": "d",
|
|
"epsilon": "e"
|
|
]
|
|
"""),
|
|
Example("""
|
|
var weirdColons = [
|
|
"a" : 1,
|
|
"b" :2,
|
|
"c" : 3
|
|
]
|
|
""")
|
|
]
|
|
}
|
|
|
|
private var alignLeftTriggeringExamples: [Example] {
|
|
return [
|
|
Example("""
|
|
doThings(arg: [
|
|
"foo": 1,
|
|
"bar": 2,
|
|
↓"fizz": 2,
|
|
↓"buzz": 2
|
|
])
|
|
"""),
|
|
Example("""
|
|
let abc = [
|
|
"alpha": "a",
|
|
↓"beta": "b",
|
|
"gamma": "g",
|
|
"delta": "d",
|
|
↓"epsilon": "e"
|
|
]
|
|
"""),
|
|
Example("""
|
|
let meals = [
|
|
"breakfast": "oatmeal",
|
|
"lunch": "sandwich",
|
|
↓"dinner": "burger"
|
|
]
|
|
""")
|
|
]
|
|
}
|
|
|
|
private var alignLeftNonTriggeringExamples: [Example] {
|
|
return [
|
|
Example("""
|
|
doThings(arg: [
|
|
"foo": 1,
|
|
"bar": 2,
|
|
"fizz": 2,
|
|
"buzz": 2
|
|
])
|
|
"""),
|
|
Example("""
|
|
let abc = [
|
|
"alpha": "a",
|
|
"beta": "b",
|
|
"gamma": "g",
|
|
"delta": "d",
|
|
"epsilon": "e"
|
|
]
|
|
"""),
|
|
Example("""
|
|
let meals = [
|
|
"breakfast": "oatmeal",
|
|
"lunch": "sandwich",
|
|
"dinner": "burger"
|
|
]
|
|
""")
|
|
]
|
|
}
|
|
|
|
private var sharedTriggeringExamples: [Example] {
|
|
return [
|
|
Example("""
|
|
let coordinates = [
|
|
CLLocationCoordinate2D(latitude: 0, longitude: 33),
|
|
↓CLLocationCoordinate2D(latitude: 0, longitude: 66),
|
|
CLLocationCoordinate2D(latitude: 0, longitude: 99)
|
|
]
|
|
"""),
|
|
Example("""
|
|
var evenNumbers: Set<Int> = [
|
|
2,
|
|
↓4,
|
|
6
|
|
]
|
|
""")
|
|
]
|
|
}
|
|
|
|
private var sharedNonTriggeringExamples: [Example] {
|
|
return [
|
|
Example("""
|
|
let coordinates = [
|
|
CLLocationCoordinate2D(latitude: 0, longitude: 33),
|
|
CLLocationCoordinate2D(latitude: 0, longitude: 66),
|
|
CLLocationCoordinate2D(latitude: 0, longitude: 99)
|
|
]
|
|
"""),
|
|
Example("""
|
|
var evenNumbers: Set<Int> = [
|
|
2,
|
|
4,
|
|
6
|
|
]
|
|
"""),
|
|
Example("""
|
|
let abc = [1, 2, 3, 4]
|
|
"""),
|
|
Example("""
|
|
let abc = [
|
|
1, 2, 3, 4
|
|
]
|
|
"""),
|
|
Example("""
|
|
let abc = [
|
|
"foo": "bar", "fizz": "buzz"
|
|
]
|
|
""")
|
|
]
|
|
}
|
|
}
|
|
}
|