Skip to content

Fix indentation of multiline strings when part of a larger expression. #532

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
Expand Down
68 changes: 68 additions & 0 deletions Tests/SwiftFormatPrettyPrintTests/StringTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}