Files

900 lines
28 KiB
Swift

//
// WrapTests.swift
// SwiftFormatTests
//
// Created by Cal Stephens on 7/28/2024.
// Copyright © 2024 Nick Lockwood. All rights reserved.
//
import XCTest
@testable import SwiftFormat
final class WrapTests: XCTestCase {
func testWrapIfStatement() {
let input = """
if let foo = foo, let bar = bar, let baz = baz {}
"""
let output = """
if let foo = foo,
let bar = bar,
let baz = baz {}
"""
let options = FormatOptions(maxWidth: 20)
testFormatting(for: input, output, rule: .wrap, options: options)
}
func testWrapIfElseStatement() {
let input = """
if let foo = foo {} else if let bar = bar {}
"""
let output = """
if let foo = foo {}
else if let bar =
bar {}
"""
let output2 = """
if let foo = foo {}
else if let bar =
bar {}
"""
let options = FormatOptions(maxWidth: 20)
testFormatting(for: input, [output, output2], rules: [.wrap], options: options)
}
func testWrapGuardStatement() {
let input = """
guard let foo = foo, let bar = bar else {
break
}
"""
let output = """
guard let foo = foo,
let bar = bar
else {
break
}
"""
let output2 = """
guard let foo = foo,
let bar = bar
else {
break
}
"""
let options = FormatOptions(maxWidth: 20)
testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: [.wrapMultilineStatementBraces])
}
func testWrapClosure() {
let input = """
let foo = { () -> Bool in true }
"""
let output = """
let foo =
{ () -> Bool in
true }
"""
let output2 = """
let foo =
{ () -> Bool in
true
}
"""
let options = FormatOptions(maxWidth: 20)
testFormatting(for: input, [output, output2], rules: [.wrap], options: options)
}
func testWrapClosure2() {
let input = """
let foo = { bar, _ in bar }
"""
let output = """
let foo =
{ bar, _ in
bar }
"""
let output2 = """
let foo =
{ bar, _ in
bar
}
"""
let options = FormatOptions(maxWidth: 20)
testFormatting(for: input, [output, output2], rules: [.wrap], options: options)
}
func testWrapClosureWithAllmanBraces() {
let input = """
let foo = { bar, _ in bar }
"""
let output = """
let foo =
{ bar, _ in
bar }
"""
let output2 = """
let foo =
{ bar, _ in
bar
}
"""
let options = FormatOptions(allmanBraces: true, maxWidth: 20)
testFormatting(for: input, [output, output2], rules: [.wrap], options: options)
}
func testWrapClosure3() {
let input = """
let foo = bar { $0.baz }
"""
let output = """
let foo = bar {
$0.baz }
"""
let output2 = """
let foo = bar {
$0.baz
}
"""
let options = FormatOptions(maxWidth: 20)
testFormatting(for: input, [output, output2], rules: [.wrap], options: options)
}
func testWrapFunctionIfReturnTypeExceedsMaxWidth() {
let input = """
func testFunc() -> ReturnType {
doSomething()
doSomething()
}
"""
let output = """
func testFunc()
-> ReturnType {
doSomething()
doSomething()
}
"""
let options = FormatOptions(maxWidth: 25)
testFormatting(for: input, output, rule: .wrap, options: options, exclude: [.wrapMultilineStatementBraces])
}
func testWrapFunctionIfReturnTypeExceedsMaxWidthWithXcodeIndentation() {
let input = """
func testFunc() -> ReturnType {
doSomething()
doSomething()
}
"""
let output = """
func testFunc()
-> ReturnType {
doSomething()
doSomething()
}
"""
let output2 = """
func testFunc()
-> ReturnType {
doSomething()
doSomething()
}
"""
let options = FormatOptions(xcodeIndentation: true, maxWidth: 25)
testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: [.wrapMultilineStatementBraces])
}
func testWrapFunctionIfReturnTypeExceedsMaxWidth2() {
let input = """
func testFunc() -> (ReturnType, ReturnType2) {
doSomething()
}
"""
let output = """
func testFunc()
-> (ReturnType, ReturnType2) {
doSomething()
}
"""
let options = FormatOptions(maxWidth: 35)
testFormatting(for: input, output, rule: .wrap, options: options, exclude: [.wrapMultilineStatementBraces])
}
func testWrapFunctionIfReturnTypeExceedsMaxWidth2WithXcodeIndentation() {
let input = """
func testFunc() throws -> (ReturnType, ReturnType2) {
doSomething()
}
"""
let output = """
func testFunc() throws
-> (ReturnType, ReturnType2) {
doSomething()
}
"""
let output2 = """
func testFunc() throws
-> (ReturnType, ReturnType2) {
doSomething()
}
"""
let options = FormatOptions(xcodeIndentation: true, maxWidth: 35)
testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: [.wrapMultilineStatementBraces])
}
func testWrapFunctionIfReturnTypeExceedsMaxWidth2WithXcodeIndentation2() {
let input = """
func testFunc() throws(Foo) -> (ReturnType, ReturnType2) {
doSomething()
}
"""
let output = """
func testFunc() throws(Foo)
-> (ReturnType, ReturnType2) {
doSomething()
}
"""
let output2 = """
func testFunc() throws(Foo)
-> (ReturnType, ReturnType2) {
doSomething()
}
"""
let options = FormatOptions(xcodeIndentation: true, maxWidth: 35)
testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: [.wrapMultilineStatementBraces])
}
func testWrapFunctionIfReturnTypeExceedsMaxWidth3() {
let input = """
func testFunc() -> (Bool, String) -> String? {
doSomething()
}
"""
let output = """
func testFunc()
-> (Bool, String) -> String? {
doSomething()
}
"""
let options = FormatOptions(maxWidth: 35)
testFormatting(for: input, output, rule: .wrap, options: options, exclude: [.wrapMultilineStatementBraces])
}
func testWrapFunctionIfReturnTypeExceedsMaxWidth3WithXcodeIndentation() {
let input = """
func testFunc() -> (Bool, String) -> String? {
doSomething()
}
"""
let output = """
func testFunc()
-> (Bool, String) -> String? {
doSomething()
}
"""
let output2 = """
func testFunc()
-> (Bool, String) -> String? {
doSomething()
}
"""
let options = FormatOptions(xcodeIndentation: true, maxWidth: 35)
testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: [.wrapMultilineStatementBraces])
}
func testWrapFunctionIfReturnTypeExceedsMaxWidth4() {
let input = """
func testFunc(_: () -> Void) -> (Bool, String) -> String? {
doSomething()
}
"""
let output = """
func testFunc(_: () -> Void)
-> (Bool, String) -> String? {
doSomething()
}
"""
let options = FormatOptions(maxWidth: 35)
testFormatting(for: input, output, rule: .wrap, options: options, exclude: [.wrapMultilineStatementBraces])
}
func testWrapFunctionIfReturnTypeExceedsMaxWidth4WithXcodeIndentation() {
let input = """
func testFunc(_: () -> Void) -> (Bool, String) -> String? {
doSomething()
}
"""
let output = """
func testFunc(_: () -> Void)
-> (Bool, String) -> String? {
doSomething()
}
"""
let output2 = """
func testFunc(_: () -> Void)
-> (Bool, String) -> String? {
doSomething()
}
"""
let options = FormatOptions(xcodeIndentation: true, maxWidth: 35)
testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: [.wrapMultilineStatementBraces])
}
func testWrapChainedFunctionAfterSubscriptCollection() {
let input = """
let foo = bar["baz"].quuz()
"""
let output = """
let foo = bar["baz"]
.quuz()
"""
let options = FormatOptions(maxWidth: 20)
testFormatting(for: input, output, rule: .wrap, options: options)
}
func testWrapChainedFunctionInSubscriptCollection() {
let input = """
let foo = bar[baz.quuz()]
"""
let output = """
let foo =
bar[baz.quuz()]
"""
let options = FormatOptions(maxWidth: 20)
testFormatting(for: input, output, rule: .wrap, options: options)
}
func testWrapThrowingFunctionIfReturnTypeExceedsMaxWidth() {
let input = """
func testFunc(_: () -> Void) throws -> (Bool, String) -> String? {
doSomething()
}
"""
let output = """
func testFunc(_: () -> Void) throws
-> (Bool, String) -> String? {
doSomething()
}
"""
let options = FormatOptions(maxWidth: 42)
testFormatting(for: input, output, rule: .wrap, options: options, exclude: [.wrapMultilineStatementBraces])
}
func testWrapTypedThrowingFunctionIfReturnTypeExceedsMaxWidth() {
let input = """
func testFunc(_: () -> Void) throws(Foo) -> (Bool, String) -> String? {
doSomething()
}
"""
let output = """
func testFunc(_: () -> Void) throws(Foo)
-> (Bool, String) -> String? {
doSomething()
}
"""
let options = FormatOptions(maxWidth: 42)
testFormatting(for: input, output, rule: .wrap, options: options, exclude: [.wrapMultilineStatementBraces])
}
func testNoWrapInterpolatedStringLiteral() {
let input = """
"a very long \\(string) literal"
"""
let options = FormatOptions(maxWidth: 20)
testFormatting(for: input, rule: .wrap, options: options)
}
func testNoWrapAtUnspacedOperator() {
let input = """
let foo = bar+baz+quux
"""
let output = """
let foo =
bar+baz+quux
"""
let options = FormatOptions(maxWidth: 15)
testFormatting(for: input, output, rule: .wrap, options: options,
exclude: [.spaceAroundOperators])
}
func testNoWrapAtUnspacedEquals() {
let input = """
let foo=bar+baz+quux
"""
let options = FormatOptions(maxWidth: 15)
testFormatting(for: input, rule: .wrap, options: options,
exclude: [.spaceAroundOperators])
}
func testNoWrapSingleParameter() {
let input = """
let fooBar = try unkeyedContainer.decode(FooBar.self)
"""
let output = """
let fooBar = try unkeyedContainer
.decode(FooBar.self)
"""
let options = FormatOptions(maxWidth: 50)
testFormatting(for: input, output, rule: .wrap, options: options)
}
func testWrapSingleParameter() {
let input = """
let fooBar = try unkeyedContainer.decode(FooBar.self)
"""
let output = """
let fooBar = try unkeyedContainer.decode(
FooBar.self
)
"""
let options = FormatOptions(maxWidth: 50, noWrapOperators: [".", "="])
testFormatting(for: input, output, rule: .wrap, options: options)
}
func testWrapFunctionArrow() {
let input = """
func foo() -> Int {}
"""
let output = """
func foo()
-> Int {}
"""
let options = FormatOptions(maxWidth: 14)
testFormatting(for: input, output, rule: .wrap, options: options)
}
func testNoWrapFunctionArrow() {
let input = """
func foo() -> Int {}
"""
let output = """
func foo(
) -> Int {}
"""
let options = FormatOptions(maxWidth: 14, noWrapOperators: ["->"])
testFormatting(for: input, output, rule: .wrap, options: options)
}
func testNoCrashWrap() {
let input = """
struct Foo {
func bar(a: Set<B>, c: D) {}
}
"""
let output = """
struct Foo {
func bar(
a: Set<
B
>,
c: D
) {}
}
"""
let options = FormatOptions(maxWidth: 10)
testFormatting(for: input, output, rule: .wrap, options: options,
exclude: [.unusedArguments])
}
func testNoCrashWrap2() {
let input = """
struct Test {
func webView(_: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
authenticationChallengeProcessor.process(challenge: challenge, completionHandler: completionHandler)
}
}
"""
let output = """
struct Test {
func webView(
_: WKWebView,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition,
URLCredential?) -> Void
) {
authenticationChallengeProcessor.process(
challenge: challenge,
completionHandler: completionHandler
)
}
}
"""
let options = FormatOptions(wrapParameters: .preserve, maxWidth: 80)
testFormatting(for: input, output, rule: .wrap, options: options,
exclude: [.indent, .wrapArguments])
}
func testWrapColorLiteral() {
let input = """
button.setTitleColor(#colorLiteral(red: 0.2392156863, green: 0.6470588235, blue: 0.3647058824, alpha: 1), for: .normal)
"""
let options = FormatOptions(maxWidth: 80, assetLiteralWidth: .visualWidth)
testFormatting(for: input, rule: .wrap, options: options)
}
func testWrapImageLiteral() {
let input = """
if let image = #imageLiteral(resourceName: \"abc.png\") {}
"""
let options = FormatOptions(maxWidth: 40, assetLiteralWidth: .visualWidth)
testFormatting(for: input, rule: .wrap, options: options)
}
func testNoWrapBeforeFirstArgumentInSingleLineStringInterpolation() {
let input = """
"a very long string literal with \\(interpolation) inside"
"""
let options = FormatOptions(maxWidth: 40)
testFormatting(for: input, rule: .wrap, options: options)
}
func testWrapBeforeFirstArgumentInMultineStringInterpolation() {
let input = """
\"""
a very long string literal with \\(interpolation) inside
\"""
"""
let output = """
\"""
a very long string literal with \\(
interpolation
) inside
\"""
"""
let options = FormatOptions(maxWidth: 40)
testFormatting(for: input, output, rule: .wrap, options: options)
}
func testPreserveMultiLineStringInterpolationWrapAfterFirst() {
let input = """
\"""
a very long string literal with \\(interpolation) inside
\"""
"""
let options = FormatOptions(wrapArguments: .afterFirst, wrapStringInterpolation: .preserve, maxWidth: 40)
testFormatting(for: input, rule: .wrap, options: options)
}
func testPreserveMultiLineStringInterpolationWrapBeforeFirst() {
let input = """
\"""
a very long string literal with \\(interpolation) inside
\"""
"""
let options = FormatOptions(wrapArguments: .beforeFirst, wrapStringInterpolation: .preserve, maxWidth: 40)
testFormatting(for: input, rule: .wrap, options: options)
}
func testPreserveCustomMultiLineStringInterpolationWrapBeforeFirst() {
let input = #"""
"""
\(raw: isPublic ? "public " : "")lazy var \(raw: name.trimmed.description): \(raw: typeName)<\(raw: genericName),\(returnType)> = {
"""
"""#
let options = FormatOptions(wrapArguments: .beforeFirst, wrapStringInterpolation: .preserve, maxWidth: 40)
testFormatting(for: input, rule: .wrap, options: options)
}
// ternary expressions
func testWrapSimpleTernaryOperator() {
let input = """
let foo = fooCondition ? longValueThatContainsFoo(bar) : longValueThatContainsBar(baaz)
"""
let output = """
let foo = fooCondition
? longValueThatContainsFoo(bar)
: longValueThatContainsBar(baaz)
"""
let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 40)
testFormatting(for: input, [output], rules: [.wrap, .wrapArguments], options: options)
}
func testRewrapsSimpleTernaryOperator() {
let input = """
let foo = fooCondition ? longValueThatContainsFoo(bar) :
longValueThatContainsBar(baaz)
"""
let output = """
let foo = fooCondition
? longValueThatContainsFoo(bar)
: longValueThatContainsBar(baaz)
"""
let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 40)
testFormatting(for: input, [output], rules: [.wrap, .wrapArguments], options: options)
}
func testWrapComplexTernaryOperator() {
let input = """
let foo = fooCondition ? Foo(property: value) : barContainer.getBar(using: barProvider)
"""
let output = """
let foo = fooCondition
? Foo(property: value)
: barContainer.getBar(using: barProvider)
"""
let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60)
testFormatting(for: input, output, rule: .wrap, options: options)
}
func testRewrapsComplexTernaryOperator() {
let input = """
let foo = fooCondition ? Foo(property: value) :
barContainer.getBar(using: barProvider)
"""
let output = """
let foo = fooCondition
? Foo(property: value)
: barContainer.getBar(using: barProvider)
"""
let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60)
testFormatting(for: input, output, rule: .wrap, options: options)
}
func testWrapsSimpleNestedTernaryOperator() {
let input = """
let foo = fooCondition ? (barCondition ? a : b) : (baazCondition ? c : d)
"""
let output = """
let foo = fooCondition
? (barCondition ? a : b)
: (baazCondition ? c : d)
"""
let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60)
testFormatting(for: input, output, rule: .wrap, options: options)
}
func testWrapsDoubleNestedTernaryOperation() {
let input = """
let foo = fooCondition ? barCondition ? longTrueBarResult : longFalseBarResult : baazCondition ? longTrueBaazResult : longFalseBaazResult
"""
let output = """
let foo = fooCondition
? barCondition
? longTrueBarResult
: longFalseBarResult
: baazCondition
? longTrueBaazResult
: longFalseBaazResult
"""
let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60)
testFormatting(for: input, output, rule: .wrap, options: options)
}
func testWrapsTripleNestedTernaryOperation() {
let input = """
let foo = fooCondition ? barCondition ? quuxCondition ? longTrueQuuxResult : longFalseQuuxResult : barCondition2 ? longTrueBarResult : longFalseBarResult : baazCondition ? longTrueBaazResult : longFalseBaazResult
"""
let output = """
let foo = fooCondition
? barCondition
? quuxCondition
? longTrueQuuxResult
: longFalseQuuxResult
: barCondition2
? longTrueBarResult
: longFalseBarResult
: baazCondition
? longTrueBaazResult
: longFalseBaazResult
"""
let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60)
testFormatting(for: input, output, rule: .wrap, options: options)
}
func testNoWrapTernaryWrappedWithinChildExpression() {
let input = """
func foo() {
return _skipString(string) ? .token(
string, Location(source: input, range: startIndex ..< index)
) : nil
}
"""
let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 0)
testFormatting(for: input, rule: .wrap, options: options)
}
func testNoWrapTernaryWrappedWithinChildExpression2() {
let input = """
let types: [PolygonType] = plane.isEqual(to: plane) ? [] : vertices.map {
let t = plane.normal.dot($0.position) - plane.w
let type: PolygonType = (t < -epsilon) ? .back : (t > epsilon) ? .front : .coplanar
polygonType = PolygonType(rawValue: polygonType.rawValue | type.rawValue)!
return type
}
"""
let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 0)
testFormatting(for: input, rule: .wrap, options: options)
}
func testNoWrapTernaryInsideStringLiteral() {
let input = """
"\\(true ? "Some string literal" : "Some other string")"
"""
let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 50)
testFormatting(for: input, rule: .wrap, options: options)
}
func testWrapTernaryInsideMultilineStringLiteral() {
let input = """
let foo = \"""
\\(true ? "Some string literal" : "Some other string")"
\"""
"""
let output = """
let foo = \"""
\\(true
? "Some string literal"
: "Some other string")"
\"""
"""
let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 50)
testFormatting(for: input, output, rule: .wrap, options: options)
}
func testNoCrashWrap3() throws {
let input = """
override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext {
let context = super.invalidationContext(forBoundsChange: newBounds) as! UICollectionViewFlowLayoutInvalidationContext
context.invalidateFlowLayoutDelegateMetrics = newBounds.size != collectionView?.bounds.size
return context
}
"""
let options = FormatOptions(wrapArguments: .afterFirst, maxWidth: 100)
let rules: [FormatRule] = [.wrap, .wrapArguments]
XCTAssertNoThrow(try format(input, rules: rules, options: options))
}
func testErrorNotReportedOnBlankLineAfterWrap() throws {
let input = """
[
abagdiasiudbaisndoanosdasdasdasdasdnaosnooanso(),
bar(),
]
"""
let options = FormatOptions(truncateBlankLines: false, maxWidth: 40)
let changes = try lint(input, rules: [.wrap, .indent], options: options)
XCTAssertEqual(changes, [.init(line: 2, rule: .wrap, filePath: nil, isMove: false)])
}
func testNoReportIndentAsWrap() throws {
let input = """
let package = Package(
name: "ExampleApp",
products: [
.executable(
name: "ExampleApp",
targets: ["ExampleApp"]
),
],
dependencies: [
.package(url: "https://example.com/package.swift", from: "1.0"),
// Plugins:
.package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.54.5"),
]
)
"""
let options = FormatOptions(truncateBlankLines: false, maxWidth: 120)
let changes = try lint(input, rules: [.wrap, .indent], options: options)
XCTAssertEqual(changes, [.init(line: 13, rule: .indent, filePath: nil, isMove: false)])
}
func testKeepTrailingCommentWithLine() {
// https://github.com/nicklockwood/SwiftFormat/issues/2261
let input = """
[
item1, // Comment 1
item2, // Comment 2
item3 // Comment 3
]
"""
let options = FormatOptions(maxWidth: 20)
testFormatting(for: input, rule: .wrap, options: options,
exclude: [.trailingCommas, .wrapSingleLineComments])
}
func testKeepTrailingCommentWithLine2() {
let input = """
[
item1, // Comment 1
item2, // Comment 2
item3 // Comment 3
]
"""
let output = """
[
item1, // Comment
// 1
item2, // Comment
// 2
item3 // Comment
// 3
]
"""
testFormatting(for: input, [output],
rules: [.wrap, .wrapSingleLineComments],
options: FormatOptions(maxWidth: 20),
exclude: [.trailingCommas])
}
func testWrapDoubleColonBreaksBeforeOperator() {
let input = """
NationalAeronauticsAndSpaceAdministration::RocketEngine
"""
let output = """
NationalAeronauticsAndSpaceAdministration
::RocketEngine
"""
let options = FormatOptions(maxWidth: 50)
testFormatting(for: input, output, rule: .wrap, options: options)
}
func testWrapDoubleColonWithSpacesBreaksBeforeOperator() {
let input = """
NationalAeronauticsAndSpaceAdministration :: RocketEngine
"""
let output = """
NationalAeronauticsAndSpaceAdministration
:: RocketEngine
"""
let output2 = """
NationalAeronauticsAndSpaceAdministration
::RocketEngine
"""
let options = FormatOptions(maxWidth: 50)
testFormatting(for: input, [output, output2], rules: [.wrap], options: options)
}
func testNoWrapEmptyFuncParens() {
let input = """
func aVeryLongFunctionNameThatExceedsTheMaxWidthLimit() {
print("hello")
}
"""
let options = FormatOptions(maxWidth: 40)
testFormatting(for: input, rule: .wrap, options: options)
}
func testNoWrapEmptyFuncParensSingleLine() {
let input = """
func aVeryLongFunctionNameThatExceedsTheMaxWidthLimit() {}
"""
let options = FormatOptions(maxWidth: 40)
testFormatting(for: input, rule: .wrap, options: options)
}
func testUnwrapAlreadyWrappedEmptyFuncParens() {
let input = """
func aVeryLongFunctionNameThatExceedsTheMaxWidthLimit(
) {
print("hello")
}
"""
let output = """
func aVeryLongFunctionNameThatExceedsTheMaxWidthLimit() {
print("hello")
}
"""
let options = FormatOptions(maxWidth: 40)
testFormatting(for: input, output, rule: .wrap, options: options)
}
}