Files
SwiftLint/Source/SwiftLintBuiltInRules/Rules/Style/CollectionAlignmentRule.swift
JP Simard 3eb3772022 Compile with -strict-concurrency=complete (#5320)
* Compile with `-strict-concurrency=complete`

Only in Bazel for now, because this is considered an unsafe flag in
SwiftPM which would lead to warnings for downstream consumers of
SwiftLint using SwiftPM.

Some imports of SwiftSyntax need the `@preconcurrency` annotation until
https://github.com/apple/swift-syntax/pull/2322 is available in a
release.

The following SwiftLint libraries have `-strict-concurrency=complete`
applied:

* SwiftLintCoreMacros
* SwiftLintBuiltInRules
* SwiftLintExtraRules

The following SwiftLint libraries don't have the flag applied and need
to be migrated:

* SwiftLintCore
* swiftlint (CLI target)

So really the rules and macros are now being compiled with
`-strict-concurrency=complete`, but the core infrastructure of SwiftLint
is not.

Still, given that Swift 6 will eventually make these warnings errors by
default, it's good to prevent issues from creeping in earlier rather
than later.

* Add CI job to build with strict concurrency
2023-11-01 15:20:40 +00:00

271 lines
9.0 KiB
Swift

import SwiftSyntax
@SwiftSyntaxRule
struct CollectionAlignmentRule: OptInRule {
var configuration = CollectionAlignmentConfiguration()
static let 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
)
}
private extension CollectionAlignmentRule {
final class Visitor: ViolationsSyntaxVisitor<ConfigurationType> {
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 = configuration.alignColons ? element.colon.positionAfterSkippingLeadingTrivia :
element.key.positionAfterSkippingLeadingTrivia
let location = locationConverter.location(for: position)
let graphemeColumn: Int
let graphemeClusters = String(
locationConverter.sourceLines[location.line - 1].utf8.prefix(location.column - 1)
)
if let graphemeClusters {
graphemeColumn = graphemeClusters.count + 1
} else {
graphemeColumn = location.column
}
return SourceLocation(
line: location.line,
column: graphemeColumn,
offset: location.offset,
file: location.file
)
}
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
]
"""),
Example("""
NSAttributedString(string: "", attributes: [.font: UIFont.systemFont(ofSize: 12, weight: .regular),
.foregroundColor: UIColor(white: 0, alpha: 0.2)])
""")
]
}
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"
]
"""),
Example("""
NSAttributedString(string: "", attributes: [.font: UIFont.systemFont(ofSize: 12, weight: .regular),
.foregroundColor: UIColor(white: 0, alpha: 0.2)])
""")
]
}
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"
]
""")
]
}
}
}