mirror of
https://github.com/nicklockwood/SwiftFormat.git
synced 2026-05-17 10:30:35 +00:00
Fix issue where redundantAsync ignored await keyword in string interpolation (#2225)
This commit is contained in:
@@ -570,11 +570,6 @@ extension Formatter {
|
||||
return false
|
||||
}
|
||||
|
||||
// If the code is in a string, then it could be inside a string interpolation
|
||||
if tokens[startOfScopeIndex] == .startOfScope("\"") || tokens[startOfScopeIndex] == .startOfScope("\"\"\"") {
|
||||
return false
|
||||
}
|
||||
|
||||
// If this is a function scope, but not the body of the function itself,
|
||||
// then this is some nested function.
|
||||
if lastSignificantKeyword(at: startOfScopeIndex, excluding: ["where"]) == "func",
|
||||
@@ -587,6 +582,28 @@ extension Formatter {
|
||||
return isInFunctionBody(of: functionDecl, at: startOfScopeIndex)
|
||||
}
|
||||
|
||||
/// Whether or not the given index is within a string body or string interpolation
|
||||
func isInStringInterpolation(at index: Int) -> Bool {
|
||||
guard let startOfScopeIndex = startOfScope(at: index) else {
|
||||
return false
|
||||
}
|
||||
|
||||
// If the code is in a string, then it could be inside a string interpolation
|
||||
if tokens[startOfScopeIndex] == .startOfScope("\"") || tokens[startOfScopeIndex] == .startOfScope("\"\"\"") {
|
||||
return true
|
||||
}
|
||||
|
||||
return isInStringInterpolation(at: startOfScopeIndex)
|
||||
}
|
||||
|
||||
/// Whether or not the `try` keyword is supported at the given index
|
||||
/// within the given function declaration, if it were throwing.
|
||||
func tryKeywordSupported(at index: Int, in functionDecl: FunctionDeclaration) -> Bool {
|
||||
isInFunctionBody(of: functionDecl, at: index)
|
||||
// String interpolation is a non-throwing autoclosure, so can't use `try`
|
||||
&& !isInStringInterpolation(at: index)
|
||||
}
|
||||
|
||||
/// Whether or not this index the start of scope of a closure literal, eg `{` but not some other type of scope.
|
||||
func isStartOfClosure(at i: Int) -> Bool {
|
||||
guard token(at: i) == .startOfScope("{") else {
|
||||
|
||||
@@ -37,7 +37,7 @@ public extension FormatRule {
|
||||
|
||||
// Only remove the `!` if we are not within a closure or nested function,
|
||||
// where it's not safe to just remove the `!` and make our function throw.
|
||||
guard formatter.isInFunctionBody(of: functionDecl, at: index) else { continue }
|
||||
guard formatter.tryKeywordSupported(at: index, in: functionDecl) else { continue }
|
||||
|
||||
formatter.removeToken(at: nextTokenIndex)
|
||||
foundAnyTryExclamationMarks = true
|
||||
|
||||
@@ -53,7 +53,7 @@ public extension FormatRule {
|
||||
}
|
||||
|
||||
// Only convert the `!` if we are within the function body
|
||||
guard formatter.isInFunctionBody(of: functionDecl, at: forceUnwrapOperator.index) else {
|
||||
guard formatter.tryKeywordSupported(at: forceUnwrapOperator.index, in: functionDecl) else {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ public extension FormatRule {
|
||||
// Convert all eligible ! operators in this expression to ? operators
|
||||
convertForceUnwrapsInExpression: for i in expressionRange.range.reversed() {
|
||||
guard formatter.tokens[i] == .operator("!", .postfix),
|
||||
formatter.isInFunctionBody(of: functionDecl, at: i)
|
||||
formatter.tryKeywordSupported(at: i, in: functionDecl)
|
||||
else { continue }
|
||||
|
||||
// Check if this force unwrap is in a function call or subscript call subexpression within this expression.
|
||||
@@ -324,7 +324,7 @@ extension Formatter {
|
||||
let firstInfixOperator = expressionRange.range.first(where: { i in
|
||||
if treatAsInfixOperator(tokens[i], i),
|
||||
let functionDecl,
|
||||
isInFunctionBody(of: functionDecl, at: i),
|
||||
tryKeywordSupported(at: i, in: functionDecl),
|
||||
tokens[i] != .operator(".", .infix)
|
||||
{
|
||||
return true
|
||||
@@ -340,7 +340,7 @@ extension Formatter {
|
||||
let lhsFormatterOffset = expressionRange.lowerBound
|
||||
|
||||
guard let lhsForceUnwrapIndex = expressionRange.range.first(where: { i in
|
||||
tokens[i] == .operator("!", .postfix) && isInFunctionBody(of: functionDecl, at: i) && i < infixIndex
|
||||
tokens[i] == .operator("!", .postfix) && tryKeywordSupported(at: i, in: functionDecl) && i < infixIndex
|
||||
}) else { return nil }
|
||||
|
||||
// Convert the absolute index to the sub-formatter's relative index
|
||||
|
||||
@@ -334,6 +334,7 @@ final class NoForceUnwrapInTestsTests: XCTestCase {
|
||||
func test_closure() {
|
||||
// Can't be try since string interpolation is a non-throwing autoclosure
|
||||
print("foo \\(bar!)")
|
||||
print("foo \\(foo!.bar!.baaz == quux)")
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -277,4 +277,21 @@ class RedundantAsyncTests: XCTestCase {
|
||||
let options = FormatOptions(redundantAsync: .always)
|
||||
testFormatting(for: input, output, rule: .redundantAsync, options: options)
|
||||
}
|
||||
|
||||
func testIssue2217_asyncNotRemovedForAwaitInStringInterpolation() {
|
||||
let input = """
|
||||
var x: String {
|
||||
get async {
|
||||
"y"
|
||||
}
|
||||
}
|
||||
|
||||
func y() async {
|
||||
"\\(await x)"
|
||||
}
|
||||
"""
|
||||
|
||||
let options = FormatOptions(redundantAsync: .always)
|
||||
testFormatting(for: input, rule: .redundantAsync, options: options)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user