mirror of
https://github.com/nicklockwood/SwiftFormat.git
synced 2026-06-16 10:34:34 +00:00
Update redundantSelf rule to use implicit self in eligible closures (#1303)
This commit is contained in:
committed by
Nick Lockwood
parent
85bedab454
commit
63abf0813e
@@ -81,7 +81,8 @@ extension Formatter {
|
||||
|
||||
// gather declared variable names, starting at index after let/var keyword
|
||||
func processDeclaredVariables(at index: inout Int, names: inout Set<String>,
|
||||
removeSelf: Bool, onlyLocal: Bool)
|
||||
removeSelf: Bool, onlyLocal: Bool,
|
||||
scopeAllowsImplicitSelfRebinding: Bool)
|
||||
{
|
||||
let isConditional = isConditionalStatement(at: index)
|
||||
var declarationIndex: Int? = -1
|
||||
@@ -107,7 +108,32 @@ extension Formatter {
|
||||
break
|
||||
}
|
||||
let name = token.unescaped()
|
||||
if name != "_", declarationIndex != nil || !isConditional {
|
||||
|
||||
// Whether or not this property is a `let self` definition
|
||||
// that rebinds implicit self for the remainder of scope.
|
||||
// This is only permitted in `weak self` closures when
|
||||
// unwrapping self like `let self = self`.
|
||||
var isPermittedImplicitSelfRebinding = false
|
||||
if name == "self",
|
||||
scopeAllowsImplicitSelfRebinding,
|
||||
let equalsIndex = self.index(of: .nonSpaceOrCommentOrLinebreak, after: index)
|
||||
{
|
||||
// If we find the end of the condition instead of an = token,
|
||||
// then this was a shorthand `if let self` condition.
|
||||
if tokens[equalsIndex] == .startOfScope("{") || tokens[equalsIndex] == .delimiter(",") || tokens[equalsIndex] == .keyword("else")
|
||||
{
|
||||
isPermittedImplicitSelfRebinding = true
|
||||
} else if tokens[equalsIndex] == Token.operator("=", .infix),
|
||||
let rhsSelfIndex = self.index(of: .nonSpaceOrCommentOrLinebreak, after: equalsIndex),
|
||||
tokens[rhsSelfIndex] == .identifier("self"),
|
||||
let nextToken = next(.nonSpaceOrCommentOrLinebreak, after: rhsSelfIndex),
|
||||
nextToken == .startOfScope("{") || nextToken == .delimiter(",") || nextToken == .keyword("else")
|
||||
{
|
||||
isPermittedImplicitSelfRebinding = true
|
||||
}
|
||||
}
|
||||
|
||||
if name != "_", declarationIndex != nil || !isConditional, !isPermittedImplicitSelfRebinding {
|
||||
locals.insert(name)
|
||||
}
|
||||
inner: while let nextIndex = self.index(of: .nonSpace, after: index) {
|
||||
|
||||
@@ -421,7 +421,7 @@ extension Formatter {
|
||||
/// Gather declared variable names, starting at index after let/var keyword
|
||||
func processDeclaredVariables(at index: inout Int, names: inout Set<String>) {
|
||||
processDeclaredVariables(at: &index, names: &names, removeSelf: false,
|
||||
onlyLocal: false)
|
||||
onlyLocal: false, scopeAllowsImplicitSelfRebinding: false)
|
||||
}
|
||||
|
||||
/// Returns true if token is inside the return type of a function or subscript
|
||||
|
||||
+168
-26
@@ -3099,7 +3099,8 @@ public struct _FormatRules {
|
||||
func processBody(at index: inout Int,
|
||||
localNames: Set<String>,
|
||||
members: Set<String>,
|
||||
typeStack: inout [String],
|
||||
typeStack: inout [(name: String, keyword: String)],
|
||||
closureStack: inout [(allowsImplicitSelf: Bool, selfCapture: String?)],
|
||||
membersByType: inout [String: Set<String>],
|
||||
classMembersByType: inout [String: Set<String>],
|
||||
usingDynamicLookup: Bool,
|
||||
@@ -3138,10 +3139,17 @@ public struct _FormatRules {
|
||||
}
|
||||
}
|
||||
}
|
||||
let inClosureDisallowingImplicitSelf = closureStack.last?.allowsImplicitSelf == false
|
||||
/// Starting in Swift 5.8, self can be rebound using a `let self = self` unwrap condition
|
||||
/// within a weak self closure. This is the only place where defining a property
|
||||
/// named self affects the behavior of implicit self.
|
||||
let scopeAllowsImplicitSelfRebinding = formatter.options.swiftVersion >= "5.8"
|
||||
&& closureStack.last?.selfCapture == "weak self"
|
||||
|
||||
// Gather members & local variables
|
||||
let type = (isTypeRoot && typeStack.count == 1) ? typeStack.first : nil
|
||||
var members = type.flatMap { membersByType[$0] } ?? members
|
||||
var classMembers = type.flatMap { classMembersByType[$0] } ?? Set<String>()
|
||||
var members = (type?.name).flatMap { membersByType[$0] } ?? members
|
||||
var classMembers = (type?.name).flatMap { classMembersByType[$0] } ?? Set<String>()
|
||||
var localNames = localNames
|
||||
if !isTypeRoot || explicitSelf != .remove {
|
||||
var i = index
|
||||
@@ -3196,11 +3204,13 @@ public struct _FormatRules {
|
||||
formatter.processDeclaredVariables(at: &i, names: &members)
|
||||
}
|
||||
} else {
|
||||
let removeSelf = explicitSelf != .insert && !usingDynamicLookup
|
||||
let removeSelf = explicitSelf != .insert && !usingDynamicLookup && !inClosureDisallowingImplicitSelf
|
||||
let onlyLocal = formatter.options.swiftVersion < "5"
|
||||
|
||||
formatter.processDeclaredVariables(at: &i, names: &localNames,
|
||||
removeSelf: removeSelf,
|
||||
onlyLocal: onlyLocal)
|
||||
onlyLocal: onlyLocal,
|
||||
scopeAllowsImplicitSelfRebinding: scopeAllowsImplicitSelfRebinding)
|
||||
}
|
||||
case .keyword("func"):
|
||||
guard let nameToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) else {
|
||||
@@ -3240,8 +3250,8 @@ public struct _FormatRules {
|
||||
}
|
||||
}
|
||||
if let type = type {
|
||||
membersByType[type] = members
|
||||
classMembersByType[type] = classMembers
|
||||
membersByType[type.name] = members
|
||||
classMembersByType[type.name] = classMembers
|
||||
}
|
||||
// Remove or add `self`
|
||||
var lastKeyword = ""
|
||||
@@ -3251,6 +3261,7 @@ public struct _FormatRules {
|
||||
token: Token.space(""),
|
||||
dynamicMemberTypes: Set<String>()
|
||||
)]
|
||||
|
||||
while let token = formatter.token(at: index) {
|
||||
switch token {
|
||||
case .keyword("is"), .keyword("as"), .keyword("try"), .keyword("await"):
|
||||
@@ -3260,13 +3271,13 @@ public struct _FormatRules {
|
||||
lastKeyword = ""
|
||||
if classOrStatic {
|
||||
processFunction(at: &index, localNames: localNames, members: classMembers,
|
||||
typeStack: &typeStack, membersByType: &membersByType,
|
||||
typeStack: &typeStack, closureStack: &closureStack, membersByType: &membersByType,
|
||||
classMembersByType: &classMembersByType,
|
||||
usingDynamicLookup: usingDynamicLookup)
|
||||
classOrStatic = false
|
||||
} else {
|
||||
processFunction(at: &index, localNames: localNames, members: members,
|
||||
typeStack: &typeStack, membersByType: &membersByType,
|
||||
typeStack: &typeStack, closureStack: &closureStack, membersByType: &membersByType,
|
||||
classMembersByType: &classMembersByType,
|
||||
usingDynamicLookup: usingDynamicLookup)
|
||||
}
|
||||
@@ -3293,6 +3304,7 @@ public struct _FormatRules {
|
||||
}
|
||||
case .keyword("extension"), .keyword("struct"), .keyword("enum"), .keyword("class"), .keyword("actor"),
|
||||
.keyword("where") where ["extension", "struct", "enum", "class", "actor"].contains(lastKeyword):
|
||||
let keyword = formatter.tokens[index].string
|
||||
guard formatter.last(.nonSpaceOrCommentOrLinebreak, before: index) != .keyword("import"),
|
||||
let scopeStart = formatter.index(of: .startOfScope("{"), after: index)
|
||||
else {
|
||||
@@ -3315,9 +3327,9 @@ public struct _FormatRules {
|
||||
usingDynamicLookup = true
|
||||
}
|
||||
index = scopeStart + 1
|
||||
typeStack.append(name)
|
||||
typeStack.append((name: name, keyword: keyword))
|
||||
processBody(at: &index, localNames: ["init"], members: [], typeStack: &typeStack,
|
||||
membersByType: &membersByType, classMembersByType: &classMembersByType,
|
||||
closureStack: &closureStack, membersByType: &membersByType, classMembersByType: &classMembersByType,
|
||||
usingDynamicLookup: usingDynamicLookup, isTypeRoot: true, isInit: false)
|
||||
typeStack.removeLast()
|
||||
case .keyword("var"), .keyword("let"):
|
||||
@@ -3344,8 +3356,9 @@ public struct _FormatRules {
|
||||
var scopedNames = localNames
|
||||
formatter.processDeclaredVariables(
|
||||
at: &index, names: &scopedNames,
|
||||
removeSelf: explicitSelf != .insert,
|
||||
onlyLocal: false
|
||||
removeSelf: explicitSelf != .insert && !inClosureDisallowingImplicitSelf,
|
||||
onlyLocal: false,
|
||||
scopeAllowsImplicitSelfRebinding: scopeAllowsImplicitSelfRebinding
|
||||
)
|
||||
while let scope = formatter.currentScope(at: index) ?? formatter.token(at: index),
|
||||
[.startOfScope("["), .startOfScope("(")].contains(scope),
|
||||
@@ -3373,7 +3386,7 @@ public struct _FormatRules {
|
||||
}
|
||||
index = startIndex + 1
|
||||
processBody(at: &index, localNames: scopedNames, members: members, typeStack: &typeStack,
|
||||
membersByType: &membersByType, classMembersByType: &classMembersByType,
|
||||
closureStack: &closureStack, membersByType: &membersByType, classMembersByType: &classMembersByType,
|
||||
usingDynamicLookup: usingDynamicLookup, isTypeRoot: false, isInit: isInit)
|
||||
lastKeyword = ""
|
||||
case "case" where ["if", "while", "guard", "for"].contains(lastKeyword):
|
||||
@@ -3398,7 +3411,7 @@ public struct _FormatRules {
|
||||
}
|
||||
index += 1
|
||||
processBody(at: &index, localNames: localNames, members: members, typeStack: &typeStack,
|
||||
membersByType: &membersByType, classMembersByType: &classMembersByType,
|
||||
closureStack: &closureStack, membersByType: &membersByType, classMembersByType: &classMembersByType,
|
||||
usingDynamicLookup: usingDynamicLookup, isTypeRoot: false, isInit: isInit)
|
||||
continue
|
||||
case .keyword("while") where lastKeyword == "repeat":
|
||||
@@ -3425,7 +3438,7 @@ public struct _FormatRules {
|
||||
localNames.insert("error") // Implicit error argument
|
||||
index += 1
|
||||
processBody(at: &index, localNames: localNames, members: members, typeStack: &typeStack,
|
||||
membersByType: &membersByType, classMembersByType: &classMembersByType,
|
||||
closureStack: &closureStack, membersByType: &membersByType, classMembersByType: &classMembersByType,
|
||||
usingDynamicLookup: usingDynamicLookup, isTypeRoot: false, isInit: isInit)
|
||||
continue
|
||||
case .startOfScope("{") where isWhereClause && scopeStack.count == 1:
|
||||
@@ -3438,7 +3451,7 @@ public struct _FormatRules {
|
||||
switch token {
|
||||
case .endOfScope("case"), .endOfScope("default"):
|
||||
let localNames = localNames
|
||||
processBody(at: &index, localNames: localNames, members: members, typeStack: &typeStack,
|
||||
processBody(at: &index, localNames: localNames, members: members, typeStack: &typeStack, closureStack: &closureStack,
|
||||
membersByType: &membersByType, classMembersByType: &classMembersByType,
|
||||
usingDynamicLookup: usingDynamicLookup, isTypeRoot: false, isInit: isInit)
|
||||
index -= 1
|
||||
@@ -3458,7 +3471,7 @@ public struct _FormatRules {
|
||||
case .startOfScope("{") where lastKeyword == "repeat":
|
||||
index += 1
|
||||
processBody(at: &index, localNames: localNames, members: members, typeStack: &typeStack,
|
||||
membersByType: &membersByType, classMembersByType: &classMembersByType,
|
||||
closureStack: &closureStack, membersByType: &membersByType, classMembersByType: &classMembersByType,
|
||||
usingDynamicLookup: usingDynamicLookup, isTypeRoot: false, isInit: isInit)
|
||||
continue
|
||||
case .startOfScope("{") where lastKeyword == "var":
|
||||
@@ -3484,11 +3497,133 @@ public struct _FormatRules {
|
||||
if let name = name {
|
||||
processAccessors(["get", "set", "willSet", "didSet"], for: name,
|
||||
at: &index, localNames: localNames, members: members,
|
||||
typeStack: &typeStack, membersByType: &membersByType,
|
||||
typeStack: &typeStack, closureStack: &closureStack, membersByType: &membersByType,
|
||||
classMembersByType: &classMembersByType,
|
||||
usingDynamicLookup: usingDynamicLookup)
|
||||
}
|
||||
continue
|
||||
case .startOfScope("{") where formatter.isStartOfClosure(at: index):
|
||||
// Parse the capture list and arguments list,
|
||||
// and record the type of `self` capture used in the closure
|
||||
var captureList: [Token]?
|
||||
var parameterList: [Token]?
|
||||
|
||||
// Handle a capture list followed by an optional parameter list:
|
||||
// `{ [self, foo] bar in` or `{ [self, foo] in` etc.
|
||||
if let captureListStartIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: index),
|
||||
formatter.tokens[captureListStartIndex] == .startOfScope("["),
|
||||
let captureListEndIndex = formatter.endOfScope(at: captureListStartIndex),
|
||||
let inIndex = formatter.index(of: .keyword("in"), after: captureListEndIndex)
|
||||
{
|
||||
captureList = Array(formatter.tokens[(captureListStartIndex + 1) ..< captureListEndIndex])
|
||||
parameterList = Array(formatter.tokens[(captureListEndIndex + 1) ..< inIndex])
|
||||
}
|
||||
|
||||
// Handle a parameter list if present without a capture list
|
||||
// e.g. `{ foo, bar in`
|
||||
else if let firstTokenInClosure = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: index),
|
||||
formatter.isInClosureArguments(at: firstTokenInClosure),
|
||||
let inIndex = formatter.index(of: .keyword("in"), after: index)
|
||||
{
|
||||
parameterList = Array(formatter.tokens[firstTokenInClosure ..< inIndex])
|
||||
}
|
||||
|
||||
var captureListEntires = (captureList ?? []).split(separator: .delimiter(","), omittingEmptySubsequences: true)
|
||||
let parameterListEntries = (parameterList ?? []).split(separator: .delimiter(","), omittingEmptySubsequences: true)
|
||||
|
||||
let supportedSelfCaptures = Set([
|
||||
"self",
|
||||
"unowned self",
|
||||
"unowned(safe) self",
|
||||
"unowned(unsafe) self",
|
||||
"weak self",
|
||||
])
|
||||
|
||||
let captureEntryStrings = captureListEntires.map { captureListEntry in
|
||||
captureListEntry
|
||||
.map { $0.string }
|
||||
.joined()
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
}
|
||||
|
||||
let selfCapture = captureEntryStrings.first(where: {
|
||||
supportedSelfCaptures.contains($0)
|
||||
})
|
||||
|
||||
captureListEntires.removeAll(where: { captureListEntry in
|
||||
let text = captureListEntry
|
||||
.map { $0.string }
|
||||
.joined()
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
|
||||
return text == selfCapture
|
||||
})
|
||||
|
||||
let localDefiningDeclarations = captureListEntires + parameterListEntries
|
||||
var closureLocalNames = localNames
|
||||
|
||||
for tokens in localDefiningDeclarations {
|
||||
guard let localIdentifier = tokens.first(where: { $0.isIdentifier }) else {
|
||||
continue
|
||||
}
|
||||
|
||||
closureLocalNames.insert(localIdentifier.string)
|
||||
}
|
||||
|
||||
/// Whether or not the closure at the current index permits implicit self.
|
||||
///
|
||||
/// SE-0269 (in Swift 5.3) allows implicit self when:
|
||||
/// - the closure captures self explicitly using [self] or [unowned self]
|
||||
/// - self is not a reference type
|
||||
///
|
||||
/// SE-0365 (in Swift 5.8) additionally allows implicit self using
|
||||
/// [weak self] captures after self has been unwrapped.
|
||||
func closureAllowsImplicitSelf() -> Bool {
|
||||
guard formatter.options.swiftVersion >= "5.3" else {
|
||||
return false
|
||||
}
|
||||
|
||||
// If self is a reference type, capturing it won't create a retain cycle,
|
||||
// so the compiler lets us use implicit self
|
||||
if let enclosingTypeKeyword = typeStack.last?.keyword,
|
||||
enclosingTypeKeyword == "struct" || enclosingTypeKeyword == "enum"
|
||||
{
|
||||
return true
|
||||
}
|
||||
|
||||
guard let selfCapture = selfCapture else {
|
||||
return false
|
||||
}
|
||||
|
||||
// If self is captured strongly, or using `unowned`, then the compiler
|
||||
// lets us use implicit self since it's already clear that this closure
|
||||
// captures self strongly
|
||||
if selfCapture == "self"
|
||||
|| selfCapture == "unowned self"
|
||||
|| selfCapture == "unowned(safe) self"
|
||||
|| selfCapture == "unowned(unsafe) self"
|
||||
{
|
||||
return true
|
||||
}
|
||||
|
||||
// This is also supported for `weak self` captures, but only
|
||||
// in Swift 5.8 or later
|
||||
if selfCapture == "weak self",
|
||||
formatter.options.swiftVersion >= "5.8"
|
||||
{
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
closureStack.append((allowsImplicitSelf: closureAllowsImplicitSelf(), selfCapture: selfCapture))
|
||||
index += 1
|
||||
processBody(at: &index, localNames: closureLocalNames, members: members, typeStack: &typeStack, closureStack: &closureStack,
|
||||
membersByType: &membersByType, classMembersByType: &classMembersByType,
|
||||
usingDynamicLookup: usingDynamicLookup, isTypeRoot: false, isInit: isInit)
|
||||
index -= 1
|
||||
closureStack.removeLast()
|
||||
case .startOfScope:
|
||||
index = formatter.endOfScope(at: index) ?? (formatter.tokens.count - 1)
|
||||
case .identifier("self"):
|
||||
@@ -3511,6 +3646,9 @@ public struct _FormatRules {
|
||||
break
|
||||
}
|
||||
}
|
||||
if let closure = closureStack.last, !closure.allowsImplicitSelf {
|
||||
break
|
||||
}
|
||||
_ = formatter.removeSelf(at: index, exclude: localNames)
|
||||
case .identifier("type"): // Special case for type(of:)
|
||||
guard let parenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: index, if: {
|
||||
@@ -3609,7 +3747,8 @@ public struct _FormatRules {
|
||||
}
|
||||
func processAccessors(_ names: [String], for name: String, at index: inout Int,
|
||||
localNames: Set<String>, members: Set<String>,
|
||||
typeStack: inout [String],
|
||||
typeStack: inout [(name: String, keyword: String)],
|
||||
closureStack: inout [(allowsImplicitSelf: Bool, selfCapture: String?)],
|
||||
membersByType: inout [String: Set<String>],
|
||||
classMembersByType: inout [String: Set<String>],
|
||||
usingDynamicLookup: Bool)
|
||||
@@ -3642,7 +3781,7 @@ public struct _FormatRules {
|
||||
}
|
||||
}
|
||||
processBody(at: &index, localNames: localNames, members: members, typeStack: &typeStack,
|
||||
membersByType: &membersByType, classMembersByType: &classMembersByType,
|
||||
closureStack: &closureStack, membersByType: &membersByType, classMembersByType: &classMembersByType,
|
||||
usingDynamicLookup: usingDynamicLookup, isTypeRoot: false, isInit: false)
|
||||
}
|
||||
if foundAccessors {
|
||||
@@ -3652,12 +3791,13 @@ public struct _FormatRules {
|
||||
index += 1
|
||||
localNames.insert(name)
|
||||
processBody(at: &index, localNames: localNames, members: members, typeStack: &typeStack,
|
||||
membersByType: &membersByType, classMembersByType: &classMembersByType,
|
||||
closureStack: &closureStack, membersByType: &membersByType, classMembersByType: &classMembersByType,
|
||||
usingDynamicLookup: usingDynamicLookup, isTypeRoot: false, isInit: false)
|
||||
}
|
||||
}
|
||||
func processFunction(at index: inout Int, localNames: Set<String>, members: Set<String>,
|
||||
typeStack: inout [String],
|
||||
typeStack: inout [(name: String, keyword: String)],
|
||||
closureStack: inout [(allowsImplicitSelf: Bool, selfCapture: String?)],
|
||||
membersByType: inout [String: Set<String>],
|
||||
classMembersByType: inout [String: Set<String>],
|
||||
usingDynamicLookup: Bool)
|
||||
@@ -3710,7 +3850,7 @@ public struct _FormatRules {
|
||||
if startToken == .keyword("subscript") {
|
||||
index = bodyStartIndex
|
||||
processAccessors(["get", "set"], for: "", at: &index, localNames: localNames,
|
||||
members: members, typeStack: &typeStack, membersByType: &membersByType,
|
||||
members: members, typeStack: &typeStack, closureStack: &closureStack, membersByType: &membersByType,
|
||||
classMembersByType: &classMembersByType,
|
||||
usingDynamicLookup: usingDynamicLookup)
|
||||
} else {
|
||||
@@ -3719,6 +3859,7 @@ public struct _FormatRules {
|
||||
localNames: localNames,
|
||||
members: members,
|
||||
typeStack: &typeStack,
|
||||
closureStack: &closureStack,
|
||||
membersByType: &membersByType,
|
||||
classMembersByType: &classMembersByType,
|
||||
usingDynamicLookup: usingDynamicLookup,
|
||||
@@ -3726,12 +3867,13 @@ public struct _FormatRules {
|
||||
isInit: startToken == .keyword("init"))
|
||||
}
|
||||
}
|
||||
var typeStack = [String]()
|
||||
var typeStack = [(name: String, keyword: String)]()
|
||||
var closureStack = [(allowsImplicitSelf: Bool, selfCapture: String?)]()
|
||||
var membersByType = [String: Set<String>]()
|
||||
var classMembersByType = [String: Set<String>]()
|
||||
var index = 0
|
||||
processBody(at: &index, localNames: [], members: [], typeStack: &typeStack,
|
||||
membersByType: &membersByType, classMembersByType: &classMembersByType,
|
||||
closureStack: &closureStack, membersByType: &membersByType, classMembersByType: &classMembersByType,
|
||||
usingDynamicLookup: false, isTypeRoot: false, isInit: false)
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ public let swiftVersionFile = ".swift-version"
|
||||
/// Supported Swift versions
|
||||
public let swiftVersions = [
|
||||
"3.x", "4.0", "4.1", "4.2",
|
||||
"5.0", "5.1", "5.2", "5.3", "5.4", "5.5", "5.6", "5.7",
|
||||
"5.0", "5.1", "5.2", "5.3", "5.4", "5.5", "5.6", "5.7", "5.8",
|
||||
]
|
||||
|
||||
/// An enumeration of the types of error that may be thrown by SwiftFormat
|
||||
|
||||
@@ -3139,6 +3139,320 @@ class RedundancyTests: RulesTests {
|
||||
testFormatting(for: input, output, rule: FormatRules.redundantSelf)
|
||||
}
|
||||
|
||||
func testRedundantSelfPreservesSelfInClosureWithExplicitStrongCaptureBefore5_3() {
|
||||
let input = """
|
||||
class Foo {
|
||||
let bar: Int
|
||||
|
||||
func baaz() {
|
||||
closure { [self] in
|
||||
print(self.bar)
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
let options = FormatOptions(swiftVersion: "5.2")
|
||||
testFormatting(for: input, rule: FormatRules.redundantSelf, options: options)
|
||||
}
|
||||
|
||||
func testRedundantSelfRemovesSelfInClosureWithExplicitStrongCapture() {
|
||||
let input = """
|
||||
class Foo {
|
||||
let foo: Int
|
||||
|
||||
func baaz() {
|
||||
closure { [self, bar] baaz, quux in
|
||||
print(self.foo)
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
let output = """
|
||||
class Foo {
|
||||
let foo: Int
|
||||
|
||||
func baaz() {
|
||||
closure { [self, bar] baaz, quux in
|
||||
print(foo)
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
let options = FormatOptions(swiftVersion: "5.3")
|
||||
testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options, exclude: ["unusedArguments"])
|
||||
}
|
||||
|
||||
func testRedundantSelfRemovesSelfInClosureWithNestedExplicitStrongCapture() {
|
||||
let input = """
|
||||
class Foo {
|
||||
let bar: Int
|
||||
|
||||
func baaz() {
|
||||
closure {
|
||||
print(self.bar)
|
||||
closure { [self] in
|
||||
print(self.bar)
|
||||
}
|
||||
print(self.bar)
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
let output = """
|
||||
class Foo {
|
||||
let bar: Int
|
||||
|
||||
func baaz() {
|
||||
closure {
|
||||
print(self.bar)
|
||||
closure { [self] in
|
||||
print(bar)
|
||||
}
|
||||
print(self.bar)
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
let options = FormatOptions(swiftVersion: "5.3")
|
||||
testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options)
|
||||
}
|
||||
|
||||
func testRedundantSelfKeepsSelfInNestedClosureWithNoExplicitStrongCapture() {
|
||||
let input = """
|
||||
class Foo {
|
||||
let bar: Int
|
||||
let baaz: Int?
|
||||
|
||||
func baaz() {
|
||||
closure { [self] in
|
||||
print(self.bar)
|
||||
closure {
|
||||
print(self.bar)
|
||||
if let baaz = self.baaz {
|
||||
print(baaz)
|
||||
}
|
||||
}
|
||||
print(self.bar)
|
||||
if let baaz = self.baaz {
|
||||
print(baaz)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
let output = """
|
||||
class Foo {
|
||||
let bar: Int
|
||||
let baaz: Int?
|
||||
|
||||
func baaz() {
|
||||
closure { [self] in
|
||||
print(bar)
|
||||
closure {
|
||||
print(self.bar)
|
||||
if let baaz = self.baaz {
|
||||
print(baaz)
|
||||
}
|
||||
}
|
||||
print(bar)
|
||||
if let baaz = baaz {
|
||||
print(baaz)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
let options = FormatOptions(swiftVersion: "5.3")
|
||||
testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options)
|
||||
}
|
||||
|
||||
func testRedundantSelfRemovesSelfInClosureCapturingStruct() {
|
||||
let input = """
|
||||
struct Foo {
|
||||
let bar: Int
|
||||
|
||||
func baaz() {
|
||||
closure {
|
||||
print(self.bar)
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
let output = """
|
||||
struct Foo {
|
||||
let bar: Int
|
||||
|
||||
func baaz() {
|
||||
closure {
|
||||
print(bar)
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
let options = FormatOptions(swiftVersion: "5.3")
|
||||
testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options)
|
||||
}
|
||||
|
||||
func testRedundantSelfRemovesSelfInClosureCapturingSelfWeakly() {
|
||||
let input = """
|
||||
class Foo {
|
||||
let bar: Int
|
||||
|
||||
func baaz() {
|
||||
closure { [weak self] in
|
||||
print(self?.bar)
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
print(self.bar)
|
||||
closure {
|
||||
print(self.bar)
|
||||
}
|
||||
closure { [self] in
|
||||
print(self.bar)
|
||||
}
|
||||
print(self.bar)
|
||||
}
|
||||
|
||||
closure { [weak self] in
|
||||
guard let self = self else {
|
||||
return
|
||||
}
|
||||
|
||||
print(self.bar)
|
||||
}
|
||||
|
||||
closure { [weak self] in
|
||||
guard let self = self ?? somethingElse else {
|
||||
return
|
||||
}
|
||||
|
||||
print(self.bar)
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
let output = """
|
||||
class Foo {
|
||||
let bar: Int
|
||||
|
||||
func baaz() {
|
||||
closure { [weak self] in
|
||||
print(self?.bar)
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
print(bar)
|
||||
closure {
|
||||
print(self.bar)
|
||||
}
|
||||
closure { [self] in
|
||||
print(bar)
|
||||
}
|
||||
print(bar)
|
||||
}
|
||||
|
||||
closure { [weak self] in
|
||||
guard let self = self else {
|
||||
return
|
||||
}
|
||||
|
||||
print(bar)
|
||||
}
|
||||
|
||||
closure { [weak self] in
|
||||
guard let self = self ?? somethingElse else {
|
||||
return
|
||||
}
|
||||
|
||||
print(self.bar)
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
let options = FormatOptions(swiftVersion: "5.8")
|
||||
testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options, exclude: ["redundantOptionalBinding"])
|
||||
}
|
||||
|
||||
func testClosureParameterListShadowingPropertyOnSelf() {
|
||||
let input = """
|
||||
class Foo {
|
||||
var bar = "bar"
|
||||
|
||||
func method() {
|
||||
closure { [self] bar in
|
||||
self.bar = bar
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
let options = FormatOptions(swiftVersion: "5.3")
|
||||
testFormatting(for: input, rule: FormatRules.redundantSelf, options: options)
|
||||
}
|
||||
|
||||
func testClosureParameterListShadowingPropertyOnSelfInStruct() {
|
||||
let input = """
|
||||
struct Foo {
|
||||
var bar = "bar"
|
||||
|
||||
func method() {
|
||||
closure { bar in
|
||||
self.bar = bar
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
let options = FormatOptions(swiftVersion: "5.3")
|
||||
testFormatting(for: input, rule: FormatRules.redundantSelf, options: options)
|
||||
}
|
||||
|
||||
func testClosureCaptureListShadowingPropertyOnSelf() {
|
||||
let input = """
|
||||
class Foo {
|
||||
var bar = "bar"
|
||||
var baaz = "baaz"
|
||||
|
||||
func method() {
|
||||
closure { [self, bar, baaz = bar] in
|
||||
self.bar = bar
|
||||
self.baaz = baaz
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
let options = FormatOptions(swiftVersion: "5.3")
|
||||
testFormatting(for: input, rule: FormatRules.redundantSelf, options: options)
|
||||
}
|
||||
|
||||
func testRedundantSelfKeepsSelfInClosureCapturingSelfWeaklyBefore5_8() {
|
||||
let input = """
|
||||
class Foo {
|
||||
let bar: Int
|
||||
|
||||
func baaz() {
|
||||
closure { [weak self] in
|
||||
print(self?.bar)
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
print(self.bar)
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
let options = FormatOptions(swiftVersion: "5.7")
|
||||
testFormatting(for: input, rule: FormatRules.redundantSelf, options: options)
|
||||
}
|
||||
|
||||
func testNonRedundantSelfNotRemovedAfterConditionalLet() {
|
||||
let input = """
|
||||
class Foo {
|
||||
|
||||
Reference in New Issue
Block a user