From e51ce8a47e6a86f9d12637ec0a5cc6367b66ffb2 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Wed, 5 Jun 2024 20:13:18 +0100 Subject: [PATCH] Correctly handle `init` accessor --- Sources/FormattingHelpers.swift | 25 ++++++++++++++---- Sources/Inference.swift | 2 +- Sources/ParsingHelpers.swift | 4 +-- Tests/ParsingHelpersTests.swift | 21 +++++++++++++++ Tests/RulesTests+Redundancy.swift | 44 +++++++++++++++++++++++++++++++ 5 files changed, 88 insertions(+), 8 deletions(-) diff --git a/Sources/FormattingHelpers.swift b/Sources/FormattingHelpers.swift index 53123b51..66ba33fa 100644 --- a/Sources/FormattingHelpers.swift +++ b/Sources/FormattingHelpers.swift @@ -2870,7 +2870,7 @@ extension Formatter { ["static", "class"].contains(string) }) if let name = name, classOrStatic || !staticSelf { - processAccessors(["get", "set", "willSet", "didSet"], for: name, + processAccessors(["get", "set", "willSet", "didSet", "init"], for: name, at: &index, localNames: localNames, members: members, typeStack: &typeStack, closureStack: &closureStack, membersByType: &membersByType, @@ -3160,8 +3160,15 @@ extension Formatter { assert(tokens[index] == .startOfScope("{")) var foundAccessors = false var localNames = localNames - while let nextIndex = self.index(of: .nonSpaceOrCommentOrLinebreak, after: index, if: { - if case let .identifier(name) = $0, names.contains(name) { return true } else { return false } + while var nextIndex = self.index(of: .nonSpaceOrCommentOrLinebreak, after: index, if: { + switch $0 { + case .keyword where $0.isAttribute: + return true + case let .identifier(name), let .keyword(name): + return names.contains(name) + default: + return false + } }), let startIndex = self.index(of: .startOfScope("{"), after: nextIndex) { foundAccessors = true index = startIndex + 1 @@ -3170,10 +3177,18 @@ extension Formatter { }), let varToken = next(.identifier, after: parenStart) { localNames.insert(varToken.unescaped()) } else { - switch tokens[nextIndex].string { + var token = tokens[nextIndex] + while token.isAttribute, + let endIndex = endOfAttribute(at: nextIndex), + let index = self.index(of: .nonSpaceOrCommentOrLinebreak, after: endIndex) + { + nextIndex = index + token = tokens[nextIndex] + } + switch token.string { case "get": localNames.insert(name) - case "set": + case "set", "init": localNames.insert(name) localNames.insert("newValue") case "willSet": diff --git a/Sources/Inference.swift b/Sources/Inference.swift index 9fa9fec2..ac74ddff 100644 --- a/Sources/Inference.swift +++ b/Sources/Inference.swift @@ -994,7 +994,7 @@ private struct Inference { prevIndex -= 1 } if let name = name { - processAccessors(["get", "set", "willSet", "didSet"], for: name, + processAccessors(["get", "set", "willSet", "didSet", "init"], for: name, at: &index, localNames: localNames, members: members, typeStack: &typeStack, membersByType: &membersByType, classMembersByType: &classMembersByType, diff --git a/Sources/ParsingHelpers.swift b/Sources/ParsingHelpers.swift index cb66a505..99bfb924 100644 --- a/Sources/ParsingHelpers.swift +++ b/Sources/ParsingHelpers.swift @@ -668,7 +668,7 @@ extension Formatter { func isAccessorKeyword(at i: Int, checkKeyword: Bool = true) -> Bool { guard !checkKeyword || - ["get", "set", "willSet", "didSet"].contains(token(at: i)?.string ?? ""), + ["get", "set", "willSet", "didSet", "init"].contains(token(at: i)?.string ?? ""), var prevIndex = index(of: .nonSpaceOrCommentOrLinebreak, before: i) else { return false @@ -1120,7 +1120,7 @@ extension Formatter { return true } return false - case "get", "set", "willSet", "didSet": + case "get", "set", "willSet", "didSet", "init": return isAccessorKeyword(at: i, checkKeyword: false) case "actor": if last(.nonSpaceOrCommentOrLinebreak, before: i)?.isOperator(ofType: .infix) == true { diff --git a/Tests/ParsingHelpersTests.swift b/Tests/ParsingHelpersTests.swift index e2ecc4ee..5ce7714d 100644 --- a/Tests/ParsingHelpersTests.swift +++ b/Tests/ParsingHelpersTests.swift @@ -625,6 +625,18 @@ class ParsingHelpersTests: XCTestCase { XCTAssert(formatter.isAccessorKeyword(at: 34)) } + func testInit() { + let formatter = Formatter(tokenize(""" + var foo: Int { + init {} + get {} + set {} + } + """)) + XCTAssert(formatter.isAccessorKeyword(at: 10)) + XCTAssert(formatter.isAccessorKeyword(at: 16)) + } + func testNotGetter() { let formatter = Formatter(tokenize(""" func foo() { @@ -644,6 +656,15 @@ class ParsingHelpersTests: XCTestCase { XCTAssert(formatter.isAccessorKeyword(at: 10, checkKeyword: false)) } + func testNotSetterInit() { + let formatter = Formatter(tokenize(""" + class Foo { + init() { print("") } + } + """)) + XCTAssertFalse(formatter.isAccessorKeyword(at: 7)) + } + // MARK: isEnumCase func testIsEnumCase() { diff --git a/Tests/RulesTests+Redundancy.swift b/Tests/RulesTests+Redundancy.swift index 5fc55507..5c3c0620 100644 --- a/Tests/RulesTests+Redundancy.swift +++ b/Tests/RulesTests+Redundancy.swift @@ -6230,6 +6230,50 @@ class RedundancyTests: RulesTests { testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) } + func testPropertyInitNotInterpretedAsTypeInit() { + let input = """ + struct MyStruct { + private var __myVar: String + var myVar: String { + @storageRestrictions(initializes: __myVar) + init(initialValue) { + __myVar = initialValue + } + set { + __myVar = newValue + } + get { + __myVar + } + } + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + } + + func testPropertyInitNotInterpretedAsTypeInit2() { + let input = """ + struct MyStruct { + private var __myVar: String + var myVar: String { + @storageRestrictions(initializes: __myVar) + init { + __myVar = newValue + } + set { + __myVar = newValue + } + get { + __myVar + } + } + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + } + // parsing bugs func testSelfRemovalParsingBug() {