Add preliminary support for typed throws

This commit is contained in:
Nick Lockwood
2024-03-02 10:20:53 +00:00
parent fdd64321c7
commit 013247c926
8 changed files with 238 additions and 6 deletions
+1 -4
View File
@@ -594,10 +594,7 @@ extension Formatter {
"func", "subscript", "catch":
return false
case "throws", "rethrows":
return next(
.nonSpaceOrLinebreak,
after: prevKeywordIndex
) == .keyword("in")
return next(.keyword, after: prevKeywordIndex) == .keyword("in")
default:
return true
}
+1 -1
View File
@@ -233,7 +233,7 @@ public struct _FormatRules {
}
return false
case "private", "fileprivate", "internal",
"init", "subscript":
"init", "subscript", "throws":
return false
case "await":
return formatter.options.swiftVersion >= "5.5" ||
+17
View File
@@ -550,6 +550,23 @@ class ParsingHelpersTests: XCTestCase {
XCTAssert(formatter.isStartOfClosure(at: 6))
}
func testTypedThrowingClosure() {
let formatter = Formatter(tokenize("""
let foo = { bar throws(Foo) in bar }
"""))
XCTAssert(formatter.isStartOfClosure(at: 6))
}
func testNestedTypedThrowingClosures() {
let formatter = Formatter(tokenize("""
try! str.withCString(encodedAs: UTF8.self) { _ throws(Foo) in
try! str.withCString(encodedAs: UTF8.self) { _ throws(Foo) in }
}
"""))
XCTAssert(formatter.isStartOfClosure(at: 15))
XCTAssert(formatter.isStartOfClosure(at: 42))
}
func testTrailingClosureOnOptionalMethod() {
let formatter = Formatter(tokenize("""
foo.bar? { print("") }
+11
View File
@@ -3710,6 +3710,17 @@ class IndentTests: RulesTests {
testFormatting(for: input, rule: FormatRules.indent, options: options)
}
func testAsyncTypedThrowsNotUnindented() {
let input = """
func multilineFunction(
foo _: String,
bar _: String)
async throws(Foo) -> String {}
"""
let options = FormatOptions(closingParenOnSameLine: true)
testFormatting(for: input, rule: FormatRules.indent, options: options)
}
func testIndentAsyncLet() {
let input = """
func foo() async {
+73
View File
@@ -852,6 +852,19 @@ class RedundancyTests: RulesTests {
testFormatting(for: input, rule: FormatRules.redundantInit, exclude: ["indent"])
}
func testNoRemoveInitInsideIfdef2() {
let input = """
func myFunc() async throws(Foo) -> String {
#if DEBUG
.init("foo")
#else
""
#endif
}
"""
testFormatting(for: input, rule: FormatRules.redundantInit, exclude: ["indent"])
}
// MARK: - redundantLetError
func testCatchLetError() {
@@ -4089,6 +4102,18 @@ class RedundancyTests: RulesTests {
testFormatting(for: input, rule: FormatRules.redundantSelf)
}
func testTypedThrowingNestedClosureInNotMistakenForForLoop() {
let input = """
func f() {
let str = "hello"
try! str.withCString(encodedAs: UTF8.self) { _ throws(Foo) in
try! str.withCString(encodedAs: UTF8.self) { _ throws(Foo) in }
}
}
"""
testFormatting(for: input, rule: FormatRules.redundantSelf)
}
func testRedundantSelfPreservesSelfInClosureWithExplicitStrongCaptureBefore5_3() {
let input = """
class Foo {
@@ -6135,6 +6160,27 @@ class RedundancyTests: RulesTests {
exclude: ["hoistPatternLet"])
}
func testSelfRemovalParsingBug7() {
let input = """
extension Dictionary where Key == String {
func requiredValue<T>(for keyPath: String) throws(Foo) -> T {
return keyPath as! T
}
func optionalValue<T>(for keyPath: String) throws(Foo) -> T? {
guard let anyValue = self[keyPath] else {
return nil
}
guard let value = anyValue as? T else {
return nil
}
return value
}
}
"""
testFormatting(for: input, rule: FormatRules.redundantSelf)
}
func testSelfNotRemovedInCaseIfElse() {
let input = """
class Foo {
@@ -6929,6 +6975,11 @@ class RedundancyTests: RulesTests {
testFormatting(for: input, rule: FormatRules.unusedArguments)
}
func testNoRemoveClosureTypedThrows() {
let input = "let foo = { () throws(Foo) in }"
testFormatting(for: input, rule: FormatRules.unusedArguments)
}
func testNoRemoveClosureGenericReturnTypes() {
let input = "let foo = { () -> Promise<String> in bar }"
testFormatting(for: input, rule: FormatRules.unusedArguments)
@@ -7097,11 +7148,22 @@ class RedundancyTests: RulesTests {
testFormatting(for: input, output, rule: FormatRules.unusedArguments)
}
func testUnusedTypedThrowingClosureArgument() {
let input = "foo = { bar throws(Foo) in \"\" }"
let output = "foo = { _ throws(Foo) in \"\" }"
testFormatting(for: input, output, rule: FormatRules.unusedArguments)
}
func testUsedThrowingClosureArgument() {
let input = "let foo = { bar throws in bar + \"\" }"
testFormatting(for: input, rule: FormatRules.unusedArguments)
}
func testUsedTypedThrowingClosureArgument() {
let input = "let foo = { bar throws(Foo) in bar + \"\" }"
testFormatting(for: input, rule: FormatRules.unusedArguments)
}
func testUnusedTrailingAsyncClosureArgument() {
let input = """
app.get { foo async in
@@ -7483,6 +7545,17 @@ class RedundancyTests: RulesTests {
testFormatting(for: input, rule: FormatRules.unusedArguments)
}
func testTypedTryAwaitArgumentNotMarkedUnused() {
let input = """
func foo(bar: String) async throws(Foo) -> String? {
let bar = try
await parse(bar)
return bar
}
"""
testFormatting(for: input, rule: FormatRules.unusedArguments)
}
func testConditionalIfLetMarkedAsUnused() {
let input = """
func foo(bar: UIViewController) {
+12
View File
@@ -199,6 +199,12 @@ class SpacingTests: RulesTests {
testFormatting(for: input, output, rule: FormatRules.spaceAroundParens, exclude: ["redundantVoidReturnType"])
}
func testAddSpaceBetweenCaptureListAndArguments8() {
let input = "{ [weak self]() throws(Foo) -> Void in }"
let output = "{ [weak self] () throws(Foo) -> Void in }"
testFormatting(for: input, output, rule: FormatRules.spaceAroundParens, exclude: ["redundantVoidReturnType"])
}
func testAddSpaceBetweenEscapingAndParenthesizedClosure() {
let input = "@escaping(() -> Void)"
let output = "@escaping (() -> Void)"
@@ -316,6 +322,12 @@ class SpacingTests: RulesTests {
testFormatting(for: input, rule: FormatRules.spaceAroundParens)
}
func testNoAddSpaceAroundTypedThrowsFunctionType() {
let input = "func foo() throws (Bar) -> Baz {}"
let output = "func foo() throws(Bar) -> Baz {}"
testFormatting(for: input, output, rule: FormatRules.spaceAroundParens)
}
// MARK: - spaceInsideParens
func testSpaceInsideParens() {
+19 -1
View File
@@ -217,6 +217,12 @@ class SyntaxTests: RulesTests {
testFormatting(for: input, output, rule: FormatRules.void)
}
func testFunctionThatReturnsAFunctionThatHasTypedThrows() {
let input = "(Void) -> Void throws(Foo) -> ()"
let output = "(Void) -> () throws(Foo) -> Void"
testFormatting(for: input, output, rule: FormatRules.void)
}
func testChainOfFunctionsIsNotChanged() {
let input = "() -> () -> () -> Void"
testFormatting(for: input, rule: FormatRules.void)
@@ -227,11 +233,21 @@ class SyntaxTests: RulesTests {
testFormatting(for: input, rule: FormatRules.void)
}
func testChainOfFunctionsWithTypedThrowsIsNotChanged() {
let input = "() -> () throws(Foo) -> () throws(Foo) -> Void"
testFormatting(for: input, rule: FormatRules.void)
}
func testVoidThrowsIsNotMangled() {
let input = "(Void) throws -> Void"
testFormatting(for: input, rule: FormatRules.void)
}
func testVoidTypedThrowsIsNotMangled() {
let input = "(Void) throws(Foo) -> Void"
testFormatting(for: input, rule: FormatRules.void)
}
func testEmptyClosureArgsNotMangled() {
let input = "{ () in }"
testFormatting(for: input, rule: FormatRules.void)
@@ -2965,8 +2981,10 @@ class SyntaxTests: RulesTests {
let input = """
func foo<Foo>(_: (Foo) -> Void) {}
func bar<Foo>(_: (Foo) throws -> Void) {}
func baz<Foo>(_: (Foo) throws(Bar) -> Void) {}
func baaz<Foo>(_: (Foo) async -> Void) {}
func quux<Foo>(_: (Foo) async throws -> Void) {}
func qux<Foo>(_: (Foo) async throws -> Void) {}
func quux<Foo>(_: (Foo) async throws(Bar) -> Void) {}
func qaax<Foo>(_: ([Foo]) -> Void) {}
func qaax<Foo>(_: ((Foo, Bar)) -> Void) {}
"""
+104
View File
@@ -873,6 +873,28 @@ class WrappingTests: RulesTests {
testFormatting(for: input, [output, output2], rules: [FormatRules.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: [FormatRules.wrap], options: options, exclude: ["wrapMultilineStatementBraces"])
}
func testWrapFunctionIfReturnTypeExceedsMaxWidth3() {
let input = """
func testFunc() -> (Bool, String) -> String? {
@@ -989,6 +1011,22 @@ class WrappingTests: RulesTests {
testFormatting(for: input, output, rule: FormatRules.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: FormatRules.wrap, options: options, exclude: ["wrapMultilineStatementBraces"])
}
func testNoWrapInterpolatedStringLiteral() {
let input = """
"a very long \\(string) literal"
@@ -1783,6 +1821,28 @@ class WrappingTests: RulesTests {
options: options)
}
func testWrapParametersListBeforeFirstInTypedThrowingClosureType() {
let input = """
var mathFunction: (Int,
Int, String) throws(Foo) -> Int = { _, _, _ in
0
}
"""
let output = """
var mathFunction: (
Int,
Int,
String
) throws(Foo) -> Int = { _, _, _ in
0
}
"""
let options = FormatOptions(wrapParameters: .beforeFirst)
testFormatting(for: input, [output],
rules: [FormatRules.wrapArguments],
options: options)
}
func testWrapParametersListBeforeFirstInRethrowingClosureType() {
let input = """
var mathFunction: (Int,
@@ -2959,6 +3019,21 @@ class WrappingTests: RulesTests {
testFormatting(for: input, rule: FormatRules.wrapArguments, options: options)
}
func testDoesntWrapReturnAndTypedEffectOnSingleLineFunctionDeclaration() {
let input = """
func singleLineFunction() async throws(Foo) -> String {}
"""
let options = FormatOptions(
wrapArguments: .beforeFirst,
closingParenOnSameLine: true,
wrapReturnType: .ifMultiline,
wrapEffects: .ifMultiline
)
testFormatting(for: input, rule: FormatRules.wrapArguments, options: options)
}
func testWrapEffectOnMultilineFunctionDeclaration() {
let input = """
func multilineFunction(
@@ -3630,6 +3705,35 @@ class WrappingTests: RulesTests {
], options: options)
}
func testWrapsMultilineStatementConsistently2_withTypedEffects() {
let input = """
func aFunc(
one _: Int,
two _: Int) async throws(Foo) -> String {
"one"
}
"""
let output = """
func aFunc(
one _: Int,
two _: Int
) async throws(Foo) -> String {
"one"
}
"""
let options = FormatOptions(
wrapArguments: .beforeFirst,
closingParenOnSameLine: false,
wrapEffects: .never
)
testFormatting(for: input, [output], rules: [
FormatRules.wrapMultilineStatementBraces,
FormatRules.wrapArguments,
], options: options)
}
func testWrapsMultilineStatementConsistently3() {
let input = """
func aFunc(