diff --git a/Sources/ParsingHelpers.swift b/Sources/ParsingHelpers.swift index 4fa2e858..b411d119 100644 --- a/Sources/ParsingHelpers.swift +++ b/Sources/ParsingHelpers.swift @@ -716,10 +716,17 @@ extension Formatter { /// If the token at the specified index is part of a conditional statement, returns the index of the first /// token in the statement (e.g. `if`, `guard`, `while`, etc.), otherwise returns nil func startOfConditionalStatement(at i: Int, excluding: Set = []) -> Int? { - guard var index = indexOfLastSignificantKeyword(at: i, excluding: excluding.union(["else", "where"])) else { + guard var index = indexOfLastSignificantKeyword(at: i, excluding: excluding.union(["else"])) else { return nil } + if tokens[index] == .keyword("where") { + if self.index(of: .endOfScope("case"), before: index) != nil { + return nil + } + index = indexOfLastSignificantKeyword(at: index, excluding: ["where"]) ?? index + } + if tokens[index] == .keyword("case"), let i = self.index( of: .nonSpaceOrCommentOrLinebreak, before: index, @@ -744,8 +751,10 @@ extension Formatter { default: return nil } - case "if", "guard", "while", "for", "case", "switch": + case "if", "guard", "while", "for", "case": return index + case "switch": + return next(.startOfScope, after: i) == .startOfScope(":") ? nil : index default: return nil } diff --git a/Tests/ParsingHelpersTests.swift b/Tests/ParsingHelpersTests.swift index bc42c40c..1300d89c 100644 --- a/Tests/ParsingHelpersTests.swift +++ b/Tests/ParsingHelpersTests.swift @@ -100,6 +100,11 @@ class ParsingHelpersTests: XCTestCase { XCTAssertFalse(formatter.isStartOfClosure(at: 11)) } + func testIfTryAndCallBracesNotTreatedAsClosure() { + let formatter = Formatter(tokenize("if try true && explode() {}")) + XCTAssertFalse(formatter.isStartOfClosure(at: 12)) + } + func testGuardElseBracesNotTreatedAsClosure() { let formatter = Formatter(tokenize("guard foo else {}")) XCTAssertFalse(formatter.isStartOfClosure(at: 6)) @@ -224,6 +229,23 @@ class ParsingHelpersTests: XCTestCase { XCTAssertFalse(formatter.isStartOfClosure(at: 28)) } + func testClosureInForInWhereClauseNotTreatedAsClosure() { + let formatter = Formatter(tokenize("for foo in foos where foo.method() { print(foo) }")) + XCTAssertFalse(formatter.isStartOfClosure(at: 16)) + } + + func testClosureInCaseWhereClause() { + let formatter = Formatter(tokenize(""" + switch foo { + case .bar + where testValues.map(String.init).compactMap { $0 } + .contains(baz): + continue + } + """)) + XCTAssertTrue(formatter.isStartOfClosure(at: 26)) + } + func testInitBracesNotTreatedAsClosure() { let formatter = Formatter(tokenize("init() { foo = 5 }")) XCTAssertFalse(formatter.isStartOfClosure(at: 4)) diff --git a/Tests/Rules/IndentTests.swift b/Tests/Rules/IndentTests.swift index 384d9504..f47176d2 100644 --- a/Tests/Rules/IndentTests.swift +++ b/Tests/Rules/IndentTests.swift @@ -4110,4 +4110,23 @@ class IndentTests: XCTestCase { let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) testFormatting(for: input, rule: .indent, options: options) } + + func testIndentSwitchCaseWhere() { + let input = """ + switch testKey { + case "organization" + where testValues.map(String.init).compactMap { try? Entity.ID($0, format: .number) } + .contains(Self.sessionInteractor.stage.value?.membership?.organization.id ?? .zero): // 2 + continue + + case "user" + where testValues.map(String.init).compactMap { try? Entity.ID($0, format: .number) } + .contains(Self.sessionInteractor.stage.value?.session?.user.id ?? .zero): // 3 + continue + } + """ + + let options = FormatOptions(indentCase: true) + testFormatting(for: input, rule: .indent, options: options, exclude: [.wrap]) + } }