diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d645a77e..ccbc4bfc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,11 @@ * Fix false positive uppercase enum case in `raw_value_for_camel_cased_codable_enum` rule [Teameh](https://github.com/teameh) +* Fix false positive in `no_space_in_method_call` rule with multiple trailing + closures (Swift 5.3). + [Marcelo Fabri](https://github.com/marcelofabri) + [#3259](https://github.com/realm/SwiftLint/issues/3259) + * Fix false negative in `explicit_acl` rule when using `extension` with Swift 5.2+. [Marcelo Fabri](https://github.com/marcelofabri) diff --git a/Source/SwiftLintFramework/Models/SwiftVersion.swift b/Source/SwiftLintFramework/Models/SwiftVersion.swift index cf628acfe..799ac34f3 100644 --- a/Source/SwiftLintFramework/Models/SwiftVersion.swift +++ b/Source/SwiftLintFramework/Models/SwiftVersion.swift @@ -31,6 +31,8 @@ public extension SwiftVersion { static let fiveDotOne = SwiftVersion(rawValue: "5.1.0") /// Swift 5.2.x - https://swift.org/download/#swift-52 static let fiveDotTwo = SwiftVersion(rawValue: "5.2.0") + /// Swift 5.3.x - https://swift.org/download/#swift-53 + static let fiveDotThree = SwiftVersion(rawValue: "5.3.0") /// The current detected Swift compiler version, based on the currently accessible SourceKit version. /// diff --git a/Source/SwiftLintFramework/Rules/Style/NoSpaceInMethodCallRule.swift b/Source/SwiftLintFramework/Rules/Style/NoSpaceInMethodCallRule.swift index 6b533728d..65207a1d9 100644 --- a/Source/SwiftLintFramework/Rules/Style/NoSpaceInMethodCallRule.swift +++ b/Source/SwiftLintFramework/Rules/Style/NoSpaceInMethodCallRule.swift @@ -13,22 +13,15 @@ public struct NoSpaceInMethodCallRule: SubstitutionCorrectableASTRule, Configura description: "Don't add a space between the method name and the parentheses.", kind: .style, minSwiftVersion: .fourDotTwo, - nonTriggeringExamples: [ - Example("foo()"), - Example("object.foo()"), - Example("object.foo(1)"), - Example("object.foo(value: 1)"), - Example("object.foo { print($0 }"), - Example("list.sorted { $0.0 < $1.0 }.map { $0.value }"), - Example("self.init(rgb: (Int) (colorInt))") - ], + nonTriggeringExamples: nonTriggeringExamples, triggeringExamples: [ Example("foo↓ ()"), Example("object.foo↓ ()"), Example("object.foo↓ (1)"), Example("object.foo↓ (value: 1)"), Example("object.foo↓ () {}"), - Example("object.foo↓ ()") + Example("object.foo↓ ()"), + Example("object.foo↓ (value: 1) { x in print(x) }") ], corrections: [ Example("foo↓ ()"): Example("foo()"), @@ -40,6 +33,34 @@ public struct NoSpaceInMethodCallRule: SubstitutionCorrectableASTRule, Configura ] ) + private static var nonTriggeringExamples: [Example] { + let commonExamples = [ + Example("foo()"), + Example("object.foo()"), + Example("object.foo(1)"), + Example("object.foo(value: 1)"), + Example("object.foo { print($0 }"), + Example("list.sorted { $0.0 < $1.0 }.map { $0.value }"), + Example("self.init(rgb: (Int) (colorInt))") + ] + + guard SwiftVersion.current >= .fiveDotThree else { + return commonExamples + } + + let swiftFiveDot3Examples = [ + Example(""" + Button { + print("Button tapped") + } label: { + Text("Button") + } + """) + ] + + return commonExamples + swiftFiveDot3Examples + } + // MARK: - ASTRule public func validate(file: SwiftLintFile, @@ -76,9 +97,7 @@ public struct NoSpaceInMethodCallRule: SubstitutionCorrectableASTRule, Configura } // Don't trigger if it's a single parameter trailing closure without parens - if let subDict = dictionary.substructure.last, - subDict.expressionKind == .closure, - let closureBodyOffset = subDict.bodyOffset, + if let closureBodyOffset = dictionary.substructure.lazy.compactMap({ $0.closureBodyOffset }).first, closureBodyOffset == bodyOffset { return [] } @@ -91,3 +110,17 @@ public struct NoSpaceInMethodCallRule: SubstitutionCorrectableASTRule, Configura return [range] } } + +private extension SourceKittenDictionary { + var closureBodyOffset: ByteCount? { + if expressionKind == .closure { + return bodyOffset + } + + if expressionKind == .argument, substructure.last?.expressionKind == .closure { + return substructure.last?.bodyOffset + } + + return nil + } +}