Files
SwiftFormat/Tests/Rules/ConditionalAssignmentTests.swift
T
2025-10-04 08:46:46 +01:00

829 lines
23 KiB
Swift

//
// ConditionalAssignmentTests.swift
// SwiftFormatTests
//
// Created by Cal Stephens on 7/28/2024.
// Copyright © 2024 Nick Lockwood. All rights reserved.
//
import XCTest
@testable import SwiftFormat
final class ConditionalAssignmentTests: XCTestCase {
func testDoesntConvertIfStatementAssignmentSwift5_8() {
let input = """
let foo: Foo
if condition {
foo = Foo("foo")
} else {
foo = Foo("bar")
}
"""
let options = FormatOptions(swiftVersion: "5.8")
testFormatting(for: input, rule: .conditionalAssignment, options: options)
}
func testConvertsIfStatementAssignment() {
let input = """
let foo: Foo
if condition {
foo = Foo("foo")
} else {
foo = Foo("bar")
}
"""
let output = """
let foo: Foo = if condition {
Foo("foo")
} else {
Foo("bar")
}
"""
let options = FormatOptions(swiftVersion: "5.9")
testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.redundantType, .wrapMultilineConditionalAssignment])
}
func testConvertsSimpleSwitchStatementAssignment() {
let input = """
let foo: Foo
switch condition {
case true:
foo = Foo("foo")
case false:
foo = Foo("bar")
}
"""
let output = """
let foo: Foo = switch condition {
case true:
Foo("foo")
case false:
Foo("bar")
}
"""
let options = FormatOptions(swiftVersion: "5.9")
testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.redundantType, .wrapMultilineConditionalAssignment])
}
func testConvertsTrivialSwitchStatementAssignment() {
let input = """
let foo: Foo
switch enumWithOnceCase(let value) {
case singleCase:
foo = value
}
"""
let output = """
let foo: Foo = switch enumWithOnceCase(let value) {
case singleCase:
value
}
"""
let options = FormatOptions(swiftVersion: "5.9")
testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.wrapMultilineConditionalAssignment])
}
func testConvertsNestedIfAndStatementAssignments() {
let input = """
let foo: Foo
switch condition {
case true:
if condition {
foo = Foo("foo")
} else {
foo = Foo("bar")
}
case false:
switch condition {
case true:
foo = Foo("baaz")
case false:
if condition {
foo = Foo("quux")
} else {
foo = Foo("quack")
}
}
}
"""
let output = """
let foo: Foo = switch condition {
case true:
if condition {
Foo("foo")
} else {
Foo("bar")
}
case false:
switch condition {
case true:
Foo("baaz")
case false:
if condition {
Foo("quux")
} else {
Foo("quack")
}
}
}
"""
let options = FormatOptions(swiftVersion: "5.9")
testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.redundantType, .wrapMultilineConditionalAssignment])
}
func testConvertsIfStatementAssignmentPreservingComment() {
let input = """
let foo: Foo
// This is a comment between the property and condition
if condition {
foo = Foo("foo")
} else {
foo = Foo("bar")
}
"""
let output = """
let foo: Foo
// This is a comment between the property and condition
= if condition {
Foo("foo")
} else {
Foo("bar")
}
"""
let options = FormatOptions(swiftVersion: "5.9")
testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.indent, .redundantType, .wrapMultilineConditionalAssignment])
}
func testDoesntConvertsIfStatementAssigningMultipleProperties() {
let input = """
let foo: Foo
let bar: Bar
if condition {
foo = Foo("foo")
bar = Bar("foo")
} else {
foo = Foo("bar")
bar = Bar("bar")
}
"""
let options = FormatOptions(swiftVersion: "5.9")
testFormatting(for: input, rule: .conditionalAssignment, options: options)
}
func testDoesntConvertsIfStatementAssigningDifferentProperties() {
let input = """
var foo: Foo?
var bar: Bar?
if condition {
foo = Foo("foo")
} else {
bar = Bar("bar")
}
"""
let options = FormatOptions(swiftVersion: "5.9")
testFormatting(for: input, rule: .conditionalAssignment, options: options)
}
func testDoesntConvertNonExhaustiveIfStatementAssignment1() {
let input = """
var foo: Foo?
if condition {
foo = Foo("foo")
} else if someOtherCondition {
foo = Foo("bar")
}
"""
let options = FormatOptions(swiftVersion: "5.9")
testFormatting(for: input, rule: .conditionalAssignment, options: options)
}
func testDoesntConvertNonExhaustiveIfStatementAssignment2() {
let input = """
var foo: Foo?
if condition {
if condition {
foo = Foo("foo")
}
} else {
foo = Foo("bar")
}
"""
let options = FormatOptions(swiftVersion: "5.9")
testFormatting(for: input, rule: .conditionalAssignment, options: options)
}
func testDoesntConvertMultiStatementIfStatementAssignment1() {
let input = """
let foo: Foo
if condition {
foo = Foo("foo")
print("Multi-statement")
} else {
foo = Foo("bar")
}
"""
let options = FormatOptions(swiftVersion: "5.9")
testFormatting(for: input, rule: .conditionalAssignment, options: options)
}
func testDoesntConvertMultiStatementIfStatementAssignment2() {
let input = """
let foo: Foo
switch condition {
case true:
foo = Foo("foo")
print("Multi-statement")
case false:
foo = Foo("bar")
}
"""
let options = FormatOptions(swiftVersion: "5.9")
testFormatting(for: input, rule: .conditionalAssignment, options: options)
}
func testDoesntConvertMultiStatementIfStatementAssignment3() {
let input = """
let foo: Foo
if condition {
if condition {
foo = Foo("bar")
} else {
foo = Foo("baaz")
}
print("Multi-statement")
} else {
foo = Foo("bar")
}
"""
let options = FormatOptions(swiftVersion: "5.9")
testFormatting(for: input, rule: .conditionalAssignment, options: options)
}
func testDoesntConvertMultiStatementIfStatementAssignment4() {
let input = """
let foo: Foo
switch condition {
case true:
if condition {
foo = Foo("bar")
} else {
foo = Foo("baaz")
}
print("Multi-statement")
case false:
foo = Foo("bar")
}
"""
let options = FormatOptions(swiftVersion: "5.9")
testFormatting(for: input, rule: .conditionalAssignment, options: options)
}
func testDoesntConvertMultiStatementIfStatementWithStringLiteral() {
let input = """
let text: String
if conditionOne {
text = "Hello World!"
doSomeStuffHere()
} else {
text = "Goodbye!"
}
"""
let options = FormatOptions(swiftVersion: "5.9")
testFormatting(for: input, rule: .conditionalAssignment, options: options)
}
func testDoesntConvertMultiStatementIfStatementWithCollectionLiteral() {
let input = """
let text: [String]
if conditionOne {
text = []
doSomeStuffHere()
} else {
text = ["Goodbye!"]
}
"""
let options = FormatOptions(swiftVersion: "5.9")
testFormatting(for: input, rule: .conditionalAssignment, options: options)
}
func testDoesntConvertMultiStatementIfStatementWithIntLiteral() {
let input = """
let number: Int?
if conditionOne {
number = 5
doSomeStuffHere()
} else {
number = 10
}
"""
let options = FormatOptions(swiftVersion: "5.9")
testFormatting(for: input, rule: .conditionalAssignment, options: options)
}
func testDoesntConvertMultiStatementIfStatementWithNilLiteral() {
let input = """
let number: Int?
if conditionOne {
number = nil
doSomeStuffHere()
} else {
number = 10
}
"""
let options = FormatOptions(swiftVersion: "5.9")
testFormatting(for: input, rule: .conditionalAssignment, options: options)
}
func testDoesntConvertMultiStatementIfStatementWithOtherProperty() {
let input = """
let number: Int?
if conditionOne {
number = someOtherProperty
doSomeStuffHere()
} else {
number = 10
}
"""
let options = FormatOptions(swiftVersion: "5.9")
testFormatting(for: input, rule: .conditionalAssignment, options: options)
}
func testDoesntConvertConditionalCastInSwift5_9() {
// The following code doesn't compile in Swift 5.9 due to this issue:
// https://github.com/apple/swift/issues/68764
//
// let result = if condition {
// foo as? String
// } else {
// "bar"
// }
//
let input = """
let result1: String?
if condition {
result1 = foo as? String
} else {
result1 = "bar"
}
let result2: String?
switch condition {
case true:
result2 = foo as? String
case false:
result2 = "bar"
}
"""
let options = FormatOptions(swiftVersion: "5.9")
testFormatting(for: input, rule: .conditionalAssignment, options: options)
}
func testAllowsAsWithinInnerScope() {
let input = """
let result: String?
switch condition {
case true:
result = method(string: foo as? String)
case false:
result = "bar"
}
"""
let output = """
let result: String? = switch condition {
case true:
method(string: foo as? String)
case false:
"bar"
}
"""
let options = FormatOptions(swiftVersion: "5.9")
testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.wrapMultilineConditionalAssignment])
}
// TODO: update branches parser to handle this case properly
func testIgnoreSwitchWithConditionalCompilation() {
let input = """
func foo() -> String? {
let result: String?
switch condition {
#if os(macOS)
case .foo:
result = method(string: foo as? String)
#endif
case .bar:
return nil
}
return result
}
"""
let options = FormatOptions(ifdefIndent: .noIndent, swiftVersion: "5.9")
testFormatting(for: input, rule: .conditionalAssignment, options: options)
}
// TODO: update branches parser to handle this scenario properly
func testIgnoreSwitchWithConditionalCompilation2() {
let input = """
func foo() -> String? {
let result: String?
switch condition {
case .foo:
result = method(string: foo as? String)
#if os(macOS)
case .bar:
return nil
#endif
}
return result
}
"""
let options = FormatOptions(ifdefIndent: .noIndent, swiftVersion: "5.9")
testFormatting(for: input, rule: .conditionalAssignment, options: options)
}
func testConvertsConditionalCastInSwift5_10() {
let input = """
let result1: String?
if condition {
result1 = foo as? String
} else {
result1 = "bar"
}
let result2: String?
switch condition {
case true:
result2 = foo as? String
case false:
result2 = "bar"
}
"""
let output = """
let result1: String? = if condition {
foo as? String
} else {
"bar"
}
let result2: String? = switch condition {
case true:
foo as? String
case false:
"bar"
}
"""
let options = FormatOptions(swiftVersion: "5.10")
testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.wrapMultilineConditionalAssignment])
}
func testConvertsSwitchWithDefaultCase() {
let input = """
let foo: Foo
switch condition {
case .foo:
foo = Foo("foo")
case .bar:
foo = Foo("bar")
default:
foo = Foo("default")
}
"""
let output = """
let foo: Foo = switch condition {
case .foo:
Foo("foo")
case .bar:
Foo("bar")
default:
Foo("default")
}
"""
let options = FormatOptions(swiftVersion: "5.9")
testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.wrapMultilineConditionalAssignment, .redundantType])
}
func testConvertsSwitchWithUnknownDefaultCase() {
let input = """
let foo: Foo
switch condition {
case .foo:
foo = Foo("foo")
case .bar:
foo = Foo("bar")
@unknown default:
foo = Foo("default")
}
"""
let output = """
let foo: Foo = switch condition {
case .foo:
Foo("foo")
case .bar:
Foo("bar")
@unknown default:
Foo("default")
}
"""
let options = FormatOptions(swiftVersion: "5.9")
testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.wrapMultilineConditionalAssignment, .redundantType])
}
func testPreservesSwitchWithReturnInDefaultCase() {
let input = """
let foo: Foo
switch condition {
case .foo:
foo = Foo("foo")
case .bar:
foo = Foo("bar")
default:
return
}
"""
let options = FormatOptions(swiftVersion: "5.9")
testFormatting(for: input, rule: .conditionalAssignment, options: options)
}
func testPreservesSwitchWithReturnInUnknownDefaultCase() {
let input = """
let foo: Foo
switch condition {
case .foo:
foo = Foo("foo")
case .bar:
foo = Foo("bar")
@unknown default:
return
}
"""
let options = FormatOptions(swiftVersion: "5.9")
testFormatting(for: input, rule: .conditionalAssignment, options: options)
}
func testDoesntConvertIfStatementWithForLoopInBranch() {
let input = """
var foo: Foo?
if condition {
foo = Foo("foo")
for foo in foos {
print(foo)
}
} else {
foo = Foo("bar")
}
"""
let options = FormatOptions(swiftVersion: "5.9")
testFormatting(for: input, rule: .conditionalAssignment, options: options)
}
func testConvertsIfStatementNotFollowingPropertyDefinition() {
let input = """
if condition {
property = Foo("foo")
} else {
property = Foo("bar")
}
"""
let output = """
property =
if condition {
Foo("foo")
} else {
Foo("bar")
}
"""
let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9")
testFormatting(for: input, [output], rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options)
}
func testPreservesIfStatementNotFollowingPropertyDefinitionWithInvalidBranch() {
let input = """
if condition {
property = Foo("foo")
} else {
property = Foo("bar")
print("A second expression on this branch")
}
if condition {
property = Foo("foo")
} else {
if otherCondition {
property = Foo("foo")
}
}
"""
let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9")
testFormatting(for: input, rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options)
}
func testPreservesNonExhaustiveIfStatementNotFollowingPropertyDefinition() {
let input = """
if condition {
property = Foo("foo")
}
if condition {
property = Foo("foo")
} else if otherCondition {
property = Foo("foo")
}
"""
let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9")
testFormatting(for: input, rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options)
}
func testConvertsSwitchStatementNotFollowingPropertyDefinition() {
let input = """
switch condition {
case true:
property = Foo("foo")
case false:
property = Foo("bar")
}
"""
let output = """
property =
switch condition {
case true:
Foo("foo")
case false:
Foo("bar")
}
"""
let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9")
testFormatting(for: input, [output], rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options)
}
func testConvertsSwitchStatementWithComplexLValueNotFollowingPropertyDefinition() {
let input = """
switch condition {
case true:
property?.foo!.bar["baaz"] = Foo("foo")
case false:
property?.foo!.bar["baaz"] = Foo("bar")
}
"""
let output = """
property?.foo!.bar["baaz"] =
switch condition {
case true:
Foo("foo")
case false:
Foo("bar")
}
"""
let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9")
testFormatting(for: input, [output], rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options)
}
func testDoesntMergePropertyWithUnrelatedCondition() {
let input = """
let differentProperty: Foo
switch condition {
case true:
property = Foo("foo")
case false:
property = Foo("bar")
}
"""
let output = """
let differentProperty: Foo
property =
switch condition {
case true:
Foo("foo")
case false:
Foo("bar")
}
"""
let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9")
testFormatting(for: input, [output], rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options)
}
func testConvertsNestedIfSwitchStatementNotFollowingPropertyDefinition() {
let input = """
switch firstCondition {
case true:
if secondCondition {
property = Foo("foo")
} else {
property = Foo("bar")
}
case false:
if thirdCondition {
property = Foo("baaz")
} else {
property = Foo("quux")
}
}
"""
let output = """
property =
switch firstCondition {
case true:
if secondCondition {
Foo("foo")
} else {
Foo("bar")
}
case false:
if thirdCondition {
Foo("baaz")
} else {
Foo("quux")
}
}
"""
let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9")
testFormatting(for: input, [output], rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options)
}
func testPreservesSwitchConditionWithIneligibleBranch() {
let input = """
switch firstCondition {
case true:
// Even though this condition is eligible to be converted,
// we leave it as-is because it's nested in an ineligible condition.
if secondCondition {
property = Foo("foo")
} else {
property = Foo("bar")
}
case false:
if thirdCondition {
property = Foo("baaz")
} else {
property = Foo("quux")
print("A second expression on this branch")
}
}
"""
let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9")
testFormatting(for: input, rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options)
}
func testPreservesIfConditionWithIneligibleBranch() {
let input = """
if firstCondition {
// Even though this condition is eligible to be converted,
// we leave it as-is because it's nested in an ineligible condition.
if secondCondition {
property = Foo("foo")
} else {
property = Foo("bar")
}
} else {
if thirdCondition {
property = Foo("baaz")
} else {
property = Foo("quux")
print("A second expression on this branch")
}
}
"""
let options = FormatOptions(swiftVersion: "5.9")
testFormatting(for: input, rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options)
}
}