mirror of
https://github.com/nicklockwood/SwiftFormat.git
synced 2026-05-17 10:30:35 +00:00
eee75a9eba
Co-authored-by: Cal Stephens <cal@calstephens.tech>
580 lines
16 KiB
Swift
580 lines
16 KiB
Swift
//
|
|
// TrailingClosuresTests.swift
|
|
// SwiftFormatTests
|
|
//
|
|
// Created by Nick Lockwood on 1/17/17.
|
|
// Copyright © 2024 Nick Lockwood. All rights reserved.
|
|
//
|
|
|
|
import XCTest
|
|
@testable import SwiftFormat
|
|
|
|
final class TrailingClosuresTests: XCTestCase {
|
|
func testAnonymousClosureArgumentMadeTrailing() {
|
|
let input = """
|
|
foo(foo: 5, { /* some code */ })
|
|
"""
|
|
let output = """
|
|
foo(foo: 5) { /* some code */ }
|
|
"""
|
|
testFormatting(for: input, output, rule: .trailingClosures)
|
|
}
|
|
|
|
func testNamedClosureArgumentNotMadeTrailing() {
|
|
let input = """
|
|
foo(foo: 5, bar: { /* some code */ })
|
|
"""
|
|
testFormatting(for: input, rule: .trailingClosures)
|
|
}
|
|
|
|
func testClosureArgumentPassedToFunctionInArgumentsNotMadeTrailing() {
|
|
let input = """
|
|
foo(bar { /* some code */ })
|
|
"""
|
|
testFormatting(for: input, rule: .trailingClosures)
|
|
}
|
|
|
|
func testClosureArgumentInFunctionWithOtherClosureArgumentsNotMadeTrailing() {
|
|
let input = """
|
|
foo(foo: { /* some code */ }, { /* some code */ })
|
|
"""
|
|
testFormatting(for: input, rule: .trailingClosures)
|
|
}
|
|
|
|
func testClosureArgumentInIfStatementNotMadeTrailing() {
|
|
let input = """
|
|
if let foo = foo(foo: 5, { /* some code */ }) {}
|
|
"""
|
|
testFormatting(for: input, rule: .trailingClosures)
|
|
}
|
|
|
|
func testClosureArgumentInCompoundIfStatementNotMadeTrailing() {
|
|
let input = """
|
|
if let foo = foo(foo: 5, { /* some code */ }), let bar = bar(bar: 2, { /* some code */ }) {}
|
|
"""
|
|
testFormatting(for: input, rule: .trailingClosures)
|
|
}
|
|
|
|
func testClosureArgumentAfterLinebreakInGuardNotMadeTrailing() {
|
|
let input = """
|
|
guard let foo =
|
|
bar({ /* some code */ })
|
|
else { return }
|
|
"""
|
|
testFormatting(for: input, rule: .trailingClosures,
|
|
exclude: [.wrapConditionalBodies])
|
|
}
|
|
|
|
func testClosureMadeTrailingForNumericTupleMember() {
|
|
let input = """
|
|
foo.1(5, { bar })
|
|
"""
|
|
let output = """
|
|
foo.1(5) { bar }
|
|
"""
|
|
testFormatting(for: input, output, rule: .trailingClosures)
|
|
}
|
|
|
|
func testAnonymousInitClosureArgumentMadeTrailing() {
|
|
let input = """
|
|
Foo.init({ foo = bar })
|
|
"""
|
|
let output = """
|
|
Foo.init { foo = bar }
|
|
"""
|
|
testFormatting(for: input, output, rule: .trailingClosures, exclude: [.redundantInit])
|
|
}
|
|
|
|
func testNamedInitClosureArgumentNotMadeTrailing() {
|
|
let input = """
|
|
Foo.init(bar: { foo = bar })
|
|
"""
|
|
testFormatting(for: input, rule: .trailingClosures, exclude: [.redundantInit])
|
|
}
|
|
|
|
func testNoRemoveParensAroundClosureFollowedByOpeningBrace() {
|
|
let input = """
|
|
foo({ bar }) { baz }
|
|
"""
|
|
testFormatting(for: input, rule: .trailingClosures)
|
|
}
|
|
|
|
func testRemoveParensAroundClosureWithInnerSpacesFollowedByUnwrapOperator() {
|
|
let input = """
|
|
foo( { bar } )?.baz
|
|
"""
|
|
let output = """
|
|
foo { bar }?.baz
|
|
"""
|
|
testFormatting(for: input, output, rule: .trailingClosures)
|
|
}
|
|
|
|
// solitary argument
|
|
|
|
func testParensAroundSolitaryClosureArgumentRemoved() {
|
|
let input = """
|
|
foo({ /* some code */ })
|
|
"""
|
|
let output = """
|
|
foo { /* some code */ }
|
|
"""
|
|
testFormatting(for: input, output, rule: .trailingClosures)
|
|
}
|
|
|
|
func testParensAroundNamedSolitaryClosureArgumentNotRemoved() {
|
|
let input = """
|
|
foo(foo: { /* some code */ })
|
|
"""
|
|
testFormatting(for: input, rule: .trailingClosures)
|
|
}
|
|
|
|
func testParensAroundSolitaryClosureArgumentInExpressionNotRemoved() {
|
|
let input = """
|
|
if let foo = foo({ /* some code */ }) {}
|
|
"""
|
|
testFormatting(for: input, rule: .trailingClosures)
|
|
}
|
|
|
|
func testParensAroundSolitaryClosureArgumentInCompoundExpressionNotRemoved() {
|
|
let input = """
|
|
if let foo = foo({ /* some code */ }), let bar = bar({ /* some code */ }) {}
|
|
"""
|
|
testFormatting(for: input, rule: .trailingClosures)
|
|
}
|
|
|
|
func testParensAroundOptionalTrailingClosureInForLoopNotRemoved() {
|
|
let input = """
|
|
for _ in bar?.map({ $0.baz }) ?? [] {}
|
|
"""
|
|
testFormatting(for: input, rule: .trailingClosures)
|
|
}
|
|
|
|
func testParensAroundTrailingClosureInGuardCaseLetNotRemoved() {
|
|
let input = """
|
|
guard case let .foo(bar) = baz.filter({ $0 == quux }).isEmpty else {}
|
|
"""
|
|
testFormatting(for: input, rule: .trailingClosures,
|
|
exclude: [.wrapConditionalBodies])
|
|
}
|
|
|
|
func testParensAroundTrailingClosureInWhereClauseLetNotRemoved() {
|
|
let input = """
|
|
for _ in bar where baz.filter({ $0 == quux }).isEmpty {}
|
|
"""
|
|
testFormatting(for: input, rule: .trailingClosures)
|
|
}
|
|
|
|
func testParensAroundTrailingClosureInSwitchNotRemoved() {
|
|
let input = """
|
|
switch foo({ $0 == bar }).count {}
|
|
"""
|
|
testFormatting(for: input, rule: .trailingClosures)
|
|
}
|
|
|
|
func testSolitaryClosureMadeTrailingInChain() {
|
|
let input = """
|
|
foo.map({ $0.path }).joined()
|
|
"""
|
|
let output = """
|
|
foo.map { $0.path }.joined()
|
|
"""
|
|
testFormatting(for: input, output, rule: .trailingClosures)
|
|
}
|
|
|
|
func testSpaceNotInsertedAfterClosureBeforeUnwrap() {
|
|
let input = """
|
|
let foo = bar.map({ foo($0) })?.baz
|
|
"""
|
|
let output = """
|
|
let foo = bar.map { foo($0) }?.baz
|
|
"""
|
|
testFormatting(for: input, output, rule: .trailingClosures)
|
|
}
|
|
|
|
func testSpaceNotInsertedAfterClosureBeforeForceUnwrap() {
|
|
let input = """
|
|
let foo = bar.map({ foo($0) })!.baz
|
|
"""
|
|
let output = """
|
|
let foo = bar.map { foo($0) }!.baz
|
|
"""
|
|
testFormatting(for: input, output, rule: .trailingClosures)
|
|
}
|
|
|
|
func testSolitaryClosureMadeTrailingForNumericTupleMember() {
|
|
let input = """
|
|
foo.1({ bar })
|
|
"""
|
|
let output = """
|
|
foo.1 { bar }
|
|
"""
|
|
testFormatting(for: input, output, rule: .trailingClosures)
|
|
}
|
|
|
|
// dispatch methods
|
|
|
|
func testDispatchAsyncClosureArgumentMadeTrailing() {
|
|
let input = """
|
|
queue.async(execute: { /* some code */ })
|
|
"""
|
|
let output = """
|
|
queue.async { /* some code */ }
|
|
"""
|
|
testFormatting(for: input, output, rule: .trailingClosures)
|
|
}
|
|
|
|
func testDispatchAsyncGroupClosureArgumentMadeTrailing() {
|
|
// TODO: async(group: , qos: , flags: , execute: )
|
|
let input = """
|
|
queue.async(group: g, execute: { /* some code */ })
|
|
"""
|
|
let output = """
|
|
queue.async(group: g) { /* some code */ }
|
|
"""
|
|
testFormatting(for: input, output, rule: .trailingClosures)
|
|
}
|
|
|
|
func testDispatchAsyncAfterClosureArgumentMadeTrailing() {
|
|
let input = """
|
|
queue.asyncAfter(deadline: t, execute: { /* some code */ })
|
|
"""
|
|
let output = """
|
|
queue.asyncAfter(deadline: t) { /* some code */ }
|
|
"""
|
|
testFormatting(for: input, output, rule: .trailingClosures)
|
|
}
|
|
|
|
func testDispatchAsyncAfterWallClosureArgumentMadeTrailing() {
|
|
let input = """
|
|
queue.asyncAfter(wallDeadline: t, execute: { /* some code */ })
|
|
"""
|
|
let output = """
|
|
queue.asyncAfter(wallDeadline: t) { /* some code */ }
|
|
"""
|
|
testFormatting(for: input, output, rule: .trailingClosures)
|
|
}
|
|
|
|
func testDispatchSyncClosureArgumentMadeTrailing() {
|
|
let input = """
|
|
queue.sync(execute: { /* some code */ })
|
|
"""
|
|
let output = """
|
|
queue.sync { /* some code */ }
|
|
"""
|
|
testFormatting(for: input, output, rule: .trailingClosures)
|
|
}
|
|
|
|
func testDispatchSyncFlagsClosureArgumentMadeTrailing() {
|
|
let input = """
|
|
queue.sync(flags: f, execute: { /* some code */ })
|
|
"""
|
|
let output = """
|
|
queue.sync(flags: f) { /* some code */ }
|
|
"""
|
|
testFormatting(for: input, output, rule: .trailingClosures)
|
|
}
|
|
|
|
// autoreleasepool
|
|
|
|
func testAutoreleasepoolMadeTrailing() {
|
|
let input = """
|
|
autoreleasepool(invoking: { /* some code */ })
|
|
"""
|
|
let output = """
|
|
autoreleasepool { /* some code */ }
|
|
"""
|
|
testFormatting(for: input, output, rule: .trailingClosures)
|
|
}
|
|
|
|
// explicit trailing closure methods
|
|
|
|
func testCustomMethodMadeTrailing() {
|
|
let input = """
|
|
foo(bar: 1, baz: { /* some code */ })
|
|
"""
|
|
let output = """
|
|
foo(bar: 1) { /* some code */ }
|
|
"""
|
|
let options = FormatOptions(trailingClosures: ["foo"])
|
|
testFormatting(for: input, output, rule: .trailingClosures, options: options)
|
|
}
|
|
|
|
// explicit non-trailing closure methods
|
|
|
|
func testPerformBatchUpdatesNotMadeTrailing() {
|
|
let input = """
|
|
collectionView.performBatchUpdates({ /* some code */ })
|
|
"""
|
|
testFormatting(for: input, rule: .trailingClosures)
|
|
}
|
|
|
|
func testNimbleExpectNotMadeTrailing() {
|
|
let input = """
|
|
expect({ bar }).to(beNil())
|
|
"""
|
|
testFormatting(for: input, rule: .trailingClosures)
|
|
}
|
|
|
|
func testCustomMethodNotMadeTrailing() {
|
|
let input = """
|
|
foo({ /* some code */ })
|
|
"""
|
|
let options = FormatOptions(neverTrailing: ["foo"])
|
|
testFormatting(for: input, rule: .trailingClosures, options: options)
|
|
}
|
|
|
|
func testOptionalClosureCallMadeTrailing() {
|
|
let input = """
|
|
myClosure?(foo: 5, { /* some code */ })
|
|
"""
|
|
let output = """
|
|
myClosure?(foo: 5) { /* some code */ }
|
|
"""
|
|
testFormatting(for: input, output, rule: .trailingClosures)
|
|
}
|
|
|
|
func testOptionalSolitaryClosureCallMadeTrailing() {
|
|
let input = """
|
|
myClosure?({ /* some code */ })
|
|
"""
|
|
let output = """
|
|
myClosure? { /* some code */ }
|
|
"""
|
|
testFormatting(for: input, output, rule: .trailingClosures)
|
|
}
|
|
|
|
func testOptionalClosureInChainMadeTrailing() {
|
|
let input = """
|
|
foo.myClosure?({ $0.path }).joined()
|
|
"""
|
|
let output = """
|
|
foo.myClosure? { $0.path }.joined()
|
|
"""
|
|
testFormatting(for: input, output, rule: .trailingClosures)
|
|
}
|
|
|
|
func testOptionalNamedClosureArgumentNotMadeTrailing() {
|
|
let input = """
|
|
myClosure?(foo: 5, bar: { /* some code */ })
|
|
"""
|
|
testFormatting(for: input, rule: .trailingClosures)
|
|
}
|
|
|
|
func testReturnTupleNotConfusedForFunctionCall() {
|
|
let input = """
|
|
return (expectation, { state in
|
|
XCTAssertEqual(state, expectedStates.removeFirst())
|
|
expectation.fulfill()
|
|
})
|
|
"""
|
|
|
|
testFormatting(for: input, rule: .trailingClosures, exclude: [.redundantParens])
|
|
}
|
|
|
|
func testClosureReturnTupleNotConfusedForFunctionCall() {
|
|
let input = """
|
|
{ _ in
|
|
(expectation, { state in
|
|
XCTAssertEqual(state, expectedStates.removeFirst())
|
|
expectation.fulfill()
|
|
})
|
|
}
|
|
"""
|
|
|
|
testFormatting(for: input, rule: .trailingClosures, exclude: [.redundantParens])
|
|
}
|
|
|
|
// multiple closures
|
|
|
|
func testMultipleTrailingClosuresWithFirstUnlabeled() {
|
|
let input = """
|
|
withAnimation(.linear, {
|
|
// perform animation
|
|
}, completion: {
|
|
// handle completion
|
|
})
|
|
"""
|
|
let output = """
|
|
withAnimation(.linear) {
|
|
// perform animation
|
|
} completion: {
|
|
// handle completion
|
|
}
|
|
"""
|
|
testFormatting(for: input, output, rule: .trailingClosures)
|
|
}
|
|
|
|
func testMultipleTrailingClosuresWithFirstLabeled() {
|
|
let input = """
|
|
withAnimation(.linear, animation: {
|
|
// perform animation
|
|
}, completion: {
|
|
// handle completion
|
|
})
|
|
"""
|
|
testFormatting(for: input, rule: .trailingClosures)
|
|
}
|
|
|
|
func testMultipleTrailingClosuresWithThreeClosures() {
|
|
let input = """
|
|
performTask(param: 1, {
|
|
// first closure
|
|
}, onSuccess: {
|
|
// success handler
|
|
}, onFailure: {
|
|
// failure handler
|
|
})
|
|
"""
|
|
let output = """
|
|
performTask(param: 1) {
|
|
// first closure
|
|
} onSuccess: {
|
|
// success handler
|
|
} onFailure: {
|
|
// failure handler
|
|
}
|
|
"""
|
|
testFormatting(for: input, output, rule: .trailingClosures)
|
|
}
|
|
|
|
func testMultipleTrailingClosuresNotAppliedWhenFirstIsLabeled() {
|
|
let input = """
|
|
someFunction(param: 1, first: {
|
|
// first closure
|
|
}, second: {
|
|
// second closure
|
|
})
|
|
"""
|
|
testFormatting(for: input, rule: .trailingClosures)
|
|
}
|
|
|
|
func testMultipleNestedClosures() {
|
|
let repeatCount = 10
|
|
let input = """
|
|
override func foo() {
|
|
bar {
|
|
var baz = 5
|
|
\(String(repeating: """
|
|
fizz {
|
|
buzz {
|
|
fizzbuzz()
|
|
}
|
|
}
|
|
|
|
""", count: repeatCount)) }
|
|
}
|
|
"""
|
|
testFormatting(for: input, rule: .trailingClosures)
|
|
}
|
|
|
|
func testMultipleTrailingClosuresWithTrailingComma() {
|
|
let input = """
|
|
withAnimationIfNeeded(
|
|
.foo,
|
|
.bar,
|
|
{ didAppear = true },
|
|
completion: { animateText = true },
|
|
)
|
|
|
|
"""
|
|
let output = """
|
|
withAnimationIfNeeded(
|
|
.foo,
|
|
.bar
|
|
) {
|
|
didAppear = true
|
|
} completion: {
|
|
animateText = true
|
|
}
|
|
|
|
"""
|
|
testFormatting(for: input, [output], rules: [.trailingClosures, .indent, .wrapArguments, .wrap])
|
|
}
|
|
|
|
func testMultipleUnlabeledClosuresNotTransformed() {
|
|
let input = """
|
|
let foo = bar(
|
|
{ baz },
|
|
{ quux }
|
|
)
|
|
"""
|
|
testFormatting(for: input, rule: .trailingClosures)
|
|
}
|
|
|
|
func testMultipleClosuresWithNonClosureArgumentInMiddle() {
|
|
let input = """
|
|
withObservationTracking(
|
|
{
|
|
_ = self[keyPath: keyPath]
|
|
},
|
|
token: {
|
|
guard !isCancelled.value else {
|
|
return nil
|
|
}
|
|
|
|
return ""
|
|
},
|
|
willChange: nil,
|
|
didChange: { [weak cancellable] in
|
|
action(self[keyPath: keyPath])
|
|
cancellable?.cancel()
|
|
}
|
|
)
|
|
"""
|
|
|
|
testFormatting(for: input, rule: .trailingClosures)
|
|
}
|
|
|
|
func testMethodWithOnlyClosureArguments() {
|
|
let input = """
|
|
withObservationTracking(
|
|
{
|
|
_ = self[keyPath: keyPath]
|
|
},
|
|
token: {
|
|
guard !isCancelled.value else {
|
|
return nil
|
|
}
|
|
|
|
return ""
|
|
},
|
|
didChange: { [weak cancellable] in
|
|
action(self[keyPath: keyPath])
|
|
cancellable?.cancel()
|
|
}
|
|
)
|
|
|
|
"""
|
|
|
|
let output = """
|
|
withObservationTracking {
|
|
_ = self[keyPath: keyPath]
|
|
} token: {
|
|
guard !isCancelled.value else {
|
|
return nil
|
|
}
|
|
|
|
return ""
|
|
} didChange: { [weak cancellable] in
|
|
action(self[keyPath: keyPath])
|
|
cancellable?.cancel()
|
|
}
|
|
|
|
"""
|
|
|
|
testFormatting(for: input, [output], rules: [.trailingClosures, .indent])
|
|
}
|
|
|
|
// property wrapper
|
|
|
|
func testPropertyWrapperDoesntFormat() {
|
|
let input = """
|
|
class A {
|
|
@BlockEquatable({ $0.isEqualTo($1) })
|
|
var field: [B] = []
|
|
}
|
|
"""
|
|
testFormatting(for: input, rule: .trailingClosures)
|
|
}
|
|
}
|