SwiftLint/Source/SwiftLintBuiltInRules/Rules/Style/ClosureParameterPositionRul...

144 lines
4.5 KiB
Swift

import SwiftSyntax
struct ClosureParameterPositionRule: SwiftSyntaxRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "closure_parameter_position",
name: "Closure Parameter Position",
description: "Closure parameters should be on the same line as opening brace",
kind: .style,
nonTriggeringExamples: [
Example("[1, 2].map { $0 + 1 }\n"),
Example("[1, 2].map({ $0 + 1 })\n"),
Example("[1, 2].map { number in\n number + 1 \n}\n"),
Example("[1, 2].map { number -> Int in\n number + 1 \n}\n"),
Example("[1, 2].map { (number: Int) -> Int in\n number + 1 \n}\n"),
Example("[1, 2].map { [weak self] number in\n number + 1 \n}\n"),
Example("[1, 2].something(closure: { number in\n number + 1 \n})\n"),
Example("let isEmpty = [1, 2].isEmpty()\n"),
Example("""
rlmConfiguration.migrationBlock.map { rlmMigration in
return { migration, schemaVersion in
rlmMigration(migration.rlmMigration, schemaVersion)
}
}
"""),
Example("""
let mediaView: UIView = { [weak self] index in
return UIView()
}(index)
""")
],
triggeringExamples: [
Example("""
[1, 2].map {
↓number in
number + 1
}
"""),
Example("""
[1, 2].map {
↓number -> Int in
number + 1
}
"""),
Example("""
[1, 2].map {
(↓number: Int) -> Int in
number + 1
}
"""),
Example("""
[1, 2].map {
[weak ↓self] ↓number in
number + 1
}
"""),
Example("""
[1, 2].map { [weak self]
↓number in
number + 1
}
"""),
Example("""
[1, 2].map({
↓number in
number + 1
})
"""),
Example("""
[1, 2].something(closure: {
↓number in
number + 1
})
"""),
Example("""
[1, 2].reduce(0) {
↓sum, ↓number in
number + sum
})
"""),
Example("""
f.completionHandler = {
↓thing in
doStuff()
}
"""),
Example("""
foo {
[weak ↓self] in
self?.bar()
}
""")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(locationConverter: file.locationConverter)
}
}
private extension ClosureParameterPositionRule {
final class Visitor: ViolationsSyntaxVisitor {
private let locationConverter: SourceLocationConverter
init(locationConverter: SourceLocationConverter) {
self.locationConverter = locationConverter
super.init(viewMode: .sourceAccurate)
}
override func visitPost(_ node: ClosureExprSyntax) {
guard let signature = node.signature else {
return
}
let leftBracePosition = node.leftBrace.positionAfterSkippingLeadingTrivia
let startLine = locationConverter.location(for: leftBracePosition).line
let localViolations = signature.positionsToCheck.filter { position in
return locationConverter.location(for: position).line != startLine
}
violations.append(contentsOf: localViolations)
}
}
}
private extension ClosureSignatureSyntax {
var positionsToCheck: [AbsolutePosition] {
var positions: [AbsolutePosition] = []
if let captureItems = capture?.items {
positions.append(contentsOf: captureItems.map(\.expression.positionAfterSkippingLeadingTrivia))
}
if let input = input?.as(ClosureParamListSyntax.self) {
positions.append(contentsOf: input.map(\.positionAfterSkippingLeadingTrivia))
} else if let input = input?.as(ClosureParameterClauseSyntax.self) {
positions.append(contentsOf: input.parameterList.map(\.positionAfterSkippingLeadingTrivia))
}
return positions
}
}