140 lines
5.3 KiB
Swift
140 lines
5.3 KiB
Swift
import SwiftSyntax
|
|
|
|
struct UnneededParenthesesInClosureArgumentRule: ConfigurationProviderRule,
|
|
SwiftSyntaxCorrectableRule, OptInRule {
|
|
var configuration = SeverityConfiguration(.warning)
|
|
|
|
init() {}
|
|
|
|
static let description = RuleDescription(
|
|
identifier: "unneeded_parentheses_in_closure_argument",
|
|
name: "Unneeded Parentheses in Closure Argument",
|
|
description: "Parentheses are not needed when declaring closure arguments",
|
|
kind: .style,
|
|
nonTriggeringExamples: [
|
|
Example("let foo = { (bar: Int) in }\n"),
|
|
Example("let foo = { bar, _ in }\n"),
|
|
Example("let foo = { bar in }\n"),
|
|
Example("let foo = { bar -> Bool in return true }\n"),
|
|
Example("""
|
|
DispatchQueue.main.async { () -> Void in
|
|
doSomething()
|
|
}
|
|
"""),
|
|
Example("""
|
|
registerFilter(name) { any, args throws -> Any? in
|
|
doSomething(any, args)
|
|
}
|
|
""", excludeFromDocumentation: true)
|
|
],
|
|
triggeringExamples: [
|
|
Example("call(arg: { ↓(bar) in })\n"),
|
|
Example("call(arg: { ↓(bar, _) in })\n"),
|
|
Example("let foo = { ↓(bar) -> Bool in return true }\n"),
|
|
Example("foo.map { ($0, $0) }.forEach { ↓(x, y) in }"),
|
|
Example("foo.bar { [weak self] ↓(x, y) in }"),
|
|
Example("""
|
|
[].first { ↓(temp) in
|
|
[].first { ↓(temp) in
|
|
[].first { ↓(temp) in
|
|
_ = temp
|
|
return false
|
|
}
|
|
return false
|
|
}
|
|
return false
|
|
}
|
|
"""),
|
|
Example("""
|
|
[].first { temp in
|
|
[].first { ↓(temp) in
|
|
[].first { ↓(temp) in
|
|
_ = temp
|
|
return false
|
|
}
|
|
return false
|
|
}
|
|
return false
|
|
}
|
|
"""),
|
|
Example("""
|
|
registerFilter(name) { ↓(any, args) throws -> Any? in
|
|
doSomething(any, args)
|
|
}
|
|
""", excludeFromDocumentation: true)
|
|
],
|
|
corrections: [
|
|
Example("call(arg: { ↓(bar) in })\n"): Example("call(arg: { bar in })\n"),
|
|
Example("call(arg: { ↓(bar, _) in })\n"): Example("call(arg: { bar, _ in })\n"),
|
|
Example("call(arg: { ↓(bar, _)in })\n"): Example("call(arg: { bar, _ in })\n"),
|
|
Example("let foo = { ↓(bar) -> Bool in return true }\n"):
|
|
Example("let foo = { bar -> Bool in return true }\n"),
|
|
Example("method { ↓(foo, bar) in }\n"): Example("method { foo, bar in }\n"),
|
|
Example("foo.map { ($0, $0) }.forEach { ↓(x, y) in }"): Example("foo.map { ($0, $0) }.forEach { x, y in }"),
|
|
Example("foo.bar { [weak self] ↓(x, y) in }"): Example("foo.bar { [weak self] x, y in }")
|
|
]
|
|
)
|
|
|
|
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
|
|
Visitor(viewMode: .sourceAccurate)
|
|
}
|
|
|
|
func makeRewriter(file: SwiftLintFile) -> ViolationsSyntaxRewriter? {
|
|
Rewriter(
|
|
locationConverter: file.locationConverter,
|
|
disabledRegions: disabledRegions(file: file)
|
|
)
|
|
}
|
|
}
|
|
|
|
private final class Visitor: ViolationsSyntaxVisitor {
|
|
override func visitPost(_ node: ClosureSignatureSyntax) {
|
|
guard let clause = node.input?.as(ParameterClauseSyntax.self),
|
|
!clause.parameterList.contains(where: { $0.type != nil }),
|
|
clause.parameterList.isNotEmpty else {
|
|
return
|
|
}
|
|
|
|
violations.append(clause.positionAfterSkippingLeadingTrivia)
|
|
}
|
|
}
|
|
|
|
private final class Rewriter: SyntaxRewriter, ViolationsSyntaxRewriter {
|
|
private(set) var correctionPositions: [AbsolutePosition] = []
|
|
let locationConverter: SourceLocationConverter
|
|
let disabledRegions: [SourceRange]
|
|
|
|
init(locationConverter: SourceLocationConverter, disabledRegions: [SourceRange]) {
|
|
self.locationConverter = locationConverter
|
|
self.disabledRegions = disabledRegions
|
|
}
|
|
|
|
override func visit(_ node: ClosureSignatureSyntax) -> ClosureSignatureSyntax {
|
|
guard
|
|
let clause = node.input?.as(ParameterClauseSyntax.self),
|
|
!clause.parameterList.contains(where: { $0.type != nil }),
|
|
clause.parameterList.isNotEmpty,
|
|
!node.isContainedIn(regions: disabledRegions, locationConverter: locationConverter)
|
|
else {
|
|
return super.visit(node)
|
|
}
|
|
|
|
let items = clause.parameterList.enumerated().compactMap { idx, param -> ClosureParamSyntax? in
|
|
guard let name = param.firstName else {
|
|
return nil
|
|
}
|
|
|
|
let isLast = idx == clause.parameterList.count - 1
|
|
return ClosureParamSyntax(
|
|
name: name,
|
|
trailingComma: isLast ? nil : .commaToken(trailingTrivia: Trivia(pieces: [.spaces(1)]))
|
|
)
|
|
}
|
|
|
|
correctionPositions.append(clause.positionAfterSkippingLeadingTrivia)
|
|
|
|
let paramList = ClosureParamListSyntax(items).with(\.trailingTrivia, .spaces(1))
|
|
return super.visit(node.with(\.input, .init(paramList)))
|
|
}
|
|
}
|