diff --git a/Sources/FormattingHelpers.swift b/Sources/FormattingHelpers.swift index 3dc0f916..8884391a 100644 --- a/Sources/FormattingHelpers.swift +++ b/Sources/FormattingHelpers.swift @@ -2924,6 +2924,7 @@ extension Formatter { usingDynamicLookup: Bool, classOrStatic: Bool) { + let funcKeywordIndex = index let startToken = tokens[index] var localNames = localNames guard let startIndex = self.index(of: .startOfScope("("), after: index), @@ -2952,23 +2953,38 @@ extension Formatter { } index = self.index(of: .delimiter(","), after: index) ?? endIndex } - guard let bodyStartIndex = self.index(after: endIndex, where: { - switch $0 { - case .startOfScope("{"): // What we're looking for - return true - case .keyword("throws"), - .keyword("rethrows"), - .keyword("where"), - .keyword("is"), - .keyword("repeat"): - return false // Keep looking - case .keyword where !$0.isAttribute: - return true // Not valid between end of arguments and start of body - default: - return false // Keep looking + + let bodyStartIndex: Int + if let functionDeclaration = parseFunctionDeclaration(keywordIndex: funcKeywordIndex) { + guard let validBodyStartIndex = functionDeclaration.bodyRange?.lowerBound else { + // Ensure we move the `redundantSelf` work index to the end of the function declaration + index = functionDeclaration.range.upperBound + return } - }), tokens[bodyStartIndex] == .startOfScope("{") else { - return + + bodyStartIndex = validBodyStartIndex + } else { + // If `parseFunctionDeclaration` fails due to some unsupported pattern, use a more permissive search. + guard let validBodyStartIndex = self.index(after: endIndex, where: { + switch $0 { + case .startOfScope("{"): // What we're looking for + return true + case .keyword("throws"), + .keyword("rethrows"), + .keyword("where"), + .keyword("is"), + .keyword("repeat"): + return false // Keep looking + case .keyword where !$0.isAttribute: + return true // Not valid between end of arguments and start of body + default: + return false // Keep looking + } + }), tokens[validBodyStartIndex] == .startOfScope("{") else { + return + } + + bodyStartIndex = validBodyStartIndex } // Functions defined inside closures with `[weak self]` captures can diff --git a/Sources/ParsingHelpers.swift b/Sources/ParsingHelpers.swift index 942b540b..0770dc8c 100644 --- a/Sources/ParsingHelpers.swift +++ b/Sources/ParsingHelpers.swift @@ -3156,7 +3156,7 @@ extension Formatter { /// The range of the `where` clause if present let whereClauseRange: ClosedRange? /// The range of the function body (`{ ... }`) if present. - /// A protocol method requirement doesn't have a body. + /// A protocol method requirement, or a function with a `@_silgen` attribute, doesn't have a body. let bodyRange: ClosedRange? /// The full range of this declaration diff --git a/Tests/ParsingHelpersTests.swift b/Tests/ParsingHelpersTests.swift index 8090dab4..f4158036 100644 --- a/Tests/ParsingHelpersTests.swift +++ b/Tests/ParsingHelpersTests.swift @@ -1180,6 +1180,9 @@ final class ParsingHelpersTests: XCTestCase { private(set) var instanceVar = "test" // trailing comment + @_silgen_name("__MARKER_functionWithNoBody") + func functionWithNoBody(_ x: String) -> Int? + @objc private var computed: String { get { @@ -1304,6 +1307,9 @@ final class ParsingHelpersTests: XCTestCase { private(set) var instanceVar = "test" // trailing comment + @_silgen_name("__MARKER_functionWithNoBody") + func functionWithNoBody(_ x: String) -> Int? + @objc private var computed: String { get { @@ -1363,6 +1369,16 @@ final class ParsingHelpersTests: XCTestCase { XCTAssertEqual( declarations[7].body?[2].tokens.string, + """ + @_silgen_name(\"__MARKER_functionWithNoBody\") + func functionWithNoBody(_ x: String) -> Int? + + + """ + ) + + XCTAssertEqual( + declarations[7].body?[3].tokens.string, """ @objc private var computed: String { diff --git a/Tests/Rules/RedundantSelfTests.swift b/Tests/Rules/RedundantSelfTests.swift index 007fa20a..4d176e73 100644 --- a/Tests/Rules/RedundantSelfTests.swift +++ b/Tests/Rules/RedundantSelfTests.swift @@ -2973,6 +2973,35 @@ final class RedundantSelfTests: XCTestCase { testFormatting(for: input, rule: .redundantSelf, options: options) } + func testFunctionWithNoBodyFollowedByStaticFunction() { + let input = """ + struct Foo { + let foo: String + + @_silgen_name("__MARKER_doIt") + func doIt(_ x: String) -> Int? + + static func bar() { + print(self.foo) + } + } + """ + + let output = """ + struct Foo { + let foo: String + + @_silgen_name("__MARKER_doIt") + func doIt(_ x: String) -> Int? + + static func bar() { + print(foo) + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + func testNoInsertSelfBeforeSet() { let input = """ class Foo {