diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 27a41f90e..24d210a11 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -3298,7 +3298,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { } /// Walks the expression and returns the leftmost multiline string literal (which might be the - /// expression itself) if the leftmost child is a multiline string literal. + /// expression itself) if the leftmost child is a multiline string literal or if it is a unary + /// operation applied to a multiline string literal. /// /// - Parameter expr: The expression whose leftmost multiline string literal should be returned. /// - Returns: The leftmost multiline string literal, or nil if the leftmost subexpression was @@ -3310,8 +3311,28 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return stringLiteralExpr case .infixOperatorExpr(let infixOperatorExpr): return leftmostMultilineStringLiteral(of: infixOperatorExpr.leftOperand) + case .asExpr(let asExpr): + return leftmostMultilineStringLiteral(of: asExpr.expression) + case .isExpr(let isExpr): + return leftmostMultilineStringLiteral(of: isExpr.expression) + case .forcedValueExpr(let forcedValueExpr): + return leftmostMultilineStringLiteral(of: forcedValueExpr.expression) + case .optionalChainingExpr(let optionalChainingExpr): + return leftmostMultilineStringLiteral(of: optionalChainingExpr.expression) + case .postfixUnaryExpr(let postfixUnaryExpr): + return leftmostMultilineStringLiteral(of: postfixUnaryExpr.expression) + case .prefixOperatorExpr(let prefixOperatorExpr): + return leftmostMultilineStringLiteral(of: prefixOperatorExpr.postfixExpression) case .ternaryExpr(let ternaryExpr): return leftmostMultilineStringLiteral(of: ternaryExpr.conditionExpression) + case .functionCallExpr(let functionCallExpr): + return leftmostMultilineStringLiteral(of: functionCallExpr.calledExpression) + case .subscriptExpr(let subscriptExpr): + return leftmostMultilineStringLiteral(of: subscriptExpr.calledExpression) + case .memberAccessExpr(let memberAccessExpr): + return memberAccessExpr.base.flatMap { leftmostMultilineStringLiteral(of: $0) } + case .postfixIfConfigExpr(let postfixIfConfigExpr): + return postfixIfConfigExpr.base.flatMap { leftmostMultilineStringLiteral(of: $0) } default: return nil } diff --git a/Tests/SwiftFormatPrettyPrintTests/StringTests.swift b/Tests/SwiftFormatPrettyPrintTests/StringTests.swift index 13644ea0d..12129d5fe 100644 --- a/Tests/SwiftFormatPrettyPrintTests/StringTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/StringTests.swift @@ -327,4 +327,72 @@ final class StringTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 20) } + + func testLeadingMultilineStringsInOtherExpressions() { + // The stacked indentation behavior needs to drill down into different node types to find the + // leftmost multiline string literal. This makes sure that we cover various cases. + let input = + #""" + let bytes = """ + { + "key": "value" + } + """.utf8.count + let json = """ + { + "key": "value" + } + """.data(using: .utf8) + let slice = """ + { + "key": "value" + } + """[...] + let forceUnwrap = """ + { + "key": "value" + } + """! + let optionalChaining = """ + { + "key": "value" + } + """? + let postfix = """ + { + "key": "value" + } + """^*^ + let prefix = +""" + { + "key": "value" + } + """ + let postfixIf = """ + { + "key": "value" + } + """ + #if FLAG + .someMethod + #endif + + // Like the infix operator cases, cast operations force the string's open quotes to wrap. + // This could be considered consistent if you look at it through the right lens. Let's make + // sure to test it so that we can see if the behavior ever changes accidentally. + let cast = + """ + { + "key": "value" + } + """ as NSString + let typecheck = + """ + { + "key": "value" + } + """ is NSString + """# + assertPrettyPrintEqual(input: input, expected: input + "\n", linelength: 100) + } }