diff --git a/Sources/SwiftBasicFormat/BasicFormat.swift b/Sources/SwiftBasicFormat/BasicFormat.swift index 9cf41e79a30..4dbd6d59ce7 100644 --- a/Sources/SwiftBasicFormat/BasicFormat.swift +++ b/Sources/SwiftBasicFormat/BasicFormat.swift @@ -209,6 +209,9 @@ open class BasicFormat: SyntaxRewriter { case \EnumCaseParameterClauseSyntax.parameters: return true case \FunctionCallExprSyntax.arguments: + if let arguments = Syntax(node).as(LabeledExprListSyntax.self) { + return argumentListIsMultiLine(arguments) + } return true case \FunctionTypeSyntax.parameters: return true @@ -219,6 +222,9 @@ open class BasicFormat: SyntaxRewriter { case \SwitchCaseSyntax.statements: return true case \TupleExprSyntax.elements: + if let elements = Syntax(node).as(LabeledExprListSyntax.self) { + return argumentListIsMultiLine(elements) + } return true case \TupleTypeSyntax.elements: return true @@ -252,6 +258,25 @@ open class BasicFormat: SyntaxRewriter { }) != nil } + /// Returns `true` if the argument list (a `LabeledExprListSyntax` used by + /// `FunctionCallExprSyntax.arguments` or `TupleExprSyntax.elements`) is + /// laid out across multiple lines at the argument level (i.e. at least one + /// argument begins on its own line). + /// + /// When this is `false`, the argument list is single-line at the argument + /// level even if individual arguments contain multi-line expressions (such + /// as closures or collection literals). In that case, the argument-list + /// indentation scope shouldn't contribute, because any inner multi-line + /// expression brings its own indentation scope. + private func argumentListIsMultiLine(_ arguments: LabeledExprListSyntax) -> Bool { + for argument in arguments { + if argument.leadingTrivia.contains(where: \.isNewline) { + return true + } + } + return false + } + open func requiresNewline(between first: TokenSyntax?, and second: TokenSyntax?) -> Bool { // We don't want to add newlines inside string interpolation. // When first or second ``TokenSyntax`` is a multiline quote we want special handling diff --git a/Tests/SwiftBasicFormatTest/BasicFormatTests.swift b/Tests/SwiftBasicFormatTest/BasicFormatTests.swift index 141981b6e21..222978d26a9 100644 --- a/Tests/SwiftBasicFormatTest/BasicFormatTests.swift +++ b/Tests/SwiftBasicFormatTest/BasicFormatTests.swift @@ -180,6 +180,36 @@ final class BasicFormatTest: XCTestCase { ) } + func testSingleLineCallWithMultiLineClosureArgument() { + assertFormattingRoundTrips( + """ + foo(someClosure: { _ in + return 1 + }) + """ + ) + } + + func testSingleLineCallWithUnlabeledMultiLineClosureArgument() { + assertFormattingRoundTrips( + """ + foo({ _ in + return 1 + }) + """ + ) + } + + func testSingleLineTupleWithMultiLineClosureElement() { + assertFormattingRoundTrips( + """ + _ = ({ _ in + return 1 + }) + """ + ) + } + func testLineWrappingInsideIndentedBlock() { assertFormatted( source: """ diff --git a/Tests/SwiftRefactorTest/CallToTrailingClosureTests.swift b/Tests/SwiftRefactorTest/CallToTrailingClosureTests.swift index 9f457e8edbc..bb79ecf7c32 100644 --- a/Tests/SwiftRefactorTest/CallToTrailingClosureTests.swift +++ b/Tests/SwiftRefactorTest/CallToTrailingClosureTests.swift @@ -79,12 +79,10 @@ final class CallToTrailingClosuresTest: XCTestCase { }) """ - // TODO: The ident here is not great. - // https://github.com/swiftlang/swift-syntax/issues/1473 let expected: ExprSyntax = """ foo({ label in return 1 - }, 1) { label2 in + }, 1) { label2 in return 2 } _: { label3 in return 3 @@ -109,13 +107,12 @@ final class CallToTrailingClosuresTest: XCTestCase { }) """ - // TODO: BasicFormat is pretty messed up here let expected: ExprSyntax = """ foo({ label in return 1 - }, 1, { label2 in + }, 1, { label2 in return 2 - }) { label3 in + }) { label3 in return 3 } named: { label4 in return 4 diff --git a/Tests/SwiftSyntaxBuilderTest/VariableTests.swift b/Tests/SwiftSyntaxBuilderTest/VariableTests.swift index 8aeaaad5d2a..2db4907216a 100644 --- a/Tests/SwiftSyntaxBuilderTest/VariableTests.swift +++ b/Tests/SwiftSyntaxBuilderTest/VariableTests.swift @@ -138,8 +138,8 @@ final class VariableTests: XCTestCase { """ var bar: [String] { bar.map({ - $0.description - }) + $0.description + }) } """ ), @@ -148,8 +148,8 @@ final class VariableTests: XCTestCase { """ inout bar: [String] { bar.map({ - $0.description - }) + $0.description + }) } """ ), diff --git a/Tests/SwiftSyntaxMacroExpansionTest/ExpressionMacroTests.swift b/Tests/SwiftSyntaxMacroExpansionTest/ExpressionMacroTests.swift index 609128b2475..40fbe8f8e89 100644 --- a/Tests/SwiftSyntaxMacroExpansionTest/ExpressionMacroTests.swift +++ b/Tests/SwiftSyntaxMacroExpansionTest/ExpressionMacroTests.swift @@ -193,7 +193,7 @@ final class ExpressionMacroTests: XCTestCase { _ = ({ () -> Bool in print("hello") return true - }, #"{ () -> Bool in\\#n print("hello")\\#n return true\\#n}"#) + }, #"{ () -> Bool in\\#n print("hello")\\#n return true\\#n}"#) """, macros: ["stringify": StringifyMacro.self], indentationWidth: indentationWidth diff --git a/Tests/SwiftSyntaxTest/DebugDescriptionTests.swift b/Tests/SwiftSyntaxTest/DebugDescriptionTests.swift index dde2045381c..993670e7b84 100644 --- a/Tests/SwiftSyntaxTest/DebugDescriptionTests.swift +++ b/Tests/SwiftSyntaxTest/DebugDescriptionTests.swift @@ -181,23 +181,23 @@ class DebugDescriptionTests: XCTestCase { sourceFile.debugInitCall(includeTrivia: true).trimmingTrailingWhitespace(), """ SourceFileSyntax( - statements: CodeBlockItemListSyntax([ - CodeBlockItemSyntax(item: CodeBlockItemSyntax.Item(FunctionCallExprSyntax( - calledExpression: ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier("test"))), - leftParen: .leftParenToken(), - arguments: LabeledExprListSyntax([ - LabeledExprSyntax( - expression: ExprSyntax(IntegerLiteralExprSyntax(literal: .integerLiteral("1"))), - trailingComma: .commaToken(trailingTrivia: .space) - ), - LabeledExprSyntax(expression: ExprSyntax(IntegerLiteralExprSyntax(literal: .integerLiteral("2")))) - ]), - rightParen: .rightParenToken(), - additionalTrailingClosures: MultipleTrailingClosureElementListSyntax([]) - ))) - ]), - endOfFileToken: .endOfFileToken() - ) + statements: CodeBlockItemListSyntax([ + CodeBlockItemSyntax(item: CodeBlockItemSyntax.Item(FunctionCallExprSyntax( + calledExpression: ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier("test"))), + leftParen: .leftParenToken(), + arguments: LabeledExprListSyntax([ + LabeledExprSyntax( + expression: ExprSyntax(IntegerLiteralExprSyntax(literal: .integerLiteral("1"))), + trailingComma: .commaToken(trailingTrivia: .space) + ), + LabeledExprSyntax(expression: ExprSyntax(IntegerLiteralExprSyntax(literal: .integerLiteral("2")))) + ]), + rightParen: .rightParenToken(), + additionalTrailingClosures: MultipleTrailingClosureElementListSyntax([]) + ))) + ]), + endOfFileToken: .endOfFileToken() + ) """ ) @@ -205,23 +205,23 @@ class DebugDescriptionTests: XCTestCase { sourceFile.debugInitCall(includeTrivia: false).trimmingTrailingWhitespace(), """ SourceFileSyntax( - statements: CodeBlockItemListSyntax([ - CodeBlockItemSyntax(item: CodeBlockItemSyntax.Item(FunctionCallExprSyntax( - calledExpression: ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier("test"))), - leftParen: .leftParenToken(), - arguments: LabeledExprListSyntax([ - LabeledExprSyntax( - expression: ExprSyntax(IntegerLiteralExprSyntax(literal: .integerLiteral("1"))), - trailingComma: .commaToken() - ), - LabeledExprSyntax(expression: ExprSyntax(IntegerLiteralExprSyntax(literal: .integerLiteral("2")))) - ]), - rightParen: .rightParenToken(), - additionalTrailingClosures: MultipleTrailingClosureElementListSyntax([]) - ))) - ]), - endOfFileToken: .endOfFileToken() - ) + statements: CodeBlockItemListSyntax([ + CodeBlockItemSyntax(item: CodeBlockItemSyntax.Item(FunctionCallExprSyntax( + calledExpression: ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier("test"))), + leftParen: .leftParenToken(), + arguments: LabeledExprListSyntax([ + LabeledExprSyntax( + expression: ExprSyntax(IntegerLiteralExprSyntax(literal: .integerLiteral("1"))), + trailingComma: .commaToken() + ), + LabeledExprSyntax(expression: ExprSyntax(IntegerLiteralExprSyntax(literal: .integerLiteral("2")))) + ]), + rightParen: .rightParenToken(), + additionalTrailingClosures: MultipleTrailingClosureElementListSyntax([]) + ))) + ]), + endOfFileToken: .endOfFileToken() + ) """ ) }